From c47fc6022d76aef014432608b796290528aa6627 Mon Sep 17 00:00:00 2001 From: Gautam Date: Wed, 28 Sep 2016 15:14:08 +0530 Subject: [PATCH 0001/2345] Update gradiesnt_descent.py --- tensorflow/python/training/gradient_descent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/training/gradient_descent.py b/tensorflow/python/training/gradient_descent.py index 3e3f573563..e60949ca87 100644 --- a/tensorflow/python/training/gradient_descent.py +++ b/tensorflow/python/training/gradient_descent.py @@ -47,7 +47,7 @@ class GradientDescentOptimizer(optimizer.Optimizer): return training_ops.apply_gradient_descent( var, math_ops.cast(self._learning_rate_tensor, var.dtype.base_dtype), - grad, + grad.indices, use_locking=self._use_locking).op def _apply_sparse(self, grad, var): -- GitLab From a76066c6a31f2297bfd1ee7373b7ef5072684d5d Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Thu, 28 Jun 2018 13:48:34 +0000 Subject: [PATCH 0002/2345] Add int16 support for Pack on GPU This fix tries to add int16 support for Pack on GPU, so that the issue raised in 20370 could be addressed. This fix is related to 20370. Signed-off-by: Yong Tang --- tensorflow/core/kernels/pack_op.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/pack_op.cc b/tensorflow/core/kernels/pack_op.cc index 5645275cfa..18ed1ea26a 100644 --- a/tensorflow/core/kernels/pack_op.cc +++ b/tensorflow/core/kernels/pack_op.cc @@ -158,7 +158,8 @@ REGISTER_PACK(string); TF_CALL_GPU_NUMBER_TYPES(REGISTER_GPU); TF_CALL_bfloat16(REGISTER_GPU); TF_CALL_int64(REGISTER_GPU); -REGISTER_GPU(bool); +TF_CALL_int16(REGISTER_GPU); +TF_CALL_bool(REGISTER_GPU); #undef REGISTER_GPU // A special GPU kernel for int32. -- GitLab From 81fefe40e1c3ad9a14d9d7d665b25d7e93fb2dfc Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Thu, 28 Jun 2018 13:49:34 +0000 Subject: [PATCH 0003/2345] Add test case for int16 support of tf.stack/Pack on gpu Signed-off-by: Yong Tang --- tensorflow/python/kernel_tests/stack_op_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/kernel_tests/stack_op_test.py b/tensorflow/python/kernel_tests/stack_op_test.py index 2f27d1839b..eadbcabfd1 100644 --- a/tensorflow/python/kernel_tests/stack_op_test.py +++ b/tensorflow/python/kernel_tests/stack_op_test.py @@ -76,7 +76,7 @@ class StackOpTest(test.TestCase): np.random.seed(7) with self.test_session(use_gpu=True): for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2): - for dtype in [np.bool, np.float32, np.int32, np.int64]: + for dtype in [np.bool, np.float32, np.int16, np.int32, np.int64]: data = np.random.randn(*shape).astype(dtype) # Stack back into a single tensorflow tensor directly using np array c = array_ops.stack(data) -- GitLab From 970972d242e26d0cdf635d3af646df1a519dc677 Mon Sep 17 00:00:00 2001 From: ThisIsPIRI <34787507+ThisIsPIRI@users.noreply.github.com> Date: Mon, 6 Aug 2018 14:41:37 +0900 Subject: [PATCH 0004/2345] Clarify what happens when a new value is input to some methods --- tensorflow/contrib/training/python/training/hparam.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/training/python/training/hparam.py b/tensorflow/contrib/training/python/training/hparam.py index 3beb7bfe30..9f5059b4b1 100644 --- a/tensorflow/contrib/training/python/training/hparam.py +++ b/tensorflow/contrib/training/python/training/hparam.py @@ -494,6 +494,7 @@ class HParams(object): value: New value of the hyperparameter. Raises: + KeyError: If the hyperparameter doesn't exist. ValueError: If there is a type mismatch. """ param_type, is_list = self._hparam_types[name] @@ -510,7 +511,7 @@ class HParams(object): setattr(self, name, _cast_to_type_if_compatible(name, param_type, value)) def del_hparam(self, name): - """Removes the hyperparameter with key 'name'. + """Removes the hyperparameter with key 'name'. Does nothing if it isn't present. Args: name: Name of the hyperparameter. @@ -532,7 +533,7 @@ class HParams(object): The `HParams` instance. Raises: - ValueError: If `values` cannot be parsed. + ValueError: If `values` cannot be parsed or a hyperparameter in `values` doesn't exist. """ type_map = dict() for name, t in self._hparam_types.items(): @@ -543,7 +544,7 @@ class HParams(object): return self.override_from_dict(values_map) def override_from_dict(self, values_dict): - """Override hyperparameter values, parsing new values from a dictionary. + """Override existing hyperparameter values, parsing new values from a dictionary. Args: values_dict: Dictionary of name:value pairs. @@ -552,6 +553,7 @@ class HParams(object): The `HParams` instance. Raises: + KeyError: If a hyperparameter in `values_dict` doesn't exist. ValueError: If `values_dict` cannot be parsed. """ for name, value in values_dict.items(): @@ -591,7 +593,7 @@ class HParams(object): sort_keys=sort_keys) def parse_json(self, values_json): - """Override hyperparameter values, parsing new values from a json object. + """Override existing hyperparameter values, parsing new values from a json object. Args: values_json: String containing a json object of name:value pairs. @@ -600,6 +602,7 @@ class HParams(object): The `HParams` instance. Raises: + KeyError: If a hyperparameter in `values_json` doesn't exist. ValueError: If `values_json` cannot be parsed. """ values_map = json.loads(values_json) -- GitLab From 26368188c018cdcc1bbb80ce8205fb04305816c2 Mon Sep 17 00:00:00 2001 From: ThisIsPIRI <34787507+ThisIsPIRI@users.noreply.github.com> Date: Mon, 6 Aug 2018 14:56:38 +0900 Subject: [PATCH 0005/2345] Add 'existing' to parse's docstring --- tensorflow/contrib/training/python/training/hparam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/training/python/training/hparam.py b/tensorflow/contrib/training/python/training/hparam.py index 9f5059b4b1..372630df81 100644 --- a/tensorflow/contrib/training/python/training/hparam.py +++ b/tensorflow/contrib/training/python/training/hparam.py @@ -521,7 +521,7 @@ class HParams(object): del self._hparam_types[name] def parse(self, values): - """Override hyperparameter values, parsing new values from a string. + """Override existing hyperparameter values, parsing new values from a string. See parse_values for more detail on the allowed format for values. -- GitLab From 0b12d0d1ef24d667199d097c200ab65042e4e697 Mon Sep 17 00:00:00 2001 From: kouml Date: Wed, 15 Aug 2018 00:55:43 +0900 Subject: [PATCH 0006/2345] avoid url error exception --- tensorflow/python/keras/utils/data_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/keras/utils/data_utils.py b/tensorflow/python/keras/utils/data_utils.py index c1ee34ae46..5c5e22f0bd 100644 --- a/tensorflow/python/keras/utils/data_utils.py +++ b/tensorflow/python/keras/utils/data_utils.py @@ -241,10 +241,10 @@ def get_file(fname, try: try: urlretrieve(origin, fpath, dl_progress) - except URLError as e: - raise Exception(error_msg.format(origin, e.errno, e.reason)) except HTTPError as e: raise Exception(error_msg.format(origin, e.code, e.msg)) + except URLError as e: + raise Exception(error_msg.format(origin, e.errno, e.reason)) except (Exception, KeyboardInterrupt) as e: if os.path.exists(fpath): os.remove(fpath) -- GitLab From 6655013cf6734dbd8dfc685cd2086fcc032dd04e Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 19 Aug 2018 13:42:27 +0200 Subject: [PATCH 0007/2345] ci_build: Upgrade the Python 'six' compatibility module --- tensorflow/tools/ci_build/install/install_pip_packages.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/ci_build/install/install_pip_packages.sh b/tensorflow/tools/ci_build/install/install_pip_packages.sh index bb316ecfc9..4943cf0ae1 100755 --- a/tensorflow/tools/ci_build/install/install_pip_packages.sh +++ b/tensorflow/tools/ci_build/install/install_pip_packages.sh @@ -31,8 +31,8 @@ pip2 install virtualenv pip3 install virtualenv # Install six. -pip2 install --upgrade six==1.10.0 -pip3 install --upgrade six==1.10.0 +pip2 install --upgrade six==1.11.0 +pip3 install --upgrade six==1.11.0 # Install absl-py. pip2 install --upgrade absl-py -- GitLab From bd6c11f878e1820417d1ceff1b02222178f60c16 Mon Sep 17 00:00:00 2001 From: Guozhong Zhuang Date: Fri, 12 Oct 2018 10:10:21 -0700 Subject: [PATCH 0008/2345] Clean out MKL_ML code from batchnorm ops --- .../core/kernels/mkl_fused_batch_norm_op.cc | 658 +----------------- 1 file changed, 2 insertions(+), 656 deletions(-) diff --git a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc index 2ec6c8fa89..4b8c066902 100644 --- a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc +++ b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc @@ -20,671 +20,19 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_types.h" #include "tensorflow/core/util/tensor_format.h" - -#ifndef INTEL_MKL_ML_ONLY +#include "tensorflow/core/util/mkl_util.h" #include "mkldnn.hpp" + using mkldnn::batch_normalization_backward; using mkldnn::batch_normalization_forward; using mkldnn::prop_kind; using mkldnn::stream; using mkldnn::use_global_stats; using mkldnn::use_scale_shift; -#else -#include "mkl_dnn.h" -#include "mkl_dnn_types.h" -#endif - -#include "tensorflow/core/util/mkl_util.h" -// TODO(inteltf) Address comments from PR 8968. namespace tensorflow { using CPUDevice = Eigen::ThreadPoolDevice; -#ifdef INTEL_MKL_ML_ONLY - -template -class MklFusedBatchNormOp : public OpKernel { - public: - explicit MklFusedBatchNormOp(OpKernelConstruction* context) - : OpKernel(context) { - float epsilon; - OP_REQUIRES_OK(context, context->GetAttr("epsilon", &epsilon)); - epsilon_ = T(epsilon); - string tensor_format; - OP_REQUIRES_OK(context, context->GetAttr("data_format", &tensor_format)); - OP_REQUIRES(context, FormatFromString(tensor_format, &tensor_format_), - errors::InvalidArgument("Invalid data format")); - OP_REQUIRES_OK(context, context->GetAttr("is_training", &is_training_)); - } - - void Compute(OpKernelContext* context) override { - MklFusedBatchNormOpContext mkl_context; - const Tensor& input = MklGetInput(context, 0); - const Tensor& scale = MklGetInput(context, 1); - const Tensor& shift = MklGetInput(context, 2); - const Tensor& est_mean = MklGetInput(context, 3); - const Tensor& est_variance = MklGetInput(context, 4); - - GetMklShape(context, 0, &(mkl_context.mkl_shape_input_shape)); - bool input_in_mkl_format = mkl_context.mkl_shape_input_shape.IsMklTensor(); - - if (!input_in_mkl_format) { - OP_REQUIRES(context, input.dims() == 4, - errors::InvalidArgument("input must be 4-dimensional", - input.shape().DebugString())); - } - OP_REQUIRES(context, scale.dims() == 1, - errors::InvalidArgument("scale must be 1-dimensional", - scale.shape().DebugString())); - OP_REQUIRES(context, shift.dims() == 1, - errors::InvalidArgument("offset must be 1-dimensional", - shift.shape().DebugString())); - OP_REQUIRES(context, est_mean.dims() == 1, - errors::InvalidArgument("estimated_mean must be 1-dimensional", - est_mean.shape().DebugString())); - - OP_REQUIRES( - context, est_variance.dims() == 1, - errors::InvalidArgument("estimated_variance must be 1-dimensional", - est_variance.shape().DebugString())); - - if (is_training_) { - OP_REQUIRES(context, est_mean.dim_size(0) == 0, - errors::InvalidArgument("estimated_mean empty for training", - est_mean.shape().DebugString())); - OP_REQUIRES(context, est_variance.dim_size(0) == 0, - errors::InvalidArgument( - "estimated_variance must be empty for training", - est_variance.shape().DebugString())); - } - - unsigned int flag_batch_norm = - is_training_ ? dnnUseScaleShift - : (dnnUseInputMeanVariance | dnnUseScaleShift); - - mkl_context.MklExtractParams(context, tensor_format_); - - // Create layout only for input data as it is used in Op primitive. - mkl_context.MklCreateInputLayout(context); - - // Create Op primitive. - CHECK_EQ(dnnBatchNormalizationCreateForward_v2_F32( - &(mkl_context.mkl_prim_batchnorm), nullptr, - mkl_context.mkl_lt_input, static_cast(epsilon_), - flag_batch_norm), - E_SUCCESS); - - // Temporary tensors with buffers for the context inputs, if - // conversion to MKL-Op specific layouts are required. It is assumed here - // that TF's 1D tensors (scale, shift, est_mean, and est_variance) won't - // require any conversion. - // Since scale-shift is combined in MKL, a buffer is required. - Tensor mkl_tmp_input_buf_tensor, mkl_tmp_scale_shift_buf_tensor; - mkl_context.MklPrepareContextInputs(context, &mkl_tmp_input_buf_tensor, - &mkl_tmp_scale_shift_buf_tensor); - - // Output data in MKL layout - Tensor* output = nullptr; - TensorShape tf_shape_output; - MklShape mkl_shape_output; - mkl_shape_output.SetMklTensor(true); - mkl_shape_output.SetMklLayout(mkl_context.mkl_prim_batchnorm, - dnnResourceDst); - mkl_shape_output.SetTfLayout(mkl_context.mkl_params.in_dim, - mkl_context.mkl_params.in_sizes, - mkl_context.mkl_params.in_strides); - mkl_shape_output.SetTfDimOrder(mkl_context.mkl_params.in_dim, - tensor_format_); - tf_shape_output.AddDim(dnnLayoutGetMemorySize_F32(static_cast( - mkl_shape_output.GetMklLayout())) / - sizeof(T)); - AllocateOutputSetMklShape(context, 0, &output, tf_shape_output, - mkl_shape_output); - mkl_context.mkl_res_batchnorm[dnnResourceDst] = - static_cast(output->flat().data()); - - // Batch mean in TF layout - Tensor* batch_mean = nullptr; - MklShape mkl_shape_batch_mean; - mkl_shape_batch_mean.SetMklTensor(false); - AllocateOutputSetMklShape(context, 1, &batch_mean, scale.shape(), - mkl_shape_batch_mean); - // Batch variance in TF layout - Tensor* batch_variance = nullptr; - MklShape mkl_shape_batch_variance; - mkl_shape_batch_variance.SetMklTensor(false); - AllocateOutputSetMklShape(context, 2, &batch_variance, scale.shape(), - mkl_shape_batch_variance); - // If training mode, set dnnResourceMean and dnnResourceVariance to - // output tensors for batch mean and variance. - // Otherwise, set dnnResourceMean and dnnResourceVariance to - // estimated mean and variance. - if (is_training_) - mkl_context.MklSetMeanVariance(*batch_mean, *batch_variance); - else - mkl_context.MklSetMeanVariance(est_mean, est_variance); - - // Now that all resources are set, it is ready for dnnExecute - CHECK_EQ(dnnExecute_F32(mkl_context.mkl_prim_batchnorm, - mkl_context.mkl_res_batchnorm), - E_SUCCESS); - - // Mean and variance (without Bessel's correction) saved for backward - // computation to serve as pre-computed mean and variance. - Tensor* saved_mean = nullptr; - MklShape mkl_shape_saved_mean; - mkl_shape_saved_mean.SetMklTensor(false); - AllocateOutputSetMklShape(context, 3, &saved_mean, scale.shape(), - mkl_shape_saved_mean); - std::memcpy( - reinterpret_cast(saved_mean->flat().data()), - reinterpret_cast(mkl_context.mkl_res_batchnorm[dnnResourceMean]), - scale.NumElements() * sizeof(float)); - Tensor* saved_variance = nullptr; - MklShape mkl_shape_saved_variance; - mkl_shape_saved_variance.SetMklTensor(false); - AllocateOutputSetMklShape(context, 4, &saved_variance, scale.shape(), - mkl_shape_saved_variance); - std::memcpy(reinterpret_cast(saved_variance->flat().data()), - reinterpret_cast( - mkl_context.mkl_res_batchnorm[dnnResourceVariance]), - scale.NumElements() * sizeof(float)); - - // Bessel's correction on variance, if training mode is on - if (is_training_) { - float* p_var = static_cast(batch_variance->flat().data()); - auto depth = mkl_context.mkl_params.depth; - size_t orig_size = mkl_context.mkl_params.in_sizes[0] * - mkl_context.mkl_params.in_sizes[1] * - mkl_context.mkl_params.in_sizes[3]; - size_t adjust_size = orig_size - 1; - float adjust_factor = (static_cast(orig_size)) / adjust_size; - for (int i = 0; i < depth; i++) p_var[i] = adjust_factor * p_var[i]; - } - - mkl_context.MklCleanup(); - } - - private: - T epsilon_; - TensorFormat tensor_format_; - bool is_training_; - - // Structure containing all info for MklOp - typedef struct { - // Parameters used for input and output layouts - struct MklBatchNormParams { - // BatchNormOp src and - size_t in_dim; - size_t in_sizes[4]; - size_t in_strides[4]; - size_t depth; // Batch normalization is done for per channel. - } mkl_params; - - MklShape mkl_shape_input_shape; - - // MKL primitive and resources for BatchNormOp - dnnPrimitive_t mkl_prim_batchnorm = nullptr; - void* mkl_res_batchnorm[dnnResourceNumber]; - - // MKL layouts for inputs in the context - dnnLayout_t mkl_lt_input = nullptr; - - void MklCleanup() { - bool input_in_mkl_format = mkl_shape_input_shape.IsMklTensor(); - if (!input_in_mkl_format) dnnLayoutDelete_F32(mkl_lt_input); - if (mkl_prim_batchnorm != nullptr) dnnDelete_F32(mkl_prim_batchnorm); - } - - void MklExtractParams(OpKernelContext* context, - const TensorFormat& tensor_format) { - const Tensor& input = MklGetInput(context, 0); - bool input_in_mkl_format = mkl_shape_input_shape.IsMklTensor(); - mkl_params.in_dim = input_in_mkl_format - ? mkl_shape_input_shape.GetDimension() - : input.dims(); - mkl_params.in_sizes[0] = static_cast( - input_in_mkl_format ? mkl_shape_input_shape.GetSizes()[0] - : GetTensorDim(input, tensor_format, 'W')); - mkl_params.in_sizes[1] = static_cast( - input_in_mkl_format ? mkl_shape_input_shape.GetSizes()[1] - : GetTensorDim(input, tensor_format, 'H')); - mkl_params.in_sizes[2] = static_cast( - input_in_mkl_format ? mkl_shape_input_shape.GetSizes()[2] - : GetTensorDim(input, tensor_format, 'C')); - mkl_params.in_sizes[3] = static_cast( - input_in_mkl_format ? mkl_shape_input_shape.GetSizes()[3] - : GetTensorDim(input, tensor_format, 'N')); - mkl_params.depth = mkl_params.in_sizes[2]; - GetStridesFromSizes(tensor_format, mkl_params.in_strides, - mkl_params.in_sizes); - } - - void MklCreateInputLayout(OpKernelContext* context) { - const Tensor& input = MklGetInput(context, 0); - bool input_in_mkl_format = mkl_shape_input_shape.IsMklTensor(); - if (input_in_mkl_format) { - mkl_lt_input = - static_cast(mkl_shape_input_shape.GetCurLayout()); - } else { - CHECK_EQ( - dnnLayoutCreate_F32(&mkl_lt_input, mkl_params.in_dim, - mkl_params.in_sizes, mkl_params.in_strides), - E_SUCCESS); - } - } - void MklPrepareContextInputs(OpKernelContext* context, - Tensor* mkl_tmp_input_buf_tensor, - Tensor* mkl_tmp_scale_shift_buf_tensor) { - bool mkl_convert_input; - dnnPrimitive_t mkl_prim_convert_input = nullptr; - dnnLayout_t mkl_lt_internal_input = nullptr; - void* mkl_buf_converted_input = nullptr; - // Compare with internal layouts and convert if needed - const Tensor& input = MklGetInput(context, 0); - void* mkl_buf_input = - const_cast(static_cast(input.flat().data())); - CHECK_EQ(dnnLayoutCreateFromPrimitive_F32( - &mkl_lt_internal_input, mkl_prim_batchnorm, dnnResourceSrc), - E_SUCCESS); - mkl_convert_input = - !dnnLayoutCompare_F32(mkl_lt_internal_input, mkl_lt_input); - if (mkl_convert_input) { - CHECK_EQ(dnnConversionCreate_F32(&mkl_prim_convert_input, mkl_lt_input, - mkl_lt_internal_input), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_input_buf_tensor, mkl_lt_internal_input, - &mkl_buf_converted_input); - CHECK_EQ(dnnConversionExecute_F32(mkl_prim_convert_input, mkl_buf_input, - mkl_buf_converted_input), - E_SUCCESS); - dnnDelete_F32(mkl_prim_convert_input); - } - dnnLayoutDelete_F32(mkl_lt_internal_input); - mkl_res_batchnorm[dnnResourceSrc] = - (mkl_convert_input) ? mkl_buf_converted_input : mkl_buf_input; - - // scale-shift layout is created from primitive. So no conversion - // is needed, however, a buffer has to be allocated. - dnnLayout_t mkl_lt_scale_shift = nullptr; - void* mkl_buf_scale_shift = nullptr; - CHECK_EQ( - dnnLayoutCreateFromPrimitive_F32( - &mkl_lt_scale_shift, mkl_prim_batchnorm, dnnResourceScaleShift), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_scale_shift_buf_tensor, - mkl_lt_scale_shift, &mkl_buf_scale_shift); - // Fill the scale-shift buffer with data, presumably buffer is 2D array - const Tensor& scale = MklGetInput(context, 1); - const Tensor& shift = MklGetInput(context, 2); - float* buf_scale_shift = static_cast(mkl_buf_scale_shift); - float* buf_scale = const_cast( - static_cast(scale.flat().data())); - float* buf_shift = const_cast( - static_cast(shift.flat().data())); - auto depth = mkl_params.depth; - for (int i = 0; i < depth; i++) { - buf_scale_shift[i] = buf_scale[i]; - buf_scale_shift[i + depth] = buf_shift[i]; - } - mkl_res_batchnorm[dnnResourceScaleShift] = mkl_buf_scale_shift; - } - - inline void MklSetMeanVariance(const Tensor& mean, const Tensor& variance) { - mkl_res_batchnorm[dnnResourceMean] = const_cast( - static_cast(mean.flat().data())); - mkl_res_batchnorm[dnnResourceVariance] = const_cast( - static_cast(variance.flat().data())); - } - } MklFusedBatchNormOpContext; -}; - -template -class MklFusedBatchNormGradOp : public OpKernel { - public: - explicit MklFusedBatchNormGradOp(OpKernelConstruction* context) - : OpKernel(context) { - float epsilon; - OP_REQUIRES_OK(context, context->GetAttr("epsilon", &epsilon)); - epsilon_ = T(epsilon); - string tensor_format; - OP_REQUIRES_OK(context, context->GetAttr("data_format", &tensor_format)); - OP_REQUIRES(context, FormatFromString(tensor_format, &tensor_format_), - errors::InvalidArgument("Invalid data format")); - } - - void Compute(OpKernelContext* context) override { - MklFusedBatchNormGradOpContext mkl_context; - - const Tensor& out_backprop = MklGetInput(context, 0); - const Tensor& input = MklGetInput(context, 1); - const Tensor& scale = MklGetInput(context, 2); - const Tensor& saved_mean = MklGetInput(context, 3); - const Tensor& saved_var = MklGetInput(context, 4); - - // Here scale, mean, and variance are 1D and considered - // those having same layout in MKL and TF - GetMklShape(context, 0, &(mkl_context.mkl_shape_out_backprop)); - GetMklShape(context, 1, &(mkl_context.mkl_shape_input_shape)); - - bool input_in_mkl_format = mkl_context.mkl_shape_input_shape.IsMklTensor(); - bool out_backprop_in_mkl_format = - mkl_context.mkl_shape_out_backprop.IsMklTensor(); - if (!out_backprop_in_mkl_format) { - OP_REQUIRES(context, out_backprop.dims() == 4, - errors::InvalidArgument("input must be 4-dimensional", - out_backprop.shape().DebugString())); - } - if (!input_in_mkl_format) { - OP_REQUIRES(context, input.dims() == 4, - errors::InvalidArgument("input must be 4-dimensional", - input.shape().DebugString())); - } - OP_REQUIRES(context, scale.dims() == 1, - errors::InvalidArgument("scale must be 1-dimensional", - scale.shape().DebugString())); - OP_REQUIRES(context, saved_mean.dims() == 1, - errors::InvalidArgument("saved mean must be 1-dimensional", - saved_mean.shape().DebugString())); - OP_REQUIRES(context, saved_var.dims() == 1, - errors::InvalidArgument("saved variance must be 1-dimensional", - saved_var.shape().DebugString())); - - mkl_context.MklExtractParams(context, tensor_format_); - - mkl_context.MklCreateInputLayout(context); - - unsigned int flag_batch_norm_grad = dnnUseScaleShift; - - // Create Backward Op primitive. - CHECK_EQ(dnnBatchNormalizationCreateBackward_v2_F32( - &(mkl_context.mkl_prim_batchnorm_bwd), nullptr, - mkl_context.mkl_lt_input, static_cast(epsilon_), - flag_batch_norm_grad), - E_SUCCESS); - - // Temporary tensors and their buffers if conversion is required - Tensor mkl_tmp_input_buf_tensor, mkl_tmp_outbackprop_buf_tensor, - mkl_tmp_scaleshift_buf_tensor; - mkl_context.MklPrepareContextInputs(context, &mkl_tmp_input_buf_tensor, - &mkl_tmp_outbackprop_buf_tensor, - &mkl_tmp_scaleshift_buf_tensor); - - // Allocate tensor for grad w.r.t. input(x) - Tensor* in_backprop = nullptr; - TensorShape tf_shape_in_backprop; - MklShape mkl_shape_in_backprop; - mkl_shape_in_backprop.SetMklTensor(true); - mkl_shape_in_backprop.SetMklLayout(mkl_context.mkl_prim_batchnorm_bwd, - dnnResourceDiffSrc); - mkl_shape_in_backprop.SetTfLayout(mkl_context.mkl_params.in_dims, - mkl_context.mkl_params.in_sizes, - mkl_context.mkl_params.in_strides); - mkl_shape_in_backprop.SetTfDimOrder(mkl_context.mkl_params.in_dims, - tensor_format_); - tf_shape_in_backprop.AddDim( - dnnLayoutGetMemorySize_F32( - static_cast(mkl_shape_in_backprop.GetMklLayout())) / - sizeof(T)); - AllocateOutputSetMklShape(context, 0, &in_backprop, tf_shape_in_backprop, - mkl_shape_in_backprop); - mkl_context.mkl_res_batchnorm_bwd[dnnResourceDiffSrc] = - static_cast(in_backprop->flat().data()); - - // grad_scale and grad_shift are combined together in MKL - // So create a single temporary buffer for those. - // Also set dnnResourceDiffScaleShift to the temporary buffer - Tensor mkl_tmp_grad_scale_shift_buf_tensor; - mkl_context.MklPrepareGradScaleShift(context, - &mkl_tmp_grad_scale_shift_buf_tensor); - - // All dnn resources are set now, ready to execute - CHECK_EQ(dnnExecute_F32(mkl_context.mkl_prim_batchnorm_bwd, - mkl_context.mkl_res_batchnorm_bwd), - E_SUCCESS); - - // Now separate out scale and shift grad and copy to individual tensors - const TensorShape& tf_shape_scale_shift = scale.shape(); - // Allocate tensor for grad w.r.t. scale (beta) - Tensor* scale_backprop = nullptr; - MklShape mkl_shape_scale_backprop; - AllocateOutputSetMklShape(context, 1, &scale_backprop, tf_shape_scale_shift, - mkl_shape_scale_backprop); - - // Allocate tensor for grad w.r.t. shift(gamma) - Tensor* shift_backprop = nullptr; - MklShape mkl_shape_shift_backprop; - AllocateOutputSetMklShape(context, 2, &shift_backprop, tf_shape_scale_shift, - mkl_shape_shift_backprop); - - // copy scale and shift grads to tensors - float* mkl_buf_scale_shift = const_cast(static_cast( - mkl_tmp_grad_scale_shift_buf_tensor.flat().data())); - float* tf_buf_scale = const_cast( - static_cast(scale_backprop->flat().data())); - float* tf_buf_shift = const_cast( - static_cast(shift_backprop->flat().data())); - auto depth = mkl_context.mkl_params.depth; - for (int i = 0; i < depth; i++) { - tf_buf_scale[i] = mkl_buf_scale_shift[i]; - tf_buf_shift[i] = mkl_buf_scale_shift[i + depth]; - } - - // Two placeholders for estimated_mean and estimated_variance, which are - // used for inference and thus not needed here for gradient computation. - Tensor* placeholder_1 = nullptr; - MklShape mkl_shape_placeholder_1; - AllocateOutputSetMklShape(context, 3, &placeholder_1, TensorShape({}), - mkl_shape_placeholder_1); - Tensor* placeholder_2 = nullptr; - MklShape mkl_shape_placeholder_2; - AllocateOutputSetMklShape(context, 4, &placeholder_2, TensorShape({}), - mkl_shape_placeholder_2); - - mkl_context.MklCleanup(); - } - - private: - T epsilon_; - TensorFormat tensor_format_; - - // Structure containing all info for MklOp - typedef struct { - // Parameters used for input and output layouts - struct MklBatchNormParams { - // BatchNormOp src and - size_t in_dims; - size_t in_sizes[4]; - size_t in_strides[4]; - size_t depth; // Batch normalization is done for per channel. - } mkl_params; - - MklShape mkl_shape_out_backprop; - MklShape mkl_shape_input_shape; - - // MKL primitive and resources for BatchNormOp - dnnPrimitive_t mkl_prim_batchnorm_bwd = nullptr; - void* mkl_res_batchnorm_bwd[dnnResourceNumber]; - - // MKL layouts for inputs in the context - dnnLayout_t mkl_lt_out_backprop = nullptr; - dnnLayout_t mkl_lt_input = nullptr; - - void MklCleanup() { - bool input_in_mkl_format = mkl_shape_input_shape.IsMklTensor(); - bool out_backprop_in_mkl_format = mkl_shape_out_backprop.IsMklTensor(); - if (!input_in_mkl_format) dnnLayoutDelete_F32(mkl_lt_input); - if (!out_backprop_in_mkl_format) dnnLayoutDelete_F32(mkl_lt_out_backprop); - - dnnDelete_F32(mkl_prim_batchnorm_bwd); - } - - void MklExtractParams(OpKernelContext* context, - const TensorFormat& tensor_format) { - const Tensor& input = MklGetInput(context, 1); - bool input_in_mkl_format = mkl_shape_input_shape.IsMklTensor(); - mkl_params.in_dims = input_in_mkl_format - ? mkl_shape_input_shape.GetDimension() - : input.dims(); - mkl_params.in_sizes[0] = static_cast( - input_in_mkl_format ? mkl_shape_input_shape.GetSizes()[0] - : GetTensorDim(input, tensor_format, 'W')); - mkl_params.in_sizes[1] = static_cast( - input_in_mkl_format ? mkl_shape_input_shape.GetSizes()[1] - : GetTensorDim(input, tensor_format, 'H')); - mkl_params.in_sizes[2] = static_cast( - input_in_mkl_format ? mkl_shape_input_shape.GetSizes()[2] - : GetTensorDim(input, tensor_format, 'C')); - mkl_params.in_sizes[3] = static_cast( - input_in_mkl_format ? mkl_shape_input_shape.GetSizes()[3] - : GetTensorDim(input, tensor_format, 'N')); - mkl_params.depth = mkl_params.in_sizes[2]; - GetStridesFromSizes(tensor_format, mkl_params.in_strides, - mkl_params.in_sizes); - } - - void MklCreateInputLayout(OpKernelContext* context) { - const Tensor& input = MklGetInput(context, 0); - bool input_in_mkl_format = mkl_shape_input_shape.IsMklTensor(); - if (input_in_mkl_format) { - mkl_lt_input = - static_cast(mkl_shape_input_shape.GetCurLayout()); - } else { - CHECK_EQ( - dnnLayoutCreate_F32(&mkl_lt_input, mkl_params.in_dims, - mkl_params.in_sizes, mkl_params.in_strides), - E_SUCCESS); - } - - bool out_backprop_in_mkl_format = mkl_shape_out_backprop.IsMklTensor(); - if (out_backprop_in_mkl_format) { - mkl_lt_out_backprop = - static_cast(mkl_shape_out_backprop.GetCurLayout()); - } else { - CHECK_EQ( - dnnLayoutCreate_F32(&mkl_lt_out_backprop, mkl_params.in_dims, - mkl_params.in_sizes, mkl_params.in_strides), - E_SUCCESS); - } - } - - void MklPrepareContextInputs(OpKernelContext* context, - Tensor* mkl_tmp_input_buf_tensor, - Tensor* mkl_tmp_outbackprop_buf_tensor, - Tensor* mkl_tmp_scaleshift_buf_tensor) { - bool mkl_convert_input; - dnnPrimitive_t mkl_prim_convert_input = nullptr; - dnnLayout_t mkl_lt_internal_input = nullptr; - void* mkl_buf_converted_input = nullptr; - // Compare with internal layouts and convert if needed - const Tensor& input = MklGetInput(context, 1); - void* mkl_buf_input = - const_cast(static_cast(input.flat().data())); - CHECK_EQ( - dnnLayoutCreateFromPrimitive_F32( - &mkl_lt_internal_input, mkl_prim_batchnorm_bwd, dnnResourceSrc), - E_SUCCESS); - mkl_convert_input = - !dnnLayoutCompare_F32(mkl_lt_internal_input, mkl_lt_input); - if (mkl_convert_input) { - CHECK_EQ(dnnConversionCreate_F32(&mkl_prim_convert_input, mkl_lt_input, - mkl_lt_internal_input), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_input_buf_tensor, mkl_lt_internal_input, - &mkl_buf_converted_input); - CHECK_EQ(dnnConversionExecute_F32(mkl_prim_convert_input, mkl_buf_input, - mkl_buf_converted_input), - E_SUCCESS); - dnnDelete_F32(mkl_prim_convert_input); - } - dnnLayoutDelete_F32(mkl_lt_internal_input); - mkl_res_batchnorm_bwd[dnnResourceSrc] = - (mkl_convert_input) ? mkl_buf_converted_input : mkl_buf_input; - - bool mkl_convert_out_backprop; - dnnPrimitive_t mkl_prim_convert_out_backprop = nullptr; - dnnLayout_t mkl_lt_internal_out_backprop = nullptr; - void* mkl_buf_converted_out_backprop = nullptr; - // Compare with internal layouts and convert if needed - const Tensor& out_backprop = MklGetInput(context, 0); - void* mkl_buf_out_backprop = const_cast( - static_cast(out_backprop.flat().data())); - CHECK_EQ(dnnLayoutCreateFromPrimitive_F32(&mkl_lt_internal_out_backprop, - mkl_prim_batchnorm_bwd, - dnnResourceDiffDst), - E_SUCCESS); - mkl_convert_out_backprop = !dnnLayoutCompare_F32( - mkl_lt_internal_out_backprop, mkl_lt_out_backprop); - if (mkl_convert_out_backprop) { - CHECK_EQ(dnnConversionCreate_F32(&mkl_prim_convert_out_backprop, - mkl_lt_out_backprop, - mkl_lt_internal_out_backprop), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_outbackprop_buf_tensor, - mkl_lt_internal_out_backprop, - &mkl_buf_converted_out_backprop); - CHECK_EQ(dnnConversionExecute_F32(mkl_prim_convert_out_backprop, - mkl_buf_out_backprop, - mkl_buf_converted_out_backprop), - E_SUCCESS); - dnnDelete_F32(mkl_prim_convert_out_backprop); - } - dnnLayoutDelete_F32(mkl_lt_internal_out_backprop); - mkl_res_batchnorm_bwd[dnnResourceDiffDst] = - (mkl_convert_out_backprop) ? mkl_buf_converted_out_backprop - : mkl_buf_out_backprop; - - // Set dnnResourceMean and dnnResourceVariance - const Tensor& saved_mean = MklGetInput(context, 3); - const Tensor& saved_var = MklGetInput(context, 4); - void* mkl_buf_saved_mean = const_cast( - static_cast(saved_mean.flat().data())); - void* mkl_buf_saved_var = const_cast( - static_cast(saved_var.flat().data())); - mkl_res_batchnorm_bwd[dnnResourceMean] = mkl_buf_saved_mean; - mkl_res_batchnorm_bwd[dnnResourceVariance] = mkl_buf_saved_var; - - // Set dnnResourceScaleShift - // Note backward Op needs only current values of scale parameters, - // shift parameters could be garbage and won't be used - const Tensor& scale = MklGetInput(context, 2); - dnnLayout_t mkl_lt_scale_shift = nullptr; - void* mkl_buf_scale_shift = nullptr; - CHECK_EQ(dnnLayoutCreateFromPrimitive_F32(&mkl_lt_scale_shift, - mkl_prim_batchnorm_bwd, - dnnResourceScaleShift), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_scaleshift_buf_tensor, mkl_lt_scale_shift, - &mkl_buf_scale_shift); - float* pscale = - const_cast(static_cast(scale.flat().data())); - float* pscale_shift = static_cast(mkl_buf_scale_shift); - auto depth = mkl_params.depth; - for (int i = 0; i < depth; i++) pscale_shift[i] = pscale[i]; - mkl_res_batchnorm_bwd[dnnResourceScaleShift] = mkl_buf_scale_shift; - dnnLayoutDelete_F32(mkl_lt_scale_shift); - } - - void MklPrepareGradScaleShift(OpKernelContext* context, - Tensor* mkl_tmp_grad_scale_shift_buf_tensor) { - dnnLayout_t mkl_lt_grad_scaleshift = nullptr; - void* mkl_buf_grad_scaleshift = nullptr; - CHECK_EQ(dnnLayoutCreateFromPrimitive_F32(&mkl_lt_grad_scaleshift, - mkl_prim_batchnorm_bwd, - dnnResourceDiffScaleShift), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_grad_scale_shift_buf_tensor, - mkl_lt_grad_scaleshift, &mkl_buf_grad_scaleshift); - mkl_res_batchnorm_bwd[dnnResourceDiffScaleShift] = - mkl_buf_grad_scaleshift; - dnnLayoutDelete_F32(mkl_lt_grad_scaleshift); - } - } MklFusedBatchNormGradOpContext; -}; -#endif - -#ifndef INTEL_MKL_ML_ONLY - struct MklBatchNormFwdParams { memory::dims src_dims; int depth; @@ -1765,8 +1113,6 @@ class MklFusedBatchNormGradOp : public OpKernel { memory::dims GetMeanVarianceDims() { return memory::dims({1, depth_}); } }; -#endif - #define REGISTER_MKL_CPU(T) \ REGISTER_KERNEL_BUILDER(Name("_MklFusedBatchNorm") \ .Device(DEVICE_CPU) \ -- GitLab From cb9dccae23f34edb15cdbe58ad6fceab702138fb Mon Sep 17 00:00:00 2001 From: Guozhong Zhuang Date: Fri, 12 Oct 2018 10:41:32 -0700 Subject: [PATCH 0009/2345] adjust headers inclusion order per review suggestion of another PR --- tensorflow/core/kernels/mkl_fused_batch_norm_op.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc index 4b8c066902..ff46e75a36 100644 --- a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc +++ b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc @@ -19,8 +19,8 @@ limitations under the License. #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_types.h" -#include "tensorflow/core/util/tensor_format.h" #include "tensorflow/core/util/mkl_util.h" +#include "tensorflow/core/util/tensor_format.h" #include "mkldnn.hpp" using mkldnn::batch_normalization_backward; -- GitLab From bae74d26f93872374b48c60a73d189df148a6f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yan=20Facai=20=28=E9=A2=9C=E5=8F=91=E6=89=8D=29?= Date: Tue, 16 Oct 2018 09:45:45 +0800 Subject: [PATCH 0010/2345] CLN: remove reshape in conv3d, becasue bias_add has supported 5-dim --- .../python/keras/layers/convolutional.py | 33 ++----------------- .../python/keras/layers/convolutional_test.py | 16 +++++++++ 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/tensorflow/python/keras/layers/convolutional.py b/tensorflow/python/keras/layers/convolutional.py index 58024677ee..f8afa0d430 100644 --- a/tensorflow/python/keras/layers/convolutional.py +++ b/tensorflow/python/keras/layers/convolutional.py @@ -199,21 +199,8 @@ class Conv(Layer): # nn.bias_add does not accept a 1D input tensor. bias = array_ops.reshape(self.bias, (1, self.filters, 1)) outputs += bias - if self.rank == 2: + else: outputs = nn.bias_add(outputs, self.bias, data_format='NCHW') - if self.rank == 3: - # As of Mar 2017, direct addition is significantly slower than - # bias_add when computing gradients. To use bias_add, we collapse Z - # and Y into a single dimension to obtain a 4D input tensor. - outputs_shape = outputs.shape.as_list() - if outputs_shape[0] is None: - outputs_shape[0] = -1 - outputs_4d = array_ops.reshape(outputs, - [outputs_shape[0], outputs_shape[1], - outputs_shape[2] * outputs_shape[3], - outputs_shape[4]]) - outputs_4d = nn.bias_add(outputs_4d, self.bias, data_format='NCHW') - outputs = array_ops.reshape(outputs_4d, outputs_shape) else: outputs = nn.bias_add(outputs, self.bias, data_format='NHWC') @@ -1127,24 +1114,10 @@ class Conv3DTranspose(Conv3D): outputs.set_shape(out_shape) if self.use_bias: - outputs_shape = outputs.shape.as_list() - if outputs_shape[0] is None: - outputs_shape[0] = -1 - if self.data_format == 'channels_first': - outputs_4d = array_ops.reshape(outputs, [ - outputs_shape[0], outputs_shape[1], - outputs_shape[2] * outputs_shape[3], outputs_shape[4] - ]) - else: - outputs_4d = array_ops.reshape(outputs, [ - outputs_shape[0], outputs_shape[1] * outputs_shape[2], - outputs_shape[3], outputs_shape[4] - ]) - outputs_4d = nn.bias_add( - outputs_4d, + outputs = nn.bias_add( + outputs, self.bias, data_format=conv_utils.convert_data_format(self.data_format, ndim=4)) - outputs = array_ops.reshape(outputs_4d, outputs_shape) if self.activation is not None: return self.activation(outputs) diff --git a/tensorflow/python/keras/layers/convolutional_test.py b/tensorflow/python/keras/layers/convolutional_test.py index 4afddbc8cc..63fb60ebaf 100644 --- a/tensorflow/python/keras/layers/convolutional_test.py +++ b/tensorflow/python/keras/layers/convolutional_test.py @@ -336,6 +336,14 @@ class Conv3DTransposeTest(test.TestCase): self.assertEqual(layer.kernel.constraint, k_constraint) self.assertEqual(layer.bias.constraint, b_constraint) + def test_conv3dtranspose_dynamic_shape(self): + with self.session(use_gpu=True): + # Won't raise error here. + layer = keras.layers.Conv3DTranspose(3, 3, data_format='channels_last') + layer.build((None, None, None, None, 1)) + layer1 = keras.layers.Conv3DTranspose(3, 3, data_format='channels_first') + layer1.build((None, 1, None, None, None)) + class SeparableConv1DTest(test.TestCase): @@ -557,6 +565,14 @@ class Conv3DTest(test.TestCase): self.assertEqual(layer.kernel.constraint, k_constraint) self.assertEqual(layer.bias.constraint, b_constraint) + def test_conv3d_dynamic_shape(self): + with self.session(use_gpu=True): + # Won't raise error here. + layer = keras.layers.Conv3D(3, 3, data_format='channels_last') + layer.build((None, None, None, None, 1)) + layer1 = keras.layers.Conv3D(3, 3, data_format='channels_first') + layer1.build((None, 1, None, None, None)) + class ZeroPaddingTest(test.TestCase): -- GitLab From 02e52ca65a91f12b9f352ffa1d0ed86a1c6dd276 Mon Sep 17 00:00:00 2001 From: Palmer Lao Date: Thu, 18 Oct 2018 16:42:15 -0700 Subject: [PATCH 0011/2345] Make shared S3 lib linkable --- tensorflow/core/platform/s3/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/platform/s3/BUILD b/tensorflow/core/platform/s3/BUILD index 41184b6fd9..7bc4d80db5 100644 --- a/tensorflow/core/platform/s3/BUILD +++ b/tensorflow/core/platform/s3/BUILD @@ -14,7 +14,7 @@ load( ) tf_cc_binary( - name = "s3_file_system.so", + name = "libs3_file_system_shared.so", srcs = [ "aws_crypto.cc", "aws_crypto.h", -- GitLab From 4182fe444d0eed807403482a165c1b547d1c25ce Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Fri, 19 Oct 2018 03:16:59 +0000 Subject: [PATCH 0012/2345] Add uint8 support for Pad with GPU This fix tries to address some of the issue raised in 17823 where Pad with GPU does not support uint8. This fix adds the uint8 support. Signed-off-by: Yong Tang --- tensorflow/core/kernels/pad_op.cc | 2 ++ tensorflow/core/kernels/pad_op_gpu.cu.cc | 1 + 2 files changed, 3 insertions(+) diff --git a/tensorflow/core/kernels/pad_op.cc b/tensorflow/core/kernels/pad_op.cc index 3b9133ed7e..691430ebaf 100644 --- a/tensorflow/core/kernels/pad_op.cc +++ b/tensorflow/core/kernels/pad_op.cc @@ -322,6 +322,7 @@ namespace functor { TF_CALL_GPU_ALL_TYPES(DECLARE_GPU_SPECS); TF_CALL_int8(DECLARE_GPU_SPECS); +TF_CALL_uint8(DECLARE_GPU_SPECS); } // namespace functor // Registration of the GPU implementations. @@ -355,6 +356,7 @@ TF_CALL_int8(DECLARE_GPU_SPECS); TF_CALL_GPU_ALL_TYPES(REGISTER_GPU_KERNEL); TF_CALL_int8(REGISTER_GPU_KERNEL); +TF_CALL_uint8(REGISTER_GPU_KERNEL); // A special GPU kernel for int32. // TODO(b/25387198): Also enable int32 in device memory. This kernel diff --git a/tensorflow/core/kernels/pad_op_gpu.cu.cc b/tensorflow/core/kernels/pad_op_gpu.cu.cc index 00ec44adc2..0cd8ef17ba 100644 --- a/tensorflow/core/kernels/pad_op_gpu.cu.cc +++ b/tensorflow/core/kernels/pad_op_gpu.cu.cc @@ -41,6 +41,7 @@ typedef Eigen::GpuDevice GPUDevice; TF_CALL_GPU_ALL_TYPES(DEFINE_GPU_SPECS); TF_CALL_int8(DEFINE_GPU_SPECS); +TF_CALL_uint8(DEFINE_GPU_SPECS); } // namespace tensorflow -- GitLab From e18fba2d7eb0ba9e01ff998ee413932ead712eb7 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Fri, 19 Oct 2018 03:18:06 +0000 Subject: [PATCH 0013/2345] Adds test case of uint8 support for Pad with GPU Signed-off-by: Yong Tang --- tensorflow/python/kernel_tests/pad_op_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/kernel_tests/pad_op_test.py b/tensorflow/python/kernel_tests/pad_op_test.py index fc302c4141..e875bb9d65 100644 --- a/tensorflow/python/kernel_tests/pad_op_test.py +++ b/tensorflow/python/kernel_tests/pad_op_test.py @@ -215,7 +215,7 @@ class PadOpTest(test.TestCase): def testIntTypes(self): # TODO(touts): Figure out why the padding tests do not work on GPU # for int types and rank > 2. - for t in [np.int8, np.int32, np.int64]: + for t in [np.int8, np.uint8, np.int32, np.int64]: self._testAll( np.random.randint(-100, 100, (4, 4, 3)).astype(t), [[1, 0], [2, 3], [0, 2]], 0) -- GitLab From 5ec7c8c199368cf950cb31113c6820d872f45de5 Mon Sep 17 00:00:00 2001 From: frreiss Date: Thu, 18 Oct 2018 19:50:42 -0700 Subject: [PATCH 0014/2345] Document undocumented ops Fix missing carriage return --- tensorflow/python/ops/check_ops.py | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tensorflow/python/ops/check_ops.py b/tensorflow/python/ops/check_ops.py index 40b111ea0c..b2511f1bfe 100644 --- a/tensorflow/python/ops/check_ops.py +++ b/tensorflow/python/ops/check_ops.py @@ -1136,6 +1136,25 @@ def _get_diff_for_monotonic_comparison(x): v1=['debugging.is_numeric_tensor', 'is_numeric_tensor']) @deprecation.deprecated_endpoints('is_numeric_tensor') def is_numeric_tensor(tensor): + """Returns `True` if the elements of `tensor` are numbers. + + Specifically, returns `True` if the dtype of `tensor` is one of the following: + + * `tf.float32` + * `tf.float64` + * `tf.int8` + * `tf.int16` + * `tf.int32` + * `tf.int64` + * `tf.uint8` + * `tf.qint8` + * `tf.qint32` + * `tf.quint8` + * `tf.complex64` + + Returns `False` if `tensor` is of a non-numeric type or if `tensor` is not + a `tf.Tensor` object. + """ return isinstance(tensor, ops.Tensor) and tensor.dtype in NUMERIC_TYPES @@ -1283,6 +1302,18 @@ def assert_same_float_dtype(tensors=None, dtype=None): 'debugging.assert_scalar', v1=['debugging.assert_scalar', 'assert_scalar']) @deprecation.deprecated_endpoints('assert_scalar') def assert_scalar(tensor, name=None): + """Statically checks whether a tensor is zero-dimensional. + + Args: + tensor: Value to test. + name: A name for this operation (optional). Defaults to "assert_scalar". + + Raises: + ValueError: If `tensor`'s shape has more than zero dimensions. + + Returns: + The input `tensor`, possibly converted to a `tf.Tensor` + """ with ops.name_scope(name, 'assert_scalar', [tensor]) as name_scope: tensor = ops.convert_to_tensor(tensor, name=name_scope) shape = tensor.get_shape() -- GitLab From d31e6ad81cf0c88e08a00a6ab16c2ac2419e7b10 Mon Sep 17 00:00:00 2001 From: Matt Conley Date: Wed, 24 Oct 2018 13:57:20 -0700 Subject: [PATCH 0015/2345] Update Cast Op test for ARM behavior --- tensorflow/python/kernel_tests/cast_op_test.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/kernel_tests/cast_op_test.py b/tensorflow/python/kernel_tests/cast_op_test.py index a5dff5df62..90c2bd0294 100644 --- a/tensorflow/python/kernel_tests/cast_op_test.py +++ b/tensorflow/python/kernel_tests/cast_op_test.py @@ -151,7 +151,7 @@ class CastOpTest(test.TestCase): # np.float64("np.inf").astype(np.int32) is negative on x86 but positive on ppc64le # Numpy link to relevant discussion - https://github.com/numpy/numpy/issues/9040 # Tensorflow link to relevant discussion - https://github.com/tensorflow/tensorflow/issues/9360 - if platform.machine() == "ppc64le": + if platform.machine() == "ppc64le" or platform.machine() == "aarch64": self._compare(-np.inf, np.int32, i4.min, False) self._compare(-np.inf, np.int64, i8.min, False) else: @@ -163,8 +163,13 @@ class CastOpTest(test.TestCase): self._compare(-np.inf, np.int64, i8.min, False) self.assertAllEqual(np.isnan(self._cast(np.nan, np.float32, False)), True) self.assertAllEqual(np.isnan(self._cast(np.nan, np.float64, False)), True) - self._compare(np.nan, np.int32, i4.min, False) - self._compare(np.nan, np.int64, i8.min, False) + # np.float64(np.nan).astype(np.int32) is 0 on ARM + if platform.machine() == "aarch64": + self._compare(np.nan, np.int32, 0, False) + self._compare(np.nan, np.int64, 0, False) + else: + self._compare(np.nan, np.int32, i4.min, False) + self._compare(np.nan, np.int64, i8.min, False) self._compare(np.inf, np.float32, np.inf, True) self._compare(np.inf, np.float64, np.inf, True) -- GitLab From 65344d0a7ebd21c71b3f3ed7cb091541504b659a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yan=20Facai=20=28=E9=A2=9C=E5=8F=91=E6=89=8D=29?= Date: Thu, 1 Nov 2018 09:36:09 +0800 Subject: [PATCH 0016/2345] TST: use testing_utils.layer_test instead --- .../python/keras/layers/convolutional_test.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tensorflow/python/keras/layers/convolutional_test.py b/tensorflow/python/keras/layers/convolutional_test.py index 63fb60ebaf..7d8051e596 100644 --- a/tensorflow/python/keras/layers/convolutional_test.py +++ b/tensorflow/python/keras/layers/convolutional_test.py @@ -339,10 +339,16 @@ class Conv3DTransposeTest(test.TestCase): def test_conv3dtranspose_dynamic_shape(self): with self.session(use_gpu=True): # Won't raise error here. - layer = keras.layers.Conv3DTranspose(3, 3, data_format='channels_last') - layer.build((None, None, None, None, 1)) - layer1 = keras.layers.Conv3DTranspose(3, 3, data_format='channels_first') - layer1.build((None, 1, None, None, None)) + testing_utils.layer_test( + keras.layers.Conv3DTranspose, + kwargs={'data_format': 'channels_first', + 'filters': 3, 'kernel_size': 3}, + input_shape=(None, 1, None, None, None)) + testing_utils.layer_test( + keras.layers.Conv3DTranspose, + kwargs={'data_format': 'channels_last', + 'filters': 3, 'kernel_size': 3}, + input_shape=(None, None, None, None, 1)) class SeparableConv1DTest(test.TestCase): @@ -568,10 +574,16 @@ class Conv3DTest(test.TestCase): def test_conv3d_dynamic_shape(self): with self.session(use_gpu=True): # Won't raise error here. - layer = keras.layers.Conv3D(3, 3, data_format='channels_last') - layer.build((None, None, None, None, 1)) - layer1 = keras.layers.Conv3D(3, 3, data_format='channels_first') - layer1.build((None, 1, None, None, None)) + testing_utils.layer_test( + keras.layers.Conv3D, + kwargs={'data_format': 'channels_first', + 'filters': 3, 'kernel_size': 3}, + input_shape=(None, 1, None, None, None)) + testing_utils.layer_test( + keras.layers.Conv3D, + kwargs={'data_format': 'channels_last', + 'filters': 3, 'kernel_size': 3}, + input_shape=(None, None, None, None, 1)) class ZeroPaddingTest(test.TestCase): -- GitLab From 35f20ca9e794b455777a52bf70b5f7d79ae60455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yan=20Facai=20=28=E9=A2=9C=E5=8F=91=E6=89=8D=29?= Date: Thu, 1 Nov 2018 10:11:14 +0800 Subject: [PATCH 0017/2345] TST: add input_data --- .../python/keras/layers/convolutional_test.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/keras/layers/convolutional_test.py b/tensorflow/python/keras/layers/convolutional_test.py index 7d8051e596..737e12a2bd 100644 --- a/tensorflow/python/keras/layers/convolutional_test.py +++ b/tensorflow/python/keras/layers/convolutional_test.py @@ -337,18 +337,21 @@ class Conv3DTransposeTest(test.TestCase): self.assertEqual(layer.bias.constraint, b_constraint) def test_conv3dtranspose_dynamic_shape(self): + input_data = np.random.random((1, 3, 3, 3, 3)) with self.session(use_gpu=True): # Won't raise error here. testing_utils.layer_test( keras.layers.Conv3DTranspose, kwargs={'data_format': 'channels_first', 'filters': 3, 'kernel_size': 3}, - input_shape=(None, 1, None, None, None)) + input_shape=(None, 3, None, None, None), + input_data=input_data) testing_utils.layer_test( keras.layers.Conv3DTranspose, kwargs={'data_format': 'channels_last', 'filters': 3, 'kernel_size': 3}, - input_shape=(None, None, None, None, 1)) + input_shape=(None, None, None, None, 3), + input_data=input_data) class SeparableConv1DTest(test.TestCase): @@ -572,18 +575,21 @@ class Conv3DTest(test.TestCase): self.assertEqual(layer.bias.constraint, b_constraint) def test_conv3d_dynamic_shape(self): + input_data = np.random.random((1, 3, 3, 3, 3)) with self.session(use_gpu=True): # Won't raise error here. testing_utils.layer_test( keras.layers.Conv3D, kwargs={'data_format': 'channels_first', 'filters': 3, 'kernel_size': 3}, - input_shape=(None, 1, None, None, None)) + input_shape=(None, 3, None, None, None), + input_data=input_data) testing_utils.layer_test( keras.layers.Conv3D, kwargs={'data_format': 'channels_last', 'filters': 3, 'kernel_size': 3}, - input_shape=(None, None, None, None, 1)) + input_shape=(None, None, None, None, 3), + input_data=input_data) class ZeroPaddingTest(test.TestCase): -- GitLab From 8de3a6ee6c7b498e32e8c22c6631a3c0a7a4af86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yan=20Facai=20=28=E9=A2=9C=E5=8F=91=E6=89=8D=29?= Date: Thu, 1 Nov 2018 10:35:48 +0800 Subject: [PATCH 0018/2345] TST: data_format=first only run for gpu --- .../python/keras/layers/convolutional_test.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tensorflow/python/keras/layers/convolutional_test.py b/tensorflow/python/keras/layers/convolutional_test.py index 737e12a2bd..ecee080937 100644 --- a/tensorflow/python/keras/layers/convolutional_test.py +++ b/tensorflow/python/keras/layers/convolutional_test.py @@ -337,21 +337,22 @@ class Conv3DTransposeTest(test.TestCase): self.assertEqual(layer.bias.constraint, b_constraint) def test_conv3dtranspose_dynamic_shape(self): - input_data = np.random.random((1, 3, 3, 3, 3)) + input_data = np.random.random((1, 3, 3, 3, 3)).astype(np.float32) with self.session(use_gpu=True): # Won't raise error here. - testing_utils.layer_test( - keras.layers.Conv3DTranspose, - kwargs={'data_format': 'channels_first', - 'filters': 3, 'kernel_size': 3}, - input_shape=(None, 3, None, None, None), - input_data=input_data) testing_utils.layer_test( keras.layers.Conv3DTranspose, kwargs={'data_format': 'channels_last', 'filters': 3, 'kernel_size': 3}, input_shape=(None, None, None, None, 3), input_data=input_data) + if test.is_gpu_available(cuda_only=True): + testing_utils.layer_test( + keras.layers.Conv3DTranspose, + kwargs={'data_format': 'channels_first', + 'filters': 3, 'kernel_size': 3}, + input_shape=(None, 3, None, None, None), + input_data=input_data) class SeparableConv1DTest(test.TestCase): @@ -575,21 +576,22 @@ class Conv3DTest(test.TestCase): self.assertEqual(layer.bias.constraint, b_constraint) def test_conv3d_dynamic_shape(self): - input_data = np.random.random((1, 3, 3, 3, 3)) + input_data = np.random.random((1, 3, 3, 3, 3)).astype(np.float32) with self.session(use_gpu=True): # Won't raise error here. - testing_utils.layer_test( - keras.layers.Conv3D, - kwargs={'data_format': 'channels_first', - 'filters': 3, 'kernel_size': 3}, - input_shape=(None, 3, None, None, None), - input_data=input_data) testing_utils.layer_test( keras.layers.Conv3D, kwargs={'data_format': 'channels_last', 'filters': 3, 'kernel_size': 3}, input_shape=(None, None, None, None, 3), input_data=input_data) + if test.is_gpu_available(cuda_only=True): + testing_utils.layer_test( + keras.layers.Conv3D, + kwargs={'data_format': 'channels_first', + 'filters': 3, 'kernel_size': 3}, + input_shape=(None, 3, None, None, None), + input_data=input_data) class ZeroPaddingTest(test.TestCase): -- GitLab From c8a0c22e3f94728af57ceaed2f53603d1d3d87a9 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Thu, 28 Jun 2018 15:10:41 +0000 Subject: [PATCH 0019/2345] Add int64 for ConcatGPU Signed-off-by: Yong Tang --- tensorflow/core/kernels/concat_lib_gpu.cc | 1 + tensorflow/core/kernels/concat_lib_gpu_impl.cu.cc | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/tensorflow/core/kernels/concat_lib_gpu.cc b/tensorflow/core/kernels/concat_lib_gpu.cc index 93e392d303..278ef2b2e8 100644 --- a/tensorflow/core/kernels/concat_lib_gpu.cc +++ b/tensorflow/core/kernels/concat_lib_gpu.cc @@ -116,6 +116,7 @@ TF_CALL_GPU_NUMBER_TYPES(REGISTER); TF_CALL_complex64(REGISTER); TF_CALL_complex128(REGISTER); TF_CALL_int64(REGISTER); +TF_CALL_int16(REGISTER); TF_CALL_bfloat16(REGISTER); TF_CALL_bool(REGISTER); TF_CALL_uint8(REGISTER); diff --git a/tensorflow/core/kernels/concat_lib_gpu_impl.cu.cc b/tensorflow/core/kernels/concat_lib_gpu_impl.cu.cc index a561d918bd..752e0ed3b7 100644 --- a/tensorflow/core/kernels/concat_lib_gpu_impl.cu.cc +++ b/tensorflow/core/kernels/concat_lib_gpu_impl.cu.cc @@ -202,6 +202,7 @@ TF_CALL_GPU_NUMBER_TYPES(REGISTER_GPUCONCAT32); TF_CALL_complex64(REGISTER_GPUCONCAT32); TF_CALL_complex128(REGISTER_GPUCONCAT32); TF_CALL_int64(REGISTER_GPUCONCAT32); +TF_CALL_int16(REGISTER_GPUCONCAT32); TF_CALL_uint8(REGISTER_GPUCONCAT32); REGISTER_GPUCONCAT32(bfloat16); REGISTER_GPUCONCAT32(bool); @@ -210,6 +211,7 @@ TF_CALL_GPU_NUMBER_TYPES(REGISTER_GPUCONCAT64); TF_CALL_complex64(REGISTER_GPUCONCAT64); TF_CALL_complex128(REGISTER_GPUCONCAT64); TF_CALL_int64(REGISTER_GPUCONCAT64); +TF_CALL_int16(REGISTER_GPUCONCAT64); TF_CALL_uint8(REGISTER_GPUCONCAT64); REGISTER_GPUCONCAT64(bfloat16); REGISTER_GPUCONCAT64(bool); @@ -218,6 +220,7 @@ TF_CALL_GPU_NUMBER_TYPES(REGISTER_GPU32); TF_CALL_complex64(REGISTER_GPU32); TF_CALL_complex128(REGISTER_GPU32); TF_CALL_int64(REGISTER_GPU32); +TF_CALL_int16(REGISTER_GPU32); TF_CALL_uint8(REGISTER_GPU32); REGISTER_GPU32(bfloat16); REGISTER_GPU32(bool); @@ -226,6 +229,7 @@ TF_CALL_GPU_NUMBER_TYPES(REGISTER_GPU64); TF_CALL_complex64(REGISTER_GPU64); TF_CALL_complex128(REGISTER_GPU64); TF_CALL_int64(REGISTER_GPU64); +TF_CALL_int16(REGISTER_GPU64); TF_CALL_uint8(REGISTER_GPU64); REGISTER_GPU64(bfloat16); REGISTER_GPU64(bool); -- GitLab From 42321707242771cf28deb1d577dfdd6a17e9eae9 Mon Sep 17 00:00:00 2001 From: Guozhong Zhuang Date: Mon, 12 Nov 2018 15:24:22 -0800 Subject: [PATCH 0020/2345] fix issues related to clang format check --- .../core/kernels/mkl_fused_batch_norm_op.cc | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc index ff46e75a36..685db657e2 100644 --- a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc +++ b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc @@ -13,15 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #ifdef INTEL_MKL - -#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" +#include "mkldnn.hpp" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_types.h" #include "tensorflow/core/util/mkl_util.h" #include "tensorflow/core/util/tensor_format.h" -#include "mkldnn.hpp" +#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" using mkldnn::batch_normalization_backward; using mkldnn::batch_normalization_forward; @@ -705,9 +704,9 @@ class MklFusedBatchNormOp : public OpKernel { std::memcpy(batch_variance_data, variance_data, depth_ * sizeof(T)); } } catch (mkldnn::error& e) { - string error_msg = "Status: " + std::to_string(e.status) + - ", message: " + string(e.message) + ", in file " + - string(__FILE__) + ":" + std::to_string(__LINE__); + string error_msg = "Status: " + std::to_string(e.status) + ", message: " + + string(e.message) + ", in file " + string(__FILE__) + + ":" + std::to_string(__LINE__); OP_REQUIRES_OK( context, errors::Aborted("Operation received an exception:", error_msg)); @@ -1029,9 +1028,9 @@ class MklFusedBatchNormGradOp : public OpKernel { reinterpret_cast(diff_weights_data + depth_), depth_ * sizeof(T)); } catch (mkldnn::error& e) { - string error_msg = "Status: " + std::to_string(e.status) + - ", message: " + string(e.message) + ", in file " + - string(__FILE__) + ":" + std::to_string(__LINE__); + string error_msg = "Status: " + std::to_string(e.status) + ", message: " + + string(e.message) + ", in file " + string(__FILE__) + + ":" + std::to_string(__LINE__); OP_REQUIRES_OK( context, errors::Aborted("Operation received an exception:", error_msg)); -- GitLab From 898bb2ad207d2b4cee188ce8b868c76bda8f0ad2 Mon Sep 17 00:00:00 2001 From: zhaoyongke Date: Sun, 18 Nov 2018 01:00:26 +0800 Subject: [PATCH 0021/2345] Take channel_multiplier into considerations. Thanks to smillius, drpngx, HuiyangFei --- .../tools/graph_transforms/fold_batch_norms.cc | 13 ++++++++++--- .../tools/graph_transforms/fold_old_batch_norms.cc | 12 ++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/tensorflow/tools/graph_transforms/fold_batch_norms.cc b/tensorflow/tools/graph_transforms/fold_batch_norms.cc index cb4230dd82..2d7bb65430 100644 --- a/tensorflow/tools/graph_transforms/fold_batch_norms.cc +++ b/tensorflow/tools/graph_transforms/fold_batch_norms.cc @@ -73,9 +73,16 @@ Status FoldBatchNorms(const GraphDef& input_graph_def, // Make sure all the inputs really are vectors, with as many entries as // there are columns in the weights. - const int weights_cols_index = conv_node.op() == "Conv2D" ? 3 : \ - (conv_node.op() == "DepthwiseConv2dNative" ? 2 : 1); - const int64 weights_cols = weights.shape().dim_size(weights_cols_index); + int64 weights_cols; + if (conv_node.op() == "Conv2D") { + weights_cols = weights.shape().dim_size(3); + } + else if (conv_node.op() == "DepthwiseConv2dNative") { + weights_cols = weights.shape().dim_size(2) * weights.shape().dim_size(3); + } + else { + weights_cols = weights.shape().dim_size(1); + } if ((mul_values.shape().dims() != 1) || (mul_values.shape().dim_size(0) != weights_cols)) { return errors::InvalidArgument( diff --git a/tensorflow/tools/graph_transforms/fold_old_batch_norms.cc b/tensorflow/tools/graph_transforms/fold_old_batch_norms.cc index 1a4b141d0e..413361b616 100644 --- a/tensorflow/tools/graph_transforms/fold_old_batch_norms.cc +++ b/tensorflow/tools/graph_transforms/fold_old_batch_norms.cc @@ -116,8 +116,16 @@ Status FuseScaleOffsetToConvWeights(const std::vector& scale_values, CHECK_EQ("Const", weights_node.op()); Tensor weights = GetNodeTensorAttr(weights_node, "value"); - const int weights_cols_idx = conv_node.op() == "Conv2D" ? 3 : 2; - const int64 weights_cols = weights.shape().dim_size(weights_cols_idx); + int64 weights_cols; + if (conv_node.op() == "Conv2D") { + weights_cols = weights.shape().dim_size(3); + } + else if (conv_node.op() == "DepthwiseConv2dNative") { + weights_cols = weights.shape().dim_size(2) * weights.shape().dim_size(3); + } + else { + weights_cols = weights.shape().dim_size(1); + } CHECK_EQ(weights_cols, scale_values.size()); // Multiply the original weights by the scale vector. -- GitLab From c1d9c390847471dc233a4a91725f98fdb861fd5a Mon Sep 17 00:00:00 2001 From: zhaoyongke Date: Sun, 18 Nov 2018 01:46:07 +0800 Subject: [PATCH 0022/2345] Add test cases for depthwise conv --- .../graph_transforms/fold_batch_norms_test.cc | 54 ++++++ .../fold_old_batch_norms_test.cc | 164 ++++++++++++++++++ 2 files changed, 218 insertions(+) diff --git a/tensorflow/tools/graph_transforms/fold_batch_norms_test.cc b/tensorflow/tools/graph_transforms/fold_batch_norms_test.cc index a5d541feb6..2b5326799e 100644 --- a/tensorflow/tools/graph_transforms/fold_batch_norms_test.cc +++ b/tensorflow/tools/graph_transforms/fold_batch_norms_test.cc @@ -87,6 +87,57 @@ class FoldBatchNormsTest : public ::testing::Test { } } + void TestFoldBatchNormsDepthwiseConv2dNative() { + auto root = tensorflow::Scope::NewRootScope(); + using namespace ::tensorflow::ops; // NOLINT(build/namespaces) + + Tensor input_data(DT_FLOAT, TensorShape({1, 1, 6, 2})); + test::FillValues( + &input_data, {1.0f, 4.0f, 2.0f, 5.0f, 3.0f, 6.0f, -1.0f, -4.0f, -2.0f, + -5.0f, -3.0f, -6.0f}); + Output input_op = + Const(root.WithOpName("input_op"), Input::Initializer(input_data)); + + Tensor weights_data(DT_FLOAT, TensorShape({1, 2, 2, 2})); + test::FillValues(&weights_data, + {1.0f, 2.0f, 3.0f, 4.0f, 0.1f, 0.2f, 0.3f, 0.4f}); + Output weights_op = + Const(root.WithOpName("weights_op"), Input::Initializer(weights_data)); + + Output conv_op = DepthwiseConv2dNative(root.WithOpName("conv_op"), input_op, weights_op, + {1, 1, 1, 1}, "VALID"); + + Tensor mul_values_data(DT_FLOAT, TensorShape({4})); + test::FillValues(&mul_values_data, {2.0f, 3.0f, 4.0f, 5.0f}); + Output mul_values_op = Const(root.WithOpName("mul_values"), + Input::Initializer(mul_values_data)); + + Output mul_op = Mul(root.WithOpName("output"), conv_op, mul_values_op); + + GraphDef original_graph_def; + TF_ASSERT_OK(root.ToGraphDef(&original_graph_def)); + + std::unique_ptr original_session(NewSession(SessionOptions())); + TF_ASSERT_OK(original_session->Create(original_graph_def)); + std::vector original_outputs; + TF_ASSERT_OK(original_session->Run({}, {"output"}, {}, &original_outputs)); + + GraphDef fused_graph_def; + TF_ASSERT_OK( + FoldBatchNorms(original_graph_def, {{}, {"output"}}, &fused_graph_def)); + + std::unique_ptr fused_session(NewSession(SessionOptions())); + TF_ASSERT_OK(fused_session->Create(fused_graph_def)); + std::vector fused_outputs; + TF_ASSERT_OK(fused_session->Run({}, {"output"}, {}, &fused_outputs)); + + test::ExpectTensorNear(original_outputs[0], fused_outputs[0], 1e-5); + + for (const NodeDef& node : fused_graph_def.node()) { + EXPECT_NE("Mul", node.op()); + } + } + void TestFoldBatchNormsConv2DShared() { auto root = tensorflow::Scope::NewRootScope(); using namespace ::tensorflow::ops; // NOLINT(build/namespaces) @@ -202,6 +253,9 @@ TEST_F(FoldBatchNormsTest, TestFoldBatchNormsConv2D) { TEST_F(FoldBatchNormsTest, TestFoldBatchNormsMatMul) { TestFoldBatchNormsMatMul(); } +TEST_F(FoldBatchNormsTest, TestFoldBatchNormsDepthwiseConv2dNative) { + TestFoldBatchNormsDepthwiseConv2dNative(); +} } // namespace graph_transforms } // namespace tensorflow diff --git a/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc b/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc index 435f46c107..45637cf9d1 100644 --- a/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc +++ b/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc @@ -121,6 +121,85 @@ class FoldOldBatchNormsTest : public ::testing::Test { } } + void TestFoldOldBatchNormsAfterDepthwiseConv2dNative() { + auto root = tensorflow::Scope::NewRootScope(); + using namespace ::tensorflow::ops; // NOLINT(build/namespaces) + + Tensor input_data(DT_FLOAT, TensorShape({1, 1, 6, 2})); + test::FillValues( + &input_data, {1.0f, 4.0f, 2.0f, 5.0f, 3.0f, 6.0f, -1.0f, -4.0f, -2.0f, + -5.0f, -3.0f, -6.0f}); + Output input_op = + Const(root.WithOpName("input_op"), Input::Initializer(input_data)); + + Tensor weights_data(DT_FLOAT, TensorShape({1, 2, 2, 2})); + test::FillValues(&weights_data, + {1.0f, 2.0f, 3.0f, 4.0f, 0.1f, 0.2f, 0.3f, 0.4f}); + Output weights_op = + Const(root.WithOpName("weights_op"), Input::Initializer(weights_data)); + + Output conv_op = DepthwiseConv2dNative(root.WithOpName("conv_op"), + input_op, weights_op, {1, 1, 1, 1}, "VALID"); + + Tensor mean_data(DT_FLOAT, TensorShape({4})); + test::FillValues(&mean_data, {10.0f, 20.0f, 30.0f, 40.0f}); + Output mean_op = + Const(root.WithOpName("mean_op"), Input::Initializer(mean_data)); + + Tensor variance_data(DT_FLOAT, TensorShape({4})); + test::FillValues(&variance_data, {0.25f, 0.5f, 0.75f, 1.0f}); + Output variance_op = Const(root.WithOpName("variance_op"), + Input::Initializer(variance_data)); + + Tensor beta_data(DT_FLOAT, TensorShape({4})); + test::FillValues(&beta_data, {0.1f, 0.6f, 1.1f, 1.6f}); + Output beta_op = + Const(root.WithOpName("beta_op"), Input::Initializer(beta_data)); + + Tensor gamma_data(DT_FLOAT, TensorShape({4})); + test::FillValues(&gamma_data, {1.0f, 2.0f, 3.0f, 4.0f}); + Output gamma_op = + Const(root.WithOpName("gamma_op"), Input::Initializer(gamma_data)); + + GraphDef original_graph_def; + TF_ASSERT_OK(root.ToGraphDef(&original_graph_def)); + + + NodeDef batch_norm_node; + batch_norm_node.set_op("BatchNormWithGlobalNormalization"); + batch_norm_node.set_name("output"); + AddNodeInput("conv_op", &batch_norm_node); + AddNodeInput("mean_op", &batch_norm_node); + AddNodeInput("variance_op", &batch_norm_node); + AddNodeInput("beta_op", &batch_norm_node); + AddNodeInput("gamma_op", &batch_norm_node); + SetNodeAttr("T", DT_FLOAT, &batch_norm_node); + SetNodeAttr("variance_epsilon", 0.00001f, &batch_norm_node); + SetNodeAttr("scale_after_normalization", false, &batch_norm_node); + *(original_graph_def.mutable_node()->Add()) = batch_norm_node; + original_graph_def.mutable_versions()->set_producer(8); + + std::unique_ptr original_session(NewSession(SessionOptions())); + TF_ASSERT_OK(original_session->Create(original_graph_def)); + std::vector original_outputs; + TF_ASSERT_OK(original_session->Run({}, {"output"}, {}, &original_outputs)); + + GraphDef fused_graph_def; + TF_ASSERT_OK(FoldOldBatchNorms(original_graph_def, {{}, {"output"}}, + &fused_graph_def)); + + std::unique_ptr fused_session(NewSession(SessionOptions())); + TF_ASSERT_OK(fused_session->Create(fused_graph_def)); + std::vector fused_outputs; + TF_ASSERT_OK(fused_session->Run({}, {"output"}, {}, &fused_outputs)); + + test::ExpectTensorNear(original_outputs[0], fused_outputs[0], 1e-5); + + for (const NodeDef& node : fused_graph_def.node()) { + EXPECT_NE("BatchNormWithGlobalNormalization", node.op()); + } + } + void TestFoldFusedBatchNorms() { auto root = tensorflow::Scope::NewRootScope(); using namespace ::tensorflow::ops; // NOLINT(build/namespaces) @@ -198,6 +277,83 @@ class FoldOldBatchNormsTest : public ::testing::Test { } } + void TestFoldFusedBatchNormsAfterDepthwiseConv2dNative() { + auto root = tensorflow::Scope::NewRootScope(); + using namespace ::tensorflow::ops; // NOLINT(build/namespaces) + + Tensor input_data(DT_FLOAT, TensorShape({1, 1, 6, 2})); + test::FillValues( + &input_data, {1.0f, 4.0f, 2.0f, 5.0f, 3.0f, 6.0f, -1.0f, -4.0f, -2.0f, + -5.0f, -3.0f, -6.0f}); + Output input_op = + Const(root.WithOpName("input_op"), Input::Initializer(input_data)); + + Tensor weights_data(DT_FLOAT, TensorShape({1, 2, 2, 2})); + test::FillValues(&weights_data, + {1.0f, 2.0f, 3.0f, 4.0f, 0.1f, 0.2f, 0.3f, 0.4f}); + Output weights_op = + Const(root.WithOpName("weights_op"), Input::Initializer(weights_data)); + + Output conv_op = DepthwiseConv2dNative(root.WithOpName("conv_op"), + input_op, weights_op, {1, 1, 1, 1}, "VALID"); + + Tensor mean_data(DT_FLOAT, TensorShape({4})); + test::FillValues(&mean_data, {10.0f, 20.0f, 30.0f, 40.0f}); + Output mean_op = + Const(root.WithOpName("mean_op"), Input::Initializer(mean_data)); + + Tensor variance_data(DT_FLOAT, TensorShape({4})); + test::FillValues(&variance_data, {0.25f, 0.5f, 0.75f, 1.0f}); + Output variance_op = Const(root.WithOpName("variance_op"), + Input::Initializer(variance_data)); + + Tensor beta_data(DT_FLOAT, TensorShape({4})); + test::FillValues(&beta_data, {0.1f, 0.6f, 1.1f, 1.6f}); + Output beta_op = + Const(root.WithOpName("beta_op"), Input::Initializer(beta_data)); + + Tensor gamma_data(DT_FLOAT, TensorShape({4})); + test::FillValues(&gamma_data, {1.0f, 2.0f, 3.0f, 4.0f}); + Output gamma_op = + Const(root.WithOpName("gamma_op"), Input::Initializer(gamma_data)); + + GraphDef original_graph_def; + TF_ASSERT_OK(root.ToGraphDef(&original_graph_def)); + + NodeDef batch_norm_node; + batch_norm_node.set_op("FusedBatchNorm"); + batch_norm_node.set_name("output"); + AddNodeInput("conv_op", &batch_norm_node); + AddNodeInput("gamma_op", &batch_norm_node); + AddNodeInput("beta_op", &batch_norm_node); + AddNodeInput("mean_op", &batch_norm_node); + AddNodeInput("variance_op", &batch_norm_node); + SetNodeAttr("T", DT_FLOAT, &batch_norm_node); + SetNodeAttr("epsilon", 0.00001f, &batch_norm_node); + SetNodeAttr("is_training", false, &batch_norm_node); + *(original_graph_def.mutable_node()->Add()) = batch_norm_node; + + std::unique_ptr original_session(NewSession(SessionOptions())); + TF_ASSERT_OK(original_session->Create(original_graph_def)); + std::vector original_outputs; + TF_ASSERT_OK(original_session->Run({}, {"output"}, {}, &original_outputs)); + + GraphDef fused_graph_def; + TF_ASSERT_OK(FoldOldBatchNorms(original_graph_def, {{}, {"output"}}, + &fused_graph_def)); + + std::unique_ptr fused_session(NewSession(SessionOptions())); + TF_ASSERT_OK(fused_session->Create(fused_graph_def)); + std::vector fused_outputs; + TF_ASSERT_OK(fused_session->Run({}, {"output"}, {}, &fused_outputs)); + + test::ExpectTensorNear(original_outputs[0], fused_outputs[0], 2e-5); + + for (const NodeDef& node : fused_graph_def.node()) { + EXPECT_NE("FusedBatchNorm", node.op()); + } + } + void TestFoldFusedBatchNormsWithConcat(const bool split) { auto root = tensorflow::Scope::NewRootScope(); using namespace ::tensorflow::ops; // NOLINT(build/namespaces) @@ -410,5 +566,13 @@ TEST_F(FoldOldBatchNormsTest, TestFoldFusedBatchNormsWithBatchToSpace) { TestFoldFusedBatchNormsWithBatchToSpace(); } +TEST_F(FoldOldBatchNormsTest, TestFoldOldBatchNormsAfterDepthwiseConv2dNative) { + TestFoldOldBatchNormsAfterDepthwiseConv2dNative(); +} + +TEST_F(FoldOldBatchNormsTest, TestFoldFusedBatchNormsAfterDepthwiseConv2dNative) { + TestFoldFusedBatchNormsAfterDepthwiseConv2dNative(); +} + } // namespace graph_transforms } // namespace tensorflow -- GitLab From 47a7c6ec8880a34c00d9ba336e4d75ba4a9cbb85 Mon Sep 17 00:00:00 2001 From: Shahzad Lone Date: Thu, 29 Nov 2018 01:20:19 -0500 Subject: [PATCH 0023/2345] Reserve because we know the size before, helps save reallocation cost. --- tensorflow/cc/tools/freeze_saved_model.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/cc/tools/freeze_saved_model.cc b/tensorflow/cc/tools/freeze_saved_model.cc index 23e9dc40d2..9469c60ec8 100644 --- a/tensorflow/cc/tools/freeze_saved_model.cc +++ b/tensorflow/cc/tools/freeze_saved_model.cc @@ -124,7 +124,9 @@ Status GetVariableNameToTensorMap( return Status::OK(); } std::vector variable_names; + variable_names.reserve(variable_names.size()); std::vector tensor_names; + tensor_names.reserve(variable_names.size()); for (const string& node_name : variable_names_set) { variable_names.push_back(node_name); NodeDef* node_def = name_to_node_map.at(node_name); -- GitLab From d45cd461242e3a27e505a67c17d5ddb7f4a84641 Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Fri, 30 Nov 2018 14:46:12 +0100 Subject: [PATCH 0024/2345] Add drop_remainder argument to bucket_by_sequence_length `tf.data.experimental.bucket_by_sequence_length` does not allow to drop the last batch in case it has fewer than `batch_size` elements. This patch does implement `drop_remainder` for `bucket_by_sequence_length` to enable thhis behaviour. `drop_remainder` is optinal and set to `False` by default to maintain compatibility. --- tensorflow/python/data/experimental/ops/grouping.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/data/experimental/ops/grouping.py b/tensorflow/python/data/experimental/ops/grouping.py index db10ea3b7f..71e4b3391f 100644 --- a/tensorflow/python/data/experimental/ops/grouping.py +++ b/tensorflow/python/data/experimental/ops/grouping.py @@ -130,7 +130,8 @@ def bucket_by_sequence_length(element_length_func, padded_shapes=None, padding_values=None, pad_to_bucket_boundary=False, - no_padding=False): + no_padding=False, + drop_remainder=False): """A transformation that buckets elements in a `Dataset` by length. Elements of the `Dataset` are grouped together by length and then are padded @@ -160,6 +161,10 @@ def bucket_by_sequence_length(element_length_func, any elements with length longer than `max(bucket_boundaries)`. no_padding: `bool`, indicates whether to pad the batch features (features need to be either of type `tf.SparseTensor` or of same shape). + drop_remainder: (Optional.) A `tf.bool` scalar `tf.Tensor`, representing + whether the last batch should be dropped in the case its has fewer than + batch_size` elements; the default behavior is not to drop the smaller + batch. Returns: A `Dataset` transformation function, which can be passed to @@ -209,7 +214,7 @@ def bucket_by_sequence_length(element_length_func, """Batch elements in dataset.""" batch_size = window_size_fn(bucket_id) if no_padding: - return grouped_dataset.batch(batch_size) + return grouped_dataset.batch(batch_size, drop_remainder=drop_remainder) none_filler = None if pad_to_bucket_boundary: err_msg = ("When pad_to_bucket_boundary=True, elements must have " @@ -227,7 +232,8 @@ def bucket_by_sequence_length(element_length_func, shapes = make_padded_shapes( padded_shapes or grouped_dataset.output_shapes, none_filler=none_filler) - return grouped_dataset.padded_batch(batch_size, shapes, padding_values) + return grouped_dataset.padded_batch(batch_size, shapes, padding_values, + drop_remainder=drop_remainder) def _apply_fn(dataset): return dataset.apply( -- GitLab From 916c5238d864e04861a58b3f77b9d29cca03b0e0 Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Sun, 2 Dec 2018 18:27:55 +0100 Subject: [PATCH 0025/2345] Add drop_reminder support to the test of bucketing of sparse tensors --- .../bucket_by_sequence_length_test.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) 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 af20e50fb9..fb2d1cf63c 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 @@ -274,11 +274,16 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): dataset = dataset.map(_to_sparse_tensor) return dataset - def _compute_expected_batches(): + def _compute_expected_batches(drop_remainder): """Computes expected batch outputs and stores in a set.""" all_expected_sparse_tensors = set() for bucket_start_len in range(min_len, max_len, bucket_size): - for batch_offset in range(0, bucket_size, batch_size): + if drop_remainder: + batch_offsets = [0] + else: + batch_offsets = range(0, bucket_size, batch_size) + + for batch_offset in batch_offsets: batch_start_len = bucket_start_len + batch_offset batch_end_len = min(batch_start_len + batch_size, bucket_start_len + bucket_size) @@ -306,16 +311,18 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): all_sparse_tensors.add(sprs_tensor) return all_sparse_tensors - dataset = _build_dataset() - boundaries = range(min_len + bucket_size + 1, max_len, bucket_size) - dataset = dataset.apply(grouping.bucket_by_sequence_length( - _element_length_fn, - boundaries, - [batch_size] * (len(boundaries) + 1), - no_padding=True)) - batches = _compute_batches(dataset) - expected_batches = _compute_expected_batches() - self.assertEqual(batches, expected_batches) + for drop_remainder in (True, False): + dataset = _build_dataset() + boundaries = range(min_len + bucket_size + 1, max_len, bucket_size) + dataset = dataset.apply(grouping.bucket_by_sequence_length( + _element_length_fn, + boundaries, + [batch_size] * (len(boundaries) + 1), + no_padding=True, + drop_remainder=drop_remainder)) + batches = _compute_batches(dataset) + expected_batches = _compute_expected_batches() + self.assertEqual(batches, expected_batches) if __name__ == "__main__": -- GitLab From d372f19f132e8f037390a9a2a6fde7bafa2620b9 Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Sun, 2 Dec 2018 18:29:03 +0100 Subject: [PATCH 0026/2345] Add a drop_reminder version of the testBucket test Squeezing both cases into a single test would make it way to complicated. Therefore, I created a separate test. --- .../bucket_by_sequence_length_test.py | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) 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 fb2d1cf63c..7b2e922d55 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 @@ -71,6 +71,117 @@ def _get_record_shape(sparse): class BucketBySequenceLengthTest(test_base.DatasetTestBase): + def testBucketDropReminder(self): + + boundaries = [10, 20, 30] + batch_sizes = [10, 8, 4, 2] + lengths = [8, 13, 25, 35] + + n_bucket_elements = [28, 7, 6, 5] + n_expected_batches = 5 + + # Expected sequence lengths of the individual batches. + expected_lengths = [] + + # Expected sum of all batches with an equal sequence length. + # : + expected_sums = dict() + + # Expected batch sizes of batches depending on the sequence length. + # : [batch1_size, ..., batchN_size] + expected_batch_sizes = dict() + + for length, batch_size, bucket_elements in zip(lengths, batch_sizes, n_bucket_elements): + # Calculate the expected sum across all batches of a specific sequence length. + expected_sums[length] = (bucket_elements - bucket_elements % batch_size) * length + # Calculate the expected occurrence of individual batch sizes. + expected_batch_sizes[length] = [batch_size] * (bucket_elements // batch_size) + # Calculate the expected occurence of individual sequence lengths. + expected_lengths.extend([length] * (bucket_elements // batch_size)) + + def build_dataset(sparse): + def _generator(): + # Produce 1 batch for each bucket + elements = [] + for bucket_elements, length in zip(n_bucket_elements, lengths): + # Using only full sequences (opposed to the strategy employed in `testBucket`) makes + # checking the sum a lot easier. + record_len = length + for _ in range(bucket_elements): + elements.append([1] * record_len) + random.shuffle(elements) + for el in elements: + yield (_format_record(el, sparse),) + dataset = dataset_ops.Dataset.from_generator( + _generator, + (_get_record_type(sparse),), + (_get_record_shape(sparse),)) + if sparse: + dataset = dataset.map(lambda x: (_to_sparse_tensor(x),)) + return dataset + + def _test_bucket_by_padding(no_padding): + dataset = build_dataset(sparse=no_padding) + dataset = dataset.apply( + grouping.bucket_by_sequence_length( + _element_length_fn, + boundaries, + batch_sizes, + no_padding=no_padding, + drop_remainder=True)) + batch, = dataset.make_one_shot_iterator().get_next() + + with self.cached_session() as sess: + batches = [] + for _ in range(n_expected_batches): + batches.append(self.evaluate(batch)) + with self.assertRaises(errors.OutOfRangeError): + self.evaluate(batch) + + generated_lengths = [] + + # : + generated_sums = dict() + + # : [, ...] + generated_batch_sizes = dict() + + for length, batch_size, bucket_elements in zip(lengths, batch_sizes, n_bucket_elements): + # Initialize the sum across all batches. + expected_sums[length] = 0 + # Initialize the individual batch sizes. + expected_batch_sizes[length] = [] + + for batch in batches: + shape = batch.dense_shape if no_padding else batch.shape + length = shape[1] + generated_lengths.append(length) + + batch_size = shape[0] + generated_batch_sizes[length].append(batch_size) + + batch_sum = batch.values.sum() if no_padding else batch.sum() + generated_sums[length] += batch_sum + + for l in lengths: + # Make sure the sum of the batch contents is correct for the individual sequence lengths. + self.assertEqual(generated_sums[l], expected_sums[l], + 'Tensor sums did not match! expected: {}, generated: {}' + .format(expected_sums, generated_sums)) + + # Make sure the individual batch sizes are generated as expected. + self.assertEqual(sorted(generated_batch_sizes[l]), sorted(expected_batch_sizes[l]), + 'Batch-sizes did not match! expected: {}, generated: {}' + .format(sorted(expected_batch_sizes[l]), sorted(generated_batch_sizes[l]))) + + # Make sure the generated sequence lengths appear as often as expected. + self.assertEqual(sorted(generated_lengths), sorted(expected_lengths), + 'The generated sequence lengths did not match! expected: {}, generated: {}' + .format(sorted(expected_lengths), sorted(generated_lengths))) + + for no_padding in (True, False): + _test_bucket_by_padding(no_padding) + def testBucket(self): boundaries = [10, 20, 30] -- GitLab From 68426789549cb9d2edc8726fc5edabf4f221bd9b Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Sun, 2 Dec 2018 18:40:12 +0100 Subject: [PATCH 0027/2345] Fix pylint warnings * Missing argument drop_remainder in the testBucketSparse test. * Insert line breaks --- .../bucket_by_sequence_length_test.py | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) 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 7b2e922d55..5b96101b7c 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 @@ -91,11 +91,15 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): # : [batch1_size, ..., batchN_size] expected_batch_sizes = dict() - for length, batch_size, bucket_elements in zip(lengths, batch_sizes, n_bucket_elements): + for length, batch_size, bucket_elements in zip(lengths, + batch_sizes, + n_bucket_elements): # Calculate the expected sum across all batches of a specific sequence length. - expected_sums[length] = (bucket_elements - bucket_elements % batch_size) * length + expected_sums[length] = \ + (bucket_elements - bucket_elements % batch_size) * length # Calculate the expected occurrence of individual batch sizes. - expected_batch_sizes[length] = [batch_size] * (bucket_elements // batch_size) + expected_batch_sizes[length] = \ + [batch_size] * (bucket_elements // batch_size) # Calculate the expected occurence of individual sequence lengths. expected_lengths.extend([length] * (bucket_elements // batch_size)) @@ -146,11 +150,13 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): # : [, ...] generated_batch_sizes = dict() - for length, batch_size, bucket_elements in zip(lengths, batch_sizes, n_bucket_elements): - # Initialize the sum across all batches. - expected_sums[length] = 0 - # Initialize the individual batch sizes. - expected_batch_sizes[length] = [] + for length, batch_size, bucket_elements in zip(lengths, + batch_sizes, + n_bucket_elements): + # Initialize the sum across all batches. + expected_sums[length] = 0 + # Initialize the individual batch sizes. + expected_batch_sizes[length] = [] for batch in batches: shape = batch.dense_shape if no_padding else batch.shape @@ -165,19 +171,26 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): for l in lengths: # Make sure the sum of the batch contents is correct for the individual sequence lengths. - self.assertEqual(generated_sums[l], expected_sums[l], - 'Tensor sums did not match! expected: {}, generated: {}' + self.assertEqual(generated_sums[l], + expected_sums[l], + 'Tensor sums did not match! ' + 'expected: {}, generated: {}' .format(expected_sums, generated_sums)) # Make sure the individual batch sizes are generated as expected. - self.assertEqual(sorted(generated_batch_sizes[l]), sorted(expected_batch_sizes[l]), - 'Batch-sizes did not match! expected: {}, generated: {}' - .format(sorted(expected_batch_sizes[l]), sorted(generated_batch_sizes[l]))) + self.assertEqual(sorted(generated_batch_sizes[l]), + sorted(expected_batch_sizes[l]), + 'Batch-sizes did not match! ' + 'expected: {}, generated: {}' + .format(sorted(expected_batch_sizes[l]), + sorted(generated_batch_sizes[l]))) # Make sure the generated sequence lengths appear as often as expected. self.assertEqual(sorted(generated_lengths), sorted(expected_lengths), - 'The generated sequence lengths did not match! expected: {}, generated: {}' - .format(sorted(expected_lengths), sorted(generated_lengths))) + 'The generated sequence lengths did not match! ' + 'expected: {}, generated: {}' + .format(sorted(expected_lengths), + sorted(generated_lengths))) for no_padding in (True, False): _test_bucket_by_padding(no_padding) @@ -432,7 +445,7 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): no_padding=True, drop_remainder=drop_remainder)) batches = _compute_batches(dataset) - expected_batches = _compute_expected_batches() + expected_batches = _compute_expected_batches(drop_remainder) self.assertEqual(batches, expected_batches) -- GitLab From 4ddd2ab07fdfea81a53d110fee19bb88c41251a0 Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Mon, 3 Dec 2018 13:58:58 +0100 Subject: [PATCH 0028/2345] Fix copy paste bug Pasted the wrong variable names from the docker test image. --- .../kernel_tests/bucket_by_sequence_length_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5b96101b7c..fab79619a0 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 @@ -154,9 +154,9 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): batch_sizes, n_bucket_elements): # Initialize the sum across all batches. - expected_sums[length] = 0 + generated_sums[length] = 0 # Initialize the individual batch sizes. - expected_batch_sizes[length] = [] + generated_batch_sizes[length] = [] for batch in batches: shape = batch.dense_shape if no_padding else batch.shape -- GitLab From c425f34ad4ef83055bb6ca5a9542d7320558c598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=9C=E5=B1=85?= Date: Thu, 6 Dec 2018 22:04:17 +0800 Subject: [PATCH 0029/2345] clang-format-3.6 regenerated files in this PR --- .../graph_transforms/fold_batch_norms.cc | 12 ++++----- .../graph_transforms/fold_batch_norms_test.cc | 8 +++--- .../graph_transforms/fold_old_batch_norms.cc | 21 +++++++-------- .../fold_old_batch_norms_test.cc | 27 ++++++++++--------- 4 files changed, 33 insertions(+), 35 deletions(-) diff --git a/tensorflow/tools/graph_transforms/fold_batch_norms.cc b/tensorflow/tools/graph_transforms/fold_batch_norms.cc index 2d7bb65430..0a37620700 100644 --- a/tensorflow/tools/graph_transforms/fold_batch_norms.cc +++ b/tensorflow/tools/graph_transforms/fold_batch_norms.cc @@ -76,11 +76,10 @@ Status FoldBatchNorms(const GraphDef& input_graph_def, int64 weights_cols; if (conv_node.op() == "Conv2D") { weights_cols = weights.shape().dim_size(3); - } - else if (conv_node.op() == "DepthwiseConv2dNative") { - weights_cols = weights.shape().dim_size(2) * weights.shape().dim_size(3); - } - else { + } else if (conv_node.op() == "DepthwiseConv2dNative") { + weights_cols = + weights.shape().dim_size(2) * weights.shape().dim_size(3); + } else { weights_cols = weights.shape().dim_size(1); } if ((mul_values.shape().dims() != 1) || @@ -96,7 +95,8 @@ Status FoldBatchNorms(const GraphDef& input_graph_def, auto scaled_weights_vector = scaled_weights.flat(); for (int64 row = 0; row < weights_vector.dimension(0); ++row) { scaled_weights_vector(row) = - weights_vector(row) * mul_values.flat()(row % weights_cols); + weights_vector(row) * + mul_values.flat()(row % weights_cols); } // Construct the new nodes. diff --git a/tensorflow/tools/graph_transforms/fold_batch_norms_test.cc b/tensorflow/tools/graph_transforms/fold_batch_norms_test.cc index 2b5326799e..885fbd59b7 100644 --- a/tensorflow/tools/graph_transforms/fold_batch_norms_test.cc +++ b/tensorflow/tools/graph_transforms/fold_batch_norms_test.cc @@ -104,10 +104,10 @@ class FoldBatchNormsTest : public ::testing::Test { Output weights_op = Const(root.WithOpName("weights_op"), Input::Initializer(weights_data)); - Output conv_op = DepthwiseConv2dNative(root.WithOpName("conv_op"), input_op, weights_op, - {1, 1, 1, 1}, "VALID"); + Output conv_op = DepthwiseConv2dNative(root.WithOpName("conv_op"), input_op, + weights_op, {1, 1, 1, 1}, "VALID"); - Tensor mul_values_data(DT_FLOAT, TensorShape({4})); + Tensor mul_values_data(DT_FLOAT, TensorShape({4})); test::FillValues(&mul_values_data, {2.0f, 3.0f, 4.0f, 5.0f}); Output mul_values_op = Const(root.WithOpName("mul_values"), Input::Initializer(mul_values_data)); @@ -136,7 +136,7 @@ class FoldBatchNormsTest : public ::testing::Test { for (const NodeDef& node : fused_graph_def.node()) { EXPECT_NE("Mul", node.op()); } - } + } void TestFoldBatchNormsConv2DShared() { auto root = tensorflow::Scope::NewRootScope(); diff --git a/tensorflow/tools/graph_transforms/fold_old_batch_norms.cc b/tensorflow/tools/graph_transforms/fold_old_batch_norms.cc index 413361b616..8c67bd23b5 100644 --- a/tensorflow/tools/graph_transforms/fold_old_batch_norms.cc +++ b/tensorflow/tools/graph_transforms/fold_old_batch_norms.cc @@ -32,9 +32,9 @@ Status ErrorIfNotVector(const Tensor& input, const string& input_name, int expected_width) { if ((input.shape().dims() != 1) || (input.shape().dim_size(0) != expected_width)) { - return errors::InvalidArgument( - input_name, - " input to batch norm has bad shape: ", input.shape().DebugString()); + return errors::InvalidArgument(input_name, + " input to batch norm has bad shape: ", + input.shape().DebugString()); } return Status::OK(); } @@ -119,11 +119,9 @@ Status FuseScaleOffsetToConvWeights(const std::vector& scale_values, int64 weights_cols; if (conv_node.op() == "Conv2D") { weights_cols = weights.shape().dim_size(3); - } - else if (conv_node.op() == "DepthwiseConv2dNative") { + } else if (conv_node.op() == "DepthwiseConv2dNative") { weights_cols = weights.shape().dim_size(2) * weights.shape().dim_size(3); - } - else { + } else { weights_cols = weights.shape().dim_size(1); } CHECK_EQ(weights_cols, scale_values.size()); @@ -134,7 +132,7 @@ Status FuseScaleOffsetToConvWeights(const std::vector& scale_values, auto scaled_weights_vector = scaled_weights.flat(); for (int64 row = 0; row < weights_vector.dimension(0); ++row) { scaled_weights_vector(row) = - weights_vector(row) * scale_values[row % weights_cols]; + weights_vector(row) * scale_values[row % weights_cols]; } // Figure out the remaining bias to add on. Tensor bias_offset(DT_FLOAT, {weights_cols}); @@ -193,7 +191,7 @@ Status FuseBatchNormWithConv(const NodeMatch& match, } Status FuseBatchNormWithBatchToSpace(const NodeMatch& match, - std::vector* new_nodes) { + std::vector* new_nodes) { // Calculate the scale and offset values to apply. std::vector scale_values; std::vector offset_values; @@ -208,9 +206,8 @@ Status FuseBatchNormWithBatchToSpace(const NodeMatch& match, const NodeDef& conv_node = conv_node_match.node; string biasadd_name = conv_node.name() + "/biasadd"; - TF_RETURN_IF_ERROR( - FuseScaleOffsetToConvWeights(scale_values, offset_values, conv_node_match, - biasadd_name , new_nodes)); + TF_RETURN_IF_ERROR(FuseScaleOffsetToConvWeights( + scale_values, offset_values, conv_node_match, biasadd_name, new_nodes)); NodeDef new_batch_to_space_node = batch_to_space_node; // reuse batch_norm node name diff --git a/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc b/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc index 45637cf9d1..925f37745c 100644 --- a/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc +++ b/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc @@ -138,8 +138,8 @@ class FoldOldBatchNormsTest : public ::testing::Test { Output weights_op = Const(root.WithOpName("weights_op"), Input::Initializer(weights_data)); - Output conv_op = DepthwiseConv2dNative(root.WithOpName("conv_op"), - input_op, weights_op, {1, 1, 1, 1}, "VALID"); + Output conv_op = DepthwiseConv2dNative(root.WithOpName("conv_op"), input_op, + weights_op, {1, 1, 1, 1}, "VALID"); Tensor mean_data(DT_FLOAT, TensorShape({4})); test::FillValues(&mean_data, {10.0f, 20.0f, 30.0f, 40.0f}); @@ -164,7 +164,6 @@ class FoldOldBatchNormsTest : public ::testing::Test { GraphDef original_graph_def; TF_ASSERT_OK(root.ToGraphDef(&original_graph_def)); - NodeDef batch_norm_node; batch_norm_node.set_op("BatchNormWithGlobalNormalization"); batch_norm_node.set_name("output"); @@ -198,7 +197,7 @@ class FoldOldBatchNormsTest : public ::testing::Test { for (const NodeDef& node : fused_graph_def.node()) { EXPECT_NE("BatchNormWithGlobalNormalization", node.op()); } - } + } void TestFoldFusedBatchNorms() { auto root = tensorflow::Scope::NewRootScope(); @@ -294,8 +293,8 @@ class FoldOldBatchNormsTest : public ::testing::Test { Output weights_op = Const(root.WithOpName("weights_op"), Input::Initializer(weights_data)); - Output conv_op = DepthwiseConv2dNative(root.WithOpName("conv_op"), - input_op, weights_op, {1, 1, 1, 1}, "VALID"); + Output conv_op = DepthwiseConv2dNative(root.WithOpName("conv_op"), input_op, + weights_op, {1, 1, 1, 1}, "VALID"); Tensor mean_data(DT_FLOAT, TensorShape({4})); test::FillValues(&mean_data, {10.0f, 20.0f, 30.0f, 40.0f}); @@ -477,16 +476,17 @@ void TestFoldFusedBatchNormsWithBatchToSpace() { Tensor block_shape_data(DT_INT32, TensorShape({2})); test::FillValues(&block_shape_data, {1, 2}); - Output block_shape_op = - Const(root.WithOpName("block_shape_op"), Input::Initializer(block_shape_data)); + Output block_shape_op = Const(root.WithOpName("block_shape_op"), + Input::Initializer(block_shape_data)); Tensor crops_data(DT_INT32, TensorShape({2, 2})); test::FillValues(&crops_data, {0, 0, 0, 1}); Output crops_op = Const(root.WithOpName("crops_op"), Input::Initializer(crops_data)); - Output batch_to_space_op = BatchToSpaceND(root.WithOpName("batch_to_space_op"), - conv_op, block_shape_op, crops_data); + Output batch_to_space_op = + BatchToSpaceND(root.WithOpName("batch_to_space_op"), conv_op, + block_shape_op, crops_data); Tensor mean_data(DT_FLOAT, TensorShape({2})); test::FillValues(&mean_data, {10.0f, 20.0f}); @@ -495,8 +495,8 @@ void TestFoldFusedBatchNormsWithBatchToSpace() { Tensor variance_data(DT_FLOAT, TensorShape({2})); test::FillValues(&variance_data, {0.25f, 0.5f}); - Output variance_op = Const(root.WithOpName("variance_op"), - Input::Initializer(variance_data)); + Output variance_op = + Const(root.WithOpName("variance_op"), Input::Initializer(variance_data)); Tensor beta_data(DT_FLOAT, TensorShape({2})); test::FillValues(&beta_data, {0.1f, 0.6f}); @@ -570,7 +570,8 @@ TEST_F(FoldOldBatchNormsTest, TestFoldOldBatchNormsAfterDepthwiseConv2dNative) { TestFoldOldBatchNormsAfterDepthwiseConv2dNative(); } -TEST_F(FoldOldBatchNormsTest, TestFoldFusedBatchNormsAfterDepthwiseConv2dNative) { +TEST_F(FoldOldBatchNormsTest, + TestFoldFusedBatchNormsAfterDepthwiseConv2dNative) { TestFoldFusedBatchNormsAfterDepthwiseConv2dNative(); } -- GitLab From 562b078b836b215b761fc91a177937cfcbdd0ea0 Mon Sep 17 00:00:00 2001 From: Nayana-ibm Date: Thu, 6 Dec 2018 11:13:12 -0500 Subject: [PATCH 0030/2345] resolve ImportError: cannot import name cloud on s390x - skip import clound --- tensorflow/contrib/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/__init__.py b/tensorflow/contrib/__init__.py index 4f1a2a5693..a23e331f4f 100644 --- a/tensorflow/contrib/__init__.py +++ b/tensorflow/contrib/__init__.py @@ -20,13 +20,14 @@ from __future__ import division from __future__ import print_function import os +import platform # Add projects here, they will show up under tf.contrib. from tensorflow.contrib import autograph from tensorflow.contrib import batching from tensorflow.contrib import bayesflow from tensorflow.contrib import checkpoint -if os.name != "nt": +if os.name != "nt" and platform.machine() != "s390x": from tensorflow.contrib import cloud from tensorflow.contrib import cluster_resolver from tensorflow.contrib import coder -- GitLab From aff52125a0d172f4c3ae8ea7c170ca0f6d97e97d Mon Sep 17 00:00:00 2001 From: Andy Craze Date: Fri, 7 Dec 2018 20:37:37 -0800 Subject: [PATCH 0031/2345] Update tables_initializer to link to guide Resolves #20629 --- tensorflow/python/ops/lookup_ops.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/python/ops/lookup_ops.py b/tensorflow/python/ops/lookup_ops.py index 758cb8041d..9302696a45 100644 --- a/tensorflow/python/ops/lookup_ops.py +++ b/tensorflow/python/ops/lookup_ops.py @@ -64,6 +64,8 @@ def initialize_all_tables(name="init_all_tables"): @tf_export(v1=["initializers.tables_initializer", "tables_initializer"]) def tables_initializer(name="init_all_tables"): """Returns an Op that initializes all tables of the default graph. + See the [Low Level Intro](https://www.tensorflow.org/guide/low_level_intro#feature_columns) + guide, for an example of usage. Args: name: Optional name for the initialization op. -- GitLab From 035955852708fba07565ead298cd54ad64ab1a55 Mon Sep 17 00:00:00 2001 From: Jacky Ko Date: Sun, 9 Dec 2018 18:33:50 +0800 Subject: [PATCH 0032/2345] static lib name change --- tensorflow/contrib/cmake/external/abseil_cpp.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/cmake/external/abseil_cpp.cmake b/tensorflow/contrib/cmake/external/abseil_cpp.cmake index 8b76f37858..539c5cbb76 100644 --- a/tensorflow/contrib/cmake/external/abseil_cpp.cmake +++ b/tensorflow/contrib/cmake/external/abseil_cpp.cmake @@ -48,7 +48,7 @@ else (systemlib_ABSEIL_CPP) set(abseil_cpp_STATIC_LIBRARIES ${abseil_cpp_BUILD}/absl/base/Release/absl_base.lib ${abseil_cpp_BUILD}/absl/base/Release/absl_dynamic_annotations.lib - ${abseil_cpp_BUILD}/absl/base/Release/absl_internal_malloc_internal.lib + ${abseil_cpp_BUILD}/absl/base/Release/absl_malloc_internal.lib ${abseil_cpp_BUILD}/absl/strings/Release/absl_strings.lib ${abseil_cpp_BUILD}/absl/strings/Release/str_format_internal.lib ${abseil_cpp_BUILD}/absl/types/Release/absl_bad_optional_access.lib) @@ -94,6 +94,8 @@ else (systemlib_ABSEIL_CPP) ) include_directories(${abseil_cpp_INCLUDE_DIR}) + message(STATUS ${abseil_cpp_INCLUDE_DIR}) + list(APPEND tensorflow_EXTERNAL_LIBRARIES ${abseil_cpp_STATIC_LIBRARIES}) list(APPEND tensorflow_EXTERNAL_DEPENDENCIES abseil_cpp_build) -- GitLab From 5a4871af05392c2e5744515a4c67bc6f0f420943 Mon Sep 17 00:00:00 2001 From: fo40225 Date: Sat, 1 Sep 2018 00:58:14 +0800 Subject: [PATCH 0033/2345] fix AttributeError: 'module' object has no attribute '???'on windows python 2.7 --- tensorflow/api_template_v1.__init__.py | 3 ++- tensorflow/contrib/cmake/python_modules.txt | 3 +++ tensorflow/contrib/cmake/tf_python.cmake | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tensorflow/api_template_v1.__init__.py b/tensorflow/api_template_v1.__init__.py index 65bdb6cb1b..b9b21bad50 100644 --- a/tensorflow/api_template_v1.__init__.py +++ b/tensorflow/api_template_v1.__init__.py @@ -40,7 +40,8 @@ if '__all__' in vars(): vars()['__all__'].append('contrib') from tensorflow.python.platform import flags # pylint: disable=g-import-not-at-top -app.flags = flags # pylint: disable=undefined-variable +from tensorflow.python.platform import app # pylint: disable=g-import-not-at-top +app.flags = flags # Make sure directory containing top level submodules is in # the __path__ so that "from tensorflow.foo import bar" works. diff --git a/tensorflow/contrib/cmake/python_modules.txt b/tensorflow/contrib/cmake/python_modules.txt index 96160568fa..21ae9a08a6 100644 --- a/tensorflow/contrib/cmake/python_modules.txt +++ b/tensorflow/contrib/cmake/python_modules.txt @@ -1,6 +1,9 @@ # python_sanity_test.py will complain about invalid or missing entries # problematic entries can be commented for temporary whitelisting tensorflow +tensorflow/compiler +tensorflow/compiler/xla +tensorflow/compiler/xla/service tensorflow/core tensorflow/core/example tensorflow/core/framework diff --git a/tensorflow/contrib/cmake/tf_python.cmake b/tensorflow/contrib/cmake/tf_python.cmake index 8faccf8d55..1fe8795ddf 100755 --- a/tensorflow/contrib/cmake/tf_python.cmake +++ b/tensorflow/contrib/cmake/tf_python.cmake @@ -802,6 +802,7 @@ add_custom_command( # tensorflow/__init__.py depends on files generated in this step. So, remove it while # this step is running since the files aren't there yet. COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/__init__.py + COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/__init__.py # Run create_python_api.py to generate API init files. COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}/tf_python "${PY_RUNTIME_ENV}" ${PYTHON_EXECUTABLE} -- GitLab From cc69b82bbcc68c21db839cca4f6fec043f6005aa Mon Sep 17 00:00:00 2001 From: Jacky Ko Date: Sun, 9 Dec 2018 19:08:32 +0800 Subject: [PATCH 0034/2345] abseil use master branch --- tensorflow/contrib/cmake/external/abseil_cpp.cmake | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/cmake/external/abseil_cpp.cmake b/tensorflow/contrib/cmake/external/abseil_cpp.cmake index 539c5cbb76..f64b60fd5e 100644 --- a/tensorflow/contrib/cmake/external/abseil_cpp.cmake +++ b/tensorflow/contrib/cmake/external/abseil_cpp.cmake @@ -39,8 +39,8 @@ else (systemlib_ABSEIL_CPP) include (ExternalProject) set(abseil_cpp_INCLUDE_DIR ${CMAKE_BINARY_DIR}/abseil_cpp/src/abseil_cpp_build) - set(abseil_cpp_URL https://github.com/abseil/abseil-cpp/archive/e01d95528ea2137a4a27a88d1f57c6cb260aafed.tar.gz) - set(abseil_cpp_HASH SHA256=84043ed402d2a2a6ba4cdddb7e85118b1158fd81fe4ac3a14adc343d054c1e2e) + set(abseil_cpp_URL https://github.com/abseil/abseil-cpp.git) + set(abseil_cpp_TAG master) set(abseil_cpp_BUILD ${CMAKE_BINARY_DIR}/abseil_cpp/src/abseil_cpp_build) if(WIN32) @@ -48,7 +48,7 @@ else (systemlib_ABSEIL_CPP) set(abseil_cpp_STATIC_LIBRARIES ${abseil_cpp_BUILD}/absl/base/Release/absl_base.lib ${abseil_cpp_BUILD}/absl/base/Release/absl_dynamic_annotations.lib - ${abseil_cpp_BUILD}/absl/base/Release/absl_malloc_internal.lib + ${abseil_cpp_BUILD}/absl/base/Release/absl_internal_malloc_internal.lib ${abseil_cpp_BUILD}/absl/strings/Release/absl_strings.lib ${abseil_cpp_BUILD}/absl/strings/Release/str_format_internal.lib ${abseil_cpp_BUILD}/absl/types/Release/absl_bad_optional_access.lib) @@ -79,8 +79,7 @@ else (systemlib_ABSEIL_CPP) ExternalProject_Add(abseil_cpp_build PREFIX abseil_cpp - URL ${abseil_cpp_URL} - URL_HASH ${abseil_cpp_HASH} + GIT_REPOSITORY ${abseil_cpp_URL} DOWNLOAD_DIR "${DOWNLOAD_LOCATION}" BUILD_IN_SOURCE 1 BUILD_BYPRODUCTS ${abseil_cpp_STATIC_LIBRARIES} -- GitLab From 956b77a2a8f8db7a57b818bc0c06669fd395d56d Mon Sep 17 00:00:00 2001 From: Jacky Ko Date: Sun, 9 Dec 2018 19:31:05 +0800 Subject: [PATCH 0035/2345] abseil lib linkage update --- tensorflow/contrib/cmake/external/abseil_cpp.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/contrib/cmake/external/abseil_cpp.cmake b/tensorflow/contrib/cmake/external/abseil_cpp.cmake index f64b60fd5e..eefa7d3f03 100644 --- a/tensorflow/contrib/cmake/external/abseil_cpp.cmake +++ b/tensorflow/contrib/cmake/external/abseil_cpp.cmake @@ -49,6 +49,8 @@ else (systemlib_ABSEIL_CPP) ${abseil_cpp_BUILD}/absl/base/Release/absl_base.lib ${abseil_cpp_BUILD}/absl/base/Release/absl_dynamic_annotations.lib ${abseil_cpp_BUILD}/absl/base/Release/absl_internal_malloc_internal.lib + ${abseil_cpp_BUILD}/absl/base/Release/absl_internal_throw_delegate.lib + ${abseil_cpp_BUILD}/absl/numeric/Release/absl_int128.lib ${abseil_cpp_BUILD}/absl/strings/Release/absl_strings.lib ${abseil_cpp_BUILD}/absl/strings/Release/str_format_internal.lib ${abseil_cpp_BUILD}/absl/types/Release/absl_bad_optional_access.lib) -- GitLab From 7578e120de2a3a5282ced8d41881f19363f83466 Mon Sep 17 00:00:00 2001 From: Dan Jarvis Date: Thu, 23 Nov 2017 13:06:02 -0500 Subject: [PATCH 0036/2345] Fix crash on closing the app when classifier failed to initialize When testing on an API 21 emulator, the classifier fails to initialize. `E/TfLiteCameraDemo: Failed to initialize an image classifier.` In this situation, the app crashes when pressing Back to exit. Here's the cause: ``` java.lang.NullPointerException: Attempt to invoke virtual method 'void com.example.android.tflitecamerademo.ImageClassifier.close()' on a null object reference at com.example.android.tflitecamerademo.Camera2BasicFragment.onDestroy(Camera2BasicFragment.java:331) at android.app.Fragment.performDestroy(Fragment.java:2266) ``` The fix is to check for null before calling `.close()`. I'll investigate why the classifier is failing to initialize separately. :-) --- .../android/tflitecamerademo/Camera2BasicFragment.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java b/tensorflow/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java index 165d335101..a7b3440536 100644 --- a/tensorflow/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java +++ b/tensorflow/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java @@ -476,7 +476,9 @@ public class Camera2BasicFragment extends Fragment @Override public void onDestroy() { - classifier.close(); + if (classifier != null) { + classifier.close(); + } super.onDestroy(); } -- GitLab From 16380e05087b41c39547e3f05e1ce85cea44efb2 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 11 Dec 2018 00:40:26 +0000 Subject: [PATCH 0037/2345] Fix warning caused by to_int32 While running tf.keras I noticed the following warning: ``` WARNING:tensorflow:From /usr/local/lib/python2.7/dist-packages/tensorflow/python/ops/math_ops.py:3064: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version. Instructions for updating: Use tf.cast instead. ``` This fix fixes the warning caused by deprecated to_int32. Signed-off-by: Yong Tang --- tensorflow/python/ops/math_ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/math_ops.py b/tensorflow/python/ops/math_ops.py index e2b634ee8f..1d85fb0193 100644 --- a/tensorflow/python/ops/math_ops.py +++ b/tensorflow/python/ops/math_ops.py @@ -3061,8 +3061,8 @@ def reduced_shape(input_shape, axes): input_shape[axes] = 1 return input_shape - input_shape = to_int32(input_shape) # [2, 3, 5, 7] - axes = to_int32(axes) # [1, 2] + input_shape = cast(input_shape, dtypes.int32) # [2, 3, 5, 7] + axes = cast(axes, dtypes.int32) # [1, 2] input_rank = array_ops.size(input_shape) # 4 axes = (axes + input_rank) % input_rank -- GitLab From 16f454f95e9d9823145d6b00a7e007afd0ea569b Mon Sep 17 00:00:00 2001 From: Luke Han Date: Tue, 11 Dec 2018 11:03:05 +0900 Subject: [PATCH 0038/2345] fix typo in scatter_nd_add docstring --- tensorflow/python/ops/state_ops.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/ops/state_ops.py b/tensorflow/python/ops/state_ops.py index 3ac69c1c20..25b31c698e 100644 --- a/tensorflow/python/ops/state_ops.py +++ b/tensorflow/python/ops/state_ops.py @@ -462,9 +462,8 @@ def scatter_nd_add(ref, indices, updates, use_locking=False, name=None): updates: A `Tensor`. Must have the same type as `ref`. A tensor of updated values to add to ref. use_locking: An optional `bool`. Defaults to `False`. - An optional bool. Defaults to True. If True, the assignment will - be protected by a lock; otherwise the behavior is undefined, - but may exhibit less contention. + If True, the assignment will be protected by a lock; + otherwise the behavior is undefined, but may exhibit less contention. name: A name for the operation (optional). Returns: -- GitLab From f0f7ed323983ab2cb157bf782950b6e2238b9f6b Mon Sep 17 00:00:00 2001 From: Jacky Ko Date: Tue, 11 Dec 2018 20:35:50 +0800 Subject: [PATCH 0039/2345] add abseil time library linking --- tensorflow/contrib/cmake/external/abseil_cpp.cmake | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/cmake/external/abseil_cpp.cmake b/tensorflow/contrib/cmake/external/abseil_cpp.cmake index b85fd48f0f..6c6a5df7f7 100644 --- a/tensorflow/contrib/cmake/external/abseil_cpp.cmake +++ b/tensorflow/contrib/cmake/external/abseil_cpp.cmake @@ -31,8 +31,8 @@ if (systemlib_ABSEIL_CPP) message(STATUS " abseil_cpp includes: ${ABSEIL_CPP_INCLUDE_DIR}") message(STATUS " abseil_cpp libraries: ${ABSEIL_CPP_LIBRARIES}") - add_custom_target(abseil_cpp) - list(APPEND tensorflow_EXTERNAL_DEPENDENCIES abseil_cpp) + add_custom_target(abseil_cpp_build) + list(APPEND tensorflow_EXTERNAL_DEPENDENCIES abseil_cpp_build) else (systemlib_ABSEIL_CPP) @@ -53,6 +53,7 @@ else (systemlib_ABSEIL_CPP) ${abseil_cpp_BUILD}/absl/numeric/Release/absl_int128.lib ${abseil_cpp_BUILD}/absl/strings/Release/absl_strings.lib ${abseil_cpp_BUILD}/absl/strings/Release/str_format_internal.lib + ${abseil_cpp_BUILD}/absl/time/Release/absl_time.lib ${abseil_cpp_BUILD}/absl/types/Release/absl_bad_optional_access.lib) else() set(abseil_cpp_STATIC_LIBRARIES @@ -64,6 +65,7 @@ else (systemlib_ABSEIL_CPP) ${abseil_cpp_BUILD}/absl/numeric/absl_int128.lib ${abseil_cpp_BUILD}/absl/strings/absl_strings.lib ${abseil_cpp_BUILD}/absl/strings/str_format_internal.lib + ${abseil_cpp_BUILD}/absl/time/absl_time.lib ${abseil_cpp_BUILD}/absl/types/absl_bad_optional_access.lib) endif() else() @@ -76,14 +78,18 @@ else (systemlib_ABSEIL_CPP) ${abseil_cpp_BUILD}/absl/numeric/libabsl_int128.a ${abseil_cpp_BUILD}/absl/strings/libabsl_strings.a ${abseil_cpp_BUILD}/absl/strings/libstr_format_internal.a + ${abseil_cpp_BUILD}/absl/time/libabsl_time.a ${abseil_cpp_BUILD}/absl/types/libabsl_bad_optional_access.a) endif() - ExternalProject_Add(abseil_cpp + ExternalProject_Add(abseil_cpp_build PREFIX abseil_cpp GIT_REPOSITORY ${abseil_cpp_URL} DOWNLOAD_DIR "${DOWNLOAD_LOCATION}" + BUILD_IN_SOURCE 1 BUILD_BYPRODUCTS ${abseil_cpp_STATIC_LIBRARIES} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Release + COMMAND ${CMAKE_COMMAND} --build . --config Release INSTALL_COMMAND "" CMAKE_CACHE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=${tensorflow_ENABLE_POSITION_INDEPENDENT_CODE} @@ -96,6 +102,6 @@ else (systemlib_ABSEIL_CPP) list(APPEND tensorflow_EXTERNAL_LIBRARIES ${abseil_cpp_STATIC_LIBRARIES}) - list(APPEND tensorflow_EXTERNAL_DEPENDENCIES abseil_cpp) + list(APPEND tensorflow_EXTERNAL_DEPENDENCIES abseil_cpp_build) endif (systemlib_ABSEIL_CPP) \ No newline at end of file -- GitLab From 568db5fb164092eaf2863f0e4a2fdb48533d76f9 Mon Sep 17 00:00:00 2001 From: Siju Samuel Date: Mon, 19 Nov 2018 07:20:36 +0530 Subject: [PATCH 0040/2345] Abs, Ceil ops initial --- tensorflow/lite/build_def.bzl | 1 + tensorflow/lite/builtin_ops.h | 1 + .../lite/core/api/flatbuffer_conversions.cc | 2 + .../lite/delegates/nnapi/nnapi_delegate.cc | 20 ++++ .../delegates/nnapi/nnapi_delegate_test.cc | 109 ++++++++++++++++++ .../writer/option_writer_generator.cc | 2 + tensorflow/lite/g3doc/tf_ops_compatibility.md | 22 ++++ tensorflow/lite/kernels/BUILD | 34 ++++++ tensorflow/lite/kernels/abs.cc | 59 ++++++++++ tensorflow/lite/kernels/abs_test.cc | 94 +++++++++++++++ tensorflow/lite/kernels/ceil.cc | 59 ++++++++++ tensorflow/lite/kernels/ceil_test.cc | 83 +++++++++++++ .../internal/optimized/legacy_optimized_ops.h | 12 ++ .../internal/optimized/optimized_ops.h | 16 +++ .../internal/reference/legacy_reference_ops.h | 12 ++ .../internal/reference/reference_ops.h | 20 ++++ tensorflow/lite/kernels/register.cc | 4 + tensorflow/lite/nnapi/NeuralNetworksShim.h | 2 + tensorflow/lite/nnapi_delegate.cc | 6 + tensorflow/lite/schema/schema.fbs | 1 + tensorflow/lite/schema/schema_generated.h | 10 +- tensorflow/lite/testing/generate_examples.py | 52 +++++++++ tensorflow/lite/toco/export_tensorflow.cc | 26 +++++ .../propagate_fixed_sizes.cc | 2 + .../reorder_elementwise_unary.cc | 2 + tensorflow/lite/toco/import_tensorflow.cc | 29 +++++ tensorflow/lite/toco/model.h | 22 ++++ tensorflow/lite/toco/tflite/operator.cc | 4 + tensorflow/lite/toco/tflite/operator_test.cc | 2 + .../lite/toco/tflite/whitelisted_flex_ops.cc | 1 + tensorflow/lite/toco/tooling_util.cc | 2 + 31 files changed, 708 insertions(+), 3 deletions(-) create mode 100644 tensorflow/lite/kernels/abs.cc create mode 100644 tensorflow/lite/kernels/abs_test.cc create mode 100644 tensorflow/lite/kernels/ceil.cc create mode 100644 tensorflow/lite/kernels/ceil_test.cc diff --git a/tensorflow/lite/build_def.bzl b/tensorflow/lite/build_def.bzl index c17eddf47b..1539ac788a 100644 --- a/tensorflow/lite/build_def.bzl +++ b/tensorflow/lite/build_def.bzl @@ -228,6 +228,7 @@ def generated_test_models(): "arg_min_max", "avg_pool", "batch_to_space_nd", + "ceil", "concat", "constant", "control_dep", diff --git a/tensorflow/lite/builtin_ops.h b/tensorflow/lite/builtin_ops.h index f97d3ac4bf..0077fade40 100644 --- a/tensorflow/lite/builtin_ops.h +++ b/tensorflow/lite/builtin_ops.h @@ -128,6 +128,7 @@ typedef enum { kTfLiteBuiltinMirrorPad = 100, kTfLiteBuiltinAbs = 101, kTfLiteBuiltinSplitV = 102, + kTfLiteBuiltinCeil = 103, } TfLiteBuiltinOperator; #ifdef __cplusplus diff --git a/tensorflow/lite/core/api/flatbuffer_conversions.cc b/tensorflow/lite/core/api/flatbuffer_conversions.cc index c00a0a3a54..8a436c440f 100644 --- a/tensorflow/lite/core/api/flatbuffer_conversions.cc +++ b/tensorflow/lite/core/api/flatbuffer_conversions.cc @@ -664,6 +664,8 @@ TfLiteStatus ParseOpData(const Operator* op, BuiltinOperator op_type, case BuiltinOperator_EQUAL: case BuiltinOperator_EXP: case BuiltinOperator_EXPAND_DIMS: + case BuiltinOperator_ABS: + case BuiltinOperator_CEIL: case BuiltinOperator_FLOOR: case BuiltinOperator_GREATER: case BuiltinOperator_GREATER_EQUAL: diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc index 4fe07004a8..1a95ac1891 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc @@ -638,6 +638,26 @@ class NNAPIDelegateKernel { return nullptr; } break; + case kTfLiteBuiltinCeil: + if (version == 1) { + return [](const NNAPIOpMappingArgs& mapping_args) + -> ANeuralNetworksOperationType { + return ANEURALNETWORKS_CEIL; + }; + } else { + return nullptr; + } + break; + case kTfLiteBuiltinAbs: + if (version == 1) { + return [](const NNAPIOpMappingArgs& mapping_args) + -> ANeuralNetworksOperationType { + return ANEURALNETWORKS_ABS; + }; + } else { + return nullptr; + } + break; case kTfLiteBuiltinRelu: if (version == 1) { return [](const NNAPIOpMappingArgs& mapping_args) diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc index ca48af0c95..3050b69028 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc @@ -1024,6 +1024,115 @@ TEST(NNAPIDelegate, FloorMultiDims) { EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); } +class CeilOpModel : public SingleOpModelWithNNAPI { + public: + CeilOpModel(std::initializer_list input_shape, TensorType input_type) { + input_ = AddInput(TensorType_FLOAT32); + output_ = AddOutput(TensorType_FLOAT32); + SetBuiltinOp(BuiltinOperator_CEIL, BuiltinOptions_NONE, 0); + BuildInterpreter({ + input_shape, + }); + } + + int input() { return input_; } + + std::vector GetOutput() { return ExtractVector(output_); } + std::vector GetOutputShape() { return GetTensorShape(output_); } + + private: + int input_; + int output_; +}; + +TEST(NNAPIDelegate, CeilSingleDim) { + CeilOpModel model({2}, TensorType_FLOAT32); + model.PopulateTensor(model.input(), {8.5, 0.0}); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), ElementsAreArray({8, 0})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2})); +} + +TEST(NNAPIDelegate, CeilMultiDims) { + CeilOpModel model({2, 1, 1, 5}, TensorType_FLOAT32); + model.PopulateTensor(model.input(), { + 0.0001, + 8.0001, + 0.9999, + 9.9999, + 0.5, + -0.0001, + -8.0001, + -0.9999, + -9.9999, + -0.5, + }); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({1, 9, 1, 10, 1, 0, -8, 0, -9, 0})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); +} + +class AbsOpModel : public SingleOpModelWithNNAPI { + public: + AbsOpModel(std::initializer_list input_shape, TensorType input_type) { + input_ = AddInput(TensorType_FLOAT32); + output_ = AddOutput(TensorType_FLOAT32); + SetBuiltinOp(BuiltinOperator_ABS, BuiltinOptions_NONE, 0); + BuildInterpreter({ + input_shape, + }); + } + + int input() { return input_; } + + std::vector GetOutput() { return ExtractVector(output_); } + std::vector GetOutputShape() { return GetTensorShape(output_); } + + private: + int input_; + int output_; +}; + +TEST(NNAPIDelegate, AbsSingleDim) { + AbsOpModel model({2}, TensorType_FLOAT32); + model.PopulateTensor(model.input(), {8.5, -2.0}); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), ElementsAreArray({8.5, 2.0})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2})); +} + +TEST(NNAPIDelegate, AbsMultiDims) { + AbsOpModel model({2, 1, 1, 5}, TensorType_FLOAT32); + model.PopulateTensor(model.input(), { + 0.0001, + 8.0001, + 0.9999, + 9.9999, + 0.5, + -0.0001, + -8.0001, + -0.9999, + -9.9999, + -0.5, + }); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({ + 0.0001, + 8.0001, + 0.9999, + 9.9999, + 0.5, + 0.0001, + 8.0001, + 0.9999, + 9.9999, + 0.5, + })); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); +} + class LocalResponseNormOpModel : public SingleOpModelWithNNAPI { public: LocalResponseNormOpModel(std::initializer_list input_shape, int radius, diff --git a/tensorflow/lite/experimental/writer/option_writer_generator.cc b/tensorflow/lite/experimental/writer/option_writer_generator.cc index fa360a2f47..d79b6e8ca9 100644 --- a/tensorflow/lite/experimental/writer/option_writer_generator.cc +++ b/tensorflow/lite/experimental/writer/option_writer_generator.cc @@ -160,6 +160,8 @@ class OpOptionData { op_to_option_["EMBEDDING_LOOKUP"] = ""; // TODO(aselle): maybe something else. op_to_option_["FLOOR"] = ""; + op_to_option_["CEIL"] = ""; + op_to_option_["ABS"] = ""; op_to_option_["HASHTABLE_LOOKUP"] = ""; // TODO(aselle): maybe something else. op_to_option_["LOGISTIC"] = ""; diff --git a/tensorflow/lite/g3doc/tf_ops_compatibility.md b/tensorflow/lite/g3doc/tf_ops_compatibility.md index dcfda72137..dc6ba8a463 100644 --- a/tensorflow/lite/g3doc/tf_ops_compatibility.md +++ b/tensorflow/lite/g3doc/tf_ops_compatibility.md @@ -362,6 +362,28 @@ Outputs { } ``` +**CEIL** + +``` +inputs { + 0: tensor +} +outputs: { + 0: result of computing element-wise ceil of the input tensor +} +``` + +**ABS** + +``` +inputs { + 0: tensor +} +outputs: { + 0: result of computing element-wise absolute value of the input tensor +} +``` + **FULLY_CONNECTED** ``` diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD index bad1c4aebf..f66f69870f 100644 --- a/tensorflow/lite/kernels/BUILD +++ b/tensorflow/lite/kernels/BUILD @@ -158,6 +158,7 @@ cc_library( cc_library( name = "builtin_op_kernels", srcs = [ + "abs.cc", "activations.cc", "add.cc", "arg_min_max.cc", @@ -167,6 +168,7 @@ cc_library( "bidirectional_sequence_lstm.cc", "bidirectional_sequence_rnn.cc", "cast.cc", + "ceil.cc", "comparisons.cc", "concatenation.cc", "conv.cc", @@ -599,6 +601,38 @@ tf_cc_test( ], ) +tf_cc_test( + name = "abs_test", + size = "small", + srcs = ["abs_test.cc"], + tags = [ + "no_oss", + "tflite_not_portable_ios", + ], + deps = [ + ":builtin_ops", + "//tensorflow/lite:framework", + "//tensorflow/lite/kernels:test_util", + "@com_google_googletest//:gtest", + ], +) + +tf_cc_test( + name = "ceil_test", + size = "small", + srcs = ["ceil_test.cc"], + tags = [ + "no_oss", + "tflite_not_portable_ios", + ], + deps = [ + ":builtin_ops", + "//tensorflow/lite:framework", + "//tensorflow/lite/kernels:test_util", + "@com_google_googletest//:gtest", + ], +) + tf_cc_test( name = "elementwise_test", size = "small", diff --git a/tensorflow/lite/kernels/abs.cc b/tensorflow/lite/kernels/abs.cc new file mode 100644 index 0000000000..b44f9de630 --- /dev/null +++ b/tensorflow/lite/kernels/abs.cc @@ -0,0 +1,59 @@ +/* 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/c_api_internal.h" +#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" +#include "tensorflow/lite/kernels/internal/tensor.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace ops { +namespace builtin { +namespace abs { + +constexpr int kInputTensor = 0; +constexpr int kOutputTensor = 0; + +TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { + const TfLiteTensor* input = GetInput(context, node, kInputTensor); + TfLiteTensor* output = GetOutput(context, node, kOutputTensor); + TF_LITE_ENSURE_EQ(context, NumInputs(node), 1); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); + TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32); + output->type = input->type; + TfLiteIntArray* output_size = TfLiteIntArrayCopy(input->dims); + return context->ResizeTensor(context, output, output_size); +} + +TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { + const TfLiteTensor* input = GetInput(context, node, kInputTensor); + TfLiteTensor* output = GetOutput(context, node, kOutputTensor); + + optimized_ops::Abs(GetTensorShape(input), GetTensorData(input), + GetTensorShape(output), GetTensorData(output)); + + return kTfLiteOk; +} +} // namespace abs + +TfLiteRegistration* Register_ABS() { + static TfLiteRegistration r = {/*init=*/nullptr, + /*free=*/nullptr, abs::Prepare, abs::Eval}; + return &r; +} + +} // namespace builtin +} // namespace ops +} // namespace tflite diff --git a/tensorflow/lite/kernels/abs_test.cc b/tensorflow/lite/kernels/abs_test.cc new file mode 100644 index 0000000000..5e3f95c43a --- /dev/null +++ b/tensorflow/lite/kernels/abs_test.cc @@ -0,0 +1,94 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include +#include "tensorflow/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::ElementsAreArray; + +class AbsOpModel : public SingleOpModel { + public: + AbsOpModel(std::initializer_list input_shape, TensorType input_type) { + input_ = AddInput(TensorType_FLOAT32); + output_ = AddOutput(TensorType_FLOAT32); + SetBuiltinOp(BuiltinOperator_ABS, BuiltinOptions_NONE, 0); + BuildInterpreter({ + input_shape, + }); + } + + int input() { return input_; } + + std::vector GetOutput() { return ExtractVector(output_); } + std::vector GetOutputShape() { return GetTensorShape(output_); } + + private: + int input_; + int output_; +}; + +TEST(AbsOpTest, SingleDim) { + AbsOpModel model({2}, TensorType_FLOAT32); + model.PopulateTensor(model.input(), {8.5, -2.0}); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), ElementsAreArray({8.5, 2.0})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2})); +} + +TEST(AbsOpTest, MultiDims) { + AbsOpModel model({2, 1, 1, 5}, TensorType_FLOAT32); + model.PopulateTensor(model.input(), { + 0.0001, + 8.0001, + 0.9999, + 9.9999, + 0.5, + -0.0001, + -8.0001, + -0.9999, + -9.9999, + -0.5, + }); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({ + 0.0001, + 8.0001, + 0.9999, + 9.9999, + 0.5, + 0.0001, + 8.0001, + 0.9999, + 9.9999, + 0.5, + })); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); +} + +} // namespace +} // namespace tflite + +int main(int argc, char** argv) { + ::tflite::LogToStderr(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tensorflow/lite/kernels/ceil.cc b/tensorflow/lite/kernels/ceil.cc new file mode 100644 index 0000000000..e0ea061f25 --- /dev/null +++ b/tensorflow/lite/kernels/ceil.cc @@ -0,0 +1,59 @@ +/* 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/c_api_internal.h" +#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" +#include "tensorflow/lite/kernels/internal/tensor.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace ops { +namespace builtin { +namespace ceil { + +constexpr int kInputTensor = 0; +constexpr int kOutputTensor = 0; + +TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { + const TfLiteTensor* input = GetInput(context, node, kInputTensor); + TfLiteTensor* output = GetOutput(context, node, kOutputTensor); + TF_LITE_ENSURE_EQ(context, NumInputs(node), 1); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); + TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32); + output->type = input->type; + TfLiteIntArray* output_size = TfLiteIntArrayCopy(input->dims); + return context->ResizeTensor(context, output, output_size); +} + +TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { + const TfLiteTensor* input = GetInput(context, node, kInputTensor); + TfLiteTensor* output = GetOutput(context, node, kOutputTensor); + + optimized_ops::Ceil(GetTensorShape(input), GetTensorData(input), + GetTensorShape(output), GetTensorData(output)); + + return kTfLiteOk; +} +} // namespace ceil + +TfLiteRegistration* Register_CEIL() { + static TfLiteRegistration r = {/*init=*/nullptr, + /*free=*/nullptr, ceil::Prepare, ceil::Eval}; + return &r; +} + +} // namespace builtin +} // namespace ops +} // namespace tflite diff --git a/tensorflow/lite/kernels/ceil_test.cc b/tensorflow/lite/kernels/ceil_test.cc new file mode 100644 index 0000000000..e120105082 --- /dev/null +++ b/tensorflow/lite/kernels/ceil_test.cc @@ -0,0 +1,83 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include +#include "tensorflow/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::ElementsAreArray; + +class CeilOpModel : public SingleOpModel { + public: + CeilOpModel(std::initializer_list input_shape, TensorType input_type) { + input_ = AddInput(TensorType_FLOAT32); + output_ = AddOutput(TensorType_FLOAT32); + SetBuiltinOp(BuiltinOperator_CEIL, BuiltinOptions_NONE, 0); + BuildInterpreter({ + input_shape, + }); + } + + int input() { return input_; } + + std::vector GetOutput() { return ExtractVector(output_); } + std::vector GetOutputShape() { return GetTensorShape(output_); } + + private: + int input_; + int output_; +}; + +TEST(CeilOpTest, SingleDim) { + CeilOpModel model({2}, TensorType_FLOAT32); + model.PopulateTensor(model.input(), {8.5, 0.0}); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), ElementsAreArray({9, 0})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2})); +} + +TEST(CeilOpTest, MultiDims) { + CeilOpModel model({2, 1, 1, 5}, TensorType_FLOAT32); + model.PopulateTensor(model.input(), { + 0.0001, + 8.0001, + 0.9999, + 9.9999, + 0.5, + -0.0001, + -8.0001, + -0.9999, + -9.9999, + -0.5, + }); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({1, 9, 1, 10, 1, 0, -8, 0, -9, 0})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); +} + +} // namespace +} // namespace tflite + +int main(int argc, char** argv) { + ::tflite::LogToStderr(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h index 5485d907c2..c24b7faa21 100644 --- a/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h @@ -1740,6 +1740,18 @@ inline void Floor(const float* input_data, const Dims<4>& input_dims, output_data); } +inline void Ceil(const float* input_data, const Dims<4>& input_dims, + float* output_data, const Dims<4>& output_dims) { + Ceil(DimsToShape(input_dims), input_data, DimsToShape(output_dims), + output_data); +} + +inline void Abs(const float* input_data, const Dims<4>& input_dims, + float* output_data, const Dims<4>& output_dims) { + Abs(DimsToShape(input_dims), input_data, DimsToShape(output_dims), + output_data); +} + inline void ResizeBilinear(const float* input_data, const Dims<4>& input_dims, const int32* output_size_data, const Dims<4>& output_size_dims, float* output_data, diff --git a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h index c79b69a22e..7f7296ae97 100644 --- a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h @@ -4898,6 +4898,22 @@ inline void Floor(const RuntimeShape& input_shape, const float* input_data, output_map.array() = Eigen::floor(input_map.array()); } +inline void Ceil(const RuntimeShape& input_shape, const float* input_data, + const RuntimeShape& output_shape, float* output_data) { + gemmlowp::ScopedProfilingLabel label("Ceil"); + auto input_map = MapAsVector(input_data, input_shape); + auto output_map = MapAsVector(output_data, output_shape); + output_map.array() = Eigen::ceil(input_map.array()); +} + +inline void Abs(const RuntimeShape& input_shape, const float* input_data, + const RuntimeShape& output_shape, float* output_data) { + gemmlowp::ScopedProfilingLabel label("Abs"); + auto input_map = MapAsVector(input_data, input_shape); + auto output_map = MapAsVector(output_data, output_shape); + output_map.array() = Eigen::abs(input_map.array()); +} + #ifdef USE_NEON inline void ResizeBilinearKernel(const float* input_ptr, int32 depth, float scale, float* output_ptr) { diff --git a/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h b/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h index 380fc8f98e..30bb92c8b3 100644 --- a/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h +++ b/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h @@ -1883,6 +1883,18 @@ inline void Floor(const float* input_data, const Dims<4>& input_dims, output_data); } +inline void Ceil(const float* input_data, const Dims<4>& input_dims, + float* output_data, const Dims<4>& output_dims) { + Ceil(DimsToShape(input_dims), input_data, DimsToShape(output_dims), + output_data); +} + +inline void Abs(const float* input_data, const Dims<4>& input_dims, + float* output_data, const Dims<4>& output_dims) { + Abs(DimsToShape(input_dims), input_data, DimsToShape(output_dims), + output_data); +} + template inline void ResizeBilinear(const T* input_data, const Dims<4>& input_dims, const int32* output_size_data, diff --git a/tensorflow/lite/kernels/internal/reference/reference_ops.h b/tensorflow/lite/kernels/internal/reference/reference_ops.h index ea3ab06da1..133a455e9e 100644 --- a/tensorflow/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/lite/kernels/internal/reference/reference_ops.h @@ -3040,6 +3040,26 @@ inline void Floor(const RuntimeShape& input_shape, const float* input_data, } } +inline void Ceil(const RuntimeShape& input_shape, const float* input_data, + const RuntimeShape& output_shape, float* output_data) { + const int flat_size = MatchingFlatSize(input_shape, output_shape); + + for (int i = 0; i < flat_size; i++) { + int offset = i; + output_data[offset] = std::ceil(input_data[offset]); + } +} + +inline void Abs(const RuntimeShape& input_shape, const float* input_data, + const RuntimeShape& output_shape, float* output_data) { + const int flat_size = MatchingFlatSize(input_shape, output_shape); + + for (int i = 0; i < flat_size; i++) { + int offset = i; + output_data[offset] = std::fabs(input_data[offset]); + } +} + template inline void Gather(const tflite::GatherParams& op_params, const RuntimeShape& input_shape, const T* input_data, diff --git a/tensorflow/lite/kernels/register.cc b/tensorflow/lite/kernels/register.cc index c0e6f6994f..caf61e9003 100644 --- a/tensorflow/lite/kernels/register.cc +++ b/tensorflow/lite/kernels/register.cc @@ -94,6 +94,8 @@ TfLiteRegistration* Register_GREATER_EQUAL(); TfLiteRegistration* Register_LESS(); TfLiteRegistration* Register_LESS_EQUAL(); TfLiteRegistration* Register_FLOOR(); +TfLiteRegistration* Register_CEIL(); +TfLiteRegistration* Register_ABS(); TfLiteRegistration* Register_TILE(); TfLiteRegistration* Register_NEG(); TfLiteRegistration* Register_SUM(); @@ -235,6 +237,8 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_LESS, Register_LESS()); AddBuiltin(BuiltinOperator_LESS_EQUAL, Register_LESS_EQUAL()); AddBuiltin(BuiltinOperator_FLOOR, Register_FLOOR()); + AddBuiltin(BuiltinOperator_CEIL, Register_CEIL()); + AddBuiltin(BuiltinOperator_ABS, Register_ABS()); AddBuiltin(BuiltinOperator_NEG, Register_NEG()); AddBuiltin(BuiltinOperator_SELECT, Register_SELECT()); AddBuiltin(BuiltinOperator_SLICE, Register_SLICE()); diff --git a/tensorflow/lite/nnapi/NeuralNetworksShim.h b/tensorflow/lite/nnapi/NeuralNetworksShim.h index c39502f4ac..037d364c3b 100644 --- a/tensorflow/lite/nnapi/NeuralNetworksShim.h +++ b/tensorflow/lite/nnapi/NeuralNetworksShim.h @@ -143,6 +143,8 @@ enum { ANEURALNETWORKS_STRIDED_SLICE = 35, ANEURALNETWORKS_SUB = 36, ANEURALNETWORKS_TRANSPOSE = 37, + ANEURALNETWORKS_CEIL = 38, + ANEURALNETWORKS_ABS = 39, }; /** diff --git a/tensorflow/lite/nnapi_delegate.cc b/tensorflow/lite/nnapi_delegate.cc index 26d75696a1..6da803ce7b 100644 --- a/tensorflow/lite/nnapi_delegate.cc +++ b/tensorflow/lite/nnapi_delegate.cc @@ -489,6 +489,12 @@ TfLiteStatus AddOpsAndParams( case tflite::BuiltinOperator_FLOOR: nn_op_type = ANEURALNETWORKS_FLOOR; break; + case tflite::BuiltinOperator_CEIL: + nn_op_type = ANEURALNETWORKS_CEIL; + break; + case tflite::BuiltinOperator_ABS: + nn_op_type = ANEURALNETWORKS_ABS; + break; case tflite::BuiltinOperator_LOGISTIC: nn_op_type = ANEURALNETWORKS_LOGISTIC; break; diff --git a/tensorflow/lite/schema/schema.fbs b/tensorflow/lite/schema/schema.fbs index 980f13b19b..2d27722415 100644 --- a/tensorflow/lite/schema/schema.fbs +++ b/tensorflow/lite/schema/schema.fbs @@ -205,6 +205,7 @@ enum BuiltinOperator : byte { MIRROR_PAD = 100, ABS = 101, SPLIT_V = 102, + CEIL = 103, } // Options for the builtin operators. diff --git a/tensorflow/lite/schema/schema_generated.h b/tensorflow/lite/schema/schema_generated.h index 637cbafabd..a605c19b52 100755 --- a/tensorflow/lite/schema/schema_generated.h +++ b/tensorflow/lite/schema/schema_generated.h @@ -520,11 +520,12 @@ enum BuiltinOperator { BuiltinOperator_MIRROR_PAD = 100, BuiltinOperator_ABS = 101, BuiltinOperator_SPLIT_V = 102, + BuiltinOperator_CEIL = 103, BuiltinOperator_MIN = BuiltinOperator_ADD, - BuiltinOperator_MAX = BuiltinOperator_SPLIT_V + BuiltinOperator_MAX = BuiltinOperator_CEIL }; -inline const BuiltinOperator (&EnumValuesBuiltinOperator())[102] { +inline const BuiltinOperator (&EnumValuesBuiltinOperator())[103] { static const BuiltinOperator values[] = { BuiltinOperator_ADD, BuiltinOperator_AVERAGE_POOL_2D, @@ -627,7 +628,9 @@ inline const BuiltinOperator (&EnumValuesBuiltinOperator())[102] { BuiltinOperator_SQUARED_DIFFERENCE, BuiltinOperator_MIRROR_PAD, BuiltinOperator_ABS, - BuiltinOperator_SPLIT_V + BuiltinOperator_SPLIT_V, + BuiltinOperator_CEIL + }; return values; } @@ -737,6 +740,7 @@ inline const char * const *EnumNamesBuiltinOperator() { "MIRROR_PAD", "ABS", "SPLIT_V", + "CEIL", nullptr }; return names; diff --git a/tensorflow/lite/testing/generate_examples.py b/tensorflow/lite/testing/generate_examples.py index dd7b3d0745..825873a884 100644 --- a/tensorflow/lite/testing/generate_examples.py +++ b/tensorflow/lite/testing/generate_examples.py @@ -3021,6 +3021,58 @@ def make_floor_tests(zip_path): make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) +def make_ceil_tests(zip_path): + """Make a set of tests to do ceil.""" + + test_parameters = [{ + "input_dtype": [tf.float32], + "input_shape": [[1], [1, 2], [5, 6, 7, 8], [3, 4, 5, 6]], + }] + + def build_graph(parameters): + """Build the ceil op testing graph.""" + input_value = tf.placeholder( + dtype=parameters["input_dtype"], + name="input1", + shape=parameters["input_shape"]) + out = tf.ceil(input_value) + return [input_value], [out] + + 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={inputs[0]: input_value}) + + make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) + + +def make_abs_tests(zip_path): + """Make a set of tests to do abs.""" + + test_parameters = [{ + "input_dtype": [tf.float32], + "input_shape": [[1], [1, 2], [5, 6, 7, 8], [3, 4, 5, 6]], + }] + + def build_graph(parameters): + """Build the abs op testing graph.""" + input_value = tf.placeholder( + dtype=parameters["input_dtype"], + name="input1", + shape=parameters["input_shape"]) + out = tf.abs(input_value) + return [input_value], [out] + + 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={inputs[0]: input_value}) + + make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) + + def make_neg_tests(zip_path): """Make a set of tests to do neg.""" diff --git a/tensorflow/lite/toco/export_tensorflow.cc b/tensorflow/lite/toco/export_tensorflow.cc index 9fff001552..9dfeaad164 100644 --- a/tensorflow/lite/toco/export_tensorflow.cc +++ b/tensorflow/lite/toco/export_tensorflow.cc @@ -1205,6 +1205,26 @@ void ConvertFloorOperator(const Model& model, const FloorOperator& src_op, (*floor_op->mutable_attr())["T"].set_type(DT_FLOAT); } +void ConvertCeilOperator(const Model& model, const CeilOperator& src_op, + GraphDef* tensorflow_graph) { + tensorflow::NodeDef* ceil_op = tensorflow_graph->add_node(); + ceil_op->set_op("Ceil"); + ceil_op->set_name(src_op.outputs[0]); + CHECK_EQ(src_op.inputs.size(), 1); + *ceil_op->add_input() = src_op.inputs[0]; + (*ceil_op->mutable_attr())["T"].set_type(DT_FLOAT); +} + +void ConvertAbsOperator(const Model& model, const AbsOperator& src_op, + GraphDef* tensorflow_graph) { + tensorflow::NodeDef* abs_op = tensorflow_graph->add_node(); + abs_op->set_op("Abs"); + abs_op->set_name(src_op.outputs[0]); + CHECK_EQ(src_op.inputs.size(), 1); + *abs_op->add_input() = src_op.inputs[0]; + (*abs_op->mutable_attr())["T"].set_type(DT_FLOAT); +} + void ConvertGatherOperator(const Model& model, const GatherOperator& src_op, GraphDef* tensorflow_graph) { tensorflow::NodeDef* gather_op = tensorflow_graph->add_node(); @@ -2169,6 +2189,12 @@ void ConvertOperator(const Model& model, const Operator& src_op, } else if (src_op.type == OperatorType::kFloor) { ConvertFloorOperator(model, static_cast(src_op), tensorflow_graph); + } else if (src_op.type == OperatorType::kCeil) { + ConvertCeilOperator(model, static_cast(src_op), + tensorflow_graph); + } else if (src_op.type == OperatorType::kAbs) { + ConvertAbsOperator(model, static_cast(src_op), + tensorflow_graph); } else if (src_op.type == OperatorType::kGather) { ConvertGatherOperator(model, static_cast(src_op), tensorflow_graph); diff --git a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc index 0e653f08a0..c6c7681c03 100644 --- a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc +++ b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc @@ -1869,6 +1869,8 @@ void ProcessMirrorPadOperator(Model* model, MirrorPadOperator* op) { case OperatorType::kAssert: case OperatorType::kCast: case OperatorType::kFloor: + case OperatorType::kCeil: + case OperatorType::kAbs: case OperatorType::kExp: case OperatorType::kSin: case OperatorType::kLogicalAnd: diff --git a/tensorflow/lite/toco/graph_transformations/reorder_elementwise_unary.cc b/tensorflow/lite/toco/graph_transformations/reorder_elementwise_unary.cc index 6a4b919854..90e64816c8 100644 --- a/tensorflow/lite/toco/graph_transformations/reorder_elementwise_unary.cc +++ b/tensorflow/lite/toco/graph_transformations/reorder_elementwise_unary.cc @@ -29,7 +29,9 @@ namespace { bool IsElementwiseOperator(OperatorType optype) { switch (optype) { + case OperatorType::kAbs: case OperatorType::kCast: + case OperatorType::kCeil: case OperatorType::kExp: case OperatorType::kFloor: case OperatorType::kNeg: diff --git a/tensorflow/lite/toco/import_tensorflow.cc b/tensorflow/lite/toco/import_tensorflow.cc index 0b2f810394..de8ae19a71 100644 --- a/tensorflow/lite/toco/import_tensorflow.cc +++ b/tensorflow/lite/toco/import_tensorflow.cc @@ -1491,6 +1491,34 @@ tensorflow::Status ConvertFloorOperator( return tensorflow::Status::OK(); } +tensorflow::Status ConvertCeilOperator( + const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, + Model* model) { + CHECK_EQ(node.op(), "Ceil"); + TF_QCHECK_OK(CheckInputsCount(node, tf_import_flags, 1)); + const auto data_type = GetDataTypeAttr(node, "T"); + CHECK(data_type == DT_FLOAT); + auto* op = new CeilOperator; + op->inputs.push_back(node.input(0)); + op->outputs.push_back(node.name()); + model->operators.emplace_back(op); + return tensorflow::Status::OK(); +} + +tensorflow::Status ConvertAbsOperator( + const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, + Model* model) { + CHECK_EQ(node.op(), "Abs"); + TF_QCHECK_OK(CheckInputsCount(node, tf_import_flags, 1)); + const auto data_type = GetDataTypeAttr(node, "T"); + CHECK(data_type == DT_FLOAT); + auto* op = new AbsOperator; + op->inputs.push_back(node.input(0)); + op->outputs.push_back(node.name()); + model->operators.emplace_back(op); + return tensorflow::Status::OK(); +} + tensorflow::Status ConvertGatherOperator( const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, Model* model) { @@ -2314,6 +2342,7 @@ ConverterMapType GetTensorFlowNodeConverterMap() { {"BatchToSpaceND", ConvertBatchToSpaceNDOperator}, {"BiasAdd", ConvertBiasAddOperator}, {"Cast", ConvertCastOperator}, + {"Ceil", ConvertCeilOperator}, {"CheckNumerics", ConvertIdentityOperator}, {"Concat", ConvertConcatOperator}, {"ConcatV2", ConvertConcatOperator}, diff --git a/tensorflow/lite/toco/model.h b/tensorflow/lite/toco/model.h index d392535f5c..69c6d7608a 100644 --- a/tensorflow/lite/toco/model.h +++ b/tensorflow/lite/toco/model.h @@ -37,11 +37,13 @@ using tflite::QuantizationParams; enum class OperatorType : uint8 { kNone, // General-purpose neural network operators. + kAbs, kAdd, kAddN, kAveragePool, kBatchMatMul, kBatchNormalization, + kCeil, kConv, kConcatenation, kDepthwiseConv, @@ -1658,6 +1660,26 @@ struct FloorOperator : Operator { FloorOperator() : Operator(OperatorType::kFloor) {} }; +// Ceil operator. +// +// Inputs: +// inputs[0]: required: the input array +// +// TensorFlow equivalent: Ceil +struct CeilOperator : Operator { + CeilOperator() : Operator(OperatorType::kCeil) {} +}; + +// Abs operator. +// +// Inputs: +// inputs[0]: required: the input array +// +// TensorFlow equivalent: Abs +struct AbsOperator : Operator { + AbsOperator() : Operator(OperatorType::kAbs) {} +}; + // Gather operator. It gathers slices from params according to indices. // Only 1-D indices are supported at the moment. // diff --git a/tensorflow/lite/toco/tflite/operator.cc b/tensorflow/lite/toco/tflite/operator.cc index 205af23da5..2122d04958 100644 --- a/tensorflow/lite/toco/tflite/operator.cc +++ b/tensorflow/lite/toco/tflite/operator.cc @@ -1649,6 +1649,10 @@ std::vector> BuildOperatorList( "DEQUANTIZE", OperatorType::kDequantize)); ops.push_back( MakeUnique>("FLOOR", OperatorType::kFloor)); + ops.push_back( + MakeUnique>("CEIL", OperatorType::kCeil)); + ops.push_back( + MakeUnique>("ABS", OperatorType::kAbs)); ops.push_back( MakeUnique>("RELU", OperatorType::kRelu)); ops.push_back(MakeUnique>( diff --git a/tensorflow/lite/toco/tflite/operator_test.cc b/tensorflow/lite/toco/tflite/operator_test.cc index 14ec89cd73..e284619903 100644 --- a/tensorflow/lite/toco/tflite/operator_test.cc +++ b/tensorflow/lite/toco/tflite/operator_test.cc @@ -114,6 +114,8 @@ TEST_F(OperatorTest, SimpleOperators) { CheckSimpleOperator("DEQUANTIZE", OperatorType::kDequantize); CheckSimpleOperator("FLOOR", OperatorType::kFloor); + CheckSimpleOperator("CEIL", OperatorType::kCeil); + CheckSimpleOperator("ABS", OperatorType::kAbs); CheckSimpleOperator("RELU", OperatorType::kRelu); CheckSimpleOperator("RELU_N1_TO_1", OperatorType::kRelu1); CheckSimpleOperator("RELU6", OperatorType::kRelu6); diff --git a/tensorflow/lite/toco/tflite/whitelisted_flex_ops.cc b/tensorflow/lite/toco/tflite/whitelisted_flex_ops.cc index 039a918af1..a96222a8a2 100644 --- a/tensorflow/lite/toco/tflite/whitelisted_flex_ops.cc +++ b/tensorflow/lite/toco/tflite/whitelisted_flex_ops.cc @@ -68,6 +68,7 @@ bool IsWhitelistedFlexOp(const std::string& tensorflow_op_name) { "BroadcastArgs", "BroadcastGradientArgs", "Cast", + "Ceil", "CheckNumerics", "ComplexAbs", "Concat", diff --git a/tensorflow/lite/toco/tooling_util.cc b/tensorflow/lite/toco/tooling_util.cc index af4cd386a2..0887dca9b5 100644 --- a/tensorflow/lite/toco/tooling_util.cc +++ b/tensorflow/lite/toco/tooling_util.cc @@ -385,6 +385,8 @@ const char* OperatorTypeName(OperatorType type) { HANDLE_OPERATORTYPENAME_CASE(ConcatV2) HANDLE_OPERATORTYPENAME_CASE(Cast) HANDLE_OPERATORTYPENAME_CASE(Floor) + HANDLE_OPERATORTYPENAME_CASE(Ceil) + HANDLE_OPERATORTYPENAME_CASE(Abs) HANDLE_OPERATORTYPENAME_CASE(Gather) HANDLE_OPERATORTYPENAME_CASE(ResizeBilinear) HANDLE_OPERATORTYPENAME_CASE(SpaceToBatchND) -- GitLab From 9e91a0899f116e8bfec46922221c45765346ce2e Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Wed, 12 Dec 2018 15:27:09 +0100 Subject: [PATCH 0041/2345] Update tensorflow.data.experimental goldens for v1 Add the drop_remainder argument for bucket_by_sequence_length to the goldens. --- .../tools/api/golden/v1/tensorflow.data.experimental.pbtxt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt index ad10b82283..b8ba3e341f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt @@ -54,7 +54,7 @@ tf_module { } member_method { 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\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'False\', \'False\'], " + 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: "choose_from_datasets" -- GitLab From c266087d4d086913194bd621cad125853eca8fd6 Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Wed, 12 Dec 2018 15:29:25 +0100 Subject: [PATCH 0042/2345] Update tensorflow.data.experimental goldens for v2 Add the drop_remainder argument for bucket_by_sequence_length to the goldens. --- .../tools/api/golden/v2/tensorflow.data.experimental.pbtxt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt index ad10b82283..b8ba3e341f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt @@ -54,7 +54,7 @@ tf_module { } member_method { 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\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'False\', \'False\'], " + 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: "choose_from_datasets" -- GitLab From e30a0a1de0b0b51da69b3d4bf5c545142accb3c2 Mon Sep 17 00:00:00 2001 From: Siju Samuel Date: Wed, 12 Dec 2018 20:03:36 +0530 Subject: [PATCH 0043/2345] Removed abs since already committed in 5916a9f0e4b5b2c4f80767ff83a001a6f86b4395 --- .../lite/core/api/flatbuffer_conversions.cc | 1 - .../lite/delegates/nnapi/nnapi_delegate.cc | 10 -- .../delegates/nnapi/nnapi_delegate_test.cc | 60 ------------ .../writer/option_writer_generator.cc | 1 - tensorflow/lite/g3doc/tf_ops_compatibility.md | 11 --- tensorflow/lite/kernels/BUILD | 17 ---- tensorflow/lite/kernels/abs.cc | 59 ------------ tensorflow/lite/kernels/abs_test.cc | 94 ------------------- .../internal/optimized/legacy_optimized_ops.h | 6 -- .../internal/optimized/optimized_ops.h | 8 -- .../internal/reference/legacy_reference_ops.h | 6 -- .../internal/reference/reference_ops.h | 10 -- tensorflow/lite/kernels/register.cc | 2 - tensorflow/lite/nnapi/NeuralNetworksShim.h | 1 - tensorflow/lite/nnapi_delegate.cc | 3 - tensorflow/lite/testing/generate_examples.py | 26 ----- tensorflow/lite/toco/export_tensorflow.cc | 13 --- .../propagate_fixed_sizes.cc | 1 - .../reorder_elementwise_unary.cc | 1 - tensorflow/lite/toco/import_tensorflow.cc | 14 --- tensorflow/lite/toco/model.h | 11 --- tensorflow/lite/toco/tflite/operator.cc | 2 - tensorflow/lite/toco/tflite/operator_test.cc | 1 - tensorflow/lite/toco/tooling_util.cc | 1 - 24 files changed, 359 deletions(-) delete mode 100644 tensorflow/lite/kernels/abs.cc delete mode 100644 tensorflow/lite/kernels/abs_test.cc diff --git a/tensorflow/lite/core/api/flatbuffer_conversions.cc b/tensorflow/lite/core/api/flatbuffer_conversions.cc index 8a436c440f..9c8eb5a2d8 100644 --- a/tensorflow/lite/core/api/flatbuffer_conversions.cc +++ b/tensorflow/lite/core/api/flatbuffer_conversions.cc @@ -664,7 +664,6 @@ TfLiteStatus ParseOpData(const Operator* op, BuiltinOperator op_type, case BuiltinOperator_EQUAL: case BuiltinOperator_EXP: case BuiltinOperator_EXPAND_DIMS: - case BuiltinOperator_ABS: case BuiltinOperator_CEIL: case BuiltinOperator_FLOOR: case BuiltinOperator_GREATER: diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc index 1a95ac1891..cac98ae3da 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc @@ -648,16 +648,6 @@ class NNAPIDelegateKernel { return nullptr; } break; - case kTfLiteBuiltinAbs: - if (version == 1) { - return [](const NNAPIOpMappingArgs& mapping_args) - -> ANeuralNetworksOperationType { - return ANEURALNETWORKS_ABS; - }; - } else { - return nullptr; - } - break; case kTfLiteBuiltinRelu: if (version == 1) { return [](const NNAPIOpMappingArgs& mapping_args) diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc index 3050b69028..f6a04e36cd 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc @@ -1073,66 +1073,6 @@ TEST(NNAPIDelegate, CeilMultiDims) { EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); } -class AbsOpModel : public SingleOpModelWithNNAPI { - public: - AbsOpModel(std::initializer_list input_shape, TensorType input_type) { - input_ = AddInput(TensorType_FLOAT32); - output_ = AddOutput(TensorType_FLOAT32); - SetBuiltinOp(BuiltinOperator_ABS, BuiltinOptions_NONE, 0); - BuildInterpreter({ - input_shape, - }); - } - - int input() { return input_; } - - std::vector GetOutput() { return ExtractVector(output_); } - std::vector GetOutputShape() { return GetTensorShape(output_); } - - private: - int input_; - int output_; -}; - -TEST(NNAPIDelegate, AbsSingleDim) { - AbsOpModel model({2}, TensorType_FLOAT32); - model.PopulateTensor(model.input(), {8.5, -2.0}); - model.Invoke(); - EXPECT_THAT(model.GetOutput(), ElementsAreArray({8.5, 2.0})); - EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2})); -} - -TEST(NNAPIDelegate, AbsMultiDims) { - AbsOpModel model({2, 1, 1, 5}, TensorType_FLOAT32); - model.PopulateTensor(model.input(), { - 0.0001, - 8.0001, - 0.9999, - 9.9999, - 0.5, - -0.0001, - -8.0001, - -0.9999, - -9.9999, - -0.5, - }); - model.Invoke(); - EXPECT_THAT(model.GetOutput(), - ElementsAreArray({ - 0.0001, - 8.0001, - 0.9999, - 9.9999, - 0.5, - 0.0001, - 8.0001, - 0.9999, - 9.9999, - 0.5, - })); - EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); -} - class LocalResponseNormOpModel : public SingleOpModelWithNNAPI { public: LocalResponseNormOpModel(std::initializer_list input_shape, int radius, diff --git a/tensorflow/lite/experimental/writer/option_writer_generator.cc b/tensorflow/lite/experimental/writer/option_writer_generator.cc index d79b6e8ca9..ba0303f9db 100644 --- a/tensorflow/lite/experimental/writer/option_writer_generator.cc +++ b/tensorflow/lite/experimental/writer/option_writer_generator.cc @@ -161,7 +161,6 @@ class OpOptionData { ""; // TODO(aselle): maybe something else. op_to_option_["FLOOR"] = ""; op_to_option_["CEIL"] = ""; - op_to_option_["ABS"] = ""; op_to_option_["HASHTABLE_LOOKUP"] = ""; // TODO(aselle): maybe something else. op_to_option_["LOGISTIC"] = ""; diff --git a/tensorflow/lite/g3doc/tf_ops_compatibility.md b/tensorflow/lite/g3doc/tf_ops_compatibility.md index dc6ba8a463..d20320a3b5 100644 --- a/tensorflow/lite/g3doc/tf_ops_compatibility.md +++ b/tensorflow/lite/g3doc/tf_ops_compatibility.md @@ -373,17 +373,6 @@ outputs: { } ``` -**ABS** - -``` -inputs { - 0: tensor -} -outputs: { - 0: result of computing element-wise absolute value of the input tensor -} -``` - **FULLY_CONNECTED** ``` diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD index f66f69870f..71d06ba4d7 100644 --- a/tensorflow/lite/kernels/BUILD +++ b/tensorflow/lite/kernels/BUILD @@ -158,7 +158,6 @@ cc_library( cc_library( name = "builtin_op_kernels", srcs = [ - "abs.cc", "activations.cc", "add.cc", "arg_min_max.cc", @@ -601,22 +600,6 @@ tf_cc_test( ], ) -tf_cc_test( - name = "abs_test", - size = "small", - srcs = ["abs_test.cc"], - tags = [ - "no_oss", - "tflite_not_portable_ios", - ], - deps = [ - ":builtin_ops", - "//tensorflow/lite:framework", - "//tensorflow/lite/kernels:test_util", - "@com_google_googletest//:gtest", - ], -) - tf_cc_test( name = "ceil_test", size = "small", diff --git a/tensorflow/lite/kernels/abs.cc b/tensorflow/lite/kernels/abs.cc deleted file mode 100644 index b44f9de630..0000000000 --- a/tensorflow/lite/kernels/abs.cc +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ - -#include "tensorflow/lite/c/c_api_internal.h" -#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" -#include "tensorflow/lite/kernels/internal/tensor.h" -#include "tensorflow/lite/kernels/kernel_util.h" - -namespace tflite { -namespace ops { -namespace builtin { -namespace abs { - -constexpr int kInputTensor = 0; -constexpr int kOutputTensor = 0; - -TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { - const TfLiteTensor* input = GetInput(context, node, kInputTensor); - TfLiteTensor* output = GetOutput(context, node, kOutputTensor); - TF_LITE_ENSURE_EQ(context, NumInputs(node), 1); - TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); - TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32); - output->type = input->type; - TfLiteIntArray* output_size = TfLiteIntArrayCopy(input->dims); - return context->ResizeTensor(context, output, output_size); -} - -TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { - const TfLiteTensor* input = GetInput(context, node, kInputTensor); - TfLiteTensor* output = GetOutput(context, node, kOutputTensor); - - optimized_ops::Abs(GetTensorShape(input), GetTensorData(input), - GetTensorShape(output), GetTensorData(output)); - - return kTfLiteOk; -} -} // namespace abs - -TfLiteRegistration* Register_ABS() { - static TfLiteRegistration r = {/*init=*/nullptr, - /*free=*/nullptr, abs::Prepare, abs::Eval}; - return &r; -} - -} // namespace builtin -} // namespace ops -} // namespace tflite diff --git a/tensorflow/lite/kernels/abs_test.cc b/tensorflow/lite/kernels/abs_test.cc deleted file mode 100644 index 5e3f95c43a..0000000000 --- a/tensorflow/lite/kernels/abs_test.cc +++ /dev/null @@ -1,94 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ - -#include -#include "tensorflow/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::ElementsAreArray; - -class AbsOpModel : public SingleOpModel { - public: - AbsOpModel(std::initializer_list input_shape, TensorType input_type) { - input_ = AddInput(TensorType_FLOAT32); - output_ = AddOutput(TensorType_FLOAT32); - SetBuiltinOp(BuiltinOperator_ABS, BuiltinOptions_NONE, 0); - BuildInterpreter({ - input_shape, - }); - } - - int input() { return input_; } - - std::vector GetOutput() { return ExtractVector(output_); } - std::vector GetOutputShape() { return GetTensorShape(output_); } - - private: - int input_; - int output_; -}; - -TEST(AbsOpTest, SingleDim) { - AbsOpModel model({2}, TensorType_FLOAT32); - model.PopulateTensor(model.input(), {8.5, -2.0}); - model.Invoke(); - EXPECT_THAT(model.GetOutput(), ElementsAreArray({8.5, 2.0})); - EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2})); -} - -TEST(AbsOpTest, MultiDims) { - AbsOpModel model({2, 1, 1, 5}, TensorType_FLOAT32); - model.PopulateTensor(model.input(), { - 0.0001, - 8.0001, - 0.9999, - 9.9999, - 0.5, - -0.0001, - -8.0001, - -0.9999, - -9.9999, - -0.5, - }); - model.Invoke(); - EXPECT_THAT(model.GetOutput(), - ElementsAreArray({ - 0.0001, - 8.0001, - 0.9999, - 9.9999, - 0.5, - 0.0001, - 8.0001, - 0.9999, - 9.9999, - 0.5, - })); - EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); -} - -} // namespace -} // namespace tflite - -int main(int argc, char** argv) { - ::tflite::LogToStderr(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h index c24b7faa21..a76649f934 100644 --- a/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h @@ -1746,12 +1746,6 @@ inline void Ceil(const float* input_data, const Dims<4>& input_dims, output_data); } -inline void Abs(const float* input_data, const Dims<4>& input_dims, - float* output_data, const Dims<4>& output_dims) { - Abs(DimsToShape(input_dims), input_data, DimsToShape(output_dims), - output_data); -} - inline void ResizeBilinear(const float* input_data, const Dims<4>& input_dims, const int32* output_size_data, const Dims<4>& output_size_dims, float* output_data, diff --git a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h index 7f7296ae97..0bd779bf3a 100644 --- a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h @@ -4906,14 +4906,6 @@ inline void Ceil(const RuntimeShape& input_shape, const float* input_data, output_map.array() = Eigen::ceil(input_map.array()); } -inline void Abs(const RuntimeShape& input_shape, const float* input_data, - const RuntimeShape& output_shape, float* output_data) { - gemmlowp::ScopedProfilingLabel label("Abs"); - auto input_map = MapAsVector(input_data, input_shape); - auto output_map = MapAsVector(output_data, output_shape); - output_map.array() = Eigen::abs(input_map.array()); -} - #ifdef USE_NEON inline void ResizeBilinearKernel(const float* input_ptr, int32 depth, float scale, float* output_ptr) { diff --git a/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h b/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h index 30bb92c8b3..431e2413e8 100644 --- a/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h +++ b/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h @@ -1889,12 +1889,6 @@ inline void Ceil(const float* input_data, const Dims<4>& input_dims, output_data); } -inline void Abs(const float* input_data, const Dims<4>& input_dims, - float* output_data, const Dims<4>& output_dims) { - Abs(DimsToShape(input_dims), input_data, DimsToShape(output_dims), - output_data); -} - template inline void ResizeBilinear(const T* input_data, const Dims<4>& input_dims, const int32* output_size_data, diff --git a/tensorflow/lite/kernels/internal/reference/reference_ops.h b/tensorflow/lite/kernels/internal/reference/reference_ops.h index 133a455e9e..6b8d817ca1 100644 --- a/tensorflow/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/lite/kernels/internal/reference/reference_ops.h @@ -3050,16 +3050,6 @@ inline void Ceil(const RuntimeShape& input_shape, const float* input_data, } } -inline void Abs(const RuntimeShape& input_shape, const float* input_data, - const RuntimeShape& output_shape, float* output_data) { - const int flat_size = MatchingFlatSize(input_shape, output_shape); - - for (int i = 0; i < flat_size; i++) { - int offset = i; - output_data[offset] = std::fabs(input_data[offset]); - } -} - template inline void Gather(const tflite::GatherParams& op_params, const RuntimeShape& input_shape, const T* input_data, diff --git a/tensorflow/lite/kernels/register.cc b/tensorflow/lite/kernels/register.cc index caf61e9003..47ae934c4b 100644 --- a/tensorflow/lite/kernels/register.cc +++ b/tensorflow/lite/kernels/register.cc @@ -95,7 +95,6 @@ TfLiteRegistration* Register_LESS(); TfLiteRegistration* Register_LESS_EQUAL(); TfLiteRegistration* Register_FLOOR(); TfLiteRegistration* Register_CEIL(); -TfLiteRegistration* Register_ABS(); TfLiteRegistration* Register_TILE(); TfLiteRegistration* Register_NEG(); TfLiteRegistration* Register_SUM(); @@ -238,7 +237,6 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_LESS_EQUAL, Register_LESS_EQUAL()); AddBuiltin(BuiltinOperator_FLOOR, Register_FLOOR()); AddBuiltin(BuiltinOperator_CEIL, Register_CEIL()); - AddBuiltin(BuiltinOperator_ABS, Register_ABS()); AddBuiltin(BuiltinOperator_NEG, Register_NEG()); AddBuiltin(BuiltinOperator_SELECT, Register_SELECT()); AddBuiltin(BuiltinOperator_SLICE, Register_SLICE()); diff --git a/tensorflow/lite/nnapi/NeuralNetworksShim.h b/tensorflow/lite/nnapi/NeuralNetworksShim.h index 037d364c3b..82c5840952 100644 --- a/tensorflow/lite/nnapi/NeuralNetworksShim.h +++ b/tensorflow/lite/nnapi/NeuralNetworksShim.h @@ -144,7 +144,6 @@ enum { ANEURALNETWORKS_SUB = 36, ANEURALNETWORKS_TRANSPOSE = 37, ANEURALNETWORKS_CEIL = 38, - ANEURALNETWORKS_ABS = 39, }; /** diff --git a/tensorflow/lite/nnapi_delegate.cc b/tensorflow/lite/nnapi_delegate.cc index 6da803ce7b..dfbb4813ad 100644 --- a/tensorflow/lite/nnapi_delegate.cc +++ b/tensorflow/lite/nnapi_delegate.cc @@ -492,9 +492,6 @@ TfLiteStatus AddOpsAndParams( case tflite::BuiltinOperator_CEIL: nn_op_type = ANEURALNETWORKS_CEIL; break; - case tflite::BuiltinOperator_ABS: - nn_op_type = ANEURALNETWORKS_ABS; - break; case tflite::BuiltinOperator_LOGISTIC: nn_op_type = ANEURALNETWORKS_LOGISTIC; break; diff --git a/tensorflow/lite/testing/generate_examples.py b/tensorflow/lite/testing/generate_examples.py index 825873a884..1dea01d59b 100644 --- a/tensorflow/lite/testing/generate_examples.py +++ b/tensorflow/lite/testing/generate_examples.py @@ -3047,32 +3047,6 @@ def make_ceil_tests(zip_path): make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) -def make_abs_tests(zip_path): - """Make a set of tests to do abs.""" - - test_parameters = [{ - "input_dtype": [tf.float32], - "input_shape": [[1], [1, 2], [5, 6, 7, 8], [3, 4, 5, 6]], - }] - - def build_graph(parameters): - """Build the abs op testing graph.""" - input_value = tf.placeholder( - dtype=parameters["input_dtype"], - name="input1", - shape=parameters["input_shape"]) - out = tf.abs(input_value) - return [input_value], [out] - - 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={inputs[0]: input_value}) - - make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) - - def make_neg_tests(zip_path): """Make a set of tests to do neg.""" diff --git a/tensorflow/lite/toco/export_tensorflow.cc b/tensorflow/lite/toco/export_tensorflow.cc index 9dfeaad164..816cb9bc28 100644 --- a/tensorflow/lite/toco/export_tensorflow.cc +++ b/tensorflow/lite/toco/export_tensorflow.cc @@ -1215,16 +1215,6 @@ void ConvertCeilOperator(const Model& model, const CeilOperator& src_op, (*ceil_op->mutable_attr())["T"].set_type(DT_FLOAT); } -void ConvertAbsOperator(const Model& model, const AbsOperator& src_op, - GraphDef* tensorflow_graph) { - tensorflow::NodeDef* abs_op = tensorflow_graph->add_node(); - abs_op->set_op("Abs"); - abs_op->set_name(src_op.outputs[0]); - CHECK_EQ(src_op.inputs.size(), 1); - *abs_op->add_input() = src_op.inputs[0]; - (*abs_op->mutable_attr())["T"].set_type(DT_FLOAT); -} - void ConvertGatherOperator(const Model& model, const GatherOperator& src_op, GraphDef* tensorflow_graph) { tensorflow::NodeDef* gather_op = tensorflow_graph->add_node(); @@ -2192,9 +2182,6 @@ void ConvertOperator(const Model& model, const Operator& src_op, } else if (src_op.type == OperatorType::kCeil) { ConvertCeilOperator(model, static_cast(src_op), tensorflow_graph); - } else if (src_op.type == OperatorType::kAbs) { - ConvertAbsOperator(model, static_cast(src_op), - tensorflow_graph); } else if (src_op.type == OperatorType::kGather) { ConvertGatherOperator(model, static_cast(src_op), tensorflow_graph); diff --git a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc index c6c7681c03..0aae68c9c4 100644 --- a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc +++ b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc @@ -1870,7 +1870,6 @@ void ProcessMirrorPadOperator(Model* model, MirrorPadOperator* op) { case OperatorType::kCast: case OperatorType::kFloor: case OperatorType::kCeil: - case OperatorType::kAbs: case OperatorType::kExp: case OperatorType::kSin: case OperatorType::kLogicalAnd: diff --git a/tensorflow/lite/toco/graph_transformations/reorder_elementwise_unary.cc b/tensorflow/lite/toco/graph_transformations/reorder_elementwise_unary.cc index 90e64816c8..98105d384e 100644 --- a/tensorflow/lite/toco/graph_transformations/reorder_elementwise_unary.cc +++ b/tensorflow/lite/toco/graph_transformations/reorder_elementwise_unary.cc @@ -29,7 +29,6 @@ namespace { bool IsElementwiseOperator(OperatorType optype) { switch (optype) { - case OperatorType::kAbs: case OperatorType::kCast: case OperatorType::kCeil: case OperatorType::kExp: diff --git a/tensorflow/lite/toco/import_tensorflow.cc b/tensorflow/lite/toco/import_tensorflow.cc index de8ae19a71..53defed6cb 100644 --- a/tensorflow/lite/toco/import_tensorflow.cc +++ b/tensorflow/lite/toco/import_tensorflow.cc @@ -1505,20 +1505,6 @@ tensorflow::Status ConvertCeilOperator( return tensorflow::Status::OK(); } -tensorflow::Status ConvertAbsOperator( - const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, - Model* model) { - CHECK_EQ(node.op(), "Abs"); - TF_QCHECK_OK(CheckInputsCount(node, tf_import_flags, 1)); - const auto data_type = GetDataTypeAttr(node, "T"); - CHECK(data_type == DT_FLOAT); - auto* op = new AbsOperator; - op->inputs.push_back(node.input(0)); - op->outputs.push_back(node.name()); - model->operators.emplace_back(op); - return tensorflow::Status::OK(); -} - tensorflow::Status ConvertGatherOperator( const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, Model* model) { diff --git a/tensorflow/lite/toco/model.h b/tensorflow/lite/toco/model.h index 69c6d7608a..c5cb215114 100644 --- a/tensorflow/lite/toco/model.h +++ b/tensorflow/lite/toco/model.h @@ -37,7 +37,6 @@ using tflite::QuantizationParams; enum class OperatorType : uint8 { kNone, // General-purpose neural network operators. - kAbs, kAdd, kAddN, kAveragePool, @@ -1670,16 +1669,6 @@ struct CeilOperator : Operator { CeilOperator() : Operator(OperatorType::kCeil) {} }; -// Abs operator. -// -// Inputs: -// inputs[0]: required: the input array -// -// TensorFlow equivalent: Abs -struct AbsOperator : Operator { - AbsOperator() : Operator(OperatorType::kAbs) {} -}; - // Gather operator. It gathers slices from params according to indices. // Only 1-D indices are supported at the moment. // diff --git a/tensorflow/lite/toco/tflite/operator.cc b/tensorflow/lite/toco/tflite/operator.cc index 2122d04958..dd47808046 100644 --- a/tensorflow/lite/toco/tflite/operator.cc +++ b/tensorflow/lite/toco/tflite/operator.cc @@ -1651,8 +1651,6 @@ std::vector> BuildOperatorList( MakeUnique>("FLOOR", OperatorType::kFloor)); ops.push_back( MakeUnique>("CEIL", OperatorType::kCeil)); - ops.push_back( - MakeUnique>("ABS", OperatorType::kAbs)); ops.push_back( MakeUnique>("RELU", OperatorType::kRelu)); ops.push_back(MakeUnique>( diff --git a/tensorflow/lite/toco/tflite/operator_test.cc b/tensorflow/lite/toco/tflite/operator_test.cc index e284619903..ba713edcb6 100644 --- a/tensorflow/lite/toco/tflite/operator_test.cc +++ b/tensorflow/lite/toco/tflite/operator_test.cc @@ -115,7 +115,6 @@ TEST_F(OperatorTest, SimpleOperators) { OperatorType::kDequantize); CheckSimpleOperator("FLOOR", OperatorType::kFloor); CheckSimpleOperator("CEIL", OperatorType::kCeil); - CheckSimpleOperator("ABS", OperatorType::kAbs); CheckSimpleOperator("RELU", OperatorType::kRelu); CheckSimpleOperator("RELU_N1_TO_1", OperatorType::kRelu1); CheckSimpleOperator("RELU6", OperatorType::kRelu6); diff --git a/tensorflow/lite/toco/tooling_util.cc b/tensorflow/lite/toco/tooling_util.cc index 0887dca9b5..9d6f554c59 100644 --- a/tensorflow/lite/toco/tooling_util.cc +++ b/tensorflow/lite/toco/tooling_util.cc @@ -386,7 +386,6 @@ const char* OperatorTypeName(OperatorType type) { HANDLE_OPERATORTYPENAME_CASE(Cast) HANDLE_OPERATORTYPENAME_CASE(Floor) HANDLE_OPERATORTYPENAME_CASE(Ceil) - HANDLE_OPERATORTYPENAME_CASE(Abs) HANDLE_OPERATORTYPENAME_CASE(Gather) HANDLE_OPERATORTYPENAME_CASE(ResizeBilinear) HANDLE_OPERATORTYPENAME_CASE(SpaceToBatchND) -- GitLab From 9d5ac5c511dca4a959014b6f9882309bc9bd703f Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Thu, 13 Dec 2018 22:40:14 +0100 Subject: [PATCH 0044/2345] Fix typos --- tensorflow/python/data/experimental/ops/grouping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/data/experimental/ops/grouping.py b/tensorflow/python/data/experimental/ops/grouping.py index 71e4b3391f..2eef36d0c8 100644 --- a/tensorflow/python/data/experimental/ops/grouping.py +++ b/tensorflow/python/data/experimental/ops/grouping.py @@ -162,8 +162,8 @@ def bucket_by_sequence_length(element_length_func, no_padding: `bool`, indicates whether to pad the batch features (features need to be either of type `tf.SparseTensor` or of same shape). drop_remainder: (Optional.) A `tf.bool` scalar `tf.Tensor`, representing - whether the last batch should be dropped in the case its has fewer than - batch_size` elements; the default behavior is not to drop the smaller + whether the last batch should be dropped in the case it has fewer than + `batch_size` elements; the default behavior is not to drop the smaller batch. Returns: -- GitLab From a06497bce4e91b267850b8b82ee4fe684c01e94b Mon Sep 17 00:00:00 2001 From: Steve Lang Date: Fri, 14 Dec 2018 09:01:27 +1100 Subject: [PATCH 0045/2345] .numpy() was missing in code example: tf.add(1, 2).numpy() --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 68d7e180d1..c512aeb06c 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ $ python ```python >>> import tensorflow as tf >>> tf.enable_eager_execution() ->>> tf.add(1, 2) +>>> tf.add(1, 2).numpy() 3 >>> hello = tf.constant('Hello, TensorFlow!') >>> hello.numpy() -- GitLab From c8c5f69e9a4d2424ba4cc603daebd4c24e5128d4 Mon Sep 17 00:00:00 2001 From: Asim Shankar Date: Thu, 13 Dec 2018 14:45:48 -0800 Subject: [PATCH 0046/2345] [Go]: Fixup paths to protocol buffers. Without this: go generate github.com/tensorflow/tensorflow/tensorflow/go/op would fail with: ../genop/internal/api_def_map.go:34:2: cannot find package "github.com/tensorflow/tensorflow/tensorflow/go/genop/internal/proto/tensorflow/core/framework_go_proto" in any of: /usr/local/go/src/github.com/tensorflow/tensorflow/tensorflow/go/genop/internal/proto/tensorflow/core/framework_go_proto (from $GOROOT) /go/src/github.com/tensorflow/tensorflow/tensorflow/go/genop/internal/proto/tensorflow/core/framework_go_proto (from $GOPATH) This breakage was probably introduced by https://github.com/tensorflow/tensorflow/pull/17262 --- tensorflow/go/genop/internal/api_def_map.go | 2 +- tensorflow/go/genop/internal/genop.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/go/genop/internal/api_def_map.go b/tensorflow/go/genop/internal/api_def_map.go index 8600452b47..0bbd88b61c 100644 --- a/tensorflow/go/genop/internal/api_def_map.go +++ b/tensorflow/go/genop/internal/api_def_map.go @@ -31,7 +31,7 @@ import ( "unsafe" "github.com/golang/protobuf/proto" - pb "github.com/tensorflow/tensorflow/tensorflow/go/genop/internal/proto/tensorflow/core/framework_go_proto" + pb "github.com/tensorflow/tensorflow/tensorflow/go/genop/internal/proto/github.com/tensorflow/tensorflow/tensorflow/go/core/framework" ) // Encapsulates a collection of API definitions. diff --git a/tensorflow/go/genop/internal/genop.go b/tensorflow/go/genop/internal/genop.go index fb81631218..1c05715a1a 100644 --- a/tensorflow/go/genop/internal/genop.go +++ b/tensorflow/go/genop/internal/genop.go @@ -47,7 +47,7 @@ import ( "unsafe" "github.com/golang/protobuf/proto" - pb "github.com/tensorflow/tensorflow/tensorflow/go/genop/internal/proto/tensorflow/core/framework_go_proto" + pb "github.com/tensorflow/tensorflow/tensorflow/go/genop/internal/proto/github.com/tensorflow/tensorflow/tensorflow/go/core/framework" ) // GenerateFunctionsForRegisteredOps writes a Go source code file to w -- GitLab From c1fc5d5036d850be852ec473f32292ca973f5bed Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Thu, 13 Dec 2018 22:57:22 +0100 Subject: [PATCH 0047/2345] Rewrite grouping tests to be @parameterized tests --- .../data/experimental/kernel_tests/BUILD | 1 + .../bucket_by_sequence_length_test.py | 60 ++++++++++++------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/tensorflow/python/data/experimental/kernel_tests/BUILD b/tensorflow/python/data/experimental/kernel_tests/BUILD index d7ca5a70e4..897e949b0f 100644 --- a/tensorflow/python/data/experimental/kernel_tests/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/BUILD @@ -23,6 +23,7 @@ py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) 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 fab79619a0..bcb7ef9496 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 @@ -19,6 +19,8 @@ from __future__ import print_function import random +from absl.testing import parameterized + from tensorflow.python.data.experimental.ops import grouping from tensorflow.python.data.kernel_tests import test_base from tensorflow.python.data.ops import dataset_ops @@ -69,9 +71,13 @@ def _get_record_shape(sparse): return tensor_shape.TensorShape([None]) -class BucketBySequenceLengthTest(test_base.DatasetTestBase): +class BucketBySequenceLengthTest(test_base.DatasetTestBase, parameterized.TestCase): - def testBucketDropReminder(self): + @parameterized.named_parameters( + ("WithoutPadding", True), + ("WithPadding", False), + ) + def testBucketDropReminder(self, param_no_padding): boundaries = [10, 20, 30] batch_sizes = [10, 8, 4, 2] @@ -192,10 +198,13 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): .format(sorted(expected_lengths), sorted(generated_lengths))) - for no_padding in (True, False): - _test_bucket_by_padding(no_padding) + _test_bucket_by_padding(param_no_padding) - def testBucket(self): + @parameterized.named_parameters( + ("WithoutPadding", True), + ("WithPadding", False), + ) + def testBucket(self, param_no_padding): boundaries = [10, 20, 30] batch_sizes = [10, 8, 4, 2] @@ -251,8 +260,7 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): self.assertEqual(sorted(batch_sizes), sorted(batch_sizes_val)) self.assertEqual(sorted(lengths), sorted(lengths_val)) - for no_padding in (True, False): - _test_bucket_by_padding(no_padding) + _test_bucket_by_padding(param_no_padding) def testPadToBoundary(self): @@ -336,7 +344,11 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): self.assertAllEqual(batches[4], [[1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]) - def testTupleElements(self): + @parameterized.named_parameters( + ("WithoutPadding", True), + ("WithPadding", False), + ) + def testTupleElements(self, param_no_padding): def build_dataset(sparse): def _generator(): @@ -364,10 +376,13 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): self.assertEqual([None, None], shapes[0].as_list()) self.assertEqual([None], shapes[1].as_list()) - for no_padding in (True, False): - _test_tuple_elements_by_padding(no_padding) + _test_tuple_elements_by_padding(param_no_padding) - def testBucketSparse(self): + @parameterized.named_parameters( + ("DoDropRemainder", True), + ("DoNotDropRemainder", False), + ) + def testBucketSparse(self, param_drop_remainder): """Tests bucketing of sparse tensors (case where `no_padding` == True). Test runs on following dataset: @@ -435,18 +450,17 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase): all_sparse_tensors.add(sprs_tensor) return all_sparse_tensors - for drop_remainder in (True, False): - dataset = _build_dataset() - boundaries = range(min_len + bucket_size + 1, max_len, bucket_size) - dataset = dataset.apply(grouping.bucket_by_sequence_length( - _element_length_fn, - boundaries, - [batch_size] * (len(boundaries) + 1), - no_padding=True, - drop_remainder=drop_remainder)) - batches = _compute_batches(dataset) - expected_batches = _compute_expected_batches(drop_remainder) - self.assertEqual(batches, expected_batches) + dataset = _build_dataset() + boundaries = range(min_len + bucket_size + 1, max_len, bucket_size) + dataset = dataset.apply(grouping.bucket_by_sequence_length( + _element_length_fn, + boundaries, + [batch_size] * (len(boundaries) + 1), + no_padding=True, + drop_remainder=param_drop_remainder)) + batches = _compute_batches(dataset) + expected_batches = _compute_expected_batches(param_drop_remainder) + self.assertEqual(batches, expected_batches) if __name__ == "__main__": -- GitLab From 2cad3742600f3e903bc5a2d1a60ef4b571e7c8f9 Mon Sep 17 00:00:00 2001 From: WeijieSun Date: Mon, 17 Dec 2018 10:23:31 +0800 Subject: [PATCH 0048/2345] remove KERB_TICKET_CACHE_PATH env --- tensorflow/core/platform/hadoop/hadoop_file_system.cc | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tensorflow/core/platform/hadoop/hadoop_file_system.cc b/tensorflow/core/platform/hadoop/hadoop_file_system.cc index eb35531e9f..9a7d98e6a0 100644 --- a/tensorflow/core/platform/hadoop/hadoop_file_system.cc +++ b/tensorflow/core/platform/hadoop/hadoop_file_system.cc @@ -59,8 +59,6 @@ class LibHDFS { std::function hdfsNewBuilder; std::function hdfsBuilderSetNameNode; std::function hdfsConfGetStr; - std::function - hdfsBuilderSetKerbTicketCachePath; std::function hdfsCloseFile; std::function hdfsPread; std::function hdfsWrite; @@ -87,7 +85,6 @@ class LibHDFS { BIND_HDFS_FUNC(hdfsNewBuilder); BIND_HDFS_FUNC(hdfsBuilderSetNameNode); BIND_HDFS_FUNC(hdfsConfGetStr); - BIND_HDFS_FUNC(hdfsBuilderSetKerbTicketCachePath); BIND_HDFS_FUNC(hdfsCloseFile); BIND_HDFS_FUNC(hdfsPread); BIND_HDFS_FUNC(hdfsWrite); @@ -166,13 +163,6 @@ Status HadoopFileSystem::Connect(StringPiece fname, hdfsFS* fs) { } else { hdfs_->hdfsBuilderSetNameNode(builder, 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 - // and related code are unnecessary. - char* ticket_cache_path = getenv("KERB_TICKET_CACHE_PATH"); - if (ticket_cache_path != nullptr) { - hdfs_->hdfsBuilderSetKerbTicketCachePath(builder, ticket_cache_path); - } *fs = hdfs_->hdfsBuilderConnect(builder); if (*fs == nullptr) { return errors::NotFound(strerror(errno)); -- GitLab From 8db73e896985d043f24592abf405fed4c867aee8 Mon Sep 17 00:00:00 2001 From: Pavel Samolysov Date: Mon, 17 Dec 2018 18:32:45 +0300 Subject: [PATCH 0049/2345] [OpenMP] Fix undeclared identifier in eigen_support.cc When OpenMP is enabled, the following error occurs during a compilation of the `tensorflow/lite/kernels/eigen_support.cc` unit: tensorflow/lite/kernels/eigen_support.cc:42:23: error: use of undeclared identifier 'context' Eigen::setNbThreads(context->recommended_num_threads); The `SetEigenNbThreads` method already gets the number of threads as the `threads` parameter and doesn't need to calculate it using a method of the `context` variable, so the invocation of the `Eigen::setNbThreads` member must be changed a little bit. Signed-off-by: Pavel Samolysov --- tensorflow/lite/kernels/eigen_support.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/eigen_support.cc b/tensorflow/lite/kernels/eigen_support.cc index bad5975a7c..e2a2c4aac9 100644 --- a/tensorflow/lite/kernels/eigen_support.cc +++ b/tensorflow/lite/kernels/eigen_support.cc @@ -39,7 +39,7 @@ void SetEigenNbThreads(int threads) { #if defined(EIGEN_HAS_OPENMP) // The global Eigen thread count is only used when OpenMP is enabled. As this // call causes problems with tsan, make it only when OpenMP is available. - Eigen::setNbThreads(context->recommended_num_threads); + Eigen::setNbThreads(threads); #endif // defined(EIGEN_HAS_OPENMP) } -- GitLab From 46288142246f2d5130e7c3ba5787f616d53e53a9 Mon Sep 17 00:00:00 2001 From: Joe Quadrino Date: Fri, 30 Nov 2018 19:42:36 -0500 Subject: [PATCH 0050/2345] Configurable AWS logging for S3 filesystem AWS logging for S3 is too verbose and should be configurable. export AWS_LOG_LEVEL=FATAL Using TF_CPP_MIN_LOG_LEVEL for the AWS log handler can lead to a high rate of aws logging which drowns out less frequent logs Related commits 7bb0592, f67a7a4 Issue #21898 --- tensorflow/core/platform/s3/aws_logging.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/platform/s3/aws_logging.cc b/tensorflow/core/platform/s3/aws_logging.cc index 44317f1a3e..8eeb2c9492 100644 --- a/tensorflow/core/platform/s3/aws_logging.cc +++ b/tensorflow/core/platform/s3/aws_logging.cc @@ -74,7 +74,9 @@ static const char* kAWSLoggingTag = "AWSLogging"; Aws::Utils::Logging::LogLevel ParseLogLevelFromEnv() { Aws::Utils::Logging::LogLevel log_level = Aws::Utils::Logging::LogLevel::Info; - const int64_t level = tensorflow::internal::MinLogLevelFromEnv(); + const int64_t level = + getenv("AWS_LOG_LEVEL") ? tensorflow::internal::LogLevelStrToInt(getenv("AWS_LOG_LEVEL")) + : tensorflow::internal::MinLogLevelFromEnv(); switch (level) { case INFO: -- GitLab From c5720d1626c3c7e85069b0436bffacc8c6ca8122 Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Wed, 19 Dec 2018 11:54:28 +0100 Subject: [PATCH 0051/2345] Update testBucketDropReminder test case See #24071 --- .../bucket_by_sequence_length_test.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) 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 e0978676fd..a95d8e1049 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 @@ -76,11 +76,12 @@ def _get_record_shape(sparse): @test_util.run_all_in_graph_and_eager_modes class BucketBySequenceLengthTest(test_base.DatasetTestBase, parameterized.TestCase): + # TODO(b/117581999): add eager coverage. @parameterized.named_parameters( ("WithoutPadding", True), ("WithPadding", False), ) - def testBucketDropReminder(self, param_no_padding): + def testSkipEagerBucketDropReminder(self, param_no_padding): boundaries = [10, 20, 30] batch_sizes = [10, 8, 4, 2] @@ -142,14 +143,15 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase, parameterized.TestCa batch_sizes, no_padding=no_padding, drop_remainder=True)) - batch, = dataset.make_one_shot_iterator().get_next() - - with self.cached_session() as sess: - batches = [] - for _ in range(n_expected_batches): - batches.append(self.evaluate(batch)) - with self.assertRaises(errors.OutOfRangeError): - self.evaluate(batch) + + get_next = self.getNext(dataset) + batches = [] + for _ in range(n_expected_batches): + batch, = self.evaluate(get_next()) + batches.append(batch) + + with self.assertRaises(errors.OutOfRangeError): + self.evaluate(get_next()) generated_lengths = [] -- GitLab From c30e6b769f8f6be9d93372a000ee330d7a5ec56a Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Wed, 19 Dec 2018 09:59:49 -0500 Subject: [PATCH 0052/2345] Update doc references to use tfp.distributions --- tensorflow/python/ops/distributions/bijector_impl.py | 2 +- .../python/ops/distributions/transformed_distribution.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/ops/distributions/bijector_impl.py b/tensorflow/python/ops/distributions/bijector_impl.py index 9c63385dd0..a347cfdec1 100644 --- a/tensorflow/python/ops/distributions/bijector_impl.py +++ b/tensorflow/python/ops/distributions/bijector_impl.py @@ -462,7 +462,7 @@ class Bijector(object): ```python - abs = tf.contrib.distributions.bijectors.AbsoluteValue() + abs = tfp.distributions.bijectors.AbsoluteValue() abs.forward(-1.) ==> 1. diff --git a/tensorflow/python/ops/distributions/transformed_distribution.py b/tensorflow/python/ops/distributions/transformed_distribution.py index 1becfc1877..3c6476864a 100644 --- a/tensorflow/python/ops/distributions/transformed_distribution.py +++ b/tensorflow/python/ops/distributions/transformed_distribution.py @@ -167,7 +167,7 @@ class TransformedDistribution(distribution_lib.Distribution): distribution: ```python - ds = tf.contrib.distributions + ds = tfp.distributions log_normal = ds.TransformedDistribution( distribution=ds.Normal(loc=0., scale=1.), bijector=ds.bijectors.Exp(), @@ -177,7 +177,7 @@ class TransformedDistribution(distribution_lib.Distribution): A `LogNormal` made from callables: ```python - ds = tf.contrib.distributions + ds = tfp.distributions log_normal = ds.TransformedDistribution( distribution=ds.Normal(loc=0., scale=1.), bijector=ds.bijectors.Inline( @@ -191,7 +191,7 @@ class TransformedDistribution(distribution_lib.Distribution): Another example constructing a Normal from a StandardNormal: ```python - ds = tf.contrib.distributions + ds = tfp.distributions normal = ds.TransformedDistribution( distribution=ds.Normal(loc=0., scale=1.), bijector=ds.bijectors.Affine( @@ -209,7 +209,7 @@ class TransformedDistribution(distribution_lib.Distribution): multivariate Normal as a `TransformedDistribution`. ```python - ds = tf.contrib.distributions + ds = tfp.distributions # We will create two MVNs with batch_shape = event_shape = 2. mean = [[-1., 0], # batch:0 [0., 1]] # batch:1 -- GitLab From 5bbdac505efbc4decaf7f15b247a26060fe25d46 Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Wed, 19 Dec 2018 12:39:03 -0800 Subject: [PATCH 0053/2345] Update lookup_ops.py --- tensorflow/python/ops/lookup_ops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/ops/lookup_ops.py b/tensorflow/python/ops/lookup_ops.py index 9302696a45..70bbbb72e6 100644 --- a/tensorflow/python/ops/lookup_ops.py +++ b/tensorflow/python/ops/lookup_ops.py @@ -64,6 +64,7 @@ def initialize_all_tables(name="init_all_tables"): @tf_export(v1=["initializers.tables_initializer", "tables_initializer"]) def tables_initializer(name="init_all_tables"): """Returns an Op that initializes all tables of the default graph. + See the [Low Level Intro](https://www.tensorflow.org/guide/low_level_intro#feature_columns) guide, for an example of usage. -- GitLab From 9e92c2fe264debad1548554182dcc9af06d2e6ee Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 19 Dec 2018 13:48:25 -0800 Subject: [PATCH 0054/2345] Fix bug with renaming output bindings --- tensorflow/contrib/tensorrt/BUILD | 1 + .../contrib/tensorrt/convert/convert_nodes.cc | 17 +++++ .../tensorrt/test/identity_output_test.py | 72 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 tensorflow/contrib/tensorrt/test/identity_output_test.py diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 784acce444..3e8c486d8c 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -491,6 +491,7 @@ cuda_py_tests( "test/binary_tensor_weight_broadcast_test.py", "test/concatenation_test.py", "test/const_broadcast_test.py", + "test/identity_output_test.py", "test/manual_test.py", "test/memory_alignment_test.py", "test/multi_connection_neighbor_engine_test.py", diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index adf8831b96..729afe6c64 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -879,6 +879,8 @@ Status Converter::ConvertNode(const NodeDef& node_def) { // We need to check the name before setting it. If the input is one of the // engine input, setting the name here will overwrite engine input // bindings which will cause runtime error. + // TODO(tmorris): Remove this work-around once we use TRT's IIdentityLayer + // in ConvertIdentity. if (output.is_tensor()) { const char* tensor_name = output.tensor()->getName(); if (!tensorflow::str_util::StartsWith(tensor_name, kInputPHName)) { @@ -939,6 +941,21 @@ Status Converter::RenameAndMarkOutputTensors( if (tensor == nullptr) { return errors::NotFound("Output tensor not found: ", output.first); } + // Check if this tensor has already been marked as an output. + // ConvertIdentity can cause the same tensor to be repeated in + // output_tensors, which can cause us to overwrite the name of the output + // tensor binding. For example, if we rename OutputPH_0 to OutputPH_1 then + // we won't be able to locate OutputPH_0 during runtime. To fix this, + // duplicate the tensor using no-op shuffle. + // TODO(tmorris): Remove this work-around once we use TRT's IIdentityLayer + // in ConvertIdentity. + if (tensorflow::str_util::StartsWith(tensor->getName(), kOutputPHName)) { + nvinfer1::IShuffleLayer* layer = network()->addShuffle(*tensor); + TFTRT_RETURN_ERROR_IF_NULLPTR( + layer, StrCat("Output Copy for ", tensor->getName())); + MarkQuantizationRangesAsInferrable(tensor, layer->getOutput(0)); + tensor = layer->getOutput(0); + } tensor->setName(output.second.c_str()); VLOG(1) << "Marking output tensor " << output.first << ", as output tensor " << output.second; diff --git a/tensorflow/contrib/tensorrt/test/identity_output_test.py b/tensorflow/contrib/tensorrt/test/identity_output_test.py new file mode 100644 index 0000000000..391434ba83 --- /dev/null +++ b/tensorflow/contrib/tensorrt/test/identity_output_test.py @@ -0,0 +1,72 @@ +# 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. +# ============================================================================== +"""Model script to test TF-TensorRT integration.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.tensorrt.test import tf_trt_integration_test_base as trt_test +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_array_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import nn +from tensorflow.python.platform import test + + +class IdentityTest(trt_test.TfTrtIntegrationTestBase): + + def _ConstOp(self, shape): + return constant_op.constant(np.random.randn(*shape), dtype=dtypes.float32) + + def GetParams(self): + """Testing conversion of BiasAdd MatMul in TF-TRT conversion.""" + input_name = "input" + input_dims = [100, 32] + g = ops.Graph() + with g.as_default(): + x = array_ops.placeholder( + dtype=dtypes.float32, shape=input_dims, name=input_name) + + b = self._ConstOp((32, 4)) + x1 = math_ops.matmul(x, b) + b = self._ConstOp((1, 4)) + x1 = x1 + b + + out1 = array_ops.identity(x1, name='output1') + out2 = array_ops.identity(x1, name='output2') + iden1 = array_ops.identity(x1) + out3 = array_ops.identity(iden1, name='output3') + + return trt_test.TfTrtIntegrationTestParams( + gdef=g.as_graph_def(), + input_names=[input_name], + input_dims=[input_dims], + output_names=['output1', 'output2', 'output3'], + expected_output_dims=[(100, 4), (100, 4), (100, 4)]) + + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["TRTEngineOp_0"] + + +if __name__ == "__main__": + test.main() -- GitLab From cfe7df2cf064f9eacdf8ab30574ebe9ec6438544 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 19 Dec 2018 14:14:28 -0800 Subject: [PATCH 0055/2345] Update comment in test --- tensorflow/contrib/tensorrt/test/identity_output_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/tensorrt/test/identity_output_test.py b/tensorflow/contrib/tensorrt/test/identity_output_test.py index 391434ba83..1aa05999bd 100644 --- a/tensorflow/contrib/tensorrt/test/identity_output_test.py +++ b/tensorflow/contrib/tensorrt/test/identity_output_test.py @@ -37,7 +37,7 @@ class IdentityTest(trt_test.TfTrtIntegrationTestBase): return constant_op.constant(np.random.randn(*shape), dtype=dtypes.float32) def GetParams(self): - """Testing conversion of BiasAdd MatMul in TF-TRT conversion.""" + """Testing engine with the same tensor repeated as output via identity.""" input_name = "input" input_dims = [100, 32] g = ops.Graph() -- GitLab From f03cf64b1295ea4e9e431a7a524cc23b2c1f3306 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 19 Dec 2018 15:10:58 -0800 Subject: [PATCH 0056/2345] Explain usage of shuffle layer --- tensorflow/contrib/tensorrt/convert/convert_nodes.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 729afe6c64..2a402fe699 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -950,6 +950,7 @@ Status Converter::RenameAndMarkOutputTensors( // TODO(tmorris): Remove this work-around once we use TRT's IIdentityLayer // in ConvertIdentity. if (tensorflow::str_util::StartsWith(tensor->getName(), kOutputPHName)) { + // Using shuffle layer for identity by not setting reshape or transpose. nvinfer1::IShuffleLayer* layer = network()->addShuffle(*tensor); TFTRT_RETURN_ERROR_IF_NULLPTR( layer, StrCat("Output Copy for ", tensor->getName())); -- GitLab From d489a943a0fbb995d48ee70558270c4a5f0f9c39 Mon Sep 17 00:00:00 2001 From: Taylor Thornton Date: Tue, 18 Dec 2018 22:19:59 -0800 Subject: [PATCH 0057/2345] upgrade aws-sdk-cpp to 1.5.8 in order to pick up the auth retry changeset that landed in that version --- third_party/aws/BUILD.bazel | 5 +++++ third_party/aws/workspace.bzl | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/third_party/aws/BUILD.bazel b/third_party/aws/BUILD.bazel index 5426f79e46..1f61858777 100644 --- a/third_party/aws/BUILD.bazel +++ b/third_party/aws/BUILD.bazel @@ -80,6 +80,11 @@ cc_library( deps = [ "@curl", ], + copts = [ + "-DAWS_SDK_VERSION_MAJOR=1", + "-DAWS_SDK_VERSION_MINOR=5", + "-DAWS_SDK_VERSION_PATCH=8" + ], ) template_rule( diff --git a/third_party/aws/workspace.bzl b/third_party/aws/workspace.bzl index c216638154..10799b5153 100644 --- a/third_party/aws/workspace.bzl +++ b/third_party/aws/workspace.bzl @@ -2,14 +2,17 @@ load("//third_party:repo.bzl", "third_party_http_archive") +# NOTE: version updates here should also update the major, minor, and patch variables declared in +# the copts field of the //third_party/aws:aws target + def repo(): third_party_http_archive( name = "aws", urls = [ - "https://mirror.bazel.build/github.com/aws/aws-sdk-cpp/archive/1.3.15.tar.gz", - "https://github.com/aws/aws-sdk-cpp/archive/1.3.15.tar.gz", + "https://mirror.bazel.build/github.com/aws/aws-sdk-cpp/archive/1.5.8.tar.gz", + "https://github.com/aws/aws-sdk-cpp/archive/1.5.8.tar.gz", ], - sha256 = "b888d8ce5fc10254c3dd6c9020c7764dd53cf39cf011249d0b4deda895de1b7c", - strip_prefix = "aws-sdk-cpp-1.3.15", + sha256 = "89905075fe50aa13e0337ff905c2e8c1ce9caf77a3504484a7cda39179120ffc", + strip_prefix = "aws-sdk-cpp-1.5.8", build_file = "//third_party/aws:BUILD.bazel", ) -- GitLab From c75023169590ef0534579758285d6a1d8ad54ad2 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 19 Dec 2018 23:38:51 +0000 Subject: [PATCH 0058/2345] Replace deprecated FastGFile with GFile FastGFile has been deprecated and replaced with GFile, though the example in speech_commands still uses FastGFile. This fix fix the issue to remove the deprecated warning: ``` WARNING:tensorflow:From :1: __init__ (from tensorflow.python.platform.gfile) is deprecated and will be removed in a future version. Instructions for updating: Use tf.gfile.GFile. ``` Signed-off-by: Yong Tang --- tensorflow/examples/speech_commands/label_wav.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/examples/speech_commands/label_wav.py b/tensorflow/examples/speech_commands/label_wav.py index 0017aec3a5..eb8323454c 100644 --- a/tensorflow/examples/speech_commands/label_wav.py +++ b/tensorflow/examples/speech_commands/label_wav.py @@ -45,7 +45,7 @@ FLAGS = None def load_graph(filename): """Unpersists graph from file as default graph.""" - with tf.gfile.FastGFile(filename, 'rb') as f: + with tf.gfile.GFile(filename, 'rb') as f: graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) tf.import_graph_def(graph_def, name='') -- GitLab From 4696da4bf9a586cf250b32a062da833af3ffd4a9 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 19 Dec 2018 23:41:52 +0000 Subject: [PATCH 0059/2345] Also replace FastGFile to GFile in label_wav_dir.py Signed-off-by: Yong Tang --- tensorflow/examples/speech_commands/label_wav_dir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/examples/speech_commands/label_wav_dir.py b/tensorflow/examples/speech_commands/label_wav_dir.py index a34db512dd..2e1890c3e8 100644 --- a/tensorflow/examples/speech_commands/label_wav_dir.py +++ b/tensorflow/examples/speech_commands/label_wav_dir.py @@ -46,7 +46,7 @@ FLAGS = None def load_graph(filename): """Unpersists graph from file as default graph.""" - with tf.gfile.FastGFile(filename, 'rb') as f: + with tf.gfile.GFile(filename, 'rb') as f: graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) tf.import_graph_def(graph_def, name='') -- GitLab From 0f10d9fcd0c47b497d1bd5069f28d21f94fedc60 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 19 Dec 2018 15:56:19 -0800 Subject: [PATCH 0060/2345] Improve comment --- tensorflow/contrib/tensorrt/test/identity_output_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/tensorrt/test/identity_output_test.py b/tensorflow/contrib/tensorrt/test/identity_output_test.py index 1aa05999bd..2be9da9ede 100644 --- a/tensorflow/contrib/tensorrt/test/identity_output_test.py +++ b/tensorflow/contrib/tensorrt/test/identity_output_test.py @@ -12,7 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Model script to test TF-TensorRT integration.""" +"""This test checks a situation where the same tensor is considered as an output +multiple times because it has been duplicated by 2+ indentity ops. Previously, +the tensor would be renamed multiple times, overwriting the output binding name +which resulted in a runtime error when the binding would not be found. +""" from __future__ import absolute_import from __future__ import division -- GitLab From 7faefa4bb665e115cc744d7895a407338624993f Mon Sep 17 00:00:00 2001 From: Clayne Robison Date: Wed, 19 Dec 2018 19:08:29 -0700 Subject: [PATCH 0061/2345] This PR tempers the unused CPU instruction warning when TensorFlow is built with support for Intel(R) MKL-DNN. Many customers have complained about this warning because they don't understand that MKL-DNN has support for JIT compilation that detects CPU features at runtime. --- tensorflow/core/platform/cpu_feature_guard.cc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tensorflow/core/platform/cpu_feature_guard.cc b/tensorflow/core/platform/cpu_feature_guard.cc index 2efe0c0876..3f0d1cdb51 100644 --- a/tensorflow/core/platform/cpu_feature_guard.cc +++ b/tensorflow/core/platform/cpu_feature_guard.cc @@ -139,7 +139,19 @@ void InfoAboutUnusedCPUFeatures() { #endif // else of if defined(_MSC_VER) && !defined(__clang__) if (!missing_instructions.empty()) { LOG(INFO) << "Your CPU supports instructions that this TensorFlow " +#ifndef INTEL_MKL << "binary was not compiled to use:" << missing_instructions; +#else + << "binary was not compiled to use:" << missing_instructions + << ". When TensorFlow is compiled with support for these " + << "instructions, performance can improve. However, because " + << "this version of TensorFlow has been compiled with support " + << "for Intel(R) MKL-DNN, which supports just-in-time " + << "compilation, many performance-critical codepaths will use " + << "these instructions, even though TensorFlow was compiled " + << "without explicit support them. See " + << "https://github.com/intel/mkl-dnn/issues/3"; +#endif } }); } -- GitLab From 915b8783a05b0da7c30ba36531ce03c811930852 Mon Sep 17 00:00:00 2001 From: Clayne Robison Date: Wed, 19 Dec 2018 19:13:27 -0700 Subject: [PATCH 0062/2345] [Intel MKL] Remove TensorFlow lite test from the public CI. MKL does not support TF lite. --- tensorflow/tools/ci_build/linux/cpu/run_mkl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/linux/cpu/run_mkl.sh b/tensorflow/tools/ci_build/linux/cpu/run_mkl.sh index 7be5f454ec..a8b73cbe0c 100755 --- a/tensorflow/tools/ci_build/linux/cpu/run_mkl.sh +++ b/tensorflow/tools/ci_build/linux/cpu/run_mkl.sh @@ -36,4 +36,4 @@ yes "" | $PYTHON_BIN_PATH configure.py bazel test --test_tag_filters=-no_oss,-oss_serial,-gpu,-benchmark-test --test_lang_filters=cc,py -k \ --jobs=${N_JOBS} --test_timeout 300,450,1200,3600 --build_tests_only \ --config=mkl --test_env=KMP_BLOCKTIME=0 --config=opt --test_output=errors -- \ - //tensorflow/... -//tensorflow/compiler/... -//tensorflow/contrib/... + //tensorflow/... -//tensorflow/compiler/... -//tensorflow/contrib/... -//tensorflow/lite/... -- GitLab From f4867d3e4fecdb1ca8a445addad383aa0292a3b3 Mon Sep 17 00:00:00 2001 From: Sami Kama Date: Wed, 19 Dec 2018 19:40:22 -0800 Subject: [PATCH 0063/2345] Initialize only CPU devices in optimize_dataset_op --- tensorflow/core/grappler/grappler_item_builder.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/grappler/grappler_item_builder.cc b/tensorflow/core/grappler/grappler_item_builder.cc index 9224ee7849..a984efd10d 100644 --- a/tensorflow/core/grappler/grappler_item_builder.cc +++ b/tensorflow/core/grappler/grappler_item_builder.cc @@ -103,7 +103,11 @@ Status OptimizeGraph(const GraphDef& graph_def_arg, GraphDef* output_graph_def, // Instantiate all variables for function library runtime creation. std::vector> devices; - TF_RETURN_IF_ERROR(DeviceFactory::AddDevices( + // Only CPU device is used so instead of calling DeviceFactory::AddDevices() + // with dummy session config, which will conflict with user defined options and + // create unwanted devices, call cpu_factory->CreateDevices() to get CPU only devices. + DeviceFactory* cpu_factory = DeviceFactory::GetFactory("CPU"); + TF_RETURN_IF_ERROR(cpu_factory->CreateDevices( options, "/job:localhost/replica:0/task:0", &devices)); Device* cpu_device = devices[0].get(); std::unique_ptr dvc_mgr(new DeviceMgr(std::move(devices))); -- GitLab From 85ef6de9fcb5977bd738e10264f0641869594b83 Mon Sep 17 00:00:00 2001 From: Sami Kama Date: Wed, 19 Dec 2018 19:52:47 -0800 Subject: [PATCH 0064/2345] Fix clang format --- tensorflow/core/grappler/grappler_item_builder.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/grappler/grappler_item_builder.cc b/tensorflow/core/grappler/grappler_item_builder.cc index a984efd10d..fc55fb5b3d 100644 --- a/tensorflow/core/grappler/grappler_item_builder.cc +++ b/tensorflow/core/grappler/grappler_item_builder.cc @@ -104,8 +104,9 @@ Status OptimizeGraph(const GraphDef& graph_def_arg, GraphDef* output_graph_def, // Instantiate all variables for function library runtime creation. std::vector> devices; // Only CPU device is used so instead of calling DeviceFactory::AddDevices() - // with dummy session config, which will conflict with user defined options and - // create unwanted devices, call cpu_factory->CreateDevices() to get CPU only devices. + // with dummy session config, which will conflict with user defined options + // and create unwanted devices, call cpu_factory->CreateDevices() to get CPU + // only devices. DeviceFactory* cpu_factory = DeviceFactory::GetFactory("CPU"); TF_RETURN_IF_ERROR(cpu_factory->CreateDevices( options, "/job:localhost/replica:0/task:0", &devices)); -- GitLab From 2565842e71e1665d8a4fc126edc40b37becfed71 Mon Sep 17 00:00:00 2001 From: Innovimax Date: Thu, 20 Dec 2018 14:11:27 +0100 Subject: [PATCH 0065/2345] fix typo --- tensorflow/java/src/gen/cc/op_gen_main.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/java/src/gen/cc/op_gen_main.cc b/tensorflow/java/src/gen/cc/op_gen_main.cc index 0d9e0883af..cf4bb03dad 100644 --- a/tensorflow/java/src/gen/cc/op_gen_main.cc +++ b/tensorflow/java/src/gen/cc/op_gen_main.cc @@ -35,7 +35,7 @@ const char kUsageHeader[] = "graph.\n\n" "Operation wrappers are generated under the path specified by the " "'--output_dir' argument. This path can be absolute or relative to the\n" - "current working directory and will be created if it does not exists.\n\n" + "current working directory and will be created if it does not exist.\n\n" "Note that the operations will not be available through the " "'org.tensorflow.op.Ops' API until the generated classes are compiled\n" "using an appropriate annotation processor.\n\n" -- GitLab From 8cf1cf01615e68b4946ebad5a49d8096f0366e7f Mon Sep 17 00:00:00 2001 From: Mahmoud Abuzaina Date: Thu, 20 Dec 2018 08:58:52 -0800 Subject: [PATCH 0066/2345] Hiding Mkl Quantized Ops --- .../api_def/base_api/api_def_QuantizedConv2DAndRelu.pbtxt | 4 ++++ .../api_def_QuantizedConv2DAndReluAndRequantize.pbtxt | 4 ++++ .../base_api/api_def_QuantizedConv2DAndRequantize.pbtxt | 4 ++++ .../api_def/base_api/api_def_QuantizedConv2DWithBias.pbtxt | 4 ++++ .../base_api/api_def_QuantizedConv2DWithBiasAndRelu.pbtxt | 4 ++++ .../api_def_QuantizedConv2DWithBiasAndReluAndRequantize.pbtxt | 4 ++++ .../api_def_QuantizedConv2DWithBiasAndRequantize.pbtxt | 4 ++++ ...QuantizedConv2DWithBiasSignedSumAndReluAndRequantize.pbtxt | 4 ++++ .../base_api/api_def_QuantizedConv2DWithBiasSumAndRelu.pbtxt | 4 ++++ ...i_def_QuantizedConv2DWithBiasSumAndReluAndRequantize.pbtxt | 4 ++++ 10 files changed, 40 insertions(+) create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndRelu.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndReluAndRequantize.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndRequantize.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBias.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndRelu.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndReluAndRequantize.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndRequantize.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSignedSumAndReluAndRequantize.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSumAndRelu.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSumAndReluAndRequantize.pbtxt diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndRelu.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndRelu.pbtxt new file mode 100644 index 0000000000..17ff15378c --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndRelu.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DAndRelu" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndReluAndRequantize.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndReluAndRequantize.pbtxt new file mode 100644 index 0000000000..b3ab3eba2c --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndReluAndRequantize.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DAndReluAndRequantize" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndRequantize.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndRequantize.pbtxt new file mode 100644 index 0000000000..8b00c2b7f6 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DAndRequantize.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DAndRequantize" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBias.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBias.pbtxt new file mode 100644 index 0000000000..f309f648ca --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBias.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DWithBias" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndRelu.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndRelu.pbtxt new file mode 100644 index 0000000000..b6b73eaae3 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndRelu.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DWithBiasAndRelu" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndReluAndRequantize.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndReluAndRequantize.pbtxt new file mode 100644 index 0000000000..101f72708a --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndReluAndRequantize.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DWithBiasAndReluAndRequantize" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndRequantize.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndRequantize.pbtxt new file mode 100644 index 0000000000..697e268415 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasAndRequantize.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DWithBiasAndRequantize" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSignedSumAndReluAndRequantize.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSignedSumAndReluAndRequantize.pbtxt new file mode 100644 index 0000000000..0cf52d6c89 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSignedSumAndReluAndRequantize.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DWithBiasSignedSumAndReluAndRequantize" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSumAndRelu.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSumAndRelu.pbtxt new file mode 100644 index 0000000000..e91a2b8dc0 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSumAndRelu.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DWithBiasSumAndRelu" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSumAndReluAndRequantize.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSumAndReluAndRequantize.pbtxt new file mode 100644 index 0000000000..fe3ec528bf --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_QuantizedConv2DWithBiasSumAndReluAndRequantize.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "QuantizedConv2DWithBiasSumAndReluAndRequantize" + visibility: HIDDEN +} -- GitLab From a9ba4a47efb80d2951ee6deea8e2d8de56dbaaf9 Mon Sep 17 00:00:00 2001 From: vitor-alves Date: Thu, 20 Dec 2018 17:26:45 -0200 Subject: [PATCH 0067/2345] Typo --- tensorflow/lite/examples/label_image/label_image.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/examples/label_image/label_image.md b/tensorflow/lite/examples/label_image/label_image.md index fd9f49918b..debe8ffbc5 100644 --- a/tensorflow/lite/examples/label_image/label_image.md +++ b/tensorflow/lite/examples/label_image/label_image.md @@ -51,7 +51,7 @@ average time: 100.986 ms 0.0235294: 514 cornet 0.0196078: 835 suit ``` -Run `interpreter->Invoker()` 100 times: +Run `interpreter->Invoke()` 100 times: ``` > ./label_image -c 100 Loaded model ./mobilenet_quant_v1_224.tflite -- GitLab From f8dd4143788857cd6655ce983937908dc4ecd232 Mon Sep 17 00:00:00 2001 From: Guozhong Zhuang Date: Thu, 20 Dec 2018 11:38:29 -0800 Subject: [PATCH 0068/2345] Limit total number of MKL primitive caching --- tensorflow/core/util/mkl_util.h | 173 ++++++++++++++++++++------ tensorflow/core/util/mkl_util_test.cc | 36 ++++++ 2 files changed, 168 insertions(+), 41 deletions(-) diff --git a/tensorflow/core/util/mkl_util.h b/tensorflow/core/util/mkl_util.h index 928807458a..34284f1e1e 100644 --- a/tensorflow/core/util/mkl_util.h +++ b/tensorflow/core/util/mkl_util.h @@ -17,6 +17,7 @@ limitations under the License. #define TENSORFLOW_CORE_UTIL_MKL_UTIL_H_ #ifdef INTEL_MKL +#include #include #include #include @@ -34,8 +35,7 @@ limitations under the License. #endif #ifdef INTEL_MKL_ML_ONLY -#error \ - "Compiling for INTEL MKL ML only is no longer supported.Please use MKL DNN (the default option for --config=mkl)" +#error "Please use INTEL MKL DNN (the default option for --config=mkl)." #endif #ifdef INTEL_MKL_ML_ONLY @@ -111,7 +111,7 @@ typedef enum { Dim3d_I = 1 } MklDnnDims3D; -// Enum used to templatize MklOp kernel implementations +// Enum used to templatize MklOp kernel implementations // that support both fp32 and int8 versions. enum class MklQuantization { QUANTIZED_VERSION, @@ -266,32 +266,32 @@ class MklShape { CHECK_EQ(dnnDelete_F32(convert), E_SUCCESS); } - // The following methods are used for serializing and de-serializing the - // contents of the mklshape object. - // The data is serialized in this order - // isMklTensor_ - // dimension_ - // sizes_ - // strides_ - // mklLayout_ - // tfLayout_ - // tf_to_mkl_dim_map_ +// The following methods are used for serializing and de-serializing the +// contents of the mklshape object. +// The data is serialized in this order +// isMklTensor_ +// dimension_ +// sizes_ +// strides_ +// mklLayout_ +// tfLayout_ +// tf_to_mkl_dim_map_ #define SIZE_OF_MKL_DNN_BUF \ (dnnLayoutSerializationBufferSize_F32()) // Size of buffer needed to // serialize dnn_layout pointer - // Size of buffer to hold the serialized object, the size is computed as - // follows sizeof(isMklTensor_) + sizeof(dimension_) + sizeof(sizes_) + - // sizeof(strides_) - // + sizeof(mklLayout_ buffer) + sizeof(tfLayout_ buffer) - // + sizeof(tf_to_mkl_dim_map_) +// Size of buffer to hold the serialized object, the size is computed as +// follows sizeof(isMklTensor_) + sizeof(dimension_) + sizeof(sizes_) + +// sizeof(strides_) +// + sizeof(mklLayout_ buffer) + sizeof(tfLayout_ buffer) +// + sizeof(tf_to_mkl_dim_map_) #define SIZE_OF_MKL_SERIAL_DATA(dims) \ (2 * sizeof(size_t) + 3 * dims * sizeof(size_t) + 2 * SIZE_OF_MKL_DNN_BUF) - // First we need to define some macro for offsets into the serial buffer where - // different elements of Mklshape is written/read from +// First we need to define some macro for offsets into the serial buffer where +// different elements of Mklshape is written/read from #define IS_MKL_TENSOR_OFFSET 0 // Location from start of buffer where isMklTensor_ is serialized @@ -808,7 +808,6 @@ inline Tensor ConvertMklToTF(OpKernelContext* context, const Tensor& mkl_tensor, return mkl_tensor; // return input since it is already TF tensor TensorShape output_shape = mkl_shape.GetTfShape(); - ; // Allocate output tensor. context->allocate_temp(DataTypeToEnum::v(), output_shape, @@ -834,9 +833,9 @@ inline Tensor ConvertMklToTF(OpKernelContext* context, const Tensor& mkl_tensor, CHECK(output_tensor.CopyFrom(mkl_tensor, output_shape)); } } catch (mkldnn::error& e) { - string error_msg = "Status: " + std::to_string(e.status) + - ", message: " + string(e.message) + ", in file " + - string(__FILE__) + ":" + std::to_string(__LINE__); + string error_msg = "Status: " + std::to_string(e.status) + ", message: " + + string(e.message) + ", in file " + string(__FILE__) + + ":" + std::to_string(__LINE__); LOG(FATAL) << "Operation received an exception: " << error_msg; } return output_tensor; @@ -2031,6 +2030,107 @@ class MklPrimitive { const mkldnn::memory::dims NONE_DIMS = {}; +// +// LRUCache is a class which implements LRU (Least Recently Use) cache. +// The implementation is similar to that of +// tensorflow/core/platform/cloud/expiring_lru_cache.h +// without its thread-safe part because the cache is supposed to be +// used as thread local (for instance, MKLPrimitive caching). +// +// The LRU list maintains objects in chronological order based on +// creation time, with the least recently accessed object at the +// tail of LRU list, while the most recently accessed object +// at the head of LRU list. +// +// This class is used to maintain an upper bound on the total number of +// cached items. When the cache reaches its capacity, the LRU item will +// be removed and replaced by a new one from SetOp call. +// +template +class LRUCache { + public: + explicit LRUCache(size_t capacity) { + capacity_ = capacity; + Clear(); + } + + T* GetOp(const string& key) { + auto it = cache_.find(key); + if (it == cache_.end()) { + return nullptr; + } + + // Move to the front of LRU list as the most recently accessed. + lru_list_.erase(it->second.lru_iterator); + lru_list_.push_front(it->first); + it->second.lru_iterator = lru_list_.begin(); + return it->second.op; + } + + void SetOp(const string& key, T* op) { + if (lru_list_.size() >= capacity_) { + Delete(); + } + + // Insert an entry to the front of the LRU list + lru_list_.push_front(key); + Entry entry(op, lru_list_.begin()); + cache_.insert(std::make_pair(key, entry)); + } + + void Clear() { + if (lru_list_.empty()) return; + + // delete the cached objects + for (auto& key : lru_list_) { + auto it = cache_.find(key); + DCHECK(it == cache_.end()); + delete it->second.op; + } + + // clean up the cache + cache_.clear(); + lru_list_.clear(); + } + + private: + struct Entry { + // The entry's value. + T* op; + + // A list iterator pointing to the entry's position in the LRU list. + std::list::iterator lru_iterator; + Entry(T* op, std::list::iterator it) { + this->op = op; + this->lru_iterator = it; + } + }; + + // Remove the least recently accessed entry from LRU list, which + // is the tail of lru_list_. Correspondingly cache_ is updated. + bool Delete() { + if (lru_list_.empty()) return false; + string key = lru_list_.back(); + auto it = cache_.find(key); + DCHECK(it == cache_.end()); + lru_list_.pop_back(); + delete it->second.op; // delete the object + cache_.erase(it); + return true; + } + + // cache capacity + size_t capacity_; + + // The cache, a map from string key to a LRU entry. + std::unordered_map cache_; + + // The LRU list of entries. + // The front of the list identifies the most recently accessed entry, + // while the back of the list is the least recently accessed entry. + std::list lru_list_; +}; + template class MklPrimitiveFactory { public: @@ -2039,23 +2139,13 @@ class MklPrimitiveFactory { ~MklPrimitiveFactory() {} MklPrimitive* GetOp(const string& key) { - auto& map = MklPrimitiveFactory::GetHashMap(); - auto stream_iter = map.find(key); - if (stream_iter == map.end()) { - return nullptr; - } else { - CHECK(stream_iter->second != nullptr) << "nullptr present in map"; - return stream_iter->second; - } + auto& lru_cache = MklPrimitiveFactory::GetLRUCache(); + return lru_cache.GetOp(key); } void SetOp(const string& key, MklPrimitive* op) { - auto& map = MklPrimitiveFactory::GetHashMap(); - auto stream_iter = map.find(key); - - CHECK(stream_iter == map.end()); - - map[key] = op; + auto& lru_cache = MklPrimitiveFactory::GetLRUCache(); + lru_cache.SetOp(key, op); } /// Function to decide whether HW has AVX512 or AVX2 @@ -2075,9 +2165,10 @@ class MklPrimitiveFactory { } private: - static inline std::unordered_map& GetHashMap() { - static thread_local std::unordered_map map_; - return map_; + static inline LRUCache& GetLRUCache() { + static const int kCapacity = 1024; // cache capacity + static thread_local LRUCache lru_cache_(kCapacity); + return lru_cache_; } }; diff --git a/tensorflow/core/util/mkl_util_test.cc b/tensorflow/core/util/mkl_util_test.cc index 4f837f105d..7ac2e510fc 100644 --- a/tensorflow/core/util/mkl_util_test.cc +++ b/tensorflow/core/util/mkl_util_test.cc @@ -84,6 +84,42 @@ TEST(MklUtilTest, MklDnnBlockedFormatTest) { EXPECT_EQ(b_md2.data.format, mkldnn_blocked); } +TEST(MklUtilTest, LRUCacheTest) { + // The cached objects are of type int* + size_t capacity = 100; + size_t num_objects = capacity + 10; + LRUCache lru_cache(capacity); + + // Test SetOp: be able to set more ops than the capacity + for (int k = 0; k < num_objects; k++) { + lru_cache.SetOp(std::to_string(k), new int(k)); + } + + // Test GetOp and capacity: + // -- Less recently accessed objects should not be + // in cache any more. + for (int k = 0; k < num_objects - capacity; k++) { + EXPECT_EQ(nullptr, lru_cache.GetOp(std::to_string(k))); + } + + // Test GetOp and capacity: + // -- most recently accessed objects should still + // be in cache. + for (int k = num_objects - capacity; k < num_objects; k++) { + int* pint = lru_cache.GetOp(std::to_string(k)); + EXPECT_NE(nullptr, pint); + EXPECT_EQ(*pint, k); + } + + // Clean up the cache + lru_cache.Clear(); + + // After clean up, there should be no cached object. + for (int k = 0; k < num_objects; k++) { + EXPECT_EQ(nullptr, lru_cache.GetOp(std::to_string(k))); + } +} + #endif // INTEL_MKL_ML_ONLY } // namespace } // namespace tensorflow -- GitLab From beed5ef6fc98f513f368e68ae08357a8db623ef0 Mon Sep 17 00:00:00 2001 From: Pooya Davoodi Date: Thu, 20 Dec 2018 11:38:58 -0800 Subject: [PATCH 0069/2345] TFTRT: Convert between str and unicode in py2 Also check for types before doing conversion. --- tensorflow/contrib/tensorrt/python/trt_convert.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/tensorrt/python/trt_convert.py b/tensorflow/contrib/tensorrt/python/trt_convert.py index 203b2697ba..3d2b6b499d 100644 --- a/tensorflow/contrib/tensorrt/python/trt_convert.py +++ b/tensorflow/contrib/tensorrt/python/trt_convert.py @@ -46,11 +46,13 @@ from tensorflow.python.saved_model import tag_constants from tensorflow.python.training import saver if _six.PY2: - _to_bytes = lambda s: s - _to_string = lambda s: s + _to_bytes = lambda s: s.encode("utf-8", errors="surrogateescape") \ + if isinstance(s, unicode) else s + _to_string = lambda s: s.decode("utf-8") if isinstance(s, str) else s else: - _to_bytes = lambda s: s.encode("utf-8", errors="surrogateescape") - _to_string = lambda s: s.decode("utf-8") + _to_bytes = lambda s: s.encode("utf-8", errors="surrogateescape") \ + if isinstance(s, str) else s + _to_string = lambda s: s.decode("utf-8") if isinstance(s, bytes) else s class TrtPrecisionMode(object): -- GitLab From 466711c40783a907cf6867cec5c13c16ed0bf257 Mon Sep 17 00:00:00 2001 From: Taylor Jakobson Date: Fri, 30 Nov 2018 13:50:27 -0600 Subject: [PATCH 0070/2345] Add support for ppc64le_dockerfiles Add support for ppc64le dockerfiles with newest assembler changes. --- tensorflow/tools/dockerfiles/assembler.py | 8 + .../dockerfiles/devel-cpu-jupyter.Dockerfile | 9 +- .../dockerfiles/devel-cpu.Dockerfile | 9 +- .../dockerfiles/devel-gpu-jupyter.Dockerfile | 53 +++--- .../dockerfiles/devel-gpu.Dockerfile | 51 +++--- .../dockerfiles/gpu-jupyter.Dockerfile | 31 ++-- .../dockerfiles/dockerfiles/gpu.Dockerfile | 31 ++-- .../ppc64le/cpu-ppc64le-jupyter.Dockerfile | 92 +++++++++++ .../ppc64le/cpu-ppc64le.Dockerfile | 75 +++++++++ .../devel-cpu-ppc64le-jupyter.Dockerfile | 125 +++++++++++++++ .../ppc64le/devel-cpu-ppc64le.Dockerfile | 108 +++++++++++++ .../devel-gpu-ppc64le-jupyter.Dockerfile | 151 ++++++++++++++++++ .../ppc64le/devel-gpu-ppc64le.Dockerfile | 134 ++++++++++++++++ .../ppc64le/gpu-ppc64le-jupyter.Dockerfile | 125 +++++++++++++++ .../ppc64le/gpu-ppc64le.Dockerfile | 108 +++++++++++++ .../tensorflow-ppc64le.partial.Dockerfile | 28 ++++ .../ubuntu/bazelbuild.partial.Dockerfile | 33 ++++ .../ubuntu/devel-cpu.partial.Dockerfile | 9 +- .../ubuntu/devel-nvidia.partial.Dockerfile | 51 +++--- .../partials/ubuntu/nvidia.partial.Dockerfile | 31 ++-- tensorflow/tools/dockerfiles/spec.yml | 71 ++++++++ tensorflow/tools/dockerfiles/tools.Dockerfile | 2 +- 22 files changed, 1216 insertions(+), 119 deletions(-) create mode 100644 tensorflow/tools/dockerfiles/dockerfiles/ppc64le/cpu-ppc64le-jupyter.Dockerfile create mode 100644 tensorflow/tools/dockerfiles/dockerfiles/ppc64le/cpu-ppc64le.Dockerfile create mode 100644 tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le-jupyter.Dockerfile create mode 100644 tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le.Dockerfile create mode 100644 tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile create mode 100644 tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile create mode 100644 tensorflow/tools/dockerfiles/dockerfiles/ppc64le/gpu-ppc64le-jupyter.Dockerfile create mode 100644 tensorflow/tools/dockerfiles/dockerfiles/ppc64le/gpu-ppc64le.Dockerfile create mode 100644 tensorflow/tools/dockerfiles/partials/tensorflow-ppc64le.partial.Dockerfile create mode 100644 tensorflow/tools/dockerfiles/partials/ubuntu/bazelbuild.partial.Dockerfile diff --git a/tensorflow/tools/dockerfiles/assembler.py b/tensorflow/tools/dockerfiles/assembler.py index 09537b7314..83b72cb5bb 100644 --- a/tensorflow/tools/dockerfiles/assembler.py +++ b/tensorflow/tools/dockerfiles/assembler.py @@ -34,6 +34,7 @@ import errno import itertools import multiprocessing import os +import platform import re import shutil import sys @@ -552,6 +553,13 @@ def main(argv): if not FLAGS.build_images: continue + # Only build images for host architecture + proc_arch = platform.processor() + is_x86 = proc_arch.startswith('x86') + if (is_x86 and any([arch in tag for arch in ['ppc64le']]) or + not is_x86 and proc_arch not in tag): + continue + # Generate a temporary Dockerfile to use to build, since docker-py # needs a filepath relative to the build context (i.e. the current # directory) diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu-jupyter.Dockerfile index c1f6dafbe0..4657f8b4c7 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu-jupyter.Dockerfile @@ -30,7 +30,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libcurl3-dev \ libfreetype6-dev \ libhdf5-serial-dev \ - libpng12-dev \ libzmq3-dev \ pkg-config \ rsync \ @@ -43,12 +42,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* - + ENV CI_BUILD_PYTHON python -# Check out TensorFlow source code if --build_arg CHECKOUT_TENSORFLOW=1 +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 ARG CHECKOUT_TF_SRC=0 -RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true ARG USE_PYTHON_3_NOT_2 ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu.Dockerfile index b4dfc8b099..ce69ee0650 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu.Dockerfile @@ -30,7 +30,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libcurl3-dev \ libfreetype6-dev \ libhdf5-serial-dev \ - libpng12-dev \ libzmq3-dev \ pkg-config \ rsync \ @@ -43,12 +42,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* - + ENV CI_BUILD_PYTHON python -# Check out TensorFlow source code if --build_arg CHECKOUT_TENSORFLOW=1 +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 ARG CHECKOUT_TF_SRC=0 -RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true ARG USE_PYTHON_3_NOT_2 ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile index 6d76c06332..e41fc18e42 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile @@ -21,23 +21,28 @@ ARG UBUNTU_VERSION=16.04 -FROM nvidia/cuda:10.0-base-ubuntu${UBUNTU_VERSION} as base - +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 +ARG LIB_DIR_PREFIX=x84_64 + +# Needed for string substitution +SHELL ["/bin/bash", "-c"] RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ - cuda-command-line-tools-10-0 \ - cuda-cublas-dev-10-0 \ - cuda-cudart-dev-10-0 \ - cuda-cufft-dev-10-0 \ - cuda-curand-dev-10-0 \ - cuda-cusolver-dev-10-0 \ - cuda-cusparse-dev-10-0 \ - libcudnn7=7.4.1.5-1+cuda10.0 \ - libcudnn7-dev=7.4.1.5-1+cuda10.0 \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-dev-${CUDA/./-} \ + cuda-cudart-dev-${CUDA/./-} \ + cuda-cufft-dev-${CUDA/./-} \ + cuda-curand-dev-${CUDA/./-} \ + cuda-cusolver-dev-${CUDA/./-} \ + cuda-cusparse-dev-${CUDA/./-} \ + libcudnn7=${CUDNN}+cuda${CUDA} \ + libcudnn7-dev=${CUDNN}+cuda${CUDA} \ libcurl3-dev \ libfreetype6-dev \ libhdf5-serial-dev \ - libpng12-dev \ libzmq3-dev \ pkg-config \ rsync \ @@ -48,14 +53,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ wget \ git \ && \ - find /usr/local/cuda-10.0/lib64/ -type f -name 'lib*_static.a' -not -name 'libcudart_static.a' -delete && \ - rm /usr/lib/x86_64-linux-gnu/libcudnn_static_v7.a + find /usr/local/cuda-${CUDA}/lib64/ -type f -name 'lib*_static.a' -not -name 'libcudart_static.a' -delete && \ + rm /usr/lib/${LIB_DIR_PREFIX}-linux-gnu/libcudnn_static_v7.a -RUN apt-get update && \ - apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda10.0 \ +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 libnvinfer-dev=5.0.2-1+cuda10.0 \ - && rm -rf /var/lib/apt/lists/* + && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*) # Configure the build for our CUDA configuration. ENV CI_BUILD_PYTHON python @@ -63,12 +69,13 @@ ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH ENV TF_NEED_CUDA 1 ENV TF_NEED_TENSORRT 1 ENV TF_CUDA_COMPUTE_CAPABILITIES=3.5,5.2,6.0,6.1,7.0 -ENV TF_CUDA_VERSION=10.0 -ENV TF_CUDNN_VERSION=7 - -# Check out TensorFlow source code if --build_arg CHECKOUT_TENSORFLOW=1 +ENV TF_CUDA_VERSION=${CUDA} +ENV TF_CUDNN_VERSION=${CUDNN%%.*} +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 ARG CHECKOUT_TF_SRC=0 -RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true ARG USE_PYTHON_3_NOT_2 ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile index 160abc8763..7ae5010079 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile @@ -21,23 +21,28 @@ ARG UBUNTU_VERSION=16.04 -FROM nvidia/cuda:10.0-base-ubuntu${UBUNTU_VERSION} as base +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 +ARG LIB_DIR_PREFIX=x84_64 +# Needed for string substitution +SHELL ["/bin/bash", "-c"] RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ - cuda-command-line-tools-10-0 \ - cuda-cublas-dev-10-0 \ - cuda-cudart-dev-10-0 \ - cuda-cufft-dev-10-0 \ - cuda-curand-dev-10-0 \ - cuda-cusolver-dev-10-0 \ - cuda-cusparse-dev-10-0 \ - libcudnn7=7.4.1.5-1+cuda10.0 \ - libcudnn7-dev=7.4.1.5-1+cuda10.0 \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-dev-${CUDA/./-} \ + cuda-cudart-dev-${CUDA/./-} \ + cuda-cufft-dev-${CUDA/./-} \ + cuda-curand-dev-${CUDA/./-} \ + cuda-cusolver-dev-${CUDA/./-} \ + cuda-cusparse-dev-${CUDA/./-} \ + libcudnn7=${CUDNN}+cuda${CUDA} \ + libcudnn7-dev=${CUDNN}+cuda${CUDA} \ libcurl3-dev \ libfreetype6-dev \ libhdf5-serial-dev \ - libpng12-dev \ libzmq3-dev \ pkg-config \ rsync \ @@ -48,14 +53,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ wget \ git \ && \ - find /usr/local/cuda-10.0/lib64/ -type f -name 'lib*_static.a' -not -name 'libcudart_static.a' -delete && \ - rm /usr/lib/x86_64-linux-gnu/libcudnn_static_v7.a + find /usr/local/cuda-${CUDA}/lib64/ -type f -name 'lib*_static.a' -not -name 'libcudart_static.a' -delete && \ + rm /usr/lib/${LIB_DIR_PREFIX}-linux-gnu/libcudnn_static_v7.a -RUN apt-get update && \ - apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda10.0 \ +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 libnvinfer-dev=5.0.2-1+cuda10.0 \ - && rm -rf /var/lib/apt/lists/* + && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*) # Configure the build for our CUDA configuration. ENV CI_BUILD_PYTHON python @@ -63,12 +69,13 @@ ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH ENV TF_NEED_CUDA 1 ENV TF_NEED_TENSORRT 1 ENV TF_CUDA_COMPUTE_CAPABILITIES=3.5,5.2,6.0,6.1,7.0 -ENV TF_CUDA_VERSION=10.0 -ENV TF_CUDNN_VERSION=7 - -# Check out TensorFlow source code if --build_arg CHECKOUT_TENSORFLOW=1 +ENV TF_CUDA_VERSION=${CUDA} +ENV TF_CUDNN_VERSION=${CUDNN%%.*} +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 ARG CHECKOUT_TF_SRC=0 -RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true ARG USE_PYTHON_3_NOT_2 ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} diff --git a/tensorflow/tools/dockerfiles/dockerfiles/gpu-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/gpu-jupyter.Dockerfile index 46252c5413..12eb5afa8a 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/gpu-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/gpu-jupyter.Dockerfile @@ -21,32 +21,37 @@ ARG UBUNTU_VERSION=16.04 -FROM nvidia/cuda:10.0-base-ubuntu${UBUNTU_VERSION} as base +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 +# Needed for string substitution +SHELL ["/bin/bash", "-c"] # Pick up some TF dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ - cuda-command-line-tools-10-0 \ - cuda-cublas-10-0 \ - cuda-cufft-10-0 \ - cuda-curand-10-0 \ - cuda-cusolver-10-0 \ - cuda-cusparse-10-0 \ - libcudnn7=7.4.1.5-1+cuda10.0 \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-${CUDA/./-} \ + cuda-cufft-${CUDA/./-} \ + cuda-curand-${CUDA/./-} \ + cuda-cusolver-${CUDA/./-} \ + cuda-cusparse-${CUDA/./-} \ + curl \ + libcudnn7=${CUDNN}+cuda${CUDA} \ libfreetype6-dev \ libhdf5-serial-dev \ - libpng12-dev \ libzmq3-dev \ pkg-config \ software-properties-common \ unzip -RUN apt-get update && \ - apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda10.0 \ +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+cuda10.0 \ + && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ && apt-get clean \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/*) # For CUDA profiling, TensorFlow requires CUPTI. ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH diff --git a/tensorflow/tools/dockerfiles/dockerfiles/gpu.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/gpu.Dockerfile index 80e427f824..00664b6b73 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/gpu.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/gpu.Dockerfile @@ -21,32 +21,37 @@ ARG UBUNTU_VERSION=16.04 -FROM nvidia/cuda:10.0-base-ubuntu${UBUNTU_VERSION} as base +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 +# Needed for string substitution +SHELL ["/bin/bash", "-c"] # Pick up some TF dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ - cuda-command-line-tools-10-0 \ - cuda-cublas-10-0 \ - cuda-cufft-10-0 \ - cuda-curand-10-0 \ - cuda-cusolver-10-0 \ - cuda-cusparse-10-0 \ - libcudnn7=7.4.1.5-1+cuda10.0 \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-${CUDA/./-} \ + cuda-cufft-${CUDA/./-} \ + cuda-curand-${CUDA/./-} \ + cuda-cusolver-${CUDA/./-} \ + cuda-cusparse-${CUDA/./-} \ + curl \ + libcudnn7=${CUDNN}+cuda${CUDA} \ libfreetype6-dev \ libhdf5-serial-dev \ - libpng12-dev \ libzmq3-dev \ pkg-config \ software-properties-common \ unzip -RUN apt-get update && \ - apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda10.0 \ +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+cuda10.0 \ + && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ && apt-get clean \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/*) # For CUDA profiling, TensorFlow requires CUPTI. ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/cpu-ppc64le-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/cpu-ppc64le-jupyter.Dockerfile new file mode 100644 index 0000000000..beb3292a9d --- /dev/null +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/cpu-ppc64le-jupyter.Dockerfile @@ -0,0 +1,92 @@ +# 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. +# ============================================================================ +# +# THIS IS A GENERATED DOCKERFILE. +# +# This file was assembled from multiple pieces, whose use is documented +# throughout. Please refer to the TensorFlow dockerfiles documentation +# for more information. + +ARG UBUNTU_VERSION=16.04 + +FROM ubuntu:${UBUNTU_VERSION} as base + +ARG USE_PYTHON_3_NOT_2 +ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} +ARG PYTHON=python${_PY_SUFFIX} +ARG PIP=pip${_PY_SUFFIX} + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +RUN apt-get update && apt-get install -y \ + ${PYTHON} \ + ${PYTHON}-pip + +RUN ${PIP} --no-cache-dir install --upgrade \ + pip \ + setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which ${PYTHON}) /usr/local/bin/python + +# Options: +# tensorflow +# tensorflow-gpu +# tf-nightly +# tf-nightly-gpu +ARG TF_PACKAGE=tensorflow +RUN apt-get update && apt-get install -y wget libhdf5-dev +RUN ${PIP} install --global-option=build_ext \ + --global-option=-I/usr/include/hdf5/serial/ \ + --global-option=-L/usr/lib/powerpc64le-linux-gnu/hdf5/serial \ + h5py + +# CACHE_STOP is used to rerun future commands, otherwise downloading the .whl will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +RUN if [ ${TF_PACKAGE} = tensorflow-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Nightly_Artifact/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tensorflow ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Nightly_Artifact/lastSuccessfulBuild/; \ + fi; \ + MAJOR=`${PYTHON} -c 'import sys; print(sys.version_info[0])'`; \ + MINOR=`${PYTHON} -c 'import sys; print(sys.version_info[1])'`; \ + PACKAGE=$(wget -qO- ${BASE}"api/xml?xpath=//fileName&wrapper=artifacts" | grep -o "[^<>]*cp${MAJOR}${MINOR}[^<>]*.whl"); \ + wget ${BASE}"artifact/tensorflow_pkg/"${PACKAGE}; \ + ${PIP} install ${PACKAGE} + +COPY bashrc /etc/bash.bashrc +RUN chmod a+rwx /etc/bash.bashrc + +RUN ${PIP} install jupyter matplotlib + +RUN mkdir -p /tf/tensorflow-tutorials && chmod -R a+rwx /tf/ +RUN mkdir /.local && chmod a+rwx /.local +RUN apt-get install -y --no-install-recommends wget +WORKDIR /tf/tensorflow-tutorials +RUN wget https://raw.githubusercontent.com/tensorflow/docs/master/site/en/tutorials/keras/basic_classification.ipynb +RUN wget https://raw.githubusercontent.com/tensorflow/docs/master/site/en/tutorials/keras/basic_text_classification.ipynb +COPY readme-for-jupyter.md README.md +RUN apt-get autoremove -y && apt-get remove -y wget +WORKDIR /tf +EXPOSE 8888 + +RUN ${PYTHON} -m ipykernel.kernelspec + +CMD ["bash", "-c", "source /etc/bash.bashrc && jupyter notebook --notebook-dir=/tf --ip 0.0.0.0 --no-browser --allow-root"] diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/cpu-ppc64le.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/cpu-ppc64le.Dockerfile new file mode 100644 index 0000000000..083d61bf9a --- /dev/null +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/cpu-ppc64le.Dockerfile @@ -0,0 +1,75 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# +# THIS IS A GENERATED DOCKERFILE. +# +# This file was assembled from multiple pieces, whose use is documented +# throughout. Please refer to the TensorFlow dockerfiles documentation +# for more information. + +ARG UBUNTU_VERSION=16.04 + +FROM ubuntu:${UBUNTU_VERSION} as base + +ARG USE_PYTHON_3_NOT_2 +ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} +ARG PYTHON=python${_PY_SUFFIX} +ARG PIP=pip${_PY_SUFFIX} + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +RUN apt-get update && apt-get install -y \ + ${PYTHON} \ + ${PYTHON}-pip + +RUN ${PIP} --no-cache-dir install --upgrade \ + pip \ + setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which ${PYTHON}) /usr/local/bin/python + +# Options: +# tensorflow +# tensorflow-gpu +# tf-nightly +# tf-nightly-gpu +ARG TF_PACKAGE=tensorflow +RUN apt-get update && apt-get install -y wget libhdf5-dev +RUN ${PIP} install --global-option=build_ext \ + --global-option=-I/usr/include/hdf5/serial/ \ + --global-option=-L/usr/lib/powerpc64le-linux-gnu/hdf5/serial \ + h5py + +# CACHE_STOP is used to rerun future commands, otherwise downloading the .whl will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +RUN if [ ${TF_PACKAGE} = tensorflow-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Nightly_Artifact/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tensorflow ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Nightly_Artifact/lastSuccessfulBuild/; \ + fi; \ + MAJOR=`${PYTHON} -c 'import sys; print(sys.version_info[0])'`; \ + MINOR=`${PYTHON} -c 'import sys; print(sys.version_info[1])'`; \ + PACKAGE=$(wget -qO- ${BASE}"api/xml?xpath=//fileName&wrapper=artifacts" | grep -o "[^<>]*cp${MAJOR}${MINOR}[^<>]*.whl"); \ + wget ${BASE}"artifact/tensorflow_pkg/"${PACKAGE}; \ + ${PIP} install ${PACKAGE} + +COPY bashrc /etc/bash.bashrc +RUN chmod a+rwx /etc/bash.bashrc diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le-jupyter.Dockerfile new file mode 100644 index 0000000000..1f32849735 --- /dev/null +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le-jupyter.Dockerfile @@ -0,0 +1,125 @@ +# 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. +# ============================================================================ +# +# THIS IS A GENERATED DOCKERFILE. +# +# This file was assembled from multiple pieces, whose use is documented +# throughout. Please refer to the TensorFlow dockerfiles documentation +# for more information. + +ARG UBUNTU_VERSION=16.04 + +FROM ubuntu:${UBUNTU_VERSION} AS base + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + curl \ + git \ + libcurl3-dev \ + libfreetype6-dev \ + libhdf5-serial-dev \ + libzmq3-dev \ + pkg-config \ + rsync \ + software-properties-common \ + unzip \ + zip \ + zlib1g-dev \ + openjdk-8-jdk \ + openjdk-8-jre-headless \ + && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +ENV CI_BUILD_PYTHON python + +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 +ARG CHECKOUT_TF_SRC=0 +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true + +ARG USE_PYTHON_3_NOT_2 +ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} +ARG PYTHON=python${_PY_SUFFIX} +ARG PIP=pip${_PY_SUFFIX} + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +RUN apt-get update && apt-get install -y \ + ${PYTHON} \ + ${PYTHON}-pip + +RUN ${PIP} --no-cache-dir install --upgrade \ + pip \ + setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which ${PYTHON}) /usr/local/bin/python + +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + git \ + openjdk-8-jdk \ + ${PYTHON}-dev \ + swig + +RUN ${PIP} --no-cache-dir install \ + Pillow \ + h5py \ + keras_applications \ + keras_preprocessing \ + matplotlib \ + mock \ + numpy \ + scipy \ + sklearn \ + pandas \ + && test "${USE_PYTHON_3_NOT_2}" -eq 1 && true || ${PIP} --no-cache-dir install \ + enum34 + + # Build and install bazel +ENV BAZEL_VERSION 0.15.0 +WORKDIR / +RUN mkdir /bazel && \ + cd /bazel && \ + curl -fSsL -O https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/bazel-$BAZEL_VERSION-dist.zip && \ + unzip bazel-$BAZEL_VERSION-dist.zip && \ + bash ./compile.sh && \ + cp output/bazel /usr/local/bin/ && \ + rm -rf /bazel && \ + cd - + +COPY bashrc /etc/bash.bashrc +RUN chmod a+rwx /etc/bash.bashrc + +RUN ${PIP} install jupyter matplotlib + +RUN mkdir -p /tf/tensorflow-tutorials && chmod -R a+rwx /tf/ +RUN mkdir /.local && chmod a+rwx /.local +RUN apt-get install -y --no-install-recommends wget +WORKDIR /tf/tensorflow-tutorials +RUN wget https://raw.githubusercontent.com/tensorflow/docs/master/site/en/tutorials/keras/basic_classification.ipynb +RUN wget https://raw.githubusercontent.com/tensorflow/docs/master/site/en/tutorials/keras/basic_text_classification.ipynb +COPY readme-for-jupyter.md README.md +RUN apt-get autoremove -y && apt-get remove -y wget +WORKDIR /tf +EXPOSE 8888 + +RUN ${PYTHON} -m ipykernel.kernelspec + +CMD ["bash", "-c", "source /etc/bash.bashrc && jupyter notebook --notebook-dir=/tf --ip 0.0.0.0 --no-browser --allow-root"] diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le.Dockerfile new file mode 100644 index 0000000000..cda51c371d --- /dev/null +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le.Dockerfile @@ -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. +# ============================================================================ +# +# THIS IS A GENERATED DOCKERFILE. +# +# This file was assembled from multiple pieces, whose use is documented +# throughout. Please refer to the TensorFlow dockerfiles documentation +# for more information. + +ARG UBUNTU_VERSION=16.04 + +FROM ubuntu:${UBUNTU_VERSION} AS base + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + curl \ + git \ + libcurl3-dev \ + libfreetype6-dev \ + libhdf5-serial-dev \ + libzmq3-dev \ + pkg-config \ + rsync \ + software-properties-common \ + unzip \ + zip \ + zlib1g-dev \ + openjdk-8-jdk \ + openjdk-8-jre-headless \ + && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +ENV CI_BUILD_PYTHON python + +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 +ARG CHECKOUT_TF_SRC=0 +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true + +ARG USE_PYTHON_3_NOT_2 +ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} +ARG PYTHON=python${_PY_SUFFIX} +ARG PIP=pip${_PY_SUFFIX} + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +RUN apt-get update && apt-get install -y \ + ${PYTHON} \ + ${PYTHON}-pip + +RUN ${PIP} --no-cache-dir install --upgrade \ + pip \ + setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which ${PYTHON}) /usr/local/bin/python + +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + git \ + openjdk-8-jdk \ + ${PYTHON}-dev \ + swig + +RUN ${PIP} --no-cache-dir install \ + Pillow \ + h5py \ + keras_applications \ + keras_preprocessing \ + matplotlib \ + mock \ + numpy \ + scipy \ + sklearn \ + pandas \ + && test "${USE_PYTHON_3_NOT_2}" -eq 1 && true || ${PIP} --no-cache-dir install \ + enum34 + + # Build and install bazel +ENV BAZEL_VERSION 0.15.0 +WORKDIR / +RUN mkdir /bazel && \ + cd /bazel && \ + curl -fSsL -O https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/bazel-$BAZEL_VERSION-dist.zip && \ + unzip bazel-$BAZEL_VERSION-dist.zip && \ + bash ./compile.sh && \ + cp output/bazel /usr/local/bin/ && \ + rm -rf /bazel && \ + cd - + +COPY bashrc /etc/bash.bashrc +RUN chmod a+rwx /etc/bash.bashrc diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile new file mode 100644 index 0000000000..d8ee19f66e --- /dev/null +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile @@ -0,0 +1,151 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# +# THIS IS A GENERATED DOCKERFILE. +# +# This file was assembled from multiple pieces, whose use is documented +# throughout. Please refer to the TensorFlow dockerfiles documentation +# for more information. + +ARG UBUNTU_VERSION=16.04 + +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 +ARG LIB_DIR_PREFIX=x84_64 + +# Needed for string substitution +SHELL ["/bin/bash", "-c"] +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-dev-${CUDA/./-} \ + cuda-cudart-dev-${CUDA/./-} \ + cuda-cufft-dev-${CUDA/./-} \ + cuda-curand-dev-${CUDA/./-} \ + cuda-cusolver-dev-${CUDA/./-} \ + cuda-cusparse-dev-${CUDA/./-} \ + libcudnn7=${CUDNN}+cuda${CUDA} \ + libcudnn7-dev=${CUDNN}+cuda${CUDA} \ + libcurl3-dev \ + libfreetype6-dev \ + libhdf5-serial-dev \ + libzmq3-dev \ + pkg-config \ + rsync \ + software-properties-common \ + unzip \ + zip \ + zlib1g-dev \ + wget \ + git \ + && \ + find /usr/local/cuda-${CUDA}/lib64/ -type f -name 'lib*_static.a' -not -name 'libcudart_static.a' -delete && \ + rm /usr/lib/${LIB_DIR_PREFIX}-linux-gnu/libcudnn_static_v7.a + +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 clean \ + && rm -rf /var/lib/apt/lists/*) + +# Configure the build for our CUDA configuration. +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 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%%.*} +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 +ARG CHECKOUT_TF_SRC=0 +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true + +ARG USE_PYTHON_3_NOT_2 +ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} +ARG PYTHON=python${_PY_SUFFIX} +ARG PIP=pip${_PY_SUFFIX} + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +RUN apt-get update && apt-get install -y \ + ${PYTHON} \ + ${PYTHON}-pip + +RUN ${PIP} --no-cache-dir install --upgrade \ + pip \ + setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which ${PYTHON}) /usr/local/bin/python + +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + git \ + openjdk-8-jdk \ + ${PYTHON}-dev \ + swig + +RUN ${PIP} --no-cache-dir install \ + Pillow \ + h5py \ + keras_applications \ + keras_preprocessing \ + matplotlib \ + mock \ + numpy \ + scipy \ + sklearn \ + pandas \ + && test "${USE_PYTHON_3_NOT_2}" -eq 1 && true || ${PIP} --no-cache-dir install \ + enum34 + + # Build and install bazel +ENV BAZEL_VERSION 0.15.0 +WORKDIR / +RUN mkdir /bazel && \ + cd /bazel && \ + curl -fSsL -O https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/bazel-$BAZEL_VERSION-dist.zip && \ + unzip bazel-$BAZEL_VERSION-dist.zip && \ + bash ./compile.sh && \ + cp output/bazel /usr/local/bin/ && \ + rm -rf /bazel && \ + cd - + +COPY bashrc /etc/bash.bashrc +RUN chmod a+rwx /etc/bash.bashrc + +RUN ${PIP} install jupyter matplotlib + +RUN mkdir -p /tf/tensorflow-tutorials && chmod -R a+rwx /tf/ +RUN mkdir /.local && chmod a+rwx /.local +RUN apt-get install -y --no-install-recommends wget +WORKDIR /tf/tensorflow-tutorials +RUN wget https://raw.githubusercontent.com/tensorflow/docs/master/site/en/tutorials/keras/basic_classification.ipynb +RUN wget https://raw.githubusercontent.com/tensorflow/docs/master/site/en/tutorials/keras/basic_text_classification.ipynb +COPY readme-for-jupyter.md README.md +RUN apt-get autoremove -y && apt-get remove -y wget +WORKDIR /tf +EXPOSE 8888 + +RUN ${PYTHON} -m ipykernel.kernelspec + +CMD ["bash", "-c", "source /etc/bash.bashrc && jupyter notebook --notebook-dir=/tf --ip 0.0.0.0 --no-browser --allow-root"] diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile new file mode 100644 index 0000000000..966070634b --- /dev/null +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile @@ -0,0 +1,134 @@ +# 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. +# ============================================================================ +# +# THIS IS A GENERATED DOCKERFILE. +# +# This file was assembled from multiple pieces, whose use is documented +# throughout. Please refer to the TensorFlow dockerfiles documentation +# for more information. + +ARG UBUNTU_VERSION=16.04 + +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 +ARG LIB_DIR_PREFIX=x84_64 + +# Needed for string substitution +SHELL ["/bin/bash", "-c"] +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-dev-${CUDA/./-} \ + cuda-cudart-dev-${CUDA/./-} \ + cuda-cufft-dev-${CUDA/./-} \ + cuda-curand-dev-${CUDA/./-} \ + cuda-cusolver-dev-${CUDA/./-} \ + cuda-cusparse-dev-${CUDA/./-} \ + libcudnn7=${CUDNN}+cuda${CUDA} \ + libcudnn7-dev=${CUDNN}+cuda${CUDA} \ + libcurl3-dev \ + libfreetype6-dev \ + libhdf5-serial-dev \ + libzmq3-dev \ + pkg-config \ + rsync \ + software-properties-common \ + unzip \ + zip \ + zlib1g-dev \ + wget \ + git \ + && \ + find /usr/local/cuda-${CUDA}/lib64/ -type f -name 'lib*_static.a' -not -name 'libcudart_static.a' -delete && \ + rm /usr/lib/${LIB_DIR_PREFIX}-linux-gnu/libcudnn_static_v7.a + +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 clean \ + && rm -rf /var/lib/apt/lists/*) + +# Configure the build for our CUDA configuration. +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 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%%.*} +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 +ARG CHECKOUT_TF_SRC=0 +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true + +ARG USE_PYTHON_3_NOT_2 +ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} +ARG PYTHON=python${_PY_SUFFIX} +ARG PIP=pip${_PY_SUFFIX} + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +RUN apt-get update && apt-get install -y \ + ${PYTHON} \ + ${PYTHON}-pip + +RUN ${PIP} --no-cache-dir install --upgrade \ + pip \ + setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which ${PYTHON}) /usr/local/bin/python + +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + git \ + openjdk-8-jdk \ + ${PYTHON}-dev \ + swig + +RUN ${PIP} --no-cache-dir install \ + Pillow \ + h5py \ + keras_applications \ + keras_preprocessing \ + matplotlib \ + mock \ + numpy \ + scipy \ + sklearn \ + pandas \ + && test "${USE_PYTHON_3_NOT_2}" -eq 1 && true || ${PIP} --no-cache-dir install \ + enum34 + + # Build and install bazel +ENV BAZEL_VERSION 0.15.0 +WORKDIR / +RUN mkdir /bazel && \ + cd /bazel && \ + curl -fSsL -O https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/bazel-$BAZEL_VERSION-dist.zip && \ + unzip bazel-$BAZEL_VERSION-dist.zip && \ + bash ./compile.sh && \ + cp output/bazel /usr/local/bin/ && \ + rm -rf /bazel && \ + cd - + +COPY bashrc /etc/bash.bashrc +RUN chmod a+rwx /etc/bash.bashrc diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/gpu-ppc64le-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/gpu-ppc64le-jupyter.Dockerfile new file mode 100644 index 0000000000..449a8d8aa8 --- /dev/null +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/gpu-ppc64le-jupyter.Dockerfile @@ -0,0 +1,125 @@ +# 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. +# ============================================================================ +# +# THIS IS A GENERATED DOCKERFILE. +# +# This file was assembled from multiple pieces, whose use is documented +# throughout. Please refer to the TensorFlow dockerfiles documentation +# for more information. + +ARG UBUNTU_VERSION=16.04 + +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 + +# Needed for string substitution +SHELL ["/bin/bash", "-c"] +# Pick up some TF dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-${CUDA/./-} \ + cuda-cufft-${CUDA/./-} \ + cuda-curand-${CUDA/./-} \ + cuda-cusolver-${CUDA/./-} \ + cuda-cusparse-${CUDA/./-} \ + curl \ + libcudnn7=${CUDNN}+cuda${CUDA} \ + libfreetype6-dev \ + libhdf5-serial-dev \ + libzmq3-dev \ + pkg-config \ + software-properties-common \ + unzip + +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 clean \ + && rm -rf /var/lib/apt/lists/*) + +# For CUDA profiling, TensorFlow requires CUPTI. +ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH + +ARG USE_PYTHON_3_NOT_2 +ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} +ARG PYTHON=python${_PY_SUFFIX} +ARG PIP=pip${_PY_SUFFIX} + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +RUN apt-get update && apt-get install -y \ + ${PYTHON} \ + ${PYTHON}-pip + +RUN ${PIP} --no-cache-dir install --upgrade \ + pip \ + setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which ${PYTHON}) /usr/local/bin/python + +# Options: +# tensorflow +# tensorflow-gpu +# tf-nightly +# tf-nightly-gpu +ARG TF_PACKAGE=tensorflow +RUN apt-get update && apt-get install -y wget libhdf5-dev +RUN ${PIP} install --global-option=build_ext \ + --global-option=-I/usr/include/hdf5/serial/ \ + --global-option=-L/usr/lib/powerpc64le-linux-gnu/hdf5/serial \ + h5py + +# CACHE_STOP is used to rerun future commands, otherwise downloading the .whl will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +RUN if [ ${TF_PACKAGE} = tensorflow-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Nightly_Artifact/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tensorflow ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Nightly_Artifact/lastSuccessfulBuild/; \ + fi; \ + MAJOR=`${PYTHON} -c 'import sys; print(sys.version_info[0])'`; \ + MINOR=`${PYTHON} -c 'import sys; print(sys.version_info[1])'`; \ + PACKAGE=$(wget -qO- ${BASE}"api/xml?xpath=//fileName&wrapper=artifacts" | grep -o "[^<>]*cp${MAJOR}${MINOR}[^<>]*.whl"); \ + wget ${BASE}"artifact/tensorflow_pkg/"${PACKAGE}; \ + ${PIP} install ${PACKAGE} + +COPY bashrc /etc/bash.bashrc +RUN chmod a+rwx /etc/bash.bashrc + +RUN ${PIP} install jupyter matplotlib + +RUN mkdir -p /tf/tensorflow-tutorials && chmod -R a+rwx /tf/ +RUN mkdir /.local && chmod a+rwx /.local +RUN apt-get install -y --no-install-recommends wget +WORKDIR /tf/tensorflow-tutorials +RUN wget https://raw.githubusercontent.com/tensorflow/docs/master/site/en/tutorials/keras/basic_classification.ipynb +RUN wget https://raw.githubusercontent.com/tensorflow/docs/master/site/en/tutorials/keras/basic_text_classification.ipynb +COPY readme-for-jupyter.md README.md +RUN apt-get autoremove -y && apt-get remove -y wget +WORKDIR /tf +EXPOSE 8888 + +RUN ${PYTHON} -m ipykernel.kernelspec + +CMD ["bash", "-c", "source /etc/bash.bashrc && jupyter notebook --notebook-dir=/tf --ip 0.0.0.0 --no-browser --allow-root"] diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/gpu-ppc64le.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/gpu-ppc64le.Dockerfile new file mode 100644 index 0000000000..f01a47f1c0 --- /dev/null +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/gpu-ppc64le.Dockerfile @@ -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. +# ============================================================================ +# +# THIS IS A GENERATED DOCKERFILE. +# +# This file was assembled from multiple pieces, whose use is documented +# throughout. Please refer to the TensorFlow dockerfiles documentation +# for more information. + +ARG UBUNTU_VERSION=16.04 + +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 + +# Needed for string substitution +SHELL ["/bin/bash", "-c"] +# Pick up some TF dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-${CUDA/./-} \ + cuda-cufft-${CUDA/./-} \ + cuda-curand-${CUDA/./-} \ + cuda-cusolver-${CUDA/./-} \ + cuda-cusparse-${CUDA/./-} \ + curl \ + libcudnn7=${CUDNN}+cuda${CUDA} \ + libfreetype6-dev \ + libhdf5-serial-dev \ + libzmq3-dev \ + pkg-config \ + software-properties-common \ + unzip + +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 clean \ + && rm -rf /var/lib/apt/lists/*) + +# For CUDA profiling, TensorFlow requires CUPTI. +ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH + +ARG USE_PYTHON_3_NOT_2 +ARG _PY_SUFFIX=${USE_PYTHON_3_NOT_2:+3} +ARG PYTHON=python${_PY_SUFFIX} +ARG PIP=pip${_PY_SUFFIX} + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +RUN apt-get update && apt-get install -y \ + ${PYTHON} \ + ${PYTHON}-pip + +RUN ${PIP} --no-cache-dir install --upgrade \ + pip \ + setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which ${PYTHON}) /usr/local/bin/python + +# Options: +# tensorflow +# tensorflow-gpu +# tf-nightly +# tf-nightly-gpu +ARG TF_PACKAGE=tensorflow +RUN apt-get update && apt-get install -y wget libhdf5-dev +RUN ${PIP} install --global-option=build_ext \ + --global-option=-I/usr/include/hdf5/serial/ \ + --global-option=-L/usr/lib/powerpc64le-linux-gnu/hdf5/serial \ + h5py + +# CACHE_STOP is used to rerun future commands, otherwise downloading the .whl will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +RUN if [ ${TF_PACKAGE} = tensorflow-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Nightly_Artifact/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tensorflow ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Nightly_Artifact/lastSuccessfulBuild/; \ + fi; \ + MAJOR=`${PYTHON} -c 'import sys; print(sys.version_info[0])'`; \ + MINOR=`${PYTHON} -c 'import sys; print(sys.version_info[1])'`; \ + PACKAGE=$(wget -qO- ${BASE}"api/xml?xpath=//fileName&wrapper=artifacts" | grep -o "[^<>]*cp${MAJOR}${MINOR}[^<>]*.whl"); \ + wget ${BASE}"artifact/tensorflow_pkg/"${PACKAGE}; \ + ${PIP} install ${PACKAGE} + +COPY bashrc /etc/bash.bashrc +RUN chmod a+rwx /etc/bash.bashrc diff --git a/tensorflow/tools/dockerfiles/partials/tensorflow-ppc64le.partial.Dockerfile b/tensorflow/tools/dockerfiles/partials/tensorflow-ppc64le.partial.Dockerfile new file mode 100644 index 0000000000..1e79574a34 --- /dev/null +++ b/tensorflow/tools/dockerfiles/partials/tensorflow-ppc64le.partial.Dockerfile @@ -0,0 +1,28 @@ +# Options: +# tensorflow +# tensorflow-gpu +# tf-nightly +# tf-nightly-gpu +ARG TF_PACKAGE=tensorflow +RUN apt-get update && apt-get install -y wget libhdf5-dev +RUN ${PIP} install --global-option=build_ext \ + --global-option=-I/usr/include/hdf5/serial/ \ + --global-option=-L/usr/lib/powerpc64le-linux-gnu/hdf5/serial \ + h5py + +# CACHE_STOP is used to rerun future commands, otherwise downloading the .whl will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +RUN if [ ${TF_PACKAGE} = tensorflow-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly-gpu ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Nightly_Artifact/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tensorflow ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Release_Build/lastSuccessfulBuild/; \ + elif [ ${TF_PACKAGE} = tf-nightly ]; then \ + BASE=https://powerci.osuosl.org/job/TensorFlow_PPC64LE_CPU_Nightly_Artifact/lastSuccessfulBuild/; \ + fi; \ + MAJOR=`${PYTHON} -c 'import sys; print(sys.version_info[0])'`; \ + MINOR=`${PYTHON} -c 'import sys; print(sys.version_info[1])'`; \ + PACKAGE=$(wget -qO- ${BASE}"api/xml?xpath=//fileName&wrapper=artifacts" | grep -o "[^<>]*cp${MAJOR}${MINOR}[^<>]*.whl"); \ + wget ${BASE}"artifact/tensorflow_pkg/"${PACKAGE}; \ + ${PIP} install ${PACKAGE} diff --git a/tensorflow/tools/dockerfiles/partials/ubuntu/bazelbuild.partial.Dockerfile b/tensorflow/tools/dockerfiles/partials/ubuntu/bazelbuild.partial.Dockerfile new file mode 100644 index 0000000000..0397ab5fa8 --- /dev/null +++ b/tensorflow/tools/dockerfiles/partials/ubuntu/bazelbuild.partial.Dockerfile @@ -0,0 +1,33 @@ +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + git \ + openjdk-8-jdk \ + ${PYTHON}-dev \ + swig + +RUN ${PIP} --no-cache-dir install \ + Pillow \ + h5py \ + keras_applications \ + keras_preprocessing \ + matplotlib \ + mock \ + numpy \ + scipy \ + sklearn \ + pandas \ + && test "${USE_PYTHON_3_NOT_2}" -eq 1 && true || ${PIP} --no-cache-dir install \ + enum34 + + # Build and install bazel +ENV BAZEL_VERSION 0.15.0 +WORKDIR / +RUN mkdir /bazel && \ + cd /bazel && \ + curl -fSsL -O https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/bazel-$BAZEL_VERSION-dist.zip && \ + unzip bazel-$BAZEL_VERSION-dist.zip && \ + bash ./compile.sh && \ + cp output/bazel /usr/local/bin/ && \ + rm -rf /bazel && \ + cd - diff --git a/tensorflow/tools/dockerfiles/partials/ubuntu/devel-cpu.partial.Dockerfile b/tensorflow/tools/dockerfiles/partials/ubuntu/devel-cpu.partial.Dockerfile index 0652ac4151..aaeda0b207 100644 --- a/tensorflow/tools/dockerfiles/partials/ubuntu/devel-cpu.partial.Dockerfile +++ b/tensorflow/tools/dockerfiles/partials/ubuntu/devel-cpu.partial.Dockerfile @@ -7,7 +7,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libcurl3-dev \ libfreetype6-dev \ libhdf5-serial-dev \ - libpng12-dev \ libzmq3-dev \ pkg-config \ rsync \ @@ -20,9 +19,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* - + ENV CI_BUILD_PYTHON python -# Check out TensorFlow source code if --build-arg CHECKOUT_TF_SRC=1 +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 ARG CHECKOUT_TF_SRC=0 -RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true diff --git a/tensorflow/tools/dockerfiles/partials/ubuntu/devel-nvidia.partial.Dockerfile b/tensorflow/tools/dockerfiles/partials/ubuntu/devel-nvidia.partial.Dockerfile index 2b4494ac59..8ce4a1879d 100644 --- a/tensorflow/tools/dockerfiles/partials/ubuntu/devel-nvidia.partial.Dockerfile +++ b/tensorflow/tools/dockerfiles/partials/ubuntu/devel-nvidia.partial.Dockerfile @@ -1,20 +1,25 @@ -FROM nvidia/cuda:10.0-base-ubuntu${UBUNTU_VERSION} as base +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 +ARG LIB_DIR_PREFIX=x84_64 +# Needed for string substitution +SHELL ["/bin/bash", "-c"] RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ - cuda-command-line-tools-10-0 \ - cuda-cublas-dev-10-0 \ - cuda-cudart-dev-10-0 \ - cuda-cufft-dev-10-0 \ - cuda-curand-dev-10-0 \ - cuda-cusolver-dev-10-0 \ - cuda-cusparse-dev-10-0 \ - libcudnn7=7.4.1.5-1+cuda10.0 \ - libcudnn7-dev=7.4.1.5-1+cuda10.0 \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-dev-${CUDA/./-} \ + cuda-cudart-dev-${CUDA/./-} \ + cuda-cufft-dev-${CUDA/./-} \ + cuda-curand-dev-${CUDA/./-} \ + cuda-cusolver-dev-${CUDA/./-} \ + cuda-cusparse-dev-${CUDA/./-} \ + libcudnn7=${CUDNN}+cuda${CUDA} \ + libcudnn7-dev=${CUDNN}+cuda${CUDA} \ libcurl3-dev \ libfreetype6-dev \ libhdf5-serial-dev \ - libpng12-dev \ libzmq3-dev \ pkg-config \ rsync \ @@ -25,14 +30,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ wget \ git \ && \ - find /usr/local/cuda-10.0/lib64/ -type f -name 'lib*_static.a' -not -name 'libcudart_static.a' -delete && \ - rm /usr/lib/x86_64-linux-gnu/libcudnn_static_v7.a + find /usr/local/cuda-${CUDA}/lib64/ -type f -name 'lib*_static.a' -not -name 'libcudart_static.a' -delete && \ + rm /usr/lib/${LIB_DIR_PREFIX}-linux-gnu/libcudnn_static_v7.a -RUN apt-get update && \ - apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda10.0 \ +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 libnvinfer-dev=5.0.2-1+cuda10.0 \ - && rm -rf /var/lib/apt/lists/* + && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*) # Configure the build for our CUDA configuration. ENV CI_BUILD_PYTHON python @@ -40,9 +46,10 @@ ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH ENV TF_NEED_CUDA 1 ENV TF_NEED_TENSORRT 1 ENV TF_CUDA_COMPUTE_CAPABILITIES=3.5,5.2,6.0,6.1,7.0 -ENV TF_CUDA_VERSION=10.0 -ENV TF_CUDNN_VERSION=7 - -# Check out TensorFlow source code if --build_arg CHECKOUT_TENSORFLOW=1 +ENV TF_CUDA_VERSION=${CUDA} +ENV TF_CUDNN_VERSION=${CUDNN%%.*} +# CACHE_STOP is used to rerun future commands, otherwise cloning tensorflow will be cached and will not pull the most recent version +ARG CACHE_STOP=1 +# Check out TensorFlow source code if --build_arg CHECKOUT_TF_SRC=1 ARG CHECKOUT_TF_SRC=0 -RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src +RUN test "${CHECKOUT_TF_SRC}" -eq 1 && git clone https://github.com/tensorflow/tensorflow.git /tensorflow_src || true diff --git a/tensorflow/tools/dockerfiles/partials/ubuntu/nvidia.partial.Dockerfile b/tensorflow/tools/dockerfiles/partials/ubuntu/nvidia.partial.Dockerfile index a6393a3280..1d40ed5f98 100644 --- a/tensorflow/tools/dockerfiles/partials/ubuntu/nvidia.partial.Dockerfile +++ b/tensorflow/tools/dockerfiles/partials/ubuntu/nvidia.partial.Dockerfile @@ -1,29 +1,34 @@ -FROM nvidia/cuda:10.0-base-ubuntu${UBUNTU_VERSION} as base +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +ARG CUDNN=7.4.1.5-1 +# Needed for string substitution +SHELL ["/bin/bash", "-c"] # Pick up some TF dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ - cuda-command-line-tools-10-0 \ - cuda-cublas-10-0 \ - cuda-cufft-10-0 \ - cuda-curand-10-0 \ - cuda-cusolver-10-0 \ - cuda-cusparse-10-0 \ - libcudnn7=7.4.1.5-1+cuda10.0 \ + cuda-command-line-tools-${CUDA/./-} \ + cuda-cublas-${CUDA/./-} \ + cuda-cufft-${CUDA/./-} \ + cuda-curand-${CUDA/./-} \ + cuda-cusolver-${CUDA/./-} \ + cuda-cusparse-${CUDA/./-} \ + curl \ + libcudnn7=${CUDNN}+cuda${CUDA} \ libfreetype6-dev \ libhdf5-serial-dev \ - libpng12-dev \ libzmq3-dev \ pkg-config \ software-properties-common \ unzip -RUN apt-get update && \ - apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda10.0 \ +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+cuda10.0 \ + && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ && apt-get clean \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/*) # For CUDA profiling, TensorFlow requires CUPTI. ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH diff --git a/tensorflow/tools/dockerfiles/spec.yml b/tensorflow/tools/dockerfiles/spec.yml index 19d96e7a3d..d19b1d15fc 100644 --- a/tensorflow/tools/dockerfiles/spec.yml +++ b/tensorflow/tools/dockerfiles/spec.yml @@ -56,6 +56,13 @@ releases: - "{ubuntu}{jupyter}" - "{ubuntu-devel}{jupyter}" + ppc64le-dockerfiles: + is_dockerfiles: true + upload_images: false + tag_specs: + - "{ubuntu-ppc64le}{jupyter}" + - "{ubuntu-devel-ppc64le}{jupyter}" + slice_sets: py: @@ -122,6 +129,70 @@ slice_sets: args: - CHECKOUT_TF_SRC=1 + ubuntu-ppc64le: + - add_to_name: "-ppc64le" + dockerfile_exclusive_name: "cpu-ppc64le" + dockerfile_subdirectory: "ppc64le" + args: + - UBUNTU_VERSION=18.04 + partials: + - ubuntu/version + - ubuntu/cpu + - ubuntu/python + - tensorflow-ppc64le + - shell + - add_to_name: "-gpu-ppc64le" + dockerfile_exclusive_name: "gpu-ppc64le" + dockerfile_subdirectory: "ppc64le" + args: + - UBUNTU_VERSION=18.04 + - ARCH=ppc64le + - CUDA=10.0 + - TF_PACKAGE=tensorflow-gpu + partials: + - ubuntu/version + - ubuntu/nvidia + - ubuntu/python + - tensorflow-ppc64le + - shell + tests: + - import-gpu.sh + test_runtime: nvidia + + ubuntu-devel-ppc64le: + - add_to_name: "devel-ppc64le" + dockerfile_exclusive_name: "devel-cpu-ppc64le" + dockerfile_subdirectory: "ppc64le" + partials: + - ubuntu/version + - ubuntu/devel-cpu + - ubuntu/python + - ubuntu/bazelbuild + - shell + tests: + - build-cpu.sh + args: + - UBUNTU_VERSION=18.04 + - CHECKOUT_TF_SRC=1 + - add_to_name: "devel-gpu-ppc64le" + dockerfile_exclusive_name: "devel-gpu-ppc64le" + dockerfile_subdirectory: "ppc64le" + args: + - UBUNTU_VERSION=18.04 + - ARCH=ppc64le + - CUDA=10.0 + - LIB_DIR_PREFIX=powerpc64le + - CHECKOUT_TF_SRC=1 + partials: + - ubuntu/version + - ubuntu/devel-nvidia + - ubuntu/python + - ubuntu/bazelbuild + - shell + tests: + - build-gpu.sh + test_runtime: nvidia + nightly: - add_to_name: "nightly" partials: diff --git a/tensorflow/tools/dockerfiles/tools.Dockerfile b/tensorflow/tools/dockerfiles/tools.Dockerfile index e8929295a5..a96b2578cb 100644 --- a/tensorflow/tools/dockerfiles/tools.Dockerfile +++ b/tensorflow/tools/dockerfiles/tools.Dockerfile @@ -17,7 +17,7 @@ # # You can use this image to quickly develop changes to the Dockerfile assembler # or set of TF Docker partials. See README.md for usage instructions. -FROM debian:stretch +FROM ubuntu:16.04 LABEL maintainer="Austin Anderson " RUN apt-get update && apt-get install -y python3 python3-pip bash curl -- GitLab From 79bc4f4bf914d4b94eef89e8df2e19ff54c2db71 Mon Sep 17 00:00:00 2001 From: Siju Samuel Date: Fri, 21 Dec 2018 10:31:19 +0530 Subject: [PATCH 0071/2345] Review comments updated, nnapi removed for ceil --- .../lite/delegates/nnapi/nnapi_delegate.cc | 10 ---- .../delegates/nnapi/nnapi_delegate_test.cc | 49 ------------------- tensorflow/lite/kernels/BUILD | 1 - .../internal/optimized/legacy_optimized_ops.h | 6 --- .../internal/reference/legacy_reference_ops.h | 6 --- tensorflow/lite/nnapi/NeuralNetworksShim.h | 1 - tensorflow/lite/nnapi_delegate.cc | 3 -- 7 files changed, 76 deletions(-) diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc index cac98ae3da..4fe07004a8 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc @@ -638,16 +638,6 @@ class NNAPIDelegateKernel { return nullptr; } break; - case kTfLiteBuiltinCeil: - if (version == 1) { - return [](const NNAPIOpMappingArgs& mapping_args) - -> ANeuralNetworksOperationType { - return ANEURALNETWORKS_CEIL; - }; - } else { - return nullptr; - } - break; case kTfLiteBuiltinRelu: if (version == 1) { return [](const NNAPIOpMappingArgs& mapping_args) diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc index f6a04e36cd..ca48af0c95 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc @@ -1024,55 +1024,6 @@ TEST(NNAPIDelegate, FloorMultiDims) { EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); } -class CeilOpModel : public SingleOpModelWithNNAPI { - public: - CeilOpModel(std::initializer_list input_shape, TensorType input_type) { - input_ = AddInput(TensorType_FLOAT32); - output_ = AddOutput(TensorType_FLOAT32); - SetBuiltinOp(BuiltinOperator_CEIL, BuiltinOptions_NONE, 0); - BuildInterpreter({ - input_shape, - }); - } - - int input() { return input_; } - - std::vector GetOutput() { return ExtractVector(output_); } - std::vector GetOutputShape() { return GetTensorShape(output_); } - - private: - int input_; - int output_; -}; - -TEST(NNAPIDelegate, CeilSingleDim) { - CeilOpModel model({2}, TensorType_FLOAT32); - model.PopulateTensor(model.input(), {8.5, 0.0}); - model.Invoke(); - EXPECT_THAT(model.GetOutput(), ElementsAreArray({8, 0})); - EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2})); -} - -TEST(NNAPIDelegate, CeilMultiDims) { - CeilOpModel model({2, 1, 1, 5}, TensorType_FLOAT32); - model.PopulateTensor(model.input(), { - 0.0001, - 8.0001, - 0.9999, - 9.9999, - 0.5, - -0.0001, - -8.0001, - -0.9999, - -9.9999, - -0.5, - }); - model.Invoke(); - EXPECT_THAT(model.GetOutput(), - ElementsAreArray({1, 9, 1, 10, 1, 0, -8, 0, -9, 0})); - EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5})); -} - class LocalResponseNormOpModel : public SingleOpModelWithNNAPI { public: LocalResponseNormOpModel(std::initializer_list input_shape, int radius, diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD index 71d06ba4d7..1d53022e03 100644 --- a/tensorflow/lite/kernels/BUILD +++ b/tensorflow/lite/kernels/BUILD @@ -605,7 +605,6 @@ tf_cc_test( size = "small", srcs = ["ceil_test.cc"], tags = [ - "no_oss", "tflite_not_portable_ios", ], deps = [ diff --git a/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h index a76649f934..5485d907c2 100644 --- a/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/legacy_optimized_ops.h @@ -1740,12 +1740,6 @@ inline void Floor(const float* input_data, const Dims<4>& input_dims, output_data); } -inline void Ceil(const float* input_data, const Dims<4>& input_dims, - float* output_data, const Dims<4>& output_dims) { - Ceil(DimsToShape(input_dims), input_data, DimsToShape(output_dims), - output_data); -} - inline void ResizeBilinear(const float* input_data, const Dims<4>& input_dims, const int32* output_size_data, const Dims<4>& output_size_dims, float* output_data, diff --git a/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h b/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h index 431e2413e8..380fc8f98e 100644 --- a/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h +++ b/tensorflow/lite/kernels/internal/reference/legacy_reference_ops.h @@ -1883,12 +1883,6 @@ inline void Floor(const float* input_data, const Dims<4>& input_dims, output_data); } -inline void Ceil(const float* input_data, const Dims<4>& input_dims, - float* output_data, const Dims<4>& output_dims) { - Ceil(DimsToShape(input_dims), input_data, DimsToShape(output_dims), - output_data); -} - template inline void ResizeBilinear(const T* input_data, const Dims<4>& input_dims, const int32* output_size_data, diff --git a/tensorflow/lite/nnapi/NeuralNetworksShim.h b/tensorflow/lite/nnapi/NeuralNetworksShim.h index 82c5840952..c39502f4ac 100644 --- a/tensorflow/lite/nnapi/NeuralNetworksShim.h +++ b/tensorflow/lite/nnapi/NeuralNetworksShim.h @@ -143,7 +143,6 @@ enum { ANEURALNETWORKS_STRIDED_SLICE = 35, ANEURALNETWORKS_SUB = 36, ANEURALNETWORKS_TRANSPOSE = 37, - ANEURALNETWORKS_CEIL = 38, }; /** diff --git a/tensorflow/lite/nnapi_delegate.cc b/tensorflow/lite/nnapi_delegate.cc index dfbb4813ad..26d75696a1 100644 --- a/tensorflow/lite/nnapi_delegate.cc +++ b/tensorflow/lite/nnapi_delegate.cc @@ -489,9 +489,6 @@ TfLiteStatus AddOpsAndParams( case tflite::BuiltinOperator_FLOOR: nn_op_type = ANEURALNETWORKS_FLOOR; break; - case tflite::BuiltinOperator_CEIL: - nn_op_type = ANEURALNETWORKS_CEIL; - break; case tflite::BuiltinOperator_LOGISTIC: nn_op_type = ANEURALNETWORKS_LOGISTIC; break; -- GitLab From 8da29925c65dc72b49f693942923519b38dd0242 Mon Sep 17 00:00:00 2001 From: Yves-Noel Weweler Date: Fri, 21 Dec 2018 15:58:22 +0100 Subject: [PATCH 0072/2345] Fix pylint warnings from Ubuntu Sanity test --- .../kernel_tests/bucket_by_sequence_length_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 a95d8e1049..4f1ea9f2ee 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 @@ -74,12 +74,13 @@ def _get_record_shape(sparse): @test_util.run_all_in_graph_and_eager_modes -class BucketBySequenceLengthTest(test_base.DatasetTestBase, parameterized.TestCase): +class BucketBySequenceLengthTest(test_base.DatasetTestBase, + parameterized.TestCase): # TODO(b/117581999): add eager coverage. @parameterized.named_parameters( - ("WithoutPadding", True), - ("WithPadding", False), + ("WithoutPadding", True), + ("WithPadding", False), ) def testSkipEagerBucketDropReminder(self, param_no_padding): -- GitLab From 7859702e8a7093f242c167ea027cc227f1f9d048 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 21 Dec 2018 08:57:29 -0800 Subject: [PATCH 0073/2345] Fix failing test in TRT4.0 - TRT bug with tensors rank < 3 in INT8 mode --- tensorflow/contrib/tensorrt/test/identity_output_test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tensorflow/contrib/tensorrt/test/identity_output_test.py b/tensorflow/contrib/tensorrt/test/identity_output_test.py index 2be9da9ede..da7d70876d 100644 --- a/tensorflow/contrib/tensorrt/test/identity_output_test.py +++ b/tensorflow/contrib/tensorrt/test/identity_output_test.py @@ -71,6 +71,11 @@ class IdentityTest(trt_test.TfTrtIntegrationTestBase): """Return the expected engines to build.""" return ["TRTEngineOp_0"] + def ShouldRunTest(self, run_params): + """Whether to run the test.""" + # TODO(aaroey): Trt 4.0 forbids conversion for tensors with rank <3 in int8 + # mode, which is a bug. Re-enable this when trt library is fixed. + return not trt_test.IsQuantizationMode(run_params.precision_mode) if __name__ == "__main__": test.main() -- GitLab From ef1fd4ab6cb076aee9af7801f5b66fb66a768fc4 Mon Sep 17 00:00:00 2001 From: Mahmoud Abuzaina Date: Fri, 21 Dec 2018 09:37:24 -0800 Subject: [PATCH 0074/2345] Moved #endif of MKL --- tensorflow/core/ops/nn_ops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index c7cd3140be..24d774ef59 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -2515,6 +2515,7 @@ NOTE Do not invoke this operator directly in Python. Graph rewrite pass is expected to invoke these operators. )doc"); +#endif // INTEL_MKL REGISTER_OP("QuantizedConv2DAndRequantize") .Input("input: Tinput") .Input("filter: Tfilter") @@ -2851,6 +2852,5 @@ REGISTER_OP("QuantizedConv2DWithBiasSignedSumAndReluAndRequantize") return Status::OK(); }); -#endif // INTEL_MKL } // namespace tensorflow -- GitLab From 6bd27704c472b8d1cbd64b600fe00f223e74b4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=8C=AF=E5=8D=8E=20=28WANG=20Zhenhua=29?= Date: Sat, 22 Dec 2018 10:35:47 +0800 Subject: [PATCH 0075/2345] lite: remove memset in resize bilinear opt op memset() is not necessarily needed here since every element in output_data memory will be updated. --- tensorflow/lite/kernels/internal/optimized/optimized_ops.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h index 7bc6b324c5..a5374827e1 100644 --- a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h @@ -5387,9 +5387,6 @@ inline void ResizeBilinearGenericSmallChannel( int32 output_height, int32 output_width, float height_scale, float width_scale, const RuntimeShape& input_shape, const T* input_data, const RuntimeShape& output_shape, T* output_data) { - memset(output_data, 0, - batches * output_height * output_width * depth * sizeof(T)); - T* output_ptr = &output_data[0]; for (int b = 0; b < batches; ++b) { for (int y = 0; y < output_height; ++y) { -- GitLab From 315e009f917aebe04a754a046a57cb3ac1d10557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=8C=AF=E5=8D=8E=20=28WANG=20Zhenhua=29?= Date: Sat, 22 Dec 2018 10:36:50 +0800 Subject: [PATCH 0076/2345] lite: perform std::floor before cast float to int This won't change the resulted value, but to have same code style in the context and reference_ops. --- tensorflow/lite/kernels/internal/optimized/optimized_ops.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h index a5374827e1..8f09fab4ca 100644 --- a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h @@ -5395,7 +5395,7 @@ inline void ResizeBilinearGenericSmallChannel( int32 y1 = std::min(y0 + 1, input_height - 1); for (int x = 0; x < output_width; ++x) { float input_x = x * width_scale; - int32 x0 = static_cast(input_x); + int32 x0 = static_cast(std::floor((input_x))); int32 x1 = std::min(x0 + 1, input_width - 1); int32 input_offset[4] = {Offset(input_shape, b, y0, x0, 0), -- GitLab From 20af3560823fcab4843c2bbabbf8414b96a1022c Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 22 Dec 2018 21:49:31 +0000 Subject: [PATCH 0077/2345] Use rate instead of keep_prob calling dropout in keras While running tf.keras I noticed the following warning: ``` WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/layers/core.py:143: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version. Instructions for updating: Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`. ``` This fix fixes the warning by replacing keep_prob with rate. Signed-off-by: Yong Tang --- tensorflow/python/keras/layers/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/keras/layers/core.py b/tensorflow/python/keras/layers/core.py index dfbab80be3..a5e1d3aab9 100644 --- a/tensorflow/python/keras/layers/core.py +++ b/tensorflow/python/keras/layers/core.py @@ -138,9 +138,10 @@ class Dropout(Layer): training = K.learning_phase() def dropped_inputs(): - return nn.dropout(inputs, 1 - self.rate, + return nn.dropout(inputs, noise_shape=self._get_noise_shape(inputs), - seed=self.seed) + seed=self.seed, + rate = self.rate) output = tf_utils.smart_cond(training, dropped_inputs, lambda: array_ops.identity(inputs)) -- GitLab From cb4b9c007b0e2f792ae921feda8f3f7956397d8e Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 22 Dec 2018 21:52:40 +0000 Subject: [PATCH 0078/2345] Pylint fix of `(bad-whitespace)` Signed-off-by: Yong Tang --- tensorflow/python/keras/layers/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/keras/layers/core.py b/tensorflow/python/keras/layers/core.py index a5e1d3aab9..7c292c819d 100644 --- a/tensorflow/python/keras/layers/core.py +++ b/tensorflow/python/keras/layers/core.py @@ -141,7 +141,7 @@ class Dropout(Layer): return nn.dropout(inputs, noise_shape=self._get_noise_shape(inputs), seed=self.seed, - rate = self.rate) + rate=self.rate) output = tf_utils.smart_cond(training, dropped_inputs, lambda: array_ops.identity(inputs)) -- GitLab From afae41cb416acff7a1a3ec41e2d91a95721e4f88 Mon Sep 17 00:00:00 2001 From: "Li, Guizi" Date: Mon, 24 Dec 2018 09:48:10 +0800 Subject: [PATCH 0079/2345] [Intel MKL] Enable pad+fusedconv fusion --- tensorflow/core/graph/mkl_layout_pass.cc | 182 ++++++++- tensorflow/core/graph/mkl_layout_pass_test.cc | 334 +++++++++++++++++ tensorflow/core/kernels/mkl_conv_ops.cc | 109 ++++-- tensorflow/core/kernels/mkl_fused_ops_test.cc | 345 +++++++++++++++--- tensorflow/core/ops/mkl_nn_ops.cc | 57 +++ 5 files changed, 934 insertions(+), 93 deletions(-) diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc index 9495132f4a..e69b1ad62e 100644 --- a/tensorflow/core/graph/mkl_layout_pass.cc +++ b/tensorflow/core/graph/mkl_layout_pass.cc @@ -280,8 +280,10 @@ class MklLayoutRewritePass : public GraphOptimizationPass { "_MklConv2DBackpropFilterWithBias"; csinfo_.mkl_fused_conv2d = "_MklFusedConv2D"; csinfo_.mkl_pad_with_conv2d = "_MklPadWithConv2D"; + csinfo_.mkl_pad_with_fused_conv2d = "_MklPadWithFusedConv2D"; csinfo_.pad = "Pad"; csinfo_.pad_with_conv2d = "__MklDummyPadWithConv2D"; + csinfo_.pad_with_fused_conv2d = "__MklDummyPadWithFusedConv2D"; // Temporarily don't convert quantized operators into MKL versions for now. // TODO(Intel-tf) Once all the relevant PRs have been merged then remove // the ifdef. @@ -423,6 +425,9 @@ class MklLayoutRewritePass : public GraphOptimizationPass { CopyAttrsDataType, AlwaysRewrite}); rinfo_.push_back({csinfo_.pad_with_conv2d, csinfo_.mkl_pad_with_conv2d, CopyAttrsPadWithConv2D, AlwaysRewrite}); + rinfo_.push_back({csinfo_.pad_with_fused_conv2d, + csinfo_.mkl_pad_with_fused_conv2d, + CopyAttrsPadWithFusedConv2D, AlwaysRewrite}); #ifdef INTEL_MKL_QUANTIZED rinfo_.push_back({csinfo_.quantized_avg_pool, mkl_op_registry::GetMklOpName(csinfo_.quantized_avg_pool), @@ -538,6 +543,9 @@ class MklLayoutRewritePass : public GraphOptimizationPass { // Merge Pad and Conv2d, only if the pad op is "Pad" // Doesn't merge if pad op is "PadV2" or "MirrorPad" + minfo_.push_back({csinfo_.pad, csinfo_.fused_conv2d, + csinfo_.pad_with_fused_conv2d, GetPadOrFusedConv2D}); + // The fusion patterns in "finfo_" that show up first will get applied // first, for example, graph "A->B->C-D" and finfo_ is {A->B->C to ABC, // A->B->C->D to ABCD}, since the first gets applied first, the final @@ -701,9 +709,11 @@ class MklLayoutRewritePass : public GraphOptimizationPass { string mkl_conv2d_with_bias; string mkl_fused_conv2d; string mkl_pad_with_conv2d; + string mkl_pad_with_fused_conv2d; string mul; string pad; string pad_with_conv2d; + string pad_with_fused_conv2d; string quantized_avg_pool; string quantized_conv2d; string quantized_conv2d_with_requantize; @@ -917,6 +927,59 @@ class MklLayoutRewritePass : public GraphOptimizationPass { return n; } + + // Find Pad or _FusedConv2D node that can be merged with input node 'm'. + // If input 'm' is Pad, then check if there exists _FusedConv2D node that can + // be merged with 'm'. If input 'm' is _FusedConv2D, then check if there + // exists Pad node that can be merged with 'm'. + static Node* GetPadOrFusedConv2D(const Node* m) { + DCHECK(m); + Node* n = nullptr; + + const Node* conv_node; + if (m->type_string() == csinfo_.pad) { + // If m is Pad, then _FusedConv2D is the output of Pad. + for (const Edge* e : m->out_edges()) { + if (!e->IsControlEdge() && + e->dst()->type_string() == csinfo_.fused_conv2d) { + n = e->dst(); + conv_node = n; + break; + } + } + } else { + DCHECK_EQ(m->type_string(), csinfo_.fused_conv2d); + // If m is _FusedConv2D, Go over all input edges + // and search for Pad Node. + for (const Edge* e : m->in_edges()) { + if (!e->IsControlEdge() && e->src()->type_string() == csinfo_.pad) { + n = e->src(); + conv_node = m; + break; + } + } + } + // Check if only VALID type of padding is used + // or not. + if (n != nullptr) { + string padding; + TF_CHECK_OK(GetNodeAttr(conv_node->def(), "padding", &padding)); + if (padding != "VALID") { + // Then do not merge. + n = nullptr; + VLOG(1) << "MklLayoutRewritePass: Could match Pad and _FusedConv2D " + << "node for merging. Only VALID type of padding in conv op " + << "can be merged with Pad op Input node: " << m->DebugString(); + } + } else { + VLOG(1) << "MklLayoutRewritePass: Could not find matching " + << "Pad and _FusedConv2D node for merging. Input node: " + << m->DebugString(); + } + + return n; + } + // Find Conv2DBackpropFilter or BiasAddGrad node that can be merged with input // node 'm'. If input 'm' is Conv2DBackpropFilter, then check if there exists // BiasAddGrad node that can be merged with 'm'. If input 'm' is BiasAddGrad, @@ -1407,9 +1470,16 @@ class MklLayoutRewritePass : public GraphOptimizationPass { bool change_format = false); static void CopyAttrsPadWithConv2D(const Node* orig_node, NodeBuilder* nb, bool change_format = false); + static void CopyAttrsPadWithFusedConv2D(const Node* orig_node, + NodeBuilder* nb, + bool change_format = false); static void CopyAttrsFromPadAndConv2D(const Node* orig_node1, const Node* orig_node2, NodeBuilder* nb, bool change_format = false); + static void CopyAttrsFromPadAndFusedConv2D(const Node* orig_node1, + const Node* orig_node2, + NodeBuilder* nb, + bool change_format = false); static void CopyAttrsPooling(const Node* orig_node, NodeBuilder* nb, bool change_format = false); static void CopyAttrsQuantizedPooling(const Node* orig_node, NodeBuilder* nb, @@ -1627,6 +1697,7 @@ int MklLayoutRewritePass::SetUpContiguousInputs( for (const Edge* e : filter_node->out_edges()) { if ((e->dst()->type_string() == csinfo_.mkl_conv2d || e->dst()->type_string() == csinfo_.mkl_pad_with_conv2d || + e->dst()->type_string() == csinfo_.mkl_pad_with_fused_conv2d || e->dst()->type_string() == csinfo_.mkl_conv2d_with_bias || e->dst()->type_string() == csinfo_.mkl_fused_conv2d) && e->dst_input() == kConv2DFilterInputSlotIdx @@ -2015,6 +2086,26 @@ void MklLayoutRewritePass::CopyAttrsPadWithConv2D(const Node* orig_node, nb->Attr("Tpaddings", Tpaddings); } +void MklLayoutRewritePass::CopyAttrsPadWithFusedConv2D(const Node* orig_node, + NodeBuilder* nb, + bool change_format) { + DataType Tpaddings; + bool is_filter_const; + + CopyAttrsFusedConv2D(orig_node, nb, change_format); + + // Get attributes from old node. + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "Tpaddings", &Tpaddings)); + // Check if filter is a constant. + Node* filter_node = nullptr; + orig_node->input_node(1, &filter_node); + is_filter_const = filter_node->IsConstant(); + + // Add attributes to new node. + nb->Attr("Tpaddings", Tpaddings); + nb->Attr("is_filter_const", is_filter_const); +} + // Used with MergePadWithConv2D void MklLayoutRewritePass::CopyAttrsFromPadAndConv2D(const Node* orig_node1, const Node* orig_node2, @@ -2049,6 +2140,42 @@ void MklLayoutRewritePass::CopyAttrsFromPadAndConv2D(const Node* orig_node1, nb->Attr("Tpaddings", Tpaddings); } +void MklLayoutRewritePass::CopyAttrsFromPadAndFusedConv2D( + const Node* orig_node1, const Node* orig_node2, NodeBuilder* nb, + bool change_format) { + DataType T; + int num_args; + string data_format; + string padding; + std::vector strides; + std::vector dilations; + float epsilon; + std::vector fused_ops; + DataType Tpaddings; + + // Get all attributes from old node. + TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "T", &T)); + TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "num_args", &num_args)); + TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "strides", &strides)); + TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "padding", &padding)); + TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "data_format", &data_format)); + TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "dilations", &dilations)); + TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "fused_ops", &fused_ops)); + TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "epsilon", &epsilon)); + TF_CHECK_OK(GetNodeAttr(orig_node2->def(), "Tpaddings", &Tpaddings)); + + // Add attributes to new node. + nb->Attr("T", T); + nb->Attr("num_args", num_args); + nb->Attr("strides", strides); + nb->Attr("padding", padding); + nb->Attr("data_format", data_format); + nb->Attr("dilations", dilations); + nb->Attr("epsilon", epsilon); + nb->Attr("Tpaddings", Tpaddings); + nb->Attr("fused_ops", fused_ops); +} + void MklLayoutRewritePass::CopyAttrsConv2DDepthwise(const Node* orig_node, NodeBuilder* nb, bool change_format) { @@ -2575,10 +2702,14 @@ Status MklLayoutRewritePass::MergeConv2DWithBiasAdd(std::unique_ptr* g, Status MklLayoutRewritePass::MergePadWithConv2D(std::unique_ptr* g, Node* m, Node* n) { DCHECK(((m->type_string() == csinfo_.pad && - n->type_string() == csinfo_.conv2d)) || + (n->type_string() == csinfo_.conv2d || + n->type_string() == csinfo_.fused_conv2d))) || ((n->type_string() == csinfo_.pad && - m->type_string() == csinfo_.conv2d))); + (m->type_string() == csinfo_.conv2d || + m->type_string() == csinfo_.fused_conv2d)))); + bool is_fused_conv2d = n->type_string() == csinfo_.fused_conv2d || + m->type_string() == csinfo_.fused_conv2d; // Conv2D is successor node, and Pad predecessor node. Node* pred = m->type_string() == csinfo_.pad ? m : n; Node* succ = m->type_string() == csinfo_.pad ? n : m; @@ -2589,7 +2720,6 @@ Status MklLayoutRewritePass::MergePadWithConv2D(std::unique_ptr* g, std::vector strides; std::vector dilations; string data_format_pred, data_format_succ; - bool use_cudnn_on_gnu; TF_CHECK_OK(GetNodeAttr(pred->def(), "T", &T_pred)); TF_CHECK_OK(GetNodeAttr(succ->def(), "T", &T_succ)); TF_CHECK_OK(GetNodeAttr(succ->def(), "padding", &padding)); @@ -2598,7 +2728,6 @@ Status MklLayoutRewritePass::MergePadWithConv2D(std::unique_ptr* g, // Data format for pad is not available and not necessary, thus // dont need to match data format for Pad TF_CHECK_OK(GetNodeAttr(succ->def(), "data_format", &data_format_succ)); - TF_CHECK_OK(GetNodeAttr(succ->def(), "use_cudnn_on_gpu", &use_cudnn_on_gnu)); // Check if the data types and devices of both succ and pred are the same. // Assert is not used, because it can be too strict. // Don't need to check for data formats because it is not available in Pad. @@ -2644,29 +2773,54 @@ Status MklLayoutRewritePass::MergePadWithConv2D(std::unique_ptr* g, } DCHECK_EQ(PadDataInputEdges, 2); - // Conv2D must have 2 data inputs: pad output and Filter + // Conv2D must have 2 data inputs: Pad output and Filter + // FusedConv2D have 3 data inputs: Pad output, Filter and Args; int ConvDataInputEdges = 0; for (const Edge* e : succ->in_edges()) { if (!e->IsControlEdge()) { ConvDataInputEdges++; } } - DCHECK_EQ(ConvDataInputEdges, 2); + + if (is_fused_conv2d) { + DCHECK_EQ(ConvDataInputEdges, 3); + } else { + DCHECK_EQ(ConvDataInputEdges, 2); + } // We will use the node name of Conv2D as the name of new node // Build new node. We use same name as original node, but change the op // name. - NodeBuilder nb(succ->name(), csinfo_.pad_with_conv2d); + + auto fused_op_name = csinfo_.pad_with_conv2d; + if (is_fused_conv2d) { + fused_op_name = csinfo_.pad_with_fused_conv2d; + } + NodeBuilder nb(succ->name(), fused_op_name); nb.Input(pred_in[0].first, pred_in[0].second); // In1 (input data) of Pad // pred_in[1] will be 2nd Tensorflow tensor for Conv2D. nb.Input(succ_in[1].first, succ_in[1].second); // In2 (filter) of conv2d // In1 of Conv2D is same as output of Pad. // Thus, only need to add In2 of Conv2D + + // FusedConv2D has one additional input, args + if (is_fused_conv2d) { + std::vector args; + args.emplace_back(succ_in[2].first, succ_in[2].second); + nb.Input(gtl::ArraySlice{ + args}); // In3 (args) of FusedConv2D + } nb.Input(pred_in[1].first, pred_in[1].second); // In2 (paddings) of Pad - // Copy attributes from Pad and conv2D to PadWithConv2D. - CopyAttrsFromPadAndConv2D(const_cast(succ), - const_cast(pred), &nb); + if (is_fused_conv2d) { + // Copy attributes from Pad and FusedConv2D to PadWithFusedConv2D. + CopyAttrsFromPadAndFusedConv2D(const_cast(succ), + const_cast(pred), &nb); + } else { + // Copy attributes from Pad and conv2D to PadWithConv2D. + CopyAttrsFromPadAndConv2D(const_cast(succ), + const_cast(pred), &nb); + } // Copy the device assigned to old node to new node. nb.Device(succ->def().device()); @@ -2871,6 +3025,13 @@ Status MklLayoutRewritePass::MergeNode(std::unique_ptr* g, Node* m, return this->MergePadWithConv2D(g, m, n); } + if (((m->type_string() == csinfo_.pad && + n->type_string() == csinfo_.fused_conv2d && FusedConv2DRewrite(n))) || + ((n->type_string() == csinfo_.pad && + m->type_string() == csinfo_.fused_conv2d && FusedConv2DRewrite(m)))) { + return this->MergePadWithConv2D(g, m, n); + } + if (((m->type_string() == csinfo_.bias_add_grad && n->type_string() == csinfo_.conv2d_grad_filter)) || ((n->type_string() == csinfo_.bias_add_grad && @@ -3021,6 +3182,7 @@ MklLayoutRewritePass::CheckForNodeRewrite(const Node* n) const { // names do not match Mkl node names. if (n->type_string() != csinfo_.conv2d_with_bias && n->type_string() != csinfo_.pad_with_conv2d && + n->type_string() != csinfo_.pad_with_fused_conv2d && n->type_string() != csinfo_.conv2d_grad_filter_with_bias && n->type_string() != csinfo_.fused_conv2d && !mkl_op_registry::IsMklOp(mkl_op_registry::GetMklOpName(n->type_string()), diff --git a/tensorflow/core/graph/mkl_layout_pass_test.cc b/tensorflow/core/graph/mkl_layout_pass_test.cc index 6e73ed1b9f..5544e2bc36 100644 --- a/tensorflow/core/graph/mkl_layout_pass_test.cc +++ b/tensorflow/core/graph/mkl_layout_pass_test.cc @@ -1243,6 +1243,340 @@ TEST_F(MklLayoutPassTest, NodeRewrite_FusedConv2D_Negative2) { "D(_FusedConv2D);E(Zeta)|A->D;B->D:1;C->D:2;C->E:1;D->E"); } +// Merge test for PadWithFusedConv2D Op with BiasAdd fusion +// padding is VALID type +// A = input(image), B = input(paddings), C= Pad = input of conv2D, +// D=input(filter), E = input(bias), F = _FusedConv2D +// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// After layout pass +// _MklPadWithFusedConv2D(A, D, E, B, DMT/_0, DMT/_1, DMT/_2, DMT/_3) +TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Positive1) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" + "node { name: 'E' op: 'Input'}" + "node { name: 'F' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'VALID' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['C', 'D', 'E']}" + "node { name: 'G' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['F', 'E'] }"); + EXPECT_EQ( + DoMklLayoutOptimizationPass(), + "A(Input);B(Int32Input);D(Input);DMT/_0(Const);DMT/_1(Const);DMT/" + "_2(Const);DMT/_3(Const);E(Input);F(_MklPadWithFusedConv2D);" + "G(Zeta)|A->F;A:control->DMT/_0:control;A:control->DMT/_1:control;" + "A:control->DMT/_2:control;A:control->DMT/_3:control;B->F:3;D->F:1;DMT/" + "_0->F:4;DMT/_1->F:5;DMT/_2->F:6;DMT/_3->F:7;E->F:2;E->G:1;F->G"); +} + +// Merge test for PadWithFusedConv2D Op with BiasAdd+Relu fusion +// padding is VALID type +// A = input(image), B = input(paddings), C= Pad = input of conv2D, +// D=input(filter), E = input(bias), F = _FusedConv2D(With relu) +// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// After layout pass +// _MklPadWithFusedConv2D(A, D, E, B, DMT/_0, DMT/_1, DMT/_2, DMT/_3) +TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Positive2) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" + "node { name: 'E' op: 'Input'}" + "node { name: 'F' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'VALID' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops'" + " value { list: {s: 'BiasAdd', s: 'Relu'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['C', 'D', 'E']}" + "node { name: 'G' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['F', 'E'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Int32Input);D(Input);DMT/_0(Const);DMT/_1(Const);DMT/" + "_2(Const);DMT/_3(Const);E(Input);F(_MklPadWithFusedConv2D);" + "G(Zeta)|A->F;A:control->DMT/_0:control;A:control->DMT/_1:control;" + "A:control->DMT/_2:control;A:control->DMT/_3:control;B->F:3;" + "D->F:1;DMT/_0->F:4;DMT/_1->F:5;DMT/_2->F:6;DMT/" + "_3->F:7;E->F:2;E->G:1;F->G"); +} + +// Merge test for PadWithFusedConv2D Op with unsupported fusion +// padding is VALID type +// A = input(image), B = input(paddings), C= Pad = input of conv2D, +// D=input(filter), E = input(bias), F = _FusedConv2D(With Unsupported) +// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// After layout pass - No merging +TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Negative1) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" + "node { name: 'E' op: 'Input'}" + "node { name: 'F' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'VALID' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops' value { list: {s: 'Unsupported'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['C', 'D', 'E']}" + "node { name: 'G' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['F', 'E'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Int32Input);C(Pad);D(Input);E(Input);F(_FusedConv2D);G(" + "Zeta)|A->C;B->C:1;C->F;D->F:1;E->F:2;E->G:1;F->G"); +} + +// Merge test for PadWithFusedConv2D Op with BiasAdd fusion +// padding is SAME type +// A = input(image), B = input(paddings), C= Pad = input of conv2D, +// D=input(filter), E = input(bias), F = _FusedConv2D +// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// After layout pass - No merging +TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Negative2) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" + "node { name: 'E' op: 'Input'}" + "node { name: 'F' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['C', 'D', 'E']}" + "node { name: 'G' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['F', 'E'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Int32Input);C(Pad);D(Input);DMT/_0(Const);DMT/" + "_1(Const);DMT/_2(Const);E(Input);F(_MklFusedConv2D);G(Zeta)|A->C;" + "B->C:1;C->F;C:control->DMT/_0:control;C:control->DMT/_1:control;" + "C:control->DMT/_2:control;D->F:1;DMT/_0->F:3;DMT/_1->F:4;DMT/" + "_2->F:5;E->F:2;E->G:1;F->G"); +} + +// Merge test for PadWithFusedConv2D Op with BiasAdd+Relu fusion +// padding is SAME type +// A = input(image), B = input(paddings), C= Pad = input of conv2D, +// D=input(filter), E = input(bias), F = _FusedConv2D(With relu) +// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// After layout pass - No merging +TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Negative3) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" + "node { name: 'E' op: 'Input'}" + "node { name: 'F' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops'" + " value { list: {s: 'BiasAdd', s: 'Relu'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['C', 'D', 'E']}" + "node { name: 'G' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['F', 'E'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Int32Input);C(Pad);D(Input);DMT/_0(Const);DMT/" + "_1(Const);DMT/_2(Const);E(Input);F(_MklFusedConv2D);G(Zeta)|A->C;" + "B->C:1;C->F;C:control->DMT/_0:control;C:control->DMT/_1:control;" + "C:control->DMT/_2:control;D->F:1;DMT/_0->F:3;DMT/_1->F:4;DMT/" + "_2->F:5;E->F:2;E->G:1;F->G"); +} + +// Test if input control edges do not duplicate after merge. +// If both the merging ops have input control edge from a common op +// then, the merged op will have only one control edge from that +// common op. +// padding is VALID type +// A = input(image), A1 = input, B = input(paddings), +// C= Pad = input of conv2D, +// D=input(filter), F=input(bias), E = _FusedConv2D, Z = Zeta +// C=Pad(A,B); E=_FusedConv2D(C,D,F); Z=Zeta(E,Y) +// A1:control->C:control +// A1:control->E:control +// After layout pass: +// _MklPadWithFusedConv2D(A, D, B, F, DMT/_0, DMT/_1, DMT/_2, DMT/_3) +// A1:control->E:control (only one control edge) +TEST_F(MklLayoutPassTest, Input_ControlEdge_PadWithFusedConv2D_Positive) { + DCHECK_EQ(kTensorOrdering, MklTfTensorOrdering::TENSORS_CONTIGUOUS); + InitGraph( + "node { name: 'A1' op: 'Input'}" + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" + "node { name: 'F' op: 'Input'}" + "node { name: 'E' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'VALID' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['C', 'D', 'F']}" + "node { name: 'Y' op: 'Input'}" + "node { name: 'Z' op: 'Zeta'" + " attr {key: 'T' value { type: DT_FLOAT } }" + " input: ['E', 'Y']}"); + Node* a1 = FindNode("A1"); + Node* c = FindNode("C"); + Node* e = FindNode("E"); + const Edge* edge = graph_.AddControlEdge(a1, c); + const Edge* edge_1 = graph_.AddControlEdge(a1, e); + ASSERT_NE(edge, nullptr); + ASSERT_NE(edge_1, nullptr); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);A1(Input);B(Int32Input);D(Input);DMT/_0(Const);DMT/" + "_1(Const);DMT/_2(Const);DMT/" + "_3(Const);E(_MklPadWithFusedConv2D);F(Input);Y(Input);Z(Zeta)|A->" + "E;A1:control->E:control;A:control->DMT/_0:control;A:control->DMT/" + "_1:control;A:control->DMT/_2:control;A:control->DMT/" + "_3:control;B->E:3;D->E:1;DMT/_0->E:4;DMT/_1->E:5;DMT/_2->E:6;DMT/" + "_3->E:7;E->Z;F->E:2;Y->Z:1"); +} + +// Test if output control edges does not duplicate after merge. +// If both the merging ops have output control edge to a common op, +// then after merge, the merged op will have only one control edge +// to that commom op. +// padding is VALID type +// A = input(image), B = input(paddings), C= Pad = input of conv2D, +// D=input(filter), F=input(bias), E = _FusedConv2D, Z = Zeta +// C=Pad(A,B); E=_FusedConv2D(C,D,F); Z=Zeta(E,Y) +// C:control->A1:control +// E:control->A1:control +// After layout pass: +// _MklPadWithFusedConv2D(A, D, B, F, DMT/_0, DMT/_1, DMT/_2, DMT/_2) +// E:control->A1:control (only one control edge) +TEST_F(MklLayoutPassTest, Output_ControlEdge_PadWithFusedConv2D_Positive) { + DCHECK_EQ(kTensorOrdering, MklTfTensorOrdering::TENSORS_CONTIGUOUS); + InitGraph( + "node { name: 'A1' op: 'Input'}" + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" + "node { name: 'F' op: 'Input'}" + "node { name: 'E' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'VALID' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['C', 'D', 'F']}" + "node { name: 'Y' op: 'Input'}" + "node { name: 'Z' op: 'Zeta'" + " attr {key: 'T' value { type: DT_FLOAT } }" + " input: ['E', 'Y']}"); + Node* a1 = FindNode("A1"); + Node* c = FindNode("C"); + Node* e = FindNode("E"); + const Edge* edge = graph_.AddControlEdge(c, a1); + const Edge* edge_1 = graph_.AddControlEdge(e, a1); + ASSERT_NE(edge, nullptr); + ASSERT_NE(edge_1, nullptr); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);A1(Input);B(Int32Input);D(Input);DMT/_0(Const);DMT/" + "_1(Const);DMT/_2(Const);DMT/" + "_3(Const);E(_MklPadWithFusedConv2D);F(Input);Y(Input);Z(Zeta)|A->" + "E;A:control->DMT/_0:control;A:control->DMT/" + "_1:control;A:control->DMT/_2:control;A:control->DMT/" + "_3:control;B->E:3;D->E:1;DMT/_0->E:4;DMT/_1->E:5;DMT/_2->E:6;DMT/" + "_3->E:7;E->Z;E:control->A1:control;F->E:2;Y->Z:1"); +} + +// Pad + _FusedConv2D with padding is VALID, +// Input node pointing to both Pad and _FusedConv2D +// Output of both Pad and _FusedConv2D feeds one node (Z as Output2) +// A = input(as image), B = input(as paddings), C= Pad +// F = input(as bias), E = _FusedConv2D, Z = Output2 +// C=Pad(A,B); E=_FusedConv2D(C,A,F); Z=Output(C,E) +// After layout pass - No merging, since Pad and _FusedConv2D both +// feed to the same node (Z) +TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Common_InOutput) { + DCHECK_EQ(kTensorOrdering, MklTfTensorOrdering::TENSORS_CONTIGUOUS); + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'F' op: 'Input'}" + "node { name: 'E' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'VALID' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['C', 'A', 'F']}" + "node { name: 'Z' op: 'Output2'" + " input: ['C', 'E']}"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Int32Input);C(Pad);DMT/_0(Const);DMT/_1(Const);DMT/" + "_2(Const);E(_MklFusedConv2D);F(Input);Z(Output2)|A->C;A->E:1;B->C:" + "1;C->E;C->Z;C:control->DMT/_0:control;C:control->DMT/" + "_1:control;C:control->DMT/_2:control;DMT/_0->E:3;DMT/_1->E:4;DMT/" + "_2->E:5;E->Z:1;F->E:2"); +} + TEST_F(MklLayoutPassTest, NodeRewrite_Conv2DGradFilter_Positive) { InitGraph( "node { name: 'A' op: 'Input'}" diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc index 8c585fb48c..ead2f1e09e 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_ops.cc @@ -21,6 +21,7 @@ limitations under the License. #include #include +#include "absl/strings/str_join.h" #include "tensorflow/core/framework/numeric_op.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" @@ -465,19 +466,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)); @@ -486,10 +486,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)); @@ -498,10 +497,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)); @@ -509,10 +507,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 @@ -894,17 +891,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.")); } } @@ -930,7 +925,7 @@ class MklConvOp : public OpKernel { memory::dims dst_dims_tf_order, dst_dims_mkl_order; // If pad with conv2d fusion is enabled - if (pad_enabled) { + if (fuse_pad_) { PadWithConvFusion(context, padding_left, padding_right); } @@ -942,7 +937,7 @@ 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, pad_enabled, is_depthwise); + &padding_right, fuse_pad_, is_depthwise); if (!context->status().ok()) return; // Check for corner case - if there is nothing to compute, return. @@ -984,7 +979,7 @@ class MklConvOp : public OpKernel { // TODO(Intel-tf) Add check to make sure pad_enabled is true only for 2D if (!is_conv2d) { OP_REQUIRES( - context, !pad_enabled, + context, !fuse_pad_, errors::InvalidArgument("Pad+Conv fusion only works for 2D")); } // Create memory for user data. @@ -1131,7 +1126,7 @@ class MklConvOp : public OpKernel { void PadWithConvFusion(OpKernelContext* context, memory::dims& padding_left, memory::dims& padding_right) { - const Tensor& paddings_tf = MklGetInput(context, 2); + 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())); @@ -1170,6 +1165,11 @@ class MklConvOp : public OpKernel { protected: void set_fuse_biasadd(bool fuse_biasadd) { fuse_biasadd_ = fuse_biasadd; } void set_fuse_relu(bool fuse_relu) { fuse_relu_ = fuse_relu; } + void set_fuse_pad(bool fuse_pad) { + fuse_pad_ = fuse_pad; + // In PadwithFusedConv OP, pad is the fourth index. + input_index_pad = 3; + } // This method is for the base class MklConvOp, which handles the // floating point implementation of Conv. The quantized conv implementations @@ -1243,9 +1243,11 @@ class MklConvOp : public OpKernel { // Initialize to values the template is instantiated with bool fuse_biasadd_ = bias_enabled; bool fuse_relu_ = false; + bool fuse_pad_ = pad_enabled; + + int input_index_pad = 2; const int kInputIndex_Src = 0, kInputIndex_Filter = 1, kInputIndex_Bias = 2; - const int kInputIndex_Pad = 2; const int kOutputIndex_Dst = 0, kOutputIndex_Filter = 1; const int kDilationH = 0, kDilationW = 1; @@ -1314,14 +1316,15 @@ class MklConvOp : public OpKernel { // Base class for fused convolution forward operations template + typename Toutput, typename Ttemp_output, typename Tpadding, + bool pad_enabled> class MklFusedConvOp : public MklConvOp { + Tpadding, false, false, false> { public: explicit MklFusedConvOp(OpKernelConstruction* context) - : MklConvOp(context) { + : MklConvOp(context) { // Since we came here through the registration of _MklFusedConv2D, get // all information from 'fused_ops' and 'num_args' std::vector fused_ops; @@ -1351,6 +1354,10 @@ class MklFusedConvOp errors::Unimplemented("Fusion is not implemented: [", str_util::Join(fused_ops, ","), "]")); } + + if (pad_enabled) { + this->set_fuse_pad(true); + } } virtual ~MklFusedConvOp() {} @@ -1663,8 +1670,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); @@ -1966,11 +1973,33 @@ TF_CALL_float(REGISTER_MKL_CPU_2D); TF_CALL_float(REGISTER_MKL_CPU_2D_DEPTHWISE); #define REGISTER_MKL_CPU_2D_FUSED(T) \ - REGISTER_KERNEL_BUILDER(Name("_MklFusedConv2D") \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklFusedConv2D") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklFusedConvOp); \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklPadWithFusedConv2D") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("Tpaddings") \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklFusedConvOp); \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklPadWithFusedConv2D") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .TypeConstraint("Tpaddings") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklFusedConvOp); \ + REGISTER_KERNEL_BUILDER(Name("__MklDummyPadWithFusedConv2D") \ .Device(DEVICE_CPU) \ .TypeConstraint("T") \ + .TypeConstraint("Tpaddings") \ .Label(mkl_op_registry::kMklOpLabel), \ - MklFusedConvOp); + MklDummyOp); +// Note we are registering _MklFusedConv2D. // We check the fused_ops attributes to decide if bias is enabled or not. TF_CALL_float(REGISTER_MKL_CPU_2D_FUSED); diff --git a/tensorflow/core/kernels/mkl_fused_ops_test.cc b/tensorflow/core/kernels/mkl_fused_ops_test.cc index 258cca9332..f1243c5b68 100644 --- a/tensorflow/core/kernels/mkl_fused_ops_test.cc +++ b/tensorflow/core/kernels/mkl_fused_ops_test.cc @@ -59,6 +59,23 @@ class ConvMklToTF : public OpsTestBase { *output = *GetOutput(0); } + // Runs a Tensorflow graph defined by the root scope, and fetches the result + // of 'fetch' node into the output Tensor. + void RunAndFetch(const tensorflow::Scope& root, const string& fetch, + Tensor* output) { + tensorflow::GraphDef graph; + TF_ASSERT_OK(root.ToGraphDef(&graph)); + + std::unique_ptr session( + tensorflow::NewSession(tensorflow::SessionOptions())); + TF_ASSERT_OK(session->Create(graph)); + + std::vector unfused_tensors; + TF_ASSERT_OK(session->Run({}, {fetch}, {}, &unfused_tensors)); + + *output = unfused_tensors[0]; + } + void ConvertAndCompare(DataType dtype, const Tensor& tensor, const Tensor& mkl_meta_tensor, const Tensor& expected) { @@ -83,23 +100,6 @@ class MklFusedConv2DOpTest : public OpsTestBase { std::function; - // Runs a Tensorflow graph defined by the root scope, and fetches the result - // of 'fetch' node into the output Tensor. - void RunAndFetch(const tensorflow::Scope& root, const string& fetch, - Tensor* output) { - tensorflow::GraphDef graph; - TF_ASSERT_OK(root.ToGraphDef(&graph)); - - std::unique_ptr session( - tensorflow::NewSession(tensorflow::SessionOptions())); - TF_ASSERT_OK(session->Create(graph)); - - std::vector unfused_tensors; - TF_ASSERT_OK(session->Run({}, {fetch}, {}, &unfused_tensors)); - - *output = unfused_tensors[0]; - } - void RunConv2DWithBias(const Tensor& input_data, const Tensor& filter_data, const Tensor& bias_data, Tensor* output, int stride = 1) { @@ -115,7 +115,8 @@ class MklFusedConv2DOpTest : public OpsTestBase { root.WithOpName("with_bias"), conv, ops::Const(root.WithOpName("bias"), Input::Initializer(bias_data))); - RunAndFetch(root, "with_bias", output); + ConvMklToTF conv_comp; + conv_comp.RunAndFetch(root, "with_bias", output); } void RunConv2DWithBiasAndRelu(const Tensor& input_data, @@ -136,7 +137,8 @@ class MklFusedConv2DOpTest : public OpsTestBase { auto with_relu = ops::Relu(root.WithOpName("with_relu"), with_bias); - RunAndFetch(root, "with_relu", output); + ConvMklToTF conv_comp; + conv_comp.RunAndFetch(root, "with_relu", output); } void RunMklFusedConv2DOp(const Tensor& image, const Tensor& filter, @@ -218,18 +220,18 @@ class MklFusedConv2DOpTest : public OpsTestBase { int depth = kDepth, int image_width = kImageWidth, int image_height = kImageHeight, int image_batch_count = kImageBatchCount) { - const BiasAddGraphRunner run_default = - [this](const Tensor& input_data, const Tensor& filter_data, - const Tensor& bias_data, Tensor* out) { - RunConv2DWithBias(input_data, filter_data, bias_data, out); - }; - - const BiasAddGraphRunner run_fused = - [this](const Tensor& input_data, const Tensor& filter_data, - const Tensor& bias_data, Tensor* out) { - RunMklFusedConv2DOp(input_data, filter_data, {bias_data}, {"BiasAdd"}, - out); - }; + const BiasAddGraphRunner run_default = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunConv2DWithBias(input_data, filter_data, bias_data, out); + }; + + const BiasAddGraphRunner run_fused = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunMklFusedConv2DOp(input_data, filter_data, {bias_data}, {"BiasAdd"}, + out); + }; VerifyBiasAddTensorsNear(depth, image_width, image_height, image_batch_count, filter_size, filter_count, @@ -243,18 +245,18 @@ class MklFusedConv2DOpTest : public OpsTestBase { int image_width = kImageWidth, int image_height = kImageHeight, int image_batch_count = kImageBatchCount) { - const BiasAddGraphRunner run_default = - [this](const Tensor& input_data, const Tensor& filter_data, - const Tensor& bias_data, Tensor* out) { - RunConv2DWithBiasAndRelu(input_data, filter_data, bias_data, out); - }; - - const BiasAddGraphRunner run_fused = - [this](const Tensor& input_data, const Tensor& filter_data, - const Tensor& bias_data, Tensor* out) { - RunMklFusedConv2DOp(input_data, filter_data, {bias_data}, - {"BiasAdd", "Relu"}, out); - }; + const BiasAddGraphRunner run_default = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunConv2DWithBiasAndRelu(input_data, filter_data, bias_data, out); + }; + + const BiasAddGraphRunner run_fused = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunMklFusedConv2DOp(input_data, filter_data, {bias_data}, + {"BiasAdd", "Relu"}, out); + }; VerifyBiasAddTensorsNear(depth, image_width, image_height, image_batch_count, filter_size, filter_count, @@ -401,5 +403,262 @@ TEST_F(FusedPadConvOpTest, PaddingConvTestNchw) { Run(DT_FLOAT, image, filter, padding, expected, "NCHW"); } + +// Testing fusion of pad and fusedconv2d + +template +class MklPadWithFusedConv2DOpTest : public OpsTestBase { + protected: + static constexpr int kDepth = 3; + static constexpr int kImageWidth = 30; + static constexpr int kImageHeight = 28; + static constexpr int kImageBatchCount = 8; + + // 0: top pad, 1 bottom pad, 2 left pad, 3 right pad + int padding_list[4]; + + using BiasAddGraphRunner = + std::function; + + void VerifyBiasAddTensorsNear(int depth, int image_width, int image_height, + int image_batch_count, int filter_size, + int filter_count, + const BiasAddGraphRunner& run_default, + const BiasAddGraphRunner& run_fused) { + DataType dtype = DataTypeToEnum::v(); + + Tensor image(dtype, {image_batch_count, image_height, image_width, depth}); + image.flat() = image.flat().setRandom(); + + Tensor filter(dtype, {filter_size, filter_size, depth, filter_count}); + filter.flat() = filter.flat().setRandom(); + + const int bias_size = filter_count; + Tensor bias(dtype, {bias_size}); + bias.flat() = bias.flat().setRandom(); + + Tensor conv_2d; + Tensor fused_conv_2d; + + run_default(image, filter, bias, &conv_2d); + run_fused(image, filter, bias, &fused_conv_2d); + + ASSERT_EQ(conv_2d.dtype(), fused_conv_2d.dtype()); + ASSERT_EQ(conv_2d.shape(), fused_conv_2d.shape()); + + test::ExpectClose(conv_2d, fused_conv_2d); + } + + // Verifies that computing Pad+Conv2D+BiasAdd in a graph is identical to + // FusedConv2D. + void VerifyConv2DWithBiasAndPad(int filter_size, int filter_count, + int depth = kDepth, + int image_width = kImageWidth, + int image_height = kImageHeight, + int image_batch_count = kImageBatchCount) { + const BiasAddGraphRunner run_default = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunMklFusedConv2DWithPadAndBias(input_data, filter_data, bias_data, out); + }; + + const BiasAddGraphRunner run_fused = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunMklFusedConv2DWithPadOp(input_data, filter_data, {bias_data}, + {"BiasAdd"}, out); + }; + + VerifyBiasAddTensorsNear(depth, image_width, image_height, + image_batch_count, filter_size, filter_count, + run_default, run_fused); + } + + // Verifies that computing Pad+Conv2D+BiasAdd+Relu in a graph is identical to + // FusedConv2D. + void VerifyConv2DWithBiasReluAndPad( + int filter_size, int filter_count, int depth = kDepth, + int image_width = kImageWidth, int image_height = kImageHeight, + int image_batch_count = kImageBatchCount) { + const BiasAddGraphRunner run_default = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunMklFusedConv2DWithPadAndBiasRelu(input_data, filter_data, bias_data, + out); + }; + + const BiasAddGraphRunner run_fused = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunMklFusedConv2DWithPadOp(input_data, filter_data, {bias_data}, + {"BiasAdd", "Relu"}, out); + }; + + VerifyBiasAddTensorsNear(depth, image_width, image_height, + image_batch_count, filter_size, filter_count, + run_default, run_fused); + } + + void RunMklFusedConv2DWithPadAndBias(const Tensor& input_data, + const Tensor& filter_data, + const Tensor& bias_data, Tensor* output, + int stride = 1) { + auto root = tensorflow::Scope::NewRootScope(); + + // As FusedConv2D only support NHWC format, so here use NHWC format. + auto padding = ops::Const(root.WithOpName("padding"), + {0, 0, padding_list[0], padding_list[1], + padding_list[2], padding_list[3], 0, 0}, + {4, 2}); + auto pad = ops::Pad( + root.WithOpName("pad"), + ops::Const(root.WithOpName("input"), Input::Initializer(input_data)), + padding); + + auto conv = ops::Conv2D( + root.WithOpName("conv"), pad, + ops::Const(root.WithOpName("filter"), Input::Initializer(filter_data)), + {1, stride, stride, 1}, "VALID"); + + auto with_bias = ops::BiasAdd( + root.WithOpName("with_bias"), conv, + ops::Const(root.WithOpName("bias"), Input::Initializer(bias_data))); + + ConvMklToTF conv_comp; + conv_comp.RunAndFetch(root, "with_bias", output); + } + + void RunMklFusedConv2DWithPadAndBiasRelu(const Tensor& input_data, + const Tensor& filter_data, + const Tensor& bias_data, + Tensor* output, int stride = 1) { + auto root = tensorflow::Scope::NewRootScope(); + + // As FusedConv2D only support NHWC format, so here use NHWC format. + auto padding = ops::Const(root.WithOpName("padding"), + {0, 0, padding_list[0], padding_list[1], + padding_list[2], padding_list[3], 0, 0}, + {4, 2}); + auto pad = ops::Pad( + root.WithOpName("pad"), + ops::Const(root.WithOpName("input"), Input::Initializer(input_data)), + padding); + + auto conv = ops::Conv2D( + root.WithOpName("conv"), pad, + ops::Const(root.WithOpName("filter"), Input::Initializer(filter_data)), + {1, stride, stride, 1}, "VALID"); + + auto with_bias = ops::BiasAdd( + root.WithOpName("with_bias"), conv, + ops::Const(root.WithOpName("bias"), Input::Initializer(bias_data))); + + auto with_relu = ops::Relu(root.WithOpName("with_relu"), with_bias); + + ConvMklToTF conv_comp; + conv_comp.RunAndFetch(root, "with_relu", output); + } + + void RunMklFusedConv2DWithPadOp(const Tensor& image, const Tensor& filter, + const std::vector& args, + const std::vector& fused_ops, + Tensor* output, int stride = 1) { + DataType dtype = DataTypeToEnum::v(); + const int num_args = static_cast(args.size()); + Tensor padding(DT_INT32, {4, 2}); + test::FillValues(&padding, {0, 0, padding_list[0], padding_list[1], + padding_list[2], padding_list[3], 0, 0}); + + TF_EXPECT_OK(NodeDefBuilder("pad_fused_conv_op", "_MklPadWithFusedConv2D") + .Input(FakeInput(dtype)) + .Input(FakeInput(dtype)) + .Attr("num_args", num_args) + .Input(FakeInput(num_args, dtype)) + .Input(FakeInput(DT_INT32)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(num_args, DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Attr("T", dtype) + .Attr("strides", {1, stride, stride, 1}) + .Attr("padding", "VALID") + .Attr("fused_ops", fused_ops) + .Attr("_kernel", "MklOp") + .Finalize(node_def())); + + TF_EXPECT_OK(InitOp()); + + AddInputFromArray(image.shape(), image.flat()); + AddInputFromArray(filter.shape(), filter.flat()); + for (const Tensor& arg : args) + AddInputFromArray(arg.shape(), arg.flat()); + AddInputFromArray(padding.shape(), padding.flat()); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + for (const Tensor& arg : args) + AddInputFromArray(dummy_shape, dummy_tensor); + TF_ASSERT_OK(RunOpKernel()); + + // Compare output to expected results + const Tensor& output_tensor = *GetOutput(0); + // Index 2 will need to be changed if the number of outputs produced + // by MklConv2D change. + const Tensor& output_meta_tensor = *GetOutput(2); + ConvMklToTF conv_comp; + conv_comp.PerformConversion(dtype, output_tensor, output_meta_tensor, + output); + } + + public: + void SetPaddingList(int top, int bottom, int left, int right) { + padding_list[0] = top; + padding_list[1] = bottom; + padding_list[2] = left; + padding_list[3] = right; + } +}; + +TYPED_TEST_CASE_P(MklPadWithFusedConv2DOpTest); + +TYPED_TEST_P(MklPadWithFusedConv2DOpTest, WithBiasAndRoundPad) { + const int filter_size = 1; + const int filter_count = 12; + this->SetPaddingList(2, 2, 1, 1); + this->VerifyConv2DWithBiasAndPad(filter_size, filter_count); +} + +TYPED_TEST_P(MklPadWithFusedConv2DOpTest, WithBiasAndPartialPad) { + const int filter_size = 1; + const int filter_count = 12; + this->SetPaddingList(4, 0, 2, 0); + this->VerifyConv2DWithBiasAndPad(filter_size, filter_count); +} + +TYPED_TEST_P(MklPadWithFusedConv2DOpTest, WithBiasReluAndRoundPad) { + const int filter_size = 1; + const int filter_count = 12; + this->SetPaddingList(2, 2, 1, 1); + this->VerifyConv2DWithBiasReluAndPad(filter_size, filter_count); +} + +TYPED_TEST_P(MklPadWithFusedConv2DOpTest, WithBiasReluAndPartialPad) { + const int filter_size = 1; + const int filter_count = 12; + this->SetPaddingList(4, 0, 2, 0); + this->VerifyConv2DWithBiasReluAndPad(filter_size, filter_count); +} + +REGISTER_TYPED_TEST_CASE_P(MklPadWithFusedConv2DOpTest, // + WithBiasAndRoundPad, // + WithBiasAndPartialPad, // + WithBiasReluAndRoundPad, // + WithBiasReluAndPartialPad); + +using MklPadWithFusedConv2DDataTypes = ::testing::Types; +INSTANTIATE_TYPED_TEST_CASE_P(Test, MklPadWithFusedConv2DOpTest, + MklPadWithFusedConv2DDataTypes); + } // namespace tensorflow #endif // INTEL_MKL diff --git a/tensorflow/core/ops/mkl_nn_ops.cc b/tensorflow/core/ops/mkl_nn_ops.cc index 658afd9901..6386bbff40 100644 --- a/tensorflow/core/ops/mkl_nn_ops.cc +++ b/tensorflow/core/ops/mkl_nn_ops.cc @@ -59,6 +59,63 @@ REGISTER_OP("_MklFusedConv2D") is expected to create these operators. )doc"); +REGISTER_OP("__MklDummyPadWithFusedConv2D") + .Input("input: T") + .Input("filter: T") + .Input("args: num_args * T") + .Input("paddings: Tpaddings") + .Output("output: T") + .Output("filter_output: T") + .Output("mkl_output: uint8") + .Output("mkl_filter_output: uint8") + .Attr("T: {float}") + .Attr("num_args: int >= 0") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr(GetConvnetDataFormatAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("fused_ops: list(string) = []") + .Attr("Tpaddings: {int32, int64} = DT_INT32") + // Attributes for the FusedBatchNorm ------------------------------------ // + .Attr("epsilon: float = 0.0001") + // ---------------------------------------------------------------------- // + .SetShapeFn(shape_inference::Conv2DShape) + .Doc(R"doc( +*NOTE*: Do not invoke this operator directly in Python. MKL DNN graph transformer + is expected to create these operators. +)doc"); + +REGISTER_OP("_MklPadWithFusedConv2D") + .Input("input: T") + .Input("filter: T") + .Input("args: num_args * T") + .Input("paddings: Tpaddings") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_args: num_args * uint8") + .Input("mkl_paddings: uint8") + .Output("output: T") + .Output("filter_output: T") + .Output("mkl_output: uint8") + .Output("mkl_filter_output: uint8") + .Attr("T: {float}") + .Attr("num_args: int >= 0") + .Attr("strides: list(int)") + .Attr("is_filter_const: bool = false") + .Attr(GetPaddingAttrString()) + .Attr(GetConvnetDataFormatAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("fused_ops: list(string) = []") + .Attr("Tpaddings: {int32, int64} = DT_INT32") + // Attributes for the FusedBatchNorm ------------------------------------ // + .Attr("epsilon: float = 0.0001") + // ---------------------------------------------------------------------- // + .SetShapeFn(shape_inference::Conv2DShape) + .Doc(R"doc( +*NOTE*: Do not invoke this operator directly in Python. MKL DNN graph transformer + is expected to create these operators. +)doc"); + REGISTER_OP("_MklQuantizedMaxPool") .Input("input: T") .Input("min_input: float") -- GitLab From 6ecb77b9bd800e2beda7a7fcefaf0ac3a6363e1f Mon Sep 17 00:00:00 2001 From: Samantha Andow Date: Wed, 26 Dec 2018 08:58:44 -0800 Subject: [PATCH 0080/2345] Fix iterable bug --- tensorflow/java/src/gen/cc/op_specs.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/java/src/gen/cc/op_specs.cc b/tensorflow/java/src/gen/cc/op_specs.cc index 4f5a491d25..4024efedef 100644 --- a/tensorflow/java/src/gen/cc/op_specs.cc +++ b/tensorflow/java/src/gen/cc/op_specs.cc @@ -91,11 +91,6 @@ class TypeResolver { Type TypeResolver::TypeOf(const OpDef_ArgDef& arg_def, bool* iterable_out) { *iterable_out = false; - if (!arg_def.number_attr().empty()) { - // when number_attr is set, argument has to be a list of tensors - *iterable_out = true; - visited_attrs_.insert(std::make_pair(arg_def.number_attr(), Type::Int())); - } Type type = Type::Wildcard(); if (arg_def.type() != DataType::DT_INVALID) { type = Type::ForDataType(arg_def.type()); @@ -122,6 +117,11 @@ Type TypeResolver::TypeOf(const OpDef_ArgDef& arg_def, bool* iterable_out) { LOG(FATAL) << "Cannot resolve data type of argument \"" << arg_def.name() << "\" in operation \"" << op_def_.name() << "\""; } + if (!arg_def.number_attr().empty()) { + // when number_attr is set, argument has to be a list of tensors + *iterable_out = true; + visited_attrs_.insert(std::make_pair(arg_def.number_attr(), Type::Int())); + } return type; } -- GitLab From 1ae58b7732b5b5c390bdec5499497a87222d6fcd Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 26 Dec 2018 20:07:56 +0000 Subject: [PATCH 0081/2345] Fix tf.version.GIT_VERSION to return str (instead of bytes) This fix fixes the issue raised in 24578 where `tf.version.GIT_VERSION` returns byte instead of string: ``` Python 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import tensorflow as tf >>> print(tf.version.GIT_VERSION) b'v1.12.0-5133-gc343196842' >>> print(tf.version.COMPILER_VERSION) 4.8.5 >>> print(tf.version.VERSION) 1.13.0-dev20181226 ``` This fix fixes 24578. Signed-off-by: Yong Tang --- tensorflow/tools/git/gen_git_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/git/gen_git_source.py b/tensorflow/tools/git/gen_git_source.py index 645d817d9f..0d0a641074 100755 --- a/tensorflow/tools/git/gen_git_source.py +++ b/tensorflow/tools/git/gen_git_source.py @@ -216,7 +216,7 @@ const int tf_monolithic_build() { return 0; #endif } -""" % git_version +""" % git_version.decode('utf-8') open(filename, "w").write(contents) -- GitLab From d0913801f880d19cdd62b0374f7a1ede2cb60416 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 26 Dec 2018 20:17:35 +0000 Subject: [PATCH 0082/2345] Update git_version to be the same type of byte before final conversion "git_version_is_invalid" should be b"git_version_is_invalid" so that `git_version` is always byte (conversion will happen later) Signed-off-by: Yong Tang --- tensorflow/tools/git/gen_git_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/git/gen_git_source.py b/tensorflow/tools/git/gen_git_source.py index 0d0a641074..f97cc8d1d6 100755 --- a/tensorflow/tools/git/gen_git_source.py +++ b/tensorflow/tools/git/gen_git_source.py @@ -189,7 +189,7 @@ def write_version_info(filename, git_version): git_version: the result of a git describe. """ if b"\"" in git_version or b"\\" in git_version: - git_version = "git_version_is_invalid" # do not cause build to fail! + git_version = b"git_version_is_invalid" # do not cause build to fail! contents = """/* Generated by gen_git_source.py */ #include const char* tf_git_version() {return "%s";} -- GitLab From a044d650c4d7b731a435cfb216d64629e20b6202 Mon Sep 17 00:00:00 2001 From: Bhavani Subramanian Date: Wed, 26 Dec 2018 14:00:58 -0800 Subject: [PATCH 0083/2345] Enabled filter caching for convolution when filter is a constant. --- tensorflow/core/graph/mkl_layout_pass.cc | 131 ++++-- tensorflow/core/graph/mkl_layout_pass_test.cc | 333 +++++++++++++ tensorflow/core/kernels/mkl_conv_ops.cc | 442 +++++++++++------- tensorflow/core/kernels/mkl_fused_ops_test.cc | 113 ++++- tensorflow/core/ops/mkl_nn_ops.cc | 12 + tensorflow/core/ops/nn_ops.cc | 19 +- 6 files changed, 831 insertions(+), 219 deletions(-) diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc index 9495132f4a..035b3a46ba 100644 --- a/tensorflow/core/graph/mkl_layout_pass.cc +++ b/tensorflow/core/graph/mkl_layout_pass.cc @@ -357,9 +357,9 @@ class MklLayoutRewritePass : public GraphOptimizationPass { CopyAttrsConcatV2, AlwaysRewrite}); rinfo_.push_back({csinfo_.conv2d, mkl_op_registry::GetMklOpName(csinfo_.conv2d), - CopyAttrsConv, AlwaysRewrite}); + CopyAttrsConvCheckConstFilter, AlwaysRewrite}); rinfo_.push_back({csinfo_.conv2d_with_bias, csinfo_.mkl_conv2d_with_bias, - CopyAttrsConv, AlwaysRewrite}); + CopyAttrsConvCheckConstFilter, AlwaysRewrite}); rinfo_.push_back({csinfo_.conv2d_grad_filter, mkl_op_registry::GetMklOpName(csinfo_.conv2d_grad_filter), CopyAttrsConv, AlwaysRewrite}); @@ -371,7 +371,7 @@ class MklLayoutRewritePass : public GraphOptimizationPass { CopyAttrsConv, AlwaysRewrite}); rinfo_.push_back({csinfo_.conv3d, mkl_op_registry::GetMklOpName(csinfo_.conv3d), - CopyAttrsConv, AlwaysRewrite}); + CopyAttrsConvCheckConstFilter, AlwaysRewrite}); rinfo_.push_back({csinfo_.conv3d_grad_filter, mkl_op_registry::GetMklOpName(csinfo_.conv3d_grad_filter), CopyAttrsConv, AlwaysRewrite}); @@ -1391,10 +1391,13 @@ class MklLayoutRewritePass : public GraphOptimizationPass { bool change_format = false); static void CopyAttrsConcatV2(const Node* orig_node, NodeBuilder* nb, bool change_format = false); - static void CopyAttrsConv2DDepthwise(const Node* orig_node, NodeBuilder* nb, - bool change_format = false); + static void CopyAttrsConvCheckConstFilter(const Node* orig_node, + NodeBuilder* nb, + bool change_format = false); static void CopyAttrsConv(const Node* orig_node, NodeBuilder* nb, bool change_format = false); + static void CopyAttrsConv2DDepthwise(const Node* orig_node, NodeBuilder* nb, + bool change_format = false); static void CopyAttrsDataType(const Node* orig_node, NodeBuilder* nb, bool change_format = false); static void CopyAttrsFusedBatchNorm(const Node* orig_node, NodeBuilder* nb, @@ -1426,6 +1429,10 @@ class MklLayoutRewritePass : public GraphOptimizationPass { bool change_format = false); static void CopyAttrsSplit(const Node* orig_node, NodeBuilder* nb, bool change_format = false); + static void CopyFormatAttrsConv(const Node* orig_node, NodeBuilder* nb, + const std::vector& strides, + const std::vector& dilations, + bool change_format = false); // Generate a graph node in graph 'g' representing a dummy Mkl tensor node, // using node for original node 'orig_node' and return it in '*out'. @@ -1929,23 +1936,10 @@ void MklLayoutRewritePass::AddWorkSpaceEdgeIfNeeded( // Op-specific functions to copy attributes from old node to new node ////////////////////////////////////////////////////////////////////////// -void MklLayoutRewritePass::CopyAttrsConv(const Node* orig_node, NodeBuilder* nb, - bool change_format) { - DataType T; +void MklLayoutRewritePass::CopyFormatAttrsConv( + const Node* orig_node, NodeBuilder* nb, const std::vector& strides, + const std::vector& dilations, bool change_format) { string data_format; - string padding; - std::vector strides; - std::vector dilations; - - // Get all attributes from old node. - TF_CHECK_OK(GetNodeAttr(orig_node->def(), "T", &T)); - TF_CHECK_OK(GetNodeAttr(orig_node->def(), "strides", &strides)); - TF_CHECK_OK(GetNodeAttr(orig_node->def(), "dilations", &dilations)); - TF_CHECK_OK(GetNodeAttr(orig_node->def(), "padding", &padding)); - - // Add attributes to new node. - nb->Attr("T", T); - nb->Attr("padding", padding); if (!change_format) { nb->Attr("strides", strides); @@ -1957,9 +1951,8 @@ void MklLayoutRewritePass::CopyAttrsConv(const Node* orig_node, NodeBuilder* nb, std::vector new_strides; std::vector new_dilations; if (strides.size() == 5) { - // "strides" and "dilations" also need to be changed according to - // "data_format", - // in this case, is "NDHWC" to "NCDHW". + // `strides` and `dilations` also need to be changed according to + // `data_format`. In this case, from `NDHWC` to `NCDHW`. new_strides = {strides[NDHWC::dim::N], strides[NDHWC::dim::C], strides[NDHWC::dim::D], strides[NDHWC::dim::H], strides[NDHWC::dim::W]}; @@ -1968,9 +1961,8 @@ void MklLayoutRewritePass::CopyAttrsConv(const Node* orig_node, NodeBuilder* nb, dilations[NDHWC::dim::D], dilations[NDHWC::dim::H], dilations[NDHWC::dim::W]}; } else { - // "strides" and "dilations" also need to be changed according to - // "data_format", - // in this case, is "NHWC" to "NCHW". + // `strides` and `dilations` also need to be changed according to + // `data_format`. In this case, from `NHWC` to `NCHW`. new_strides = {strides[NHWC::dim::N], strides[NHWC::dim::C], strides[NHWC::dim::H], strides[NHWC::dim::W]}; @@ -1983,6 +1975,56 @@ void MklLayoutRewritePass::CopyAttrsConv(const Node* orig_node, NodeBuilder* nb, } } +void MklLayoutRewritePass::CopyAttrsConvCheckConstFilter(const Node* orig_node, + NodeBuilder* nb, + bool change_format) { + DataType T; + string padding; + std::vector strides; + std::vector dilations; + bool is_filter_const; + + // Get all attributes from old node. + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "T", &T)); + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "strides", &strides)); + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "dilations", &dilations)); + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "padding", &padding)); + + // Check if filter is a constant. + Node* filter_node = nullptr; + orig_node->input_node(1, &filter_node); + is_filter_const = filter_node->IsConstant(); + + // Add attributes to new node. + nb->Attr("T", T); + nb->Attr("padding", padding); + nb->Attr("is_filter_const", is_filter_const); + + // Add attributes related to `data_format`. + CopyFormatAttrsConv(orig_node, nb, strides, dilations, change_format); +} + +void MklLayoutRewritePass::CopyAttrsConv(const Node* orig_node, NodeBuilder* nb, + bool change_format) { + DataType T; + string padding; + std::vector strides; + std::vector dilations; + + // Get all attributes from old node. + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "T", &T)); + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "strides", &strides)); + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "dilations", &dilations)); + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "padding", &padding)); + + // Add attributes to new node. + nb->Attr("T", T); + nb->Attr("padding", padding); + + // Add attributes related to `data_format`. + CopyFormatAttrsConv(orig_node, nb, strides, dilations, change_format); +} + // Used in rinfo when replacing __MklDummyPadWithConv2D by _MklPadWithConv2D void MklLayoutRewritePass::CopyAttrsPadWithConv2D(const Node* orig_node, NodeBuilder* nb, @@ -1993,6 +2035,7 @@ void MklLayoutRewritePass::CopyAttrsPadWithConv2D(const Node* orig_node, string padding; std::vector strides; std::vector dilations; + bool is_filter_const; bool use_cudnn_on_gpu; // Get all attributes from old node. @@ -2005,8 +2048,14 @@ void MklLayoutRewritePass::CopyAttrsPadWithConv2D(const Node* orig_node, GetNodeAttr(orig_node->def(), "use_cudnn_on_gpu", &use_cudnn_on_gpu)); TF_CHECK_OK(GetNodeAttr(orig_node->def(), "Tpaddings", &Tpaddings)); + // Check if filter is a constant. + Node* filter_node = nullptr; + orig_node->input_node(1, &filter_node); + is_filter_const = filter_node->IsConstant(); + // Add attributes to new node. nb->Attr("T", T); + nb->Attr("is_filter_const", is_filter_const); nb->Attr("strides", strides); nb->Attr("dilations", dilations); nb->Attr("padding", padding); @@ -2057,6 +2106,7 @@ void MklLayoutRewritePass::CopyAttrsConv2DDepthwise(const Node* orig_node, string padding; std::vector strides; std::vector dilations; + bool is_filter_const; // Get all attributes from old node. TF_CHECK_OK(GetNodeAttr(orig_node->def(), "T", &T)); @@ -2065,12 +2115,18 @@ void MklLayoutRewritePass::CopyAttrsConv2DDepthwise(const Node* orig_node, TF_CHECK_OK(GetNodeAttr(orig_node->def(), "padding", &padding)); TF_CHECK_OK(GetNodeAttr(orig_node->def(), "data_format", &data_format)); + // Check if filter is a constant. + Node* filter_node = nullptr; + orig_node->input_node(1, &filter_node); + is_filter_const = filter_node->IsConstant(); + // Add attributes to new node. nb->Attr("T", T); nb->Attr("strides", strides); nb->Attr("dilations", dilations); nb->Attr("padding", padding); nb->Attr("data_format", data_format); + nb->Attr("is_filter_const", is_filter_const); } void MklLayoutRewritePass::CopyAttrsAddN(const Node* orig_node, NodeBuilder* nb, @@ -2206,6 +2262,7 @@ void MklLayoutRewritePass::CopyAttrsQuantizedConv2D(const Node* orig_node, string padding; string data_format("NHWC"); std::vector strides, dilations; + bool is_filter_const; // Get all attributes from old node. TF_CHECK_OK(GetNodeAttr(orig_node->def(), "Tinput", &Tinput)); @@ -2215,6 +2272,11 @@ void MklLayoutRewritePass::CopyAttrsQuantizedConv2D(const Node* orig_node, TF_CHECK_OK(GetNodeAttr(orig_node->def(), "strides", &strides)); TF_CHECK_OK(GetNodeAttr(orig_node->def(), "dilations", &dilations)); + // Check if filter is a constant. + Node* filter_node = nullptr; + orig_node->input_node(1, &filter_node); + is_filter_const = filter_node->IsConstant(); + // Add attributes to new node. nb->Attr("Tinput", Tinput); nb->Attr("Tfilter", Tfilter); @@ -2224,7 +2286,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); - // Requantization attr Tbias + nb->Attr("is_filter_const", is_filter_const); + + // Requantization attr Tbias. DataType Tbias; Status bias_status = GetNodeAttr(orig_node->def(), "Tbias", &Tbias); if (bias_status.ToString() == "OK") nb->Attr("Tbias", Tbias); @@ -2253,6 +2317,7 @@ void MklLayoutRewritePass::CopyAttrsReshape(const Node* orig_node, // Get all attributes from old node. TF_CHECK_OK(GetNodeAttr(orig_node->def(), "T", &T)); TF_CHECK_OK(GetNodeAttr(orig_node->def(), "Tshape", &Tshape)); + // Add attributes to new node. nb->Attr("T", T); nb->Attr("Tshape", Tshape); @@ -2266,6 +2331,7 @@ void MklLayoutRewritePass::CopyAttrsSlice(const Node* orig_node, // Get all attributes from old node. TF_CHECK_OK(GetNodeAttr(orig_node->def(), "T", &T)); TF_CHECK_OK(GetNodeAttr(orig_node->def(), "Index", &Index)); + // Add attributes to new node. nb->Attr("T", T); nb->Attr("Index", Index); @@ -2353,6 +2419,7 @@ void MklLayoutRewritePass::CopyAttrsFusedConv2D(const Node* orig_node, std::vector strides; std::vector dilations; std::vector fused_ops; + bool is_filter_const; // Get all attributes from old node. TF_CHECK_OK(GetNodeAttr(orig_node->def(), "T", &T)); @@ -2364,6 +2431,11 @@ void MklLayoutRewritePass::CopyAttrsFusedConv2D(const Node* orig_node, TF_CHECK_OK(GetNodeAttr(orig_node->def(), "fused_ops", &fused_ops)); TF_CHECK_OK(GetNodeAttr(orig_node->def(), "epsilon", &epsilon)); + // Check if filter is a constant. + Node* filter_node = nullptr; + orig_node->input_node(1, &filter_node); + is_filter_const = filter_node->IsConstant(); + // Add attributes to new node. nb->Attr("T", T); nb->Attr("num_args", num_args); @@ -2373,6 +2445,7 @@ void MklLayoutRewritePass::CopyAttrsFusedConv2D(const Node* orig_node, nb->Attr("dilations", dilations); nb->Attr("fused_ops", fused_ops); nb->Attr("epsilon", epsilon); + nb->Attr("is_filter_const", is_filter_const); } ////////////////////////////////////////////////////////////////////////// @@ -2505,7 +2578,7 @@ Status MklLayoutRewritePass::MergeConv2DWithBiasAdd(std::unique_ptr* g, nb.Input(succ_in[1].first, succ_in[1].second); // In2 of BiasAdd // Copy attributes from Conv2D to Conv2DWithBias. - CopyAttrsConv(const_cast(pred), &nb); + CopyAttrsConvCheckConstFilter(const_cast(pred), &nb); // Copy the device assigned to old node to new node. nb.Device(succ->def().device()); diff --git a/tensorflow/core/graph/mkl_layout_pass_test.cc b/tensorflow/core/graph/mkl_layout_pass_test.cc index 6e73ed1b9f..02b8e23927 100644 --- a/tensorflow/core/graph/mkl_layout_pass_test.cc +++ b/tensorflow/core/graph/mkl_layout_pass_test.cc @@ -123,6 +123,29 @@ class MklLayoutPassTest : public ::testing::Test { return result; } + // Returns the attribute value only from the first node + template + T DoMklLayoutOptimizationPassGetAttrVal(const string& attr, + const string& node_name) { + string before = CanonicalGraphString(&graph_); + LOG(ERROR) << "Before MKL layout rewrite pass: " << before; + + std::unique_ptr* ug = new std::unique_ptr(&graph_); + RunMklLayoutRewritePass(ug); + + string result = CanonicalGraphString(&graph_); + LOG(ERROR) << "After MKL layout rewrite pass: " << result; + + T attr_val{}; + for (const Node* n : graph_.nodes()) { + if (IncludeNode(n) && n->type_string() == node_name) { + TF_CHECK_OK(GetNodeAttr(n->def(), attr, &attr_val)); + break; + } + } + return attr_val; + } + const string& OriginalGraph() const { return original_; } Graph graph_; @@ -2561,6 +2584,316 @@ TEST_F(MklLayoutPassTest, PostRewriteFixUpPass) { ///////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////// +// Unit tests related to filter caching. +// +// These tests check if the attribute `is_filter_const` is set to true +// when filter is a constant and false otherwise for various operators +// such as Conv2D, Conv2DWithBias, Conv3D etc. + +// Conv2D op where filter is a constant. +TEST_F(MklLayoutPassTest, Conv2D_FilterCaching_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Const' " // Filter + " attr { key: 'dtype' value { type: DT_FLOAT } }" + " attr { key: 'value' value { " + " tensor { dtype: DT_FLOAT tensor_shape { dim { size: 1 } } " + " int_val: 0 } } } }" + "node { name: 'C' op: 'Conv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['B', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklConv2D"), + true); +} + +// Conv2D op where filter is NOT a constant. +TEST_F(MklLayoutPassTest, Conv2D_FilterCaching_Negative) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Input'}" // Filter + "node { name: 'C' op: 'Conv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['B', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklConv2D"), + false); +} + +// Conv2D + BiasAdd fusion where filter is a constant. +TEST_F(MklLayoutPassTest, Conv2DWithBias_FilterCaching_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Const'" // Filter + " attr { key: 'dtype' value { type: DT_FLOAT } }" + " attr { key: 'value' value { " + " tensor { dtype: DT_FLOAT tensor_shape { dim { size: 1 } } " + " int_val: 0 } } } }" + "node { name: 'C' op: 'Conv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" + "node { name: 'E' op: 'BiasAdd'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " input: ['C', 'D'] }" + "node { name: 'Y' op: 'Input'}" + "node { name: 'Z' op: 'Zeta'" + " attr {key: 'T' value { type: DT_FLOAT } }" + " input: ['E', 'Y']}"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklConv2DWithBias"), + true); +} + +// Conv2D + BiasAdd fusion where filter is NOT a constant. +TEST_F(MklLayoutPassTest, Conv2DWithBias_FilterCaching_Negative) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Input'}" // Filter + "node { name: 'C' op: 'Conv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" + "node { name: 'E' op: 'BiasAdd'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " input: ['C', 'D'] }" + "node { name: 'Y' op: 'Input'}" + "node { name: 'Z' op: 'Zeta'" + " attr {key: 'T' value { type: DT_FLOAT } }" + " input: ['E', 'Y']}"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklConv2DWithBias"), + false); +} + +// Conv3D op where filter is a constant. +TEST_F(MklLayoutPassTest, Conv3D_FilterCaching_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Const' " // Filter + " attr { key: 'dtype' value { type: DT_FLOAT } }" + " attr { key: 'value' value { " + " tensor { dtype: DT_FLOAT tensor_shape { dim { size: 1 } } " + " int_val: 0 } } } }" + "node { name: 'C' op: 'Conv3D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCDHW' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1, " + "i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1, " + "i:1} } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['B', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklConv3D"), + true); +} + +// Conv3D op where filter is NOT a constant. +TEST_F(MklLayoutPassTest, Conv3D_FilterCaching_Negative) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Input'}" // Filter + "node { name: 'C' op: 'Conv3D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCDHW' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1, " + "i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1, " + "i:1} } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['B', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklConv3D"), + false); +} + +// Pad + Conv2D fusion where filter is a constant. +TEST_F(MklLayoutPassTest, PadWithConv2D_FilterCaching_Positive) { + DCHECK_EQ(kTensorOrdering, MklTfTensorOrdering::TENSORS_CONTIGUOUS); + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Const'" // Filter + " attr { key: 'dtype' value { type: DT_FLOAT } }" + " attr { key: 'value' value { " + " tensor { dtype: DT_FLOAT tensor_shape { dim { size: 1 } } " + " int_val: 0 } } } }" + "node { name: 'E' op: 'Conv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NHWC' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'VALID' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['C', 'D'] }" + "node { name: 'Y' op: 'Input'}" + "node { name: 'Z' op: 'Zeta'" + " attr {key: 'T' value { type: DT_FLOAT } }" + " input: ['E', 'Y']}"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklPadWithConv2D"), + true); +} + +// Pad + Conv2D fusion where filter is NOT a constant. +TEST_F(MklLayoutPassTest, PadWithConv2D_FilterCaching_Negative) { + DCHECK_EQ(kTensorOrdering, MklTfTensorOrdering::TENSORS_CONTIGUOUS); + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Pad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'Tpaddings' value { type: DT_INT32 } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Input'}" // Filter + "node { name: 'E' op: 'Conv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NHWC' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'VALID' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['C', 'D'] }" + "node { name: 'Y' op: 'Input'}" + "node { name: 'Z' op: 'Zeta'" + " attr {key: 'T' value { type: DT_FLOAT } }" + " input: ['E', 'Y']}"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklPadWithConv2D"), + false); +} + +// _FusedConv2D + BiasAdd fusion where filter is a constant. +TEST_F(MklLayoutPassTest, FusedConv2DWithBias_FilterCaching_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Const'" // Filter + " attr { key: 'dtype' value { type: DT_FLOAT } }" + " attr { key: 'value' value { " + " tensor { dtype: DT_FLOAT tensor_shape { dim { size: 1 } } " + " int_val: 0 } } } }" + "node { name: 'C' op: 'Input'}" + "node { name: 'D' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['A', 'B', 'C']}" + "node { name: 'E' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['D', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklFusedConv2D"), + true); +} + +// _FusedConv2D + BiasAdd fusion where filter is NOT a constant. +TEST_F(MklLayoutPassTest, FusedConv2DWithBias_FilterCaching_Negative) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Input'}" // Filter + "node { name: 'C' op: 'Input'}" + "node { name: 'D' op: '_FusedConv2D'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'num_args' value { i: 1 } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" + " attr { key: 'epsilon' value { f: 0.001 }}" + " input: ['A', 'B', 'C']}" + "node { name: 'E' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['D', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal("is_filter_const", + "_MklFusedConv2D"), + false); +} + +// Depthwise Conv2D op where filter is a constant. +TEST_F(MklLayoutPassTest, DepthwiseConv2dNative_FilterCaching_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Const'" // Filter + " attr { key: 'dtype' value { type: DT_FLOAT } }" + " attr { key: 'value' value { " + " tensor { dtype: DT_FLOAT tensor_shape { dim { size: 1 } } " + " int_val: 0 } } } }" + "node { name: 'C' op: 'DepthwiseConv2dNative'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['B', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal( + "is_filter_const", "_MklDepthwiseConv2dNative"), + true); +} + +// Depthwise Conv2D op where filter is NOT a constant. +TEST_F(MklLayoutPassTest, DepthwiseConv2dNative_FilterCaching_Negative) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Input'}" // Filter + "node { name: 'C' op: 'DepthwiseConv2dNative'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['A', 'B']}" + "node { name: 'D' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['B', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPassGetAttrVal( + "is_filter_const", "_MklDepthwiseConv2dNative"), + false); +} +///////////////////////////////////////////////////////////////////// + static void BM_MklLayoutRewritePass(int iters, int op_nodes) { testing::StopTiming(); string s; diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc index 8c585fb48c..ae3ba50133 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_ops.cc @@ -91,6 +91,9 @@ struct MklConvFwdParams { padding_left(padding_left), padding_right(padding_right) {} }; + +typedef mkldnn::convolution_forward::primitive_desc ConvFwdPd; + // With quantization, input, filter, and output can have different types // so we use differnt template parameter for each type template (const_cast(dst_data))); context_.fwd_stream->submit(context_.fwd_primitives); - // after exec, set data handle back + // After exec, set data handle back context_.src_mem->set_data_handle(DummyData); context_.filter_mem->set_data_handle(DummyData); context_.bias_mem->set_data_handle(DummyData); @@ -148,7 +151,7 @@ class MklConvFwdPrimitive : public MklPrimitive { static_cast(const_cast(dst_data))); context_.fwd_stream->submit(context_.fwd_primitives); - // after execution, set data handle back + // After execution, set data handle back context_.src_mem->set_data_handle(DummyData); context_.filter_mem->set_data_handle(DummyData); context_.dst_mem->set_data_handle(DummyData); @@ -158,15 +161,14 @@ class MklConvFwdPrimitive : public MklPrimitive { memory::format GetFilterMemoryFormat() const { return context_.filter_fmt; } - std::shared_ptr - GetPrimitiveDesc() const { + std::shared_ptr GetPrimitiveDesc() const { return context_.fwd_pd; } private: // Primitive reuse context for Conv2D Fwd op struct ConvFwdContext { - // expected memory format for this primitive instance + // Expected memory format for this primitive instance memory::format src_fmt; memory::format filter_fmt; @@ -176,17 +178,17 @@ class MklConvFwdPrimitive : public MklPrimitive { std::shared_ptr bias_mem; std::shared_ptr dst_mem; - // desc & prmitive desc + // Desc & prmitive desc std::shared_ptr fwd_desc; - // memory desc + // Memory desc std::shared_ptr src_md; std::shared_ptr filter_md; std::shared_ptr bias_md; std::shared_ptr dst_md; - // convolution primitive - std::shared_ptr fwd_pd; + // Convolution primitive + std::shared_ptr fwd_pd; std::shared_ptr conv_fwd; std::shared_ptr fwd_stream; @@ -209,7 +211,7 @@ class MklConvFwdPrimitive : public MklPrimitive { }; void Setup(const MklConvFwdParams& convFwdDims) { - // create memory descriptors for convolution data w/ no specified format + // Create memory descriptors for convolution data w/ no specified format context_.src_md.reset(new memory::desc( {convFwdDims.src_dims}, MklDnnType(), memory::format::any)); @@ -223,7 +225,7 @@ class MklConvFwdPrimitive : public MklPrimitive { context_.bias_md.reset(new memory::desc( {convFwdDims.bias_dims}, MklDnnType(), memory::format::any)); - // create a convolution + // Create a convolution if (!convFwdDims.bias_dims.empty()) { context_.fwd_desc.reset(new convolution_forward::desc( prop_kind::forward, convolution_direct, *context_.src_md, @@ -238,8 +240,7 @@ class MklConvFwdPrimitive : public MklPrimitive { convFwdDims.padding_right, padding_kind::zero)); } - context_.fwd_pd.reset(new convolution_forward::primitive_desc( - *context_.fwd_desc, cpu_engine_)); + context_.fwd_pd.reset(new ConvFwdPd(*context_.fwd_desc, cpu_engine_)); // Check if there is any fusions as post-ops auto const& post_op_params = convFwdDims.post_op_params; @@ -270,21 +271,20 @@ class MklConvFwdPrimitive : public MklPrimitive { } } post_ops_attr.set_post_ops(post_ops); - context_.fwd_pd.reset(new convolution_forward::primitive_desc( - *context_.fwd_desc, post_ops_attr, cpu_engine_)); + context_.fwd_pd.reset( + new ConvFwdPd(*context_.fwd_desc, post_ops_attr, cpu_engine_)); } else { - context_.fwd_pd.reset(new convolution_forward::primitive_desc( - *context_.fwd_desc, cpu_engine_)); + context_.fwd_pd.reset(new ConvFwdPd(*context_.fwd_desc, cpu_engine_)); } - // store the expected memory format + // Store the expected memory format context_.src_fmt = static_cast( context_.fwd_pd.get()->src_primitive_desc().desc().data.format); context_.filter_fmt = static_cast( context_.fwd_pd.get()->weights_primitive_desc().desc().data.format); - // create memory primitive based on dummy data + // Create memory primitive based on dummy data context_.src_mem.reset( new memory(context_.fwd_pd.get()->src_primitive_desc(), DummyData)); context_.filter_mem.reset( @@ -292,7 +292,7 @@ class MklConvFwdPrimitive : public MklPrimitive { context_.dst_mem.reset( new memory(context_.fwd_pd.get()->dst_primitive_desc(), DummyData)); - // create convolution primitive and add it to net + // Create convolution primitive and add it to net if (!convFwdDims.bias_dims.empty()) { context_.bias_mem.reset(new memory( {{{convFwdDims.bias_dims}, MklDnnType(), memory::format::x}, @@ -323,11 +323,12 @@ class MklConvFwdPrimitiveFactory : public MklPrimitiveFactory { const MklConvFwdParams& convFwdDims, bool do_not_cache) { MklConvFwdPrimitive* conv_fwd = nullptr; - if (do_not_cache) { /* Always create new primitive */ + if (do_not_cache) { + // Always create a new primitive conv_fwd = new MklConvFwdPrimitive( convFwdDims); } else { - // try to find a suitable one in pool + // Try to find a suitable one in pool conv_fwd = dynamic_cast< MklConvFwdPrimitive*>( MklConvFwdPrimitiveFactory::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)); @@ -486,10 +486,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)); @@ -498,10 +497,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)); @@ -509,10 +507,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 @@ -874,6 +871,9 @@ class MklConvOp : public OpKernel { errors::InvalidArgument("Current implementation does not yet support " "strides in the batch and depth dimensions.")); OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_)); + is_filter_const_ = false; + OP_REQUIRES_OK(context, + context->GetAttr("is_filter_const", &is_filter_const_)); if (strides_.size() == 4) { OP_REQUIRES(context, dilations_.size() == 4, @@ -894,17 +894,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.")); } } @@ -915,6 +913,10 @@ class MklConvOp : public OpKernel { const Tensor& src_tensor = MklGetInput(context, kInputIndex_Src); const Tensor& filter_tensor = MklGetInput(context, kInputIndex_Filter); + // Data from persistent (cached) filter tensor + const Tensor& cached_filter_data_tensor = + *cached_filter_data_ptensor_.AccessTensor(context); + MklDnnShape src_mkl_shape, filter_mkl_shape; GetMklShape(context, kInputIndex_Src, &src_mkl_shape); GetMklShape(context, kInputIndex_Filter, &filter_mkl_shape); @@ -956,11 +958,9 @@ class MklConvOp : public OpKernel { AllocateOutputSetMklShape(context, kOutputIndex_Dst, &dst_tensor, src_tf_shape, dst_mkl_shape); - // MklConv2D/3D also outputs converted filter - // as 2nd output of Conv2D/3D. + // MklConv2D/3D also outputs converted filter as 2nd output. filter_mkl_shape.SetMklTensor(false); Tensor* output_filter_tensor = nullptr; - // MklConv2D also outputs converted filter as 2nd output. if (typeid(Tinput) == typeid(float) && typeid(Tfilter) == typeid(float) && typeid(Toutput) == typeid(float)) { @@ -974,6 +974,12 @@ class MklConvOp : public OpKernel { bool is_conv2d = (strides_.size() == 4); + if (!is_conv2d) { + OP_REQUIRES( + context, !pad_enabled, + errors::InvalidArgument("Pad + Conv fusion only works for 2D")); + } + // TODO 3-D support for Depthwise is not there if (is_depthwise) { OP_REQUIRES(context, is_conv2d, @@ -993,10 +999,11 @@ class MklConvOp : public OpKernel { auto tf_fmt = is_conv2d ? TFDataFormatToMklDnnDataFormat(data_format_) : TFDataFormatToMklDnn3DDataFormat(data_format_); - // If input is in MKL layout, then simply grab input layout; otherwise, - // construct input Tf layout. For TF layout, although input shape - // (src_dims) required is in MKL-DNN order, the layout is Tensorflow's - // layout depending on data format: + // If input is in MKL layout, then simply grab the layout; otherwise, + // construct TF layout for input. + // For constructing TF layout for input, although input shape (src_dims) + // is required to be in MKL-DNN order, the input layout is actually in + // TF layout depending on the data format: // Conv2D: NHWC or NCHW // Conv3D: NDHWC or NCDHW auto src_md = src_mkl_shape.IsMklTensor() @@ -1005,8 +1012,8 @@ class MklConvOp : public OpKernel { src.SetUsrMem(src_md, &src_tensor); // Although filter shape (filter_dims) required is in MKL-DNN order, - // the layout is Tensorflow's layout (HWIO) and (HWIGO)for depthwise/group - // convolutions + // the layout is Tensorflow's layout (HWIO) and (HWIGO) for + // depthwise/group convolutions. auto filter_format = is_conv2d ? (is_depthwise ? memory::format::hwigo : memory::format::hwio) @@ -1018,54 +1025,42 @@ class MklConvOp : public OpKernel { ? filter_mkl_shape.GetMklLayout() : memory::desc(filter_dims, MklDnnType(), filter_format); filter.SetUsrMem(filter_md, &filter_tensor); - // MKLDNN dilation starts from 0. - for (int i = 0; i < dilations.size(); i++) dilations[i] -= 1; - // In some cases, primitve descriptor includes potentialy large buffers, - // we don't cache those primitves if the env variable - // TF_MKL_OPTIMIZE_PRIMITIVE_MEMUSE is true. MKL DNN allocates buffers - // in the following cases + // MKLDNN dilations start from 0. + for (int i = 0; i < dilations.size(); i++) --dilations[i]; + + // In some cases, primitive descriptor could potentially contain + // large buffers. As a result, we don't cache these primitives if the + // environment variable `TF_MKL_OPTIMIZE_PRIMITIVE_MEMUSE` is set to True. + // MKL-DNN allocates buffers in the following cases: // 1. Legacy CPU without AVX512/AVX2, or - // 2. 1x1 convolution with stride != 1 + // 2. 1x1 convolution with strides != 1 bool do_not_cache = MklPrimitiveFactory::IsPrimitiveMemOptEnabled() && (src_dims[MklDnnDims::Dim_N] > kSmallBatchSize) && (MklPrimitiveFactory::IsLegacyPlatform() || IsConv1x1StrideNot1(filter_dims, strides)); - // get a conv2d fwd from primitive pool + // Get a conv2d fwd from primitive pool MklConvFwdPrimitive* conv_fwd = nullptr; + memory::dims bias_dims = {}; if (fuse_biasadd_) { - memory::dims bias_dims = {}; conv_utl.GetBiasSizeInMklOrder(kInputIndex_Bias, &bias_dims); - MklConvFwdParams convFwdDims(src_dims, filter_dims, bias_dims, - dst_dims_mkl_order, strides, dilations, - padding_left, padding_right); - - // TODO(mdfaijul): Extend the basic parameters for data types and - // fusions - this->ExtendConvFwdParams(context, convFwdDims); - - conv_fwd = MklConvFwdPrimitiveFactory::Get(convFwdDims, - do_not_cache); - } else { - MklConvFwdParams convFwdDims(src_dims, filter_dims, NONE_DIMS, - dst_dims_mkl_order, strides, dilations, - padding_left, padding_right); + } + MklConvFwdParams convFwdDims( + src_dims, filter_dims, fuse_biasadd_ ? bias_dims : NONE_DIMS, + dst_dims_mkl_order, strides, dilations, padding_left, padding_right); - // Extend the basic parameters for data types and fusions - this->ExtendConvFwdParams(context, convFwdDims); + // TODO(mdfaijul): Extend the basic parameters for data types and fusions + this->ExtendConvFwdParams(context, convFwdDims); - conv_fwd = MklConvFwdPrimitiveFactory::Get(convFwdDims, - do_not_cache); - } + conv_fwd = MklConvFwdPrimitiveFactory::Get(convFwdDims, + do_not_cache); - // allocate output tensors output_tensor and filter_out_tensor - std::shared_ptr conv_fwd_pd = - conv_fwd->GetPrimitiveDesc(); + // Allocate output tensors `output_tensor` and `filter_out_tensor` + std::shared_ptr conv_fwd_pd = conv_fwd->GetPrimitiveDesc(); AllocateOutputTensor(context, *conv_fwd_pd, dst_dims_mkl_order, tf_fmt, &dst_tensor); Tensor* filter_out_tensor = nullptr; @@ -1079,9 +1074,10 @@ class MklConvOp : public OpKernel { Ttemp_output* dst_data = reinterpret_cast(dst_tensor->flat().data()); - // check whether src/filter need reorder + // Check whether src and filter needs to be reordered Tinput* src_data = nullptr; if (src_md.data.format != conv_fwd->GetSrcMemoryFormat()) { + // Reorder src src.SetUsrMem(src_md, &src_tensor); src.CheckReorderToOpMem(conv_fwd_pd.get()->src_primitive_desc()); src_data = static_cast(src.GetOpMem().get_data_handle()); @@ -1089,25 +1085,43 @@ class MklConvOp : public OpKernel { src_data = static_cast( const_cast(src_tensor.flat().data())); } + Tfilter* filter_data = nullptr; if (filter_md.data.format != conv_fwd->GetFilterMemoryFormat()) { - filter.SetUsrMem(filter_md, &filter_tensor); - if (filter_out_tensor == nullptr) { - filter.CheckReorderToOpMem( - conv_fwd_pd.get()->weights_primitive_desc()); - } else { - filter.CheckReorderToOpMem( - conv_fwd_pd.get()->weights_primitive_desc(), - filter.GetTensorBuffer(filter_out_tensor)); + bool is_filter_cached = false; + // If filter is a constant, we can avoid the conversion of filter from + // Tensorflow format to MKL format by caching the filter when it is + // converted for the first time. This cached filter can then be reused + // in subsequent iterations. + if (is_filter_const_) { + if (IsFilterCacheEmpty(context)) { + // Cache filter if it is not already cached. + CacheFilter(context, conv_fwd_pd, filter_data, filter_tensor, + filter, filter_md); + } + filter_data = + GetCachedFilter(context, conv_fwd->GetFilterMemoryFormat()); + is_filter_cached = (filter_data != nullptr); + } + if (!is_filter_cached) { + filter.SetUsrMem(filter_md, &filter_tensor); + if (filter_out_tensor == nullptr) { + filter.CheckReorderToOpMem( + conv_fwd_pd.get()->weights_primitive_desc()); + } else { + filter.CheckReorderToOpMem( + conv_fwd_pd.get()->weights_primitive_desc(), + filter.GetTensorBuffer(filter_out_tensor)); + } + filter_data = + static_cast(filter.GetOpMem().get_data_handle()); } - filter_data = - static_cast(filter.GetOpMem().get_data_handle()); } else { filter_data = static_cast( const_cast(filter_tensor.flat().data())); } - // execute convolution + // Execute convolution if (fuse_biasadd_) { const Tensor& bias_tensor = MklGetInput(context, kInputIndex_Bias); Tbias* bias_data = @@ -1117,7 +1131,7 @@ class MklConvOp : public OpKernel { conv_fwd->Execute(src_data, filter_data, dst_data); } - // delete primitive since it is not cached. + // Delete primitive since it is not cached. if (do_not_cache) delete conv_fwd; } catch (mkldnn::error& e) { string error_msg = tensorflow::strings::StrCat( @@ -1135,18 +1149,22 @@ class MklConvOp : public OpKernel { OP_REQUIRES(context, paddings_tf.dims() == 2, errors::InvalidArgument("paddings must be 2-dimensional: ", paddings_tf.shape().DebugString())); - Tpadding* paddings = nullptr; - // To get individual pad, need to flatten the tensor - paddings = static_cast( + + // Flatten tensor to get individual paddings. + Tpadding* paddings = static_cast( const_cast(paddings_tf.flat().data())); - // For NHWC format: - // paddings[0], paddings[1], paddings[6], paddings[7] should be zero - // if the paddings_tf is [ [0, 0] [1,2] [3,4] [0,0] ] - // paddings = {0, 0, 1, 2, 3, 4, 0, 0} ; flat method is row major - // then, values are: top = 1, bottom =2, left=3, right=4 - // For NCHW format: - // paddings[0], paddings[1], paddings[2], paddings[3] should be zero - // similar explanation as NHWC format will apply. + + // If the data format is NHWC, indices 0, 1, 6 and 7 of paddings(_tf) + // will be zero. + // Example: + // paddings_tf = [ [0, 0] [1, 2] [3, 4] [0, 0] ], + // flat method = row-major, then: + // paddings = {0, 0, 1, 2, 3, 4, 0, 0}. + // Hence, the values are: top = 1, bottom = 2, left = 3, right = 4. + // + // Similarly, if the data format is NCHW, indices 0, 1, 2 and 3 of + // paddings(_tf) will be zero. + // i.e. for the above example, paddings = {0, 0, 0, 0, 1, 2, 3, 4}. int64 pad_top, pad_left; int64 pad_bottom, pad_right; string data_format = ToString(data_format_); @@ -1161,6 +1179,7 @@ class MklConvOp : public OpKernel { pad_left = paddings[6]; pad_right = paddings[7]; } + // Create padding arrays for MKL DNN convolutions. // MKL-DNN uses asymetric padding. padding_left = {static_cast(pad_top), static_cast(pad_left)}; @@ -1183,30 +1202,26 @@ class MklConvOp : public OpKernel { params.dtypes.append(typeid(Toutput).name()); // Add fusions as post ops - // Note: Fusion of BiasAdd is handled directly inside MklConvOp by - // checking fuse_biasadd_ flag. + // NOTE: Fusion of BiasAdd is handled directly inside MklConvOp by + // checking `fuse_biasadd_` flag. if (fuse_relu_) params.post_op_params.push_back({"relu", {1.0, 0.0, 0.0}}); } - virtual Tbias* GetBiasHandle( - OpKernelContext* context, - std::shared_ptr& - conv2d_fwd_pd, - const Tensor& bias_tensor) { + virtual Tbias* GetBiasHandle(OpKernelContext* context, + std::shared_ptr& conv2d_fwd_pd, + const Tensor& bias_tensor) { if (fuse_biasadd_) { return static_cast( const_cast(bias_tensor.flat().data())); - } else { - return nullptr; } + return nullptr; } - // Allocate output tensor. - virtual void AllocateOutputTensor( - OpKernelContext* context, - const convolution_forward::primitive_desc& conv_prim_desc, - const memory::dims& output_dims_mkl_order, - memory::format output_tf_format, Tensor** output_tensor) { + virtual void AllocateOutputTensor(OpKernelContext* context, + const ConvFwdPd& conv_prim_desc, + const memory::dims& output_dims_mkl_order, + memory::format output_tf_format, + Tensor** output_tensor) { CHECK_NOTNULL(output_tensor); auto dst_pd = conv_prim_desc.dst_primitive_desc(); @@ -1237,8 +1252,12 @@ class MklConvOp : public OpKernel { private: std::vector strides_; std::vector dilations_; + bool is_filter_const_; + mutex mu_; Padding padding_; TensorFormat data_format_; + PersistentTensor cached_filter_data_ptensor_ GUARDED_BY(mu_); + PersistentTensor cached_filter_md_ptensor_ GUARDED_BY(mu_); // Initialize to values the template is instantiated with bool fuse_biasadd_ = bias_enabled; @@ -1249,11 +1268,35 @@ class MklConvOp : public OpKernel { const int kOutputIndex_Dst = 0, kOutputIndex_Filter = 1; const int kDilationH = 0, kDilationW = 1; - // Allocate filter output tensor. - void AllocateFilterOutputTensor( - OpKernelContext* context, - const convolution_forward::primitive_desc& conv_prim_desc, - const memory::dims& filter_dims_tf_order, Tensor** filter_tensor) { + // Allocate persistent tensors for cached filter data and + // cached filter memory descriptor (data format) + void AllocatePersistTensor(OpKernelContext* context, + const ConvFwdPd& conv_prim_desc, + Tensor** filter_tensor) { + CHECK_NOTNULL(filter_tensor); + TensorShape filter_tf_shape; + filter_tf_shape.AddDim( + (conv_prim_desc.weights_primitive_desc().get_size() / sizeof(Tfilter))); + OP_REQUIRES_OK(context, context->allocate_persistent( + DataTypeToEnum::value, filter_tf_shape, + &cached_filter_data_ptensor_, filter_tensor)); + + Tensor* second_tensor = nullptr; + TensorShape filter_mkl_format; + filter_mkl_format.AddDim( + sizeof(conv_prim_desc.weights_primitive_desc().desc().data.format) / + sizeof(DT_INT32)); + OP_REQUIRES_OK(context, context->allocate_persistent( + DT_INT32, filter_mkl_format, + &cached_filter_md_ptensor_, &second_tensor)); + second_tensor->scalar()() = + conv_prim_desc.weights_primitive_desc().desc().data.format; + } + + void AllocateFilterOutputTensor(OpKernelContext* context, + const ConvFwdPd& conv_prim_desc, + const memory::dims& filter_dims_tf_order, + Tensor** filter_tensor) { CHECK_NOTNULL(filter_tensor); auto filter_pd = conv_prim_desc.weights_primitive_desc(); @@ -1276,12 +1319,14 @@ class MklConvOp : public OpKernel { AllocateOutputSetMklShape(context, kOutputIndex_Filter, filter_tensor, filter_tf_shape, filter_mkl_shape); } + // Prepare and execute net - checks for input and output reorders. - void PrepareAndExecuteNet( - const convolution_forward::primitive_desc& conv_prim_desc, - MklDnnData* src, MklDnnData* filter, - MklDnnData* bias, MklDnnData* output, - Tensor* filter_out_tensor) { + void PrepareAndExecuteNet(const ConvFwdPd& conv_prim_desc, + MklDnnData* src, + MklDnnData* filter, + MklDnnData* bias, + MklDnnData* output, + Tensor* filter_out_tensor) { CHECK_NOTNULL(filter_out_tensor); // Create reorders between user layout and MKL layout if it is needed and @@ -1310,6 +1355,67 @@ class MklConvOp : public OpKernel { stream(stream::kind::eager).submit(net).wait(); } + + inline bool IsFilterCacheEmpty(OpKernelContext* context) { + tf_shared_lock lock(mu_); + SHARED_LOCKS_REQUIRED(mu_) { + const Tensor& cached_filter_data_tensor = + *cached_filter_data_ptensor_.AccessTensor(context); + return (cached_filter_data_tensor.NumElements() == 0); + } + } + + // Cache the converted filter in a persistent tensor. + // Only one thread can execute this method at any given time. + void CacheFilter(OpKernelContext* context, + const std::shared_ptr& conv_fwd_pd, + Tfilter* filter_data, const Tensor& filter_tensor, + MklDnnData& filter, const memory::desc& filter_md) { + mutex_lock lock(mu_); + EXCLUSIVE_LOCKS_REQUIRED(mu_) { + const Tensor& cached_filter_data_tensor = + *cached_filter_data_ptensor_.AccessTensor(context); + + // If filter is already cached, there's nothing to do. + if (cached_filter_data_tensor.NumElements() > 0) { + return; + } + + // Otherwise, cache filter + filter.SetUsrMem(filter_md, &filter_tensor); + filter.CheckReorderToOpMem(conv_fwd_pd.get()->weights_primitive_desc()); + filter_data = static_cast(filter.GetOpMem().get_data_handle()); + + Tensor* filter_tensor_ptr = nullptr; + AllocatePersistTensor(context, *conv_fwd_pd, &filter_tensor_ptr); + void* cached_filter_data = filter.GetTensorBuffer(filter_tensor_ptr); + size_t cached_filter_data_size = + filter.GetOpMem().get_primitive_desc().get_size(); + memcpy(cached_filter_data, filter_data, cached_filter_data_size); + } + } + + Tfilter* GetCachedFilter(OpKernelContext* context, + const memory::format& filter_mf) { + tf_shared_lock lock(mu_); + SHARED_LOCKS_REQUIRED(mu_) { + const Tensor& cached_filter_data = + *cached_filter_data_ptensor_.AccessTensor(context); + const Tensor& cached_filter_md = + *cached_filter_md_ptensor_.AccessTensor(context); + + // Check if the memory descriptor of the cached weights is same as + // filter_mf. If so, we can used the cached weights; otherwise + // return NULL. + // TODO (bhavanis): Do we need to cast filter_mf before the check? + if (cached_filter_md.scalar().size() && + cached_filter_md.scalar()() == filter_mf) { + return static_cast( + const_cast(cached_filter_data.flat().data())); + } + return nullptr; + } + } }; // Base class for fused convolution forward operations @@ -1356,7 +1462,7 @@ class MklFusedConvOp virtual ~MklFusedConvOp() {} }; -// We create new class for each verison of Quantized Convolution and inherit +// We create new class for each version of Quantized Convolution and inherit // from the FP32 version of the base class template @@ -1378,7 +1484,13 @@ class MklQuantizedConv2DOp explicit MklQuantizedConv2DOp(OpKernelConstruction* context) : MklConvOp(context) {} + bias_enabled, false, false>(context) { + bool is_filter_const; + OP_REQUIRES_OK(context, + context->GetAttr("is_filter_const", &is_filter_const)); + OP_REQUIRES(context, is_filter_const == true, + errors::InvalidArgument("Filter must be a constant")); + } void Compute(OpKernelContext* context) override { // Compute int32 output tensor @@ -1402,9 +1514,9 @@ class MklQuantizedConv2DOp float max_output_value; if (std::is_same::value || std::is_same::value) { - // This is the case the convolution and requantization are fused. - // min_freezed_output and max_freezed_output are the actual range - // for the output + // This is the case when convolution and requantization is fused. + // min_freezed_output and max_freezed_output are the actual ranges + // for the output. min_output_value = context->input(6 + bias_index_offset).flat()(0); max_output_value = context->input(7 + bias_index_offset).flat()(0); } else { @@ -1473,10 +1585,9 @@ class MklQuantizedConv2DOp } } - Tbias* GetBiasHandle( - OpKernelContext* context, - std::shared_ptr& conv_fwd_pd, - const Tensor& bias_tensor) override { + Tbias* GetBiasHandle(OpKernelContext* context, + std::shared_ptr& conv_fwd_pd, + const Tensor& bias_tensor) override { int bias_index_offset; bias_index_offset = bias_enabled ? 1 : 0; @@ -1534,7 +1645,13 @@ class MklQuantizedConv2DReluOp explicit MklQuantizedConv2DReluOp(OpKernelConstruction* context) : MklQuantizedConv2DOp(context) {} + bias_enabled>(context) { + bool is_filter_const; + OP_REQUIRES_OK(context, + context->GetAttr("is_filter_const", &is_filter_const)); + OP_REQUIRES(context, is_filter_const == true, + errors::InvalidArgument("Filter must be a constant")); + } protected: void ExtendConvFwdParams(OpKernelContext* context, @@ -1565,7 +1682,13 @@ class MklQuantizedConv2DSumReluOp explicit MklQuantizedConv2DSumReluOp(OpKernelConstruction* context) : MklQuantizedConv2DOp(context) {} + bias_enabled>(context) { + bool is_filter_const; + OP_REQUIRES_OK(context, + context->GetAttr("is_filter_const", &is_filter_const)); + OP_REQUIRES(context, is_filter_const == true, + errors::InvalidArgument("Filter must be a constant")); + } protected: void ExtendConvFwdParams(OpKernelContext* context, @@ -1605,12 +1728,11 @@ class MklQuantizedConv2DSumReluOp params.post_op_params.push_back({"relu", {1.0, 0.0, 0.0}}); } - // Allocate output tensor. - void AllocateOutputTensor( - OpKernelContext* context, - const convolution_forward::primitive_desc& conv_prim_desc, - const memory::dims& output_dims_mkl_order, - memory::format output_tf_format, Tensor** output_tensor) override { + void AllocateOutputTensor(OpKernelContext* context, + const ConvFwdPd& conv_prim_desc, + const memory::dims& output_dims_mkl_order, + memory::format output_tf_format, + Tensor** output_tensor) override { int summand_idx = context->num_inputs() / 2 - 1; float reorder_sum_scale = 1.0; if (std::is_same::value) { @@ -1663,8 +1785,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); diff --git a/tensorflow/core/kernels/mkl_fused_ops_test.cc b/tensorflow/core/kernels/mkl_fused_ops_test.cc index 258cca9332..6d2e4fae7c 100644 --- a/tensorflow/core/kernels/mkl_fused_ops_test.cc +++ b/tensorflow/core/kernels/mkl_fused_ops_test.cc @@ -218,18 +218,18 @@ class MklFusedConv2DOpTest : public OpsTestBase { int depth = kDepth, int image_width = kImageWidth, int image_height = kImageHeight, int image_batch_count = kImageBatchCount) { - const BiasAddGraphRunner run_default = - [this](const Tensor& input_data, const Tensor& filter_data, - const Tensor& bias_data, Tensor* out) { - RunConv2DWithBias(input_data, filter_data, bias_data, out); - }; - - const BiasAddGraphRunner run_fused = - [this](const Tensor& input_data, const Tensor& filter_data, - const Tensor& bias_data, Tensor* out) { - RunMklFusedConv2DOp(input_data, filter_data, {bias_data}, {"BiasAdd"}, - out); - }; + const BiasAddGraphRunner run_default = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunConv2DWithBias(input_data, filter_data, bias_data, out); + }; + + const BiasAddGraphRunner run_fused = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunMklFusedConv2DOp(input_data, filter_data, {bias_data}, {"BiasAdd"}, + out); + }; VerifyBiasAddTensorsNear(depth, image_width, image_height, image_batch_count, filter_size, filter_count, @@ -243,18 +243,18 @@ class MklFusedConv2DOpTest : public OpsTestBase { int image_width = kImageWidth, int image_height = kImageHeight, int image_batch_count = kImageBatchCount) { - const BiasAddGraphRunner run_default = - [this](const Tensor& input_data, const Tensor& filter_data, - const Tensor& bias_data, Tensor* out) { - RunConv2DWithBiasAndRelu(input_data, filter_data, bias_data, out); - }; - - const BiasAddGraphRunner run_fused = - [this](const Tensor& input_data, const Tensor& filter_data, - const Tensor& bias_data, Tensor* out) { - RunMklFusedConv2DOp(input_data, filter_data, {bias_data}, - {"BiasAdd", "Relu"}, out); - }; + const BiasAddGraphRunner run_default = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunConv2DWithBiasAndRelu(input_data, filter_data, bias_data, out); + }; + + const BiasAddGraphRunner run_fused = [this]( + const Tensor& input_data, const Tensor& filter_data, + const Tensor& bias_data, Tensor* out) { + RunMklFusedConv2DOp(input_data, filter_data, {bias_data}, + {"BiasAdd", "Relu"}, out); + }; VerifyBiasAddTensorsNear(depth, image_width, image_height, image_batch_count, filter_size, filter_count, @@ -401,5 +401,70 @@ TEST_F(FusedPadConvOpTest, PaddingConvTestNchw) { Run(DT_FLOAT, image, filter, padding, expected, "NCHW"); } + +class FilterCacheTest : public OpsTestBase { + public: + template + void Run(DataType dtype, Tensor& image, Tensor& filter, Tensor& expected, + const bool is_filter_const) { + const int stride = 1; + + TF_EXPECT_OK(NodeDefBuilder("conv2d_filter_cache", "_MklConv2D") + .Input(FakeInput(dtype)) // Input + .Input(FakeInput(dtype)) // Filter + .Input(FakeInput(DT_UINT8)) // MKl second tensor + .Input(FakeInput(DT_UINT8)) // MKl second tensor + .Attr("padding", "VALID") + .Attr("data_format", "NHWC") + .Attr("is_filter_const", is_filter_const) + .Attr("T", dtype) + .Attr("strides", {1, stride, stride, 1}) + .Attr("_kernel", "MklOp") + .Finalize(node_def())); + TF_EXPECT_OK(InitOp()); + + // Setting up inputs and execute + AddInputFromArray(image.shape(), image.flat()); + AddInputFromArray(filter.shape(), filter.flat()); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + + TF_ASSERT_OK(RunOpKernel()); + + // Compare output to expected results + const Tensor& first = *GetOutput(0); + const Tensor& second = *GetOutput(2); + ConvMklToTF conv_comp; + conv_comp.ConvertAndCompare(dtype, first, second, expected); + + // Run kernel again to ensure cached data is being reused + TF_ASSERT_OK(RunOpKernel()); + + // Compare output to expected results + const Tensor& first_new = *GetOutput(0); + const Tensor& second_new = *GetOutput(2); + ConvMklToTF conv_comp_new; + conv_comp_new.ConvertAndCompare(dtype, first_new, second_new, expected); + } +}; + +TEST_F(FilterCacheTest, Conv2DFilterCacheTest) { + const int depth = 1; + const int image_width = 4; + const int image_height = 3; + const int image_batch_count = 1; + Tensor image(DT_FLOAT, {image_batch_count, image_height, image_width, depth}); + test::FillValues(&image, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}); + + const int filter_size = 3; + const int filter_count = 1; + Tensor filter(DT_FLOAT, {filter_size, filter_size, depth, filter_count}); + test::FillValues(&filter, {1, 4, 7, 2, 5, 8, 3, 6, 9}); + + Tensor expected(DT_FLOAT, TensorShape({1, 1, 2, 1})); + test::FillValues(&expected, {312, 357}); + + Run(DT_FLOAT, image, filter, expected, true); +} } // namespace tensorflow #endif // INTEL_MKL diff --git a/tensorflow/core/ops/mkl_nn_ops.cc b/tensorflow/core/ops/mkl_nn_ops.cc index 658afd9901..f7b81912b9 100644 --- a/tensorflow/core/ops/mkl_nn_ops.cc +++ b/tensorflow/core/ops/mkl_nn_ops.cc @@ -46,6 +46,7 @@ REGISTER_OP("_MklFusedConv2D") .Attr("T: {float}") .Attr("num_args: int >= 0") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = false") .Attr(GetPaddingAttrString()) .Attr(GetConvnetDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") @@ -145,6 +146,7 @@ REGISTER_OP("_MklQuantizedConv2D") .Attr("out_type: quantizedtype = DT_QINT32") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -189,6 +191,7 @@ REGISTER_OP("_MklQuantizedConv2DAndRequantize") .Attr("out_type: quantizedtype = DT_QINT8") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -233,6 +236,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBias") .Attr("out_type: quantizedtype = DT_QINT32") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -281,6 +285,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasAndRequantize") .Attr("out_type: quantizedtype = DT_QINT8") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -322,6 +327,7 @@ REGISTER_OP("_MklQuantizedConv2DAndRelu") .Attr("out_type: quantizedtype = DT_QINT32") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -366,6 +372,7 @@ REGISTER_OP("_MklQuantizedConv2DAndReluAndRequantize") .Attr("out_type: quantizedtype = DT_QUINT8") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -410,6 +417,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasAndRelu") .Attr("out_type: quantizedtype = DT_QINT32") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -458,6 +466,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasAndReluAndRequantize") .Attr("out_type: quantizedtype = DT_QUINT8") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -505,6 +514,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasSumAndRelu") .Attr("out_type: quantizedtype = DT_QINT32") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -560,6 +570,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasSumAndReluAndRequantize") .Attr("out_type: quantizedtype = DT_QUINT8") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -617,6 +628,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasSignedSumAndReluAndRequantize") .Attr("out_type: quantizedtype = DT_QUINT8") .Attr("data_format: string = 'NHWC'") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index 0e94259f75..2bdd02a76c 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -1210,9 +1210,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. @@ -1266,9 +1266,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 @@ -1551,6 +1551,7 @@ REGISTER_OP("_MklDepthwiseConv2dNative") .Output("mkl_filter_output: uint8") .Attr("T: {half, bfloat16, float, double}") .Attr("strides: list(int)") + .Attr("is_filter_const: bool = false") .Attr(GetPaddingAttrString()) .Attr(GetConvnetDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") @@ -1568,6 +1569,7 @@ REGISTER_OP("_MklConv2D") .Attr("T: {half, float, double}") .Attr("strides: list(int)") .Attr("use_cudnn_on_gpu: bool = true") + .Attr("is_filter_const: bool = false") .Attr(GetPaddingAttrString()) .Attr(GetConvnetDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") @@ -1587,6 +1589,7 @@ REGISTER_OP("__MklDummyConv2DWithBias") .Attr("T: {half, float, double}") .Attr("strides: list(int)") .Attr("use_cudnn_on_gpu: bool = true") + .Attr("is_filter_const: bool = false") .Attr(GetPaddingAttrString()) .Attr(GetConvnetDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") @@ -1614,6 +1617,7 @@ REGISTER_OP("_MklConv2DWithBias") .Attr("T: {half, float, double}") .Attr("strides: list(int)") .Attr("use_cudnn_on_gpu: bool = true") + .Attr("is_filter_const: bool = false") .Attr(GetPaddingAttrString()) .Attr(GetConvnetDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") @@ -1634,6 +1638,7 @@ REGISTER_OP("__MklDummyPadWithConv2D") .Attr("T: {half, float, double}") .Attr("strides: list(int)") .Attr("use_cudnn_on_gpu: bool = true") + .Attr("is_filter_const: bool = false") .Attr(GetPaddingAttrString()) .Attr(GetConvnetDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") @@ -1664,6 +1669,7 @@ REGISTER_OP("_MklPadWithConv2D") .Attr("use_cudnn_on_gpu: bool = true") .Attr(GetPaddingAttrString()) .Attr(GetConvnetDataFormatAttrString()) + .Attr("is_filter_const: bool = false") .Attr("dilations: list(int) = [1, 1, 1, 1]") .Attr("Tpaddings: {int32, int64} = DT_INT32") .SetShapeFn(shape_inference::Conv2DShape) @@ -1849,6 +1855,7 @@ REGISTER_OP("_MklConv3D") .Output("mkl_filter_output: uint8") .Attr("T: {half, float, double}") .Attr("strides: list(int) >= 5") + .Attr("is_filter_const: bool = false") .Attr(GetPaddingAttrString()) .Attr(GetConvnet3dDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1, 1]") -- GitLab From 17bc7e61e566db5b3288b2afe6dd8cbc1152c2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yan=20Facai=20=28=E9=A2=9C=E5=8F=91=E6=89=8D=29?= Date: Tue, 18 Dec 2018 08:51:25 +0800 Subject: [PATCH 0084/2345] ENH: The gradient op of bias_add supports 3/4/5D NCHW format --- tensorflow/core/kernels/bias_op.cc | 46 +++++++++---------- tensorflow/core/kernels/bias_op_gpu.cu.cc | 4 +- tensorflow/core/kernels/bias_op_gpu.h | 2 +- .../python/kernel_tests/bias_op_test.py | 38 ++++++++++++--- tensorflow/python/ops/nn_grad.py | 6 +-- 5 files changed, 58 insertions(+), 38 deletions(-) diff --git a/tensorflow/core/kernels/bias_op.cc b/tensorflow/core/kernels/bias_op.cc index d4f4b43d63..df12dafdeb 100644 --- a/tensorflow/core/kernels/bias_op.cc +++ b/tensorflow/core/kernels/bias_op.cc @@ -18,13 +18,13 @@ limitations under the License. #define EIGEN_USE_THREADS #include "tensorflow/core/kernels/bias_op.h" -#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/framework/numeric_op.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/kernels/bounds_check.h" #include "tensorflow/core/util/tensor_format.h" +#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #if GOOGLE_CUDA #include "tensorflow/core/kernels/bias_op_gpu.h" @@ -153,13 +153,13 @@ class BiasOp : public BinaryOp { bias.tensor().reshape(four_dims).broadcast(broad_cast_dims); } break; case 5: { - Eigen::DSizes four_dims(1, channel, 1, 1, 1); + Eigen::DSizes five_dims(1, channel, 1, 1, 1); Eigen::DSizes broad_cast_dims(batch, 1, height, width, depth); const Device& d = context->eigen_device(); output->tensor().device(d) = input.tensor() + - bias.tensor().reshape(four_dims).broadcast(broad_cast_dims); + bias.tensor().reshape(five_dims).broadcast(broad_cast_dims); } break; default: OP_REQUIRES(context, false, @@ -269,28 +269,24 @@ class BiasGradOp : public OpKernel { output->template flat().setZero(); } else { // Added by intel_tf to support NCHW on CPU regardless of MKL used or not. - // TODO(yongtang): Add 3/4/5 dimensional data support for NCHW format. if (data_format_ == FORMAT_NCHW) { - OP_REQUIRES(context, output_backprop.dims() == 4, - errors::InvalidArgument( - "NCHW format supports only 4D input/output tensor.")); - Eigen::DSizes four_dims(batch, channel, height, width); + Eigen::DSizes three_dims(batch, channel, + height * width * depth); #ifdef EIGEN_HAS_INDEX_LIST using idx0 = Eigen::type2index<0>; using idx2 = Eigen::type2index<2>; - using idx3 = Eigen::type2index<3>; - Eigen::IndexList reduction_axes; + Eigen::IndexList reduction_axes; #else - Eigen::array reduction_axes = {0, 2, 3}; + Eigen::array reduction_axes = {0, 2}; #endif output->template flat().device(context->eigen_device()) = output_backprop.flat() .template cast::type>() - .reshape(four_dims) + .reshape(three_dims) .sum(reduction_axes) .template cast(); // End of code by intel_tf. } else { - Eigen::DSizes two_dims(batch * height * width, + Eigen::DSizes two_dims(batch * height * width * depth, channel); #ifdef EIGEN_HAS_INDEX_LIST Eigen::IndexList > reduction_axis; @@ -496,21 +492,21 @@ class BiasGradOp : public OpKernel { void ComputeWithCustomKernel(OpKernelContext* context, const Tensor& output_backprop, int32 batch, - int32 width, int32 height, int32 channel, - Tensor* output) { + int32 width, int32 height, int32 depth, + int32 channel, Tensor* output) { BiasGradGPU::compute(context->template eigen_device(), output_backprop.template flat().data(), output->flat().data(), batch, width, height, - channel, data_format_); + depth, channel, data_format_); } void ComputeWithReduceSum(OpKernelContext* context, const Tensor& output_backprop, int32 batch, - int32 width, int32 height, int32 channel, - Tensor* output) { + int32 width, int32 height, int32 depth, + int32 channel, Tensor* output) { if (data_format_ == FORMAT_NCHW) { int32 row_count = batch * channel; - int32 col_count = height * width; + int32 col_count = height * width * depth; Tensor temp_grad_outputs; // For 'NCHW' format, we perform reduction twice: first HW, then N. TensorShape temp_grad_output_shape{row_count, col_count}; @@ -528,7 +524,7 @@ class BiasGradOp : public OpKernel { row_count, col_count); } else { // For 'NHWC', we simply apply reduction once on NHW. - int32 row_count = batch * height * width; + int32 row_count = batch * height * width * depth; int32 col_count = channel; BiasGradGPU::DoColReduction( context, const_cast(output->flat().data()), @@ -561,7 +557,7 @@ class BiasGradOp : public OpKernel { int device_id = stream->parent()->device_ordinal(); DataType dtype = output_backprop.dtype(); BiasAddParams bias_parameters = { - {batch, height * width, channel}, + {batch, height * width * depth, channel}, data_format_, dtype, device_id, @@ -576,7 +572,7 @@ class BiasGradOp : public OpKernel { stream->InitTimer(&timer); stream->ThenStartTimer(&timer); ComputeWithCustomKernel(context, output_backprop, batch, width, height, - channel, output); + depth, channel, output); stream->ThenStopTimer(&timer); uint64 elapsed_microseconds = timer.Microseconds(); VLOG(1) << "BiasAddGrad " << bias_parameters.ToString() @@ -589,7 +585,7 @@ class BiasGradOp : public OpKernel { // Try reduction and profile. stream->ThenStartTimer(&timer); ComputeWithReduceSum(context, output_backprop, batch, width, height, - channel, output); + depth, channel, output); stream->ThenStopTimer(&timer); elapsed_microseconds = timer.Microseconds(); @@ -610,11 +606,11 @@ class BiasGradOp : public OpKernel { // Choose the best algorithm based on autotune results. if (algo_config.get_mode() == BiasAddGradGPUMode::kReduction) { ComputeWithReduceSum(context, output_backprop, batch, width, height, - channel, output); + depth, channel, output); } else { // Default to the customized kernel. ComputeWithCustomKernel(context, output_backprop, batch, width, height, - channel, output); + depth, channel, output); } } diff --git a/tensorflow/core/kernels/bias_op_gpu.cu.cc b/tensorflow/core/kernels/bias_op_gpu.cu.cc index 24fea8a8e6..006fa1dc71 100644 --- a/tensorflow/core/kernels/bias_op_gpu.cu.cc +++ b/tensorflow/core/kernels/bias_op_gpu.cu.cc @@ -195,10 +195,10 @@ __global__ void BiasGradNCHW_SharedAtomics(const T* output_backprop, template void BiasGradGPU::compute(const GPUDevice& d, const T* output_backprop, T* bias_backprop, int32 batch, int32 height, - int32 width, int32 channel, + int32 width, int32 depth, int32 channel, TensorFormat data_format) { const int32 bias_size = channel; - const int32 image_size = height * width; + const int32 image_size = height * width * depth; const int32 total_count = batch * bias_size * image_size; if (total_count == 0) { return; diff --git a/tensorflow/core/kernels/bias_op_gpu.h b/tensorflow/core/kernels/bias_op_gpu.h index a0b2ce4f9b..372a403e68 100644 --- a/tensorflow/core/kernels/bias_op_gpu.h +++ b/tensorflow/core/kernels/bias_op_gpu.h @@ -39,7 +39,7 @@ template struct BiasGradGPU { static void compute(const GPUDevice& device, const T* output_backprop, T* bias_backprop, int32 batch, int32 height, int32 width, - int32 channel, TensorFormat data_format); + int32 depth, int32 channel, TensorFormat data_format); static void DoRowReduction(OpKernelContext* context, T* output, const T* input, int rows, int cols); diff --git a/tensorflow/python/kernel_tests/bias_op_test.py b/tensorflow/python/kernel_tests/bias_op_test.py index 66f442dbdd..c3976194a0 100644 --- a/tensorflow/python/kernel_tests/bias_op_test.py +++ b/tensorflow/python/kernel_tests/bias_op_test.py @@ -196,9 +196,7 @@ class BiasAddTest(test.TestCase): self.assertAllClose(grad_jacob_t, grad_jacob_n, threshold, threshold) @test_util.run_deprecated_v1 - def testGradientTensor(self): - # TODO(yongtang): BiasAddGrad with NCHW only works 4D. Reenable once - # all dimensions are supported. + def testGradientTensor2D(self): for (data_format, use_gpu) in ("NHWC", False), ("NHWC", True): for dtype in (dtypes.float16, dtypes.float32, dtypes.float64): np_input = np.array( @@ -207,9 +205,19 @@ class BiasAddTest(test.TestCase): bias = np.array([1.3, 2.4], dtype=dtype.as_numpy_dtype) self._testGradient(np_input, bias, dtype, data_format, use_gpu) + @test_util.run_deprecated_v1 + def testGradientTensor3D(self): + for (data_format, use_gpu) in [("NHWC", False), ("NHWC", True), + ("NCHW", False), ("NCHW", True)]: + for dtype in (dtypes.float16, dtypes.float32, dtypes.float64): + np_input = np.array( + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0], + dtype=dtype.as_numpy_dtype).reshape(1, 3, 2) + bias = np.array([1.3, 2.4], dtype=dtype.as_numpy_dtype) + self._testGradient(np_input, bias, dtype, data_format, use_gpu) + @test_util.run_deprecated_v1 def testGradientTensor4D(self): - # BiasAddGrad with NCHW support 4D so all are enabled. for (data_format, use_gpu) in [("NHWC", False), ("NHWC", True), ("NCHW", False), ("NCHW", True)]: for dtype in (dtypes.float16, dtypes.float32, dtypes.float64): @@ -219,6 +227,17 @@ class BiasAddTest(test.TestCase): bias = np.array([1.3, 2.4], dtype=dtype.as_numpy_dtype) self._testGradient(np_input, bias, dtype, data_format, use_gpu) + @test_util.run_deprecated_v1 + def testGradientTensor5D(self): + for (data_format, use_gpu) in [("NHWC", False), ("NHWC", True), + ("NCHW", False), ("NCHW", True)]: + for dtype in (dtypes.float16, dtypes.float32, dtypes.float64): + np_input = np.arange( + 1.0, 49.0, dtype=dtype.as_numpy_dtype).reshape( + [1, 2, 3, 4, 2]).astype(np.float32) + bias = np.array([1.3, 2.4], dtype=dtype.as_numpy_dtype) + self._testGradient(np_input, bias, dtype, data_format, use_gpu) + @test_util.run_deprecated_v1 def testEmpty(self): np.random.seed(7) @@ -227,10 +246,15 @@ class BiasAddTest(test.TestCase): @test_util.run_deprecated_v1 def testEmptyGradient(self): - # TODO(yongtang): BiasAddGrad with NCHW only works 4D. Reenable once - # all dimensions are supported. for (data_format, use_gpu) in ("NHWC", False), ("NHWC", True): - for shape in (0, 0), (2, 0), (0, 2), (4, 3, 0), (4, 0, 3), (0, 4, 3): + for shape in (0, 0), (2, 0), (0, 2): + self._testGradient( + np.random.randn(*shape), + np.random.randn(shape[-1]), dtypes.float64, data_format, use_gpu) + + for (data_format, use_gpu) in [("NHWC", False), ("NHWC", True), + ("NCHW", False), ("NCHW", True)]: + for shape in (4, 3, 0), (4, 0, 3), (0, 4, 3): self._testGradient( np.random.randn(*shape), np.random.randn(shape[-1]), dtypes.float64, data_format, use_gpu) diff --git a/tensorflow/python/ops/nn_grad.py b/tensorflow/python/ops/nn_grad.py index 34404edc9a..7131e4abc4 100644 --- a/tensorflow/python/ops/nn_grad.py +++ b/tensorflow/python/ops/nn_grad.py @@ -314,10 +314,10 @@ def _BiasAddGradGrad(op, received_grad): if data_format == b"NCHW": expanded_shape = array_ops.concat([ - array_ops.ones_like(shape[:-3]), bias_shape, - array_ops.ones_like(shape[-2:]) + array_ops.ones_like(shape[:1]), bias_shape, + array_ops.ones_like(shape[2:]) ], 0) - tile_mults = array_ops.concat([shape[:-3], [1], shape[-2:]], 0) + tile_mults = array_ops.concat([shape[:1], [1], shape[2:]], 0) else: expanded_shape = array_ops.concat( [array_ops.ones_like(shape[:-1]), bias_shape], 0) -- GitLab From 96f41397357f96c41459cf351f8853caa8d724c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 28 Dec 2018 15:18:06 +0800 Subject: [PATCH 0085/2345] Fix typo in the documentation of tf.function "`add_noise()` will return a different output every time it is invoked. However, `add_noise` will return the same value every time it is called..." => the second `add_noise` should be `traced` --- tensorflow/python/eager/def_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index ebc47d1566..b25df5a47c 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -765,7 +765,7 @@ def function(func=None, ``` `add_noise()` will return a different output every time it is invoked. - However, `add_noise` will return the same value every time it is called, + However, `traced()` will return the same value every time it is called, since a particular random value generated by the `np.random.randn` call will be inserted in the traced/staged TensorFlow graph as a constant. In this particular example, replacing `np.random.randn(5, 5)` with -- GitLab From 9fb415c6eac3cd245de736e7ebc54094416c1013 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 30 Dec 2018 01:07:37 +0000 Subject: [PATCH 0086/2345] Fix TypeError when using tf.keras.utils.plot_model This fix fixes the issue raised in 24622 where a TypeError was raised when using tf.keras.utils.plot_model. This fix fixes 24622. Signed-off-by: Yong Tang --- tensorflow/python/keras/utils/vis_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/keras/utils/vis_utils.py b/tensorflow/python/keras/utils/vis_utils.py index 82bc2755bd..fa848ae8a7 100644 --- a/tensorflow/python/keras/utils/vis_utils.py +++ b/tensorflow/python/keras/utils/vis_utils.py @@ -120,7 +120,7 @@ def model_to_dot(model, show_shapes=False, show_layer_names=True, rankdir='TB'): for i, node in enumerate(layer._inbound_nodes): node_key = layer.name + '_ib-' + str(i) if node_key in model._network_nodes: # pylint: disable=protected-access - for inbound_layer in node.inbound_layers: + for inbound_layer in nest.flatten(node.inbound_layers): inbound_layer_id = str(id(inbound_layer)) layer_id = str(id(layer)) dot.add_edge(pydot.Edge(inbound_layer_id, layer_id)) -- GitLab From 129bf6c79a6793951d0110d31b1b2c0198479fff Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 30 Dec 2018 01:09:11 +0000 Subject: [PATCH 0087/2345] Add missing python import Signed-off-by: Yong Tang --- tensorflow/python/keras/utils/vis_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/keras/utils/vis_utils.py b/tensorflow/python/keras/utils/vis_utils.py index fa848ae8a7..c7c45f381e 100644 --- a/tensorflow/python/keras/utils/vis_utils.py +++ b/tensorflow/python/keras/utils/vis_utils.py @@ -67,6 +67,7 @@ def model_to_dot(model, show_shapes=False, show_layer_names=True, rankdir='TB'): """ from tensorflow.python.keras.layers.wrappers import Wrapper from tensorflow.python.keras.models import Sequential + from tensorflow.python.util import nest _check_pydot() dot = pydot.Dot() -- GitLab From 53e677de105c2edbfdc2d86ceb31d06765b08512 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 30 Dec 2018 10:07:06 +0000 Subject: [PATCH 0088/2345] Fix incorrect display of keras model summary This fix fixes the issue raised in 24627 where the display of keras model summary is incorrect (regression from 1.12). The reason was that `layer.name` (vs. layer object itself) should be used, This fix fixes 24627. Signed-off-by: Yong Tang --- tensorflow/python/keras/utils/layer_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/keras/utils/layer_utils.py b/tensorflow/python/keras/utils/layer_utils.py index ead5afd1ae..1d85e8a25f 100644 --- a/tensorflow/python/keras/utils/layer_utils.py +++ b/tensorflow/python/keras/utils/layer_utils.py @@ -196,7 +196,7 @@ def print_summary(model, line_length=None, positions=None, print_fn=None): continue for inbound_layer, node_index, tensor_index, _ in node.iterate_inbound(): - connections.append('{}[{}][{}]'.format(inbound_layer, node_index, + connections.append('{}[{}][{}]'.format(inbound_layer.name, node_index, tensor_index)) name = layer.name -- GitLab From 85f3821cf8c4cf8354e2aff6e8a4b7c1c5f2fd88 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 31 Dec 2018 00:39:14 +0000 Subject: [PATCH 0089/2345] Replace deprecated test_session with cached_session in quantile_ops_test This fix replace deprecated test_session with cached_session in quantile_ops_test.py to remove warnings during tests Signed-off-by: Yong Tang --- .../python/kernel_tests/boosted_trees/quantile_ops_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/kernel_tests/boosted_trees/quantile_ops_test.py b/tensorflow/python/kernel_tests/boosted_trees/quantile_ops_test.py index 37a60fa0e3..12eb58536a 100644 --- a/tensorflow/python/kernel_tests/boosted_trees/quantile_ops_test.py +++ b/tensorflow/python/kernel_tests/boosted_trees/quantile_ops_test.py @@ -145,7 +145,7 @@ class QuantileOpsTest(test_util.TensorFlowTestCase): save_dir = os.path.join(self.get_temp_dir(), "save_restore") save_path = os.path.join(tempfile.mkdtemp(prefix=save_dir), "hash") - with self.test_session() as sess: + with self.cached_session() as sess: accumulator = boosted_trees_ops.QuantileAccumulator( num_streams=2, num_quantiles=3, epsilon=self.eps, name="q0") @@ -177,7 +177,7 @@ class QuantileOpsTest(test_util.TensorFlowTestCase): save_dir = os.path.join(self.get_temp_dir(), "save_restore") save_path = os.path.join(tempfile.mkdtemp(prefix=save_dir), "hash") - with self.test_session() as sess: + with self.cached_session() as sess: accumulator = boosted_trees_ops.QuantileAccumulator( num_streams=2, num_quantiles=3, epsilon=self.eps, name="q0") -- GitLab From 1f6a9fc270bd356e817f208b9386732f03824c65 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 31 Dec 2018 00:40:43 +0000 Subject: [PATCH 0090/2345] Fix test_session warnings during tests by replacing it with session() Signed-off-by: Yong Tang --- .../python/kernel_tests/boosted_trees/quantile_ops_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/kernel_tests/boosted_trees/quantile_ops_test.py b/tensorflow/python/kernel_tests/boosted_trees/quantile_ops_test.py index 12eb58536a..0315456447 100644 --- a/tensorflow/python/kernel_tests/boosted_trees/quantile_ops_test.py +++ b/tensorflow/python/kernel_tests/boosted_trees/quantile_ops_test.py @@ -164,7 +164,7 @@ class QuantileOpsTest(test_util.TensorFlowTestCase): self.assertAllClose(self._feature_1_boundaries, buckets[1].eval()) save.save(sess, save_path) - with self.test_session(graph=ops.Graph()) as sess: + with self.session(graph=ops.Graph()) as sess: accumulator = boosted_trees_ops.QuantileAccumulator( num_streams=2, num_quantiles=3, epsilon=self.eps, name="q0") save = saver.Saver() @@ -195,7 +195,7 @@ class QuantileOpsTest(test_util.TensorFlowTestCase): self.assertAllClose(self._feature_0_boundaries, buckets[0].eval()) self.assertAllClose(self._feature_1_boundaries, buckets[1].eval()) - with self.test_session(graph=ops.Graph()) as sess: + with self.session(graph=ops.Graph()) as sess: accumulator = boosted_trees_ops.QuantileAccumulator( num_streams=2, num_quantiles=3, epsilon=self.eps, name="q0") save = saver.Saver() -- GitLab From 1f31207ab85639eea00e2fa04586a6529de4784f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 31 Dec 2018 11:50:57 +0800 Subject: [PATCH 0091/2345] Fix model_to_dot() for Sequential models The model_to_dot() function does not display the input layer of Sequential models correctly. See https://github.com/keras-team/keras/issues/10638 This fix seems to have been applied already in keras-team/keras, but not in tf.keras. --- tensorflow/python/keras/utils/vis_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/keras/utils/vis_utils.py b/tensorflow/python/keras/utils/vis_utils.py index 82bc2755bd..f56b2720b5 100644 --- a/tensorflow/python/keras/utils/vis_utils.py +++ b/tensorflow/python/keras/utils/vis_utils.py @@ -77,7 +77,7 @@ def model_to_dot(model, show_shapes=False, show_layer_names=True, rankdir='TB'): if isinstance(model, Sequential): if not model.built: model.build() - layers = model.layers + layers = model._layers # Create graph nodes. for layer in layers: -- GitLab From 68eb3b63b0eae2dc1eab5697872d54f5bc472f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 31 Dec 2018 19:11:17 +0800 Subject: [PATCH 0092/2345] Add model_to_dot and print_summary to tf.keras.utils Fixes #24639 --- tensorflow/python/keras/utils/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/python/keras/utils/__init__.py b/tensorflow/python/keras/utils/__init__.py index 61940ad789..66d9817a6a 100644 --- a/tensorflow/python/keras/utils/__init__.py +++ b/tensorflow/python/keras/utils/__init__.py @@ -34,10 +34,12 @@ from tensorflow.python.keras.utils.generic_utils import serialize_keras_object from tensorflow.python.keras.utils.io_utils import HDF5Matrix from tensorflow.python.keras.utils.layer_utils import convert_all_kernels_in_model from tensorflow.python.keras.utils.layer_utils import get_source_inputs +from tensorflow.python.keras.utils.layer_utils import print_summary from tensorflow.python.keras.utils.losses_utils import squeeze_or_expand_dimensions from tensorflow.python.keras.utils.multi_gpu_utils import multi_gpu_model from tensorflow.python.keras.utils.np_utils import normalize from tensorflow.python.keras.utils.np_utils import to_categorical +from tensorflow.python.keras.utils.vis_utils import model_to_dot from tensorflow.python.keras.utils.vis_utils import plot_model del absolute_import -- GitLab From ceb64c15db61855183d3b88504b5469af0f423c7 Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Tue, 1 Jan 2019 01:25:55 +0530 Subject: [PATCH 0093/2345] Add take_while experimental dataset op (tests pending) --- .../core/kernels/data/experimental/BUILD | 14 + .../experimental/take_while_dataset_op.cc | 242 ++++++++++++++++++ .../core/ops/experimental_dataset_ops.cc | 11 + .../python/data/experimental/__init__.py | 1 + tensorflow/python/data/experimental/ops/BUILD | 14 + .../data/experimental/ops/take_while_ops.py | 79 ++++++ 6 files changed, 361 insertions(+) create mode 100644 tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc create mode 100644 tensorflow/python/data/experimental/ops/take_while_ops.py diff --git a/tensorflow/core/kernels/data/experimental/BUILD b/tensorflow/core/kernels/data/experimental/BUILD index 7433303f77..4ce14b0140 100644 --- a/tensorflow/core/kernels/data/experimental/BUILD +++ b/tensorflow/core/kernels/data/experimental/BUILD @@ -293,6 +293,19 @@ tf_kernel_library( ], ) +tf_kernel_library( + name = "take_while_dataset_op", + srcs = ["take_while_dataset_op.cc"], + deps = [ + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:dataset_ops_op_lib", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core/kernels/data:captured_function", + ], +) + tf_kernel_library( name = "to_tf_record_op", srcs = ["to_tf_record_op.cc"], @@ -365,6 +378,7 @@ tf_kernel_library( ":sql_dataset_op", ":stats_aggregator_ops", ":stats_dataset_ops", + ":take_while_dataset_op", ":threadpool_dataset_op", ":to_tf_record_op", ":unbatch_dataset_op", diff --git a/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc new file mode 100644 index 0000000000..271b919bc9 --- /dev/null +++ b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc @@ -0,0 +1,242 @@ +/* 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 +#include + +#include "tensorflow/core/common_runtime/function.h" +#include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/framework/partial_tensor_shape.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/kernels/data/captured_function.h" + +namespace tensorflow { +namespace data { +namespace { + +// See documentation in ../../ops/dataset_ops.cc for a high-level +// description of the following op. + +class TakeWhileDatasetOp : public UnaryDatasetOpKernel { + public: + explicit TakeWhileDatasetOp(OpKernelConstruction* ctx) + : UnaryDatasetOpKernel(ctx) { + OP_REQUIRES_OK(ctx, ctx->GetAttr("predicate", &func_)); + OP_REQUIRES_OK( + ctx, ctx->GetAttr("preserve_cardinality", &preserve_cardinality_)); + } + + void MakeDataset(OpKernelContext* ctx, DatasetBase* input, + DatasetBase** output) override { + std::unique_ptr captured_func; + OP_REQUIRES_OK(ctx, CapturedFunction::Create(func_, ctx, "other_arguments", + &captured_func)); + + // TODO (squadrick): check short-circuit + *output = new Dataset(ctx, input, func_, std::move(captured_func), + preserve_cardinality_); + } + + private: + class Dataset : public DatasetBase { + public: + Dataset(OpKernelContext* ctx, const DatasetBase* input, + const NameAttrList& func, + std::unique_ptr captured_func, + bool preserve_cardinality) + : DatasetBase(DatasetContext(ctx)), + input_(input), + func_(func), + captured_func_(std::move(captured_func)), + preserve_cardinality_(preserve_cardinality) { + input_->Ref(); + } + + ~Dataset() override { input_->Unref(); } + + std::unique_ptr MakeIteratorInternal( + const string& prefix) const override { + return std::unique_ptr( + new Iterator({this, strings::StrCat(prefix, "::TakeWhile")})); + } + + const DataTypeVector& output_dtypes() const override { + return input_->output_dtypes(); + } + const std::vector& output_shapes() const override { + return input_->output_shapes(); + } + + string DebugString() const override { return "TakeWhileDatasetOp::Dataset"; } + + int64 Cardinality() const override { return input_->Cardinality(); } + + protected: + Status AsGraphDefInternal(SerializationContext* ctx, + DatasetGraphDefBuilder* b, + Node** output) const override { + TF_RETURN_IF_ERROR(b->AddFunction(ctx, func_.name())); + Node* input_node; + TF_RETURN_IF_ERROR(b->AddInputDataset(ctx, input_, &input_node)); + + std::vector other_arguments; + other_arguments.reserve(captured_func_->captured_inputs().size()); + DataTypeVector other_arguments_types; + other_arguments_types.reserve(captured_func_->captured_inputs().size()); + for (const Tensor& t : captured_func_->captured_inputs()) { + Node* node; + DatasetBase* input; + Status s = GetDatasetFromVariantTensor(t, &input); + if (s.ok()) { + TF_RETURN_IF_ERROR(b->AddInputDataset(ctx, input, &node)); + } else { + TF_RETURN_IF_ERROR(b->AddTensor(t, &node)); + } + other_arguments.emplace_back(node); + other_arguments_types.emplace_back(t.dtype()); + } + AttrValue f_attr; + b->BuildAttrValue(func_, &f_attr); + + AttrValue other_arguments_types_attr; + b->BuildAttrValue(other_arguments_types, &other_arguments_types_attr); + + AttrValue preserve_cardinality_attr; + b->BuildAttrValue(preserve_cardinality_, &preserve_cardinality_attr); + + TF_RETURN_IF_ERROR(b->AddDataset( + this, {std::make_pair(0, input_node)}, + {std::make_pair(1, other_arguments)}, + {std::make_pair("predicate", f_attr), + std::make_pair("Targuments", other_arguments_types_attr), + std::make_pair("preserve_cardinality", + preserve_cardinality_attr)}, + output)); + return Status::OK(); + } + + private: + class Iterator : public DatasetIterator { + public: + explicit Iterator(const Params& params) + : DatasetIterator(params) {} + + Status Initialize(IteratorContext* ctx) override { + TF_RETURN_IF_ERROR( + dataset()->input_->MakeIterator(ctx, prefix(), &input_impl_)); + return dataset()->captured_func_->Instantiate( + ctx, &instantiated_captured_func_); + } + + Status GetNextInternal(IteratorContext* ctx, + std::vector* out_tensors, + bool* end_of_sequence) override { + { + tf_shared_lock l(mu_); + if(!input_impl_) { + *end_of_sequence = true; + return Status::OK(); + } + TF_RETURN_IF_ERROR( + input_impl_->GetNext(ctx, out_tensors, end_of_sequence)); + } + if (*end_of_sequence) { + mutex_lock l(mu_); + input_impl_.reset(); + return Status::OK(); + } + + std::vector bool_output; + + Status s = instantiated_captured_func_->RunWithBorrowedArgs( + ctx, *out_tensors, &bool_output); + + if (s.ok()) { + if(bool_output.size() != 1 || bool_output[0].dtype() != DT_BOOL || + bool_output[0].NumElements() != 1) { + return errors::InvalidArgument( + "`predicate` must returns a scalar bool tensor."); + } + auto cond = bool_output[0].scalar()(); + if (!cond) { // predicate is false + *end_of_sequence = true; + return Status::OK(); + } + } else if (errors::IsOutOfRange(s)) { + if (dataset()->preserve_cardinality_) { + // To guarantee that the transformation preserves the cardinality of + // the dataset, we convert `OutOfRange` to `InvalidArgument` as the + // former may be interpreted by a caller as the end of sequence. + return errors::InvalidArgument( + "Function invocation produced OutOfRangeError: ", + s.error_message()); + } else { + // `f` may deliberately raise `errors::OutOfRange` to indicate + // that we should terminate the iteration early. + *end_of_sequence = true; + return Status::OK(); + } + } + return s; + } + + protected: + std::shared_ptr CreateNode( + IteratorContext* ctx, model::Node::Args args) const override { + return model::MakeKnownRatioNode(std::move(args), + /*ratio=*/1); + } + + Status SaveInternal(IteratorStateWriter* writer) override { + mutex_lock l(mu_); + if (input_impl_) + TF_RETURN_IF_ERROR(SaveInput(writer, input_impl_)); + else + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name("input_impls_empty"), "")); + return Status::OK(); + } + + Status RestoreInternal(IteratorContext* ctx, + IteratorStateReader* reader) override { + mutex_lock l(mu_); + if (reader->Contains(full_name("input_impls_empty"))) + input_impl_.reset(); + else + TF_RETURN_IF_ERROR(RestoreInput(ctx, reader, input_impl_)); + return Status::OK(); + } + + private: + mutex mu_; + std::unique_ptr input_impl_ GUARDED_BY(mu_); + std::unique_ptr instantiated_captured_func_; + }; + + const DatasetBase* const input_; + const NameAttrList func_; + const std::unique_ptr captured_func_; + const bool preserve_cardinality_; + }; + + NameAttrList func_; + bool preserve_cardinality_; +}; + +REGISTER_KERNEL_BUILDER(Name("ExperimentalTakeWhileDataset").Device(DEVICE_CPU), + TakeWhileDatasetOp); + +} // namespace +} // namespace data +} // namespace tensorflow diff --git a/tensorflow/core/ops/experimental_dataset_ops.cc b/tensorflow/core/ops/experimental_dataset_ops.cc index f904e2536d..d10a13f06f 100644 --- a/tensorflow/core/ops/experimental_dataset_ops.cc +++ b/tensorflow/core/ops/experimental_dataset_ops.cc @@ -352,6 +352,17 @@ REGISTER_OP("ExperimentalStatsAggregatorSummary") .Output("summary: string") .SetShapeFn(shape_inference::ScalarShape); +REGISTER_OP("ExperimentalTakeWhileDataset") + .Input("input_dataset: variant") + .Input("other_arguments: Targuments") + .Output("handle: variant") + .Attr("predicate: func") + .Attr("Targuments: list(type) >= 0") + .Attr("output_types: list(type) >= 1") + .Attr("output_shapes: list(shape) >= 1") + .Attr("preserve_cardinality: bool = false") + .SetShapeFn(shape_inference::ScalarShape); + REGISTER_OP("ExperimentalUnbatchDataset") .Input("input_dataset: variant") .Output("handle: variant") diff --git a/tensorflow/python/data/experimental/__init__.py b/tensorflow/python/data/experimental/__init__.py index ffc2e5ef5f..016acefa1f 100644 --- a/tensorflow/python/data/experimental/__init__.py +++ b/tensorflow/python/data/experimental/__init__.py @@ -115,6 +115,7 @@ from tensorflow.python.data.experimental.ops.shuffle_ops import shuffle_and_repe from tensorflow.python.data.experimental.ops.stats_aggregator import StatsAggregator 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 from tensorflow.python.data.experimental.ops.threading_options import ThreadingOptions from tensorflow.python.data.experimental.ops.unique import unique from tensorflow.python.data.experimental.ops.writers import TFRecordWriter diff --git a/tensorflow/python/data/experimental/ops/BUILD b/tensorflow/python/data/experimental/ops/BUILD index 60c20e0bcf..95e9509678 100644 --- a/tensorflow/python/data/experimental/ops/BUILD +++ b/tensorflow/python/data/experimental/ops/BUILD @@ -354,6 +354,19 @@ py_library( ], ) +py_library( + name = "take_while_ops", + srcs = ["take_while_ops.py"], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:experimental_dataset_ops_gen", + "//tensorflow/python:framework_ops", + "//tensorflow/python:dtypes", + "//tensorflow/python:function", + "//tensorflow/python/data/ops:dataset_ops", + ], +) + py_library( name = "threading_options", srcs = ["threading_options.py"], @@ -454,6 +467,7 @@ py_library( ":shuffle_ops", ":sleep", ":stats_ops", + "take_while_ops", ":threadpool", ":unique", ":writers", diff --git a/tensorflow/python/data/experimental/ops/take_while_ops.py b/tensorflow/python/data/experimental/ops/take_while_ops.py new file mode 100644 index 0000000000..edbecd0666 --- /dev/null +++ b/tensorflow/python/data/experimental/ops/take_while_ops.py @@ -0,0 +1,79 @@ +# 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. +# ============================================================================== +"""take-while dataset transformation.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.data.util import structure as structure_lib +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import gen_experimental_dataset_ops +from tensorflow.python.util.tf_export import tf_export + + +class _TakeWhileDataset(dataset_ops.UnaryUnchangedStructureDataset): + """A dataset that stops iteration when `predicate` returns false.""" + + def __init__(self, input_dataset, predicate): + """See `take_while()` for details.""" + + self._input_dataset = input_dataset + wrapped_func = dataset_ops.StructuredFunctionWrapper( + predicate, + self._transformation_name(), + dataset=self._input_dataset) + + if not wrapped_func.output_structure.is_compatible_with( + structure_lib.TensorStructure(dtypes.bool, [])): + raise ValueError("`predicate` must return a scalar boolean tensor.") + + self._predicate = wrapped_func + + variant_tensor = gen_experimental_dataset_ops.experimental_take_while_dataset( + self._input_dataset._variant_tensor, + other_arguments=self._predicate.function.captured_inputs, + predicate=self._predicate.function, + preserve_cardinality=True, + **dataset_ops.flat_structure(self)) + super(_TakeWhileDataset, self).__init__(input_dataset, variant_tensor) + + def _functions(self): + return [self._predicate] + + def _transformation_name(self): + return "tf.data.experimental.take_while()" + + +@tf_export("data.experimental.take_while") +def take_while(predicate): + """A transformation that stops dataset iteration based on a `predicate` condition + + Args: + predicate: A function that maps a nested structure of tensors + (having shapes and types defined by `self.output_shapes` and + `self.output_types`) to a scalar `tf.bool` tensor. + + Returns: + A `Dataset` transformation function, which can be passed to + `tf.data.Dataset.apply`. + """ + def _apply_fn(dataset): + return _TakeWhileDataset(dataset, predicate) + + return _apply_fn -- GitLab From 15d49deb3c5d4048718af19a2e0e0279e8f204d5 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 1 Jan 2019 00:16:30 +0000 Subject: [PATCH 0094/2345] Update eigen library to 88fc23324517 to fix 24457 This fix updates eigen library to 88fc23324517 so that the issue raised in 24457 could be fixed. This fix fixes 24457. Signed-off-by: Yong Tang --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 11ce55feda..d32caceb9e 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -136,11 +136,11 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "eigen_archive", build_file = clean_dep("//third_party:eigen.BUILD"), - sha256 = "753fbb58d0a49b6bcbcfb126ebfa2e21fc97f7471529ba835a096008ce588d8a", - strip_prefix = "eigen-eigen-9f48e814419e", + sha256 = "9de38f2d162c51599b802f7c36d9f3773980d19ac908c61638f8344d2c10e1ca", + strip_prefix = "eigen-eigen-88fc23324517", urls = [ - "https://mirror.bazel.build/bitbucket.org/eigen/eigen/get/9f48e814419e.tar.gz", - "https://bitbucket.org/eigen/eigen/get/9f48e814419e.tar.gz", + "https://mirror.bazel.build/bitbucket.org/eigen/eigen/get/88fc23324517..tar.gz", + "https://bitbucket.org/eigen/eigen/get/88fc23324517.tar.gz", ], ) -- GitLab From fcffdde4d6a918a7971631b444e8fc0cb8b8244b Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 1 Jan 2019 19:19:48 +0530 Subject: [PATCH 0095/2345] SynchronousMemcpy changed to SynchronousMemcpyD2H and SynchronousMemcpyH2D --- .../core/common_runtime/gpu/gpu_debug_allocator.cc | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc index 989ddbe4af..7268d6cf6a 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc @@ -44,7 +44,8 @@ bool CheckMask(se::StreamExecutor* exec, void* ptr, int64* mask) { se::DeviceMemory gpu_ptr{se::DeviceMemoryBase{ptr, MASK_BYTES}}; int64 tmp[MASK_WORDS]; - if (!exec->SynchronousMemcpy(&tmp, gpu_ptr, MASK_BYTES)) { + Status result = exec->SynchronousMemcpyD2H(gpu_ptr, MASK_BYTES, tmp); + if (!result.ok()) { LOG(FATAL) << "Could not copy debug mask"; } @@ -63,7 +64,8 @@ bool CheckMask(se::StreamExecutor* exec, void* ptr, int64* mask) { void InitMask(se::StreamExecutor* exec, void* ptr, int64* mask) { se::DeviceMemory gpu_ptr{se::DeviceMemoryBase{ptr, MASK_BYTES}}; - if (!exec->SynchronousMemcpy(&gpu_ptr, mask, MASK_BYTES)) { + Status result = exec->SynchronousMemcpyH2D(mask, MASK_BYTES, &gpu_ptr); + if (!result.ok()) { LOG(FATAL) << "Could not copy debug mask"; } } @@ -171,7 +173,9 @@ void* GPUNanResetAllocator::AllocateRaw(size_t alignment, size_t num_bytes) { se::DeviceMemory nan_ptr{ se::DeviceMemoryBase{static_cast(allocated_ptr), req_size}}; - if (!stream_exec_->SynchronousMemcpy(&nan_ptr, &nans[0], req_size)) { + Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, + &nan_ptr); + if (!result.ok()) { LOG(ERROR) << "Could not initialize to NaNs"; } @@ -185,7 +189,9 @@ void GPUNanResetAllocator::DeallocateRaw(void* ptr) { std::nanf("")); se::DeviceMemory nan_ptr{ se::DeviceMemoryBase{static_cast(ptr), req_size}}; - if (!stream_exec_->SynchronousMemcpy(&nan_ptr, &nans[0], req_size)) { + Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, + &nan_ptr); + if (!result.ok()) { LOG(ERROR) << "Could not initialize to NaNs"; } } -- GitLab From 3c80e17e79c1920dbba1201d0c50a818772f7e1f Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 1 Jan 2019 19:23:23 +0530 Subject: [PATCH 0096/2345] Removed unnecessary spaces --- tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc index 7268d6cf6a..77737a76b6 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc @@ -173,7 +173,7 @@ void* GPUNanResetAllocator::AllocateRaw(size_t alignment, size_t num_bytes) { se::DeviceMemory nan_ptr{ se::DeviceMemoryBase{static_cast(allocated_ptr), req_size}}; - Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, + Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, &nan_ptr); if (!result.ok()) { LOG(ERROR) << "Could not initialize to NaNs"; @@ -189,7 +189,7 @@ void GPUNanResetAllocator::DeallocateRaw(void* ptr) { std::nanf("")); se::DeviceMemory nan_ptr{ se::DeviceMemoryBase{static_cast(ptr), req_size}}; - Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, + Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, &nan_ptr); if (!result.ok()) { LOG(ERROR) << "Could not initialize to NaNs"; -- GitLab From 5356b6898b512d3202088299ab422208fc5f8bb5 Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Tue, 1 Jan 2019 22:26:40 +0530 Subject: [PATCH 0097/2345] Make required changes Remove preserve_cardinality Update license year Propagate predicate's error to the caller Inline _transformation_name --- .../experimental/take_while_dataset_op.cc | 46 ++++--------------- .../core/ops/experimental_dataset_ops.cc | 1 - .../data/experimental/ops/take_while_ops.py | 8 +--- 3 files changed, 12 insertions(+), 43 deletions(-) diff --git a/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc index 271b919bc9..b18cc32051 100644 --- a/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc @@ -1,4 +1,4 @@ -/* 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. @@ -33,8 +33,6 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { explicit TakeWhileDatasetOp(OpKernelConstruction* ctx) : UnaryDatasetOpKernel(ctx) { OP_REQUIRES_OK(ctx, ctx->GetAttr("predicate", &func_)); - OP_REQUIRES_OK( - ctx, ctx->GetAttr("preserve_cardinality", &preserve_cardinality_)); } void MakeDataset(OpKernelContext* ctx, DatasetBase* input, @@ -44,22 +42,19 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { &captured_func)); // TODO (squadrick): check short-circuit - *output = new Dataset(ctx, input, func_, std::move(captured_func), - preserve_cardinality_); + *output = new Dataset(ctx, input, func_, std::move(captured_func)); } private: class Dataset : public DatasetBase { public: Dataset(OpKernelContext* ctx, const DatasetBase* input, - const NameAttrList& func, - std::unique_ptr captured_func, - bool preserve_cardinality) + const NameAttrList& func, + std::unique_ptr captured_func) : DatasetBase(DatasetContext(ctx)), input_(input), func_(func), - captured_func_(std::move(captured_func)), - preserve_cardinality_(preserve_cardinality) { + captured_func_(std::move(captured_func)) { input_->Ref(); } @@ -74,13 +69,14 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { const DataTypeVector& output_dtypes() const override { return input_->output_dtypes(); } + const std::vector& output_shapes() const override { return input_->output_shapes(); } string DebugString() const override { return "TakeWhileDatasetOp::Dataset"; } - int64 Cardinality() const override { return input_->Cardinality(); } + int64 Cardinality() const override { return kUnknownCardinality; } protected: Status AsGraphDefInternal(SerializationContext* ctx, @@ -112,16 +108,11 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { AttrValue other_arguments_types_attr; b->BuildAttrValue(other_arguments_types, &other_arguments_types_attr); - AttrValue preserve_cardinality_attr; - b->BuildAttrValue(preserve_cardinality_, &preserve_cardinality_attr); - TF_RETURN_IF_ERROR(b->AddDataset( this, {std::make_pair(0, input_node)}, {std::make_pair(1, other_arguments)}, {std::make_pair("predicate", f_attr), - std::make_pair("Targuments", other_arguments_types_attr), - std::make_pair("preserve_cardinality", - preserve_cardinality_attr)}, + std::make_pair("Targuments", other_arguments_types_attr)}, output)); return Status::OK(); } @@ -168,27 +159,12 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { return errors::InvalidArgument( "`predicate` must returns a scalar bool tensor."); } - auto cond = bool_output[0].scalar()(); - if (!cond) { // predicate is false - *end_of_sequence = true; - return Status::OK(); - } - } else if (errors::IsOutOfRange(s)) { - if (dataset()->preserve_cardinality_) { - // To guarantee that the transformation preserves the cardinality of - // the dataset, we convert `OutOfRange` to `InvalidArgument` as the - // former may be interpreted by a caller as the end of sequence. - return errors::InvalidArgument( - "Function invocation produced OutOfRangeError: ", - s.error_message()); - } else { - // `f` may deliberately raise `errors::OutOfRange` to indicate - // that we should terminate the iteration early. + if (!bool_output[0].scalar()()) { *end_of_sequence = true; return Status::OK(); } } - return s; + return s; // propagate error to caller } protected: @@ -227,11 +203,9 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { const DatasetBase* const input_; const NameAttrList func_; const std::unique_ptr captured_func_; - const bool preserve_cardinality_; }; NameAttrList func_; - bool preserve_cardinality_; }; REGISTER_KERNEL_BUILDER(Name("ExperimentalTakeWhileDataset").Device(DEVICE_CPU), diff --git a/tensorflow/core/ops/experimental_dataset_ops.cc b/tensorflow/core/ops/experimental_dataset_ops.cc index d10a13f06f..9ac50ffc72 100644 --- a/tensorflow/core/ops/experimental_dataset_ops.cc +++ b/tensorflow/core/ops/experimental_dataset_ops.cc @@ -360,7 +360,6 @@ REGISTER_OP("ExperimentalTakeWhileDataset") .Attr("Targuments: list(type) >= 0") .Attr("output_types: list(type) >= 1") .Attr("output_shapes: list(shape) >= 1") - .Attr("preserve_cardinality: bool = false") .SetShapeFn(shape_inference::ScalarShape); REGISTER_OP("ExperimentalUnbatchDataset") diff --git a/tensorflow/python/data/experimental/ops/take_while_ops.py b/tensorflow/python/data/experimental/ops/take_while_ops.py index edbecd0666..224eb8aa77 100644 --- a/tensorflow/python/data/experimental/ops/take_while_ops.py +++ b/tensorflow/python/data/experimental/ops/take_while_ops.py @@ -1,4 +1,4 @@ -# 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. @@ -36,7 +36,7 @@ class _TakeWhileDataset(dataset_ops.UnaryUnchangedStructureDataset): self._input_dataset = input_dataset wrapped_func = dataset_ops.StructuredFunctionWrapper( predicate, - self._transformation_name(), + "tf.data.experimental.take_while()", dataset=self._input_dataset) if not wrapped_func.output_structure.is_compatible_with( @@ -49,16 +49,12 @@ class _TakeWhileDataset(dataset_ops.UnaryUnchangedStructureDataset): self._input_dataset._variant_tensor, other_arguments=self._predicate.function.captured_inputs, predicate=self._predicate.function, - preserve_cardinality=True, **dataset_ops.flat_structure(self)) super(_TakeWhileDataset, self).__init__(input_dataset, variant_tensor) def _functions(self): return [self._predicate] - def _transformation_name(self): - return "tf.data.experimental.take_while()" - @tf_export("data.experimental.take_while") def take_while(predicate): -- GitLab From 4e07d4ae73de5632bb49514a5b486140bdd8516e Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Tue, 1 Jan 2019 23:47:28 +0530 Subject: [PATCH 0098/2345] Add take_while serialization test --- .../take_while_dataset_serialization_test.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tensorflow/python/data/experimental/kernel_tests/serialization/take_while_dataset_serialization_test.py diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/take_while_dataset_serialization_test.py b/tensorflow/python/data/experimental/kernel_tests/serialization/take_while_dataset_serialization_test.py new file mode 100644 index 0000000000..9b5498cca5 --- /dev/null +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/take_while_dataset_serialization_test.py @@ -0,0 +1,45 @@ +# 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 the TakeWhileDataset serialization.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.data.experimental.kernel_tests.serialization import dataset_serialization_test_base +from tensorflow.python.data.experimental.ops import take_while_ops +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.platform import test + + +class TakeWhileDatasetSerializationTest( + dataset_serialization_test_base.DatasetSerializationTestBase): + + def _build_dataset(self, num_elements, upper_bound): + return dataset_ops.Dataset.range(num_elements).apply( + take_while_ops.take_while(lambda x: x < upper_bound)) + + def testCore(self): + def run_test(num_elem1, num_elem2, upper_bound): + self.run_core_tests(lambda: self._build_dataset(num_elem1, upper_bound), + lambda: self._build_dataset(num_elem2, upper_bound), + upper_bound) + + run_test(23, 10, 7) + run_test(10, 50, 0) + run_test(25, 30, 25) + + +if __name__ == "__main__": + test.main() -- GitLab From ab7465e957d2f5b6f8ada982cf91266629092677 Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Wed, 2 Jan 2019 19:23:57 +0530 Subject: [PATCH 0099/2345] Add take_while tests --- .../data/experimental/kernel_tests/BUILD | 22 +++++ .../kernel_tests/serialization/BUILD | 18 ++++ .../kernel_tests/take_while_test.py | 94 +++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 tensorflow/python/data/experimental/kernel_tests/take_while_test.py diff --git a/tensorflow/python/data/experimental/kernel_tests/BUILD b/tensorflow/python/data/experimental/kernel_tests/BUILD index 9362a3e8eb..b4196685e2 100644 --- a/tensorflow/python/data/experimental/kernel_tests/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/BUILD @@ -638,6 +638,28 @@ py_library( ], ) +py_test( + name = "take_while_test", + size = "small", + srcs = ["take_while_test.py"], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:constant_op", + "//tensorflow/python:dtypes", + "//tensorflow/python:errors", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:script_ops", + "//tensorflow/python:sparse_tensor", + "//tensorflow/python/data/experimental/ops:take_while_ops", + "//tensorflow/python/data/kernel_tests:test_base", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/eager:context", + "//third_party/py/numpy", + ], +) + py_test( name = "tf_record_writer_test", size = "small", diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD index 4a2e28f496..00b05e4d30 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD @@ -666,6 +666,24 @@ py_test( ], ) +py_test( + name = "take_while_dataset_serialization_test", + size = "small", + srcs = ["take_while_dataset_serialization_test.py"], + srcs_version = "PY2AND3", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:take_while_ops", + "//tensorflow/python/data/ops:dataset_ops", + ], +) + py_test( name = "textline_dataset_serialization_test", size = "medium", diff --git a/tensorflow/python/data/experimental/kernel_tests/take_while_test.py b/tensorflow/python/data/experimental/kernel_tests/take_while_test.py new file mode 100644 index 0000000000..d561a3cd35 --- /dev/null +++ b/tensorflow/python/data/experimental/kernel_tests/take_while_test.py @@ -0,0 +1,94 @@ +# 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.data.experimental.Dataset.take_while()`.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.python.data.kernel_tests import test_base +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.data.experimental.ops import take_while_ops +from tensorflow.python.framework import errors +from tensorflow.python.framework import sparse_tensor +from tensorflow.python.framework import test_util +from tensorflow.python.framework import constant_op +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import functional_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.platform import test + + +@test_util.run_all_in_graph_and_eager_modes +class TakeWhileTest(test_base.DatasetTestBase): + + def testTakeWhileDataset(self): + def do_test(num_elements, window_size): + def predicate_func(elem): + return array_ops.shape(elem)[0] > (window_size - 1) + + flatten_func = lambda x: dataset_ops.Dataset.from_tensor_slices(x) + take_while = take_while_ops.take_while(predicate_func) + + dataset = dataset_ops.Dataset.range(num_elements).batch(window_size) + dataset = dataset.apply(take_while).flat_map(flatten_func) + + self.assertDatasetProduces(dataset, + np.arange(int(num_elements / window_size) * window_size)) + + do_test(14, 2) + do_test(15, 2) + do_test(100, 3) + + def testTakeWhileDatasetRange(self): + def get_dataset(num_elemets, upper_bound): + return dataset_ops.Dataset.range(num_elemets).apply( + take_while_ops.take_while(lambda x: x < upper_bound)) + + def do_test(num_elemets, upper_bound): + self.assertDatasetProduces(get_dataset(num_elemets, upper_bound), + np.arange(upper_bound)) + + def out_of_bounds(num_elemets, upper_bound): + with self.assertRaises(errors.OutOfRangeError): + self.assertDatasetProduces(get_dataset(num_elemets, upper_bound), + np.arange(upper_bound)) + + do_test(10, 2) + do_test(16, 7) + do_test(100, 99) + out_of_bounds(100, 101) + out_of_bounds(0, 1) + + def testTakeWhileDatasetString(self): + def stringNotEquals(string): + return lambda x: math_ops.not_equal(x, constant_op.constant(string)) + + string = ["this", "is", "the", "test", "for", "strings"] + dataset = dataset_ops.Dataset.from_tensor_slices(string).apply( + take_while_ops.take_while(stringNotEquals("test"))) + + next_element = self.getNext(dataset) + self.assertEqual(b"this", self.evaluate(next_element())) + self.assertEqual(b"is", self.evaluate(next_element())) + self.assertEqual(b"the", self.evaluate(next_element())) + + with self.assertRaises(errors.OutOfRangeError): + self.assertEqual(b"test", self.evaluate(next_element())) + + +if __name__ == "__main__": + test.main() -- GitLab From 276a456b3551a329881e0104edf8ea848d446033 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 2 Jan 2019 13:25:11 -0800 Subject: [PATCH 0100/2345] Remove unused imports --- tensorflow/contrib/tensorrt/test/identity_output_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tensorflow/contrib/tensorrt/test/identity_output_test.py b/tensorflow/contrib/tensorrt/test/identity_output_test.py index da7d70876d..b376e30476 100644 --- a/tensorflow/contrib/tensorrt/test/identity_output_test.py +++ b/tensorflow/contrib/tensorrt/test/identity_output_test.py @@ -29,9 +29,7 @@ from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.ops import array_ops -from tensorflow.python.ops import gen_array_ops from tensorflow.python.ops import math_ops -from tensorflow.python.ops import nn from tensorflow.python.platform import test -- GitLab From f92510eb61a469c0ea4652f8cd2582f71a821d80 Mon Sep 17 00:00:00 2001 From: Clayne Robison Date: Wed, 2 Jan 2019 15:25:54 -0700 Subject: [PATCH 0101/2345] [Intel MKL] Updating the public MKL Dockerfiles to reflect Ubuntu 18.04 use of python 3.6; also updating the default TensorFlow branch. --- tensorflow/tools/docker/Dockerfile.devel-mkl | 41 ++++++------------- .../tools/docker/Dockerfile.devel-mkl-horovod | 30 +++++++------- tensorflow/tools/docker/Dockerfile.mkl | 18 ++++---- .../tools/docker/Dockerfile.mkl-horovod | 26 ++++++------ 4 files changed, 47 insertions(+), 68 deletions(-) diff --git a/tensorflow/tools/docker/Dockerfile.devel-mkl b/tensorflow/tools/docker/Dockerfile.devel-mkl index 4eefd31d00..32aa00bdff 100755 --- a/tensorflow/tools/docker/Dockerfile.devel-mkl +++ b/tensorflow/tools/docker/Dockerfile.devel-mkl @@ -3,13 +3,18 @@ FROM ubuntu:18.04 LABEL maintainer="Clayne Robison " # These parameters can be overridden by parameterized_docker_build.sh -ARG TF_BUILD_VERSION=r1.12 +ARG TF_BUILD_VERSION=r1.13 ARG PYTHON="python" ARG PYTHON3_DEV="" ARG WHL_DIR="/tmp/pip" ARG PIP="pip" -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN apt-get update && apt-get install -y --no-install-recommends --fix-missing \ + ${PYTHON} \ + ${PYTHON}-dev \ + ${PYTHON}-pip \ + ${PYTHON}-setuptools \ + ${PYTHON}-wheel \ build-essential \ curl \ git \ @@ -17,35 +22,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libfreetype6-dev \ libhdf5-serial-dev \ libpng-dev \ - libzmq3-dev \ libssl-dev \ + libzmq3-dev \ + openjdk-8-jdk \ + openjdk-8-jre-headless \ pkg-config \ rsync \ software-properties-common \ unzip \ zip \ zlib1g-dev \ - openjdk-8-jdk \ - openjdk-8-jre-headless - -#install Python 3 -RUN if [ ${PYTHON} = "python3.6" ]; then \ - curl https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tar.xz -o /opt/python.tar.xz && \ - cd /opt && tar xvf python.tar.xz && \ - cd /opt/*/ && ./configure && \ - make && make install; \ - else \ - apt-get install -y --no-install-recommends \ - python-dev \ - ${PYTHON3_DEV}; \ - fi - -RUN apt-get clean && \ + && \ + apt-get clean && \ rm -rf /var/lib/apt/lists/* -RUN curl -fSsL -O https://bootstrap.pypa.io/get-pip.py && \ - ${PYTHON} get-pip.py && \ - rm get-pip.py RUN ${PIP} --no-cache-dir install \ Pillow \ @@ -57,17 +47,12 @@ RUN ${PIP} --no-cache-dir install \ matplotlib \ mock \ numpy \ + pandas \ scipy \ sklearn \ - pandas \ && \ ${PYTHON} -m ipykernel.kernelspec -RUN if [ "${PYTHON}" = "python3" ]; then \ - ln -s -f /usr/bin/python3 /usr/bin/python; \ - elif [ "${PYTHON}" = "python3.6" ]; then \ - ln -s -f /usr/local/bin/python3.6 /usr/bin/python; \ - fi # Set up our notebook config. COPY jupyter_notebook_config.py /root/.jupyter/ diff --git a/tensorflow/tools/docker/Dockerfile.devel-mkl-horovod b/tensorflow/tools/docker/Dockerfile.devel-mkl-horovod index 3810daefa5..21140918aa 100755 --- a/tensorflow/tools/docker/Dockerfile.devel-mkl-horovod +++ b/tensorflow/tools/docker/Dockerfile.devel-mkl-horovod @@ -3,42 +3,43 @@ FROM ubuntu:18.04 LABEL maintainer="Cong Xu " # These parameters can be overridden by parameterized_docker_build.sh -ARG TF_BUILD_VERSION=r1.11 +ARG TF_BUILD_VERSION=r1.13 ARG PYTHON="python" ARG PYTHON3_DEV="" ARG WHL_DIR="/tmp/pip" ARG PIP="pip" -RUN apt-get update && apt-get install -y --no-install-recommends \ + +RUN apt-get update && apt-get install -y --no-install-recommends --fix-missing \ + ${PYTHON} \ + ${PYTHON}-dev \ + ${PYTHON}-pip \ + ${PYTHON}-setuptools \ + ${PYTHON}-wheel \ build-essential \ curl \ git \ libcurl3-dev \ libfreetype6-dev \ libhdf5-serial-dev \ + libnuma-dev \ libpng-dev \ libzmq3-dev \ + openjdk-8-jdk \ + openjdk-8-jre-headless \ + openssh-client \ + openssh-server \ pkg-config \ - python-dev \ - ${PYTHON3_DEV} \ rsync \ software-properties-common \ unzip \ + wget \ zip \ zlib1g-dev \ - openjdk-8-jdk \ - openjdk-8-jre-headless \ - wget \ - libnuma-dev \ - openssh-client \ - openssh-server \ && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* -RUN curl -fSsL -O https://bootstrap.pypa.io/get-pip.py && \ - ${PYTHON} get-pip.py && \ - rm get-pip.py RUN ${PIP} --no-cache-dir install \ Pillow \ @@ -56,9 +57,6 @@ RUN ${PIP} --no-cache-dir install \ && \ ${PYTHON} -m ipykernel.kernelspec -RUN if [ "${PYTHON}" = "python3" ]; then \ - ln -s -f /usr/bin/python3 /usr/bin/python; \ - fi # Set up our notebook config. COPY jupyter_notebook_config.py /root/.jupyter/ diff --git a/tensorflow/tools/docker/Dockerfile.mkl b/tensorflow/tools/docker/Dockerfile.mkl index dad27697fa..3f7729ba59 100755 --- a/tensorflow/tools/docker/Dockerfile.mkl +++ b/tensorflow/tools/docker/Dockerfile.mkl @@ -6,13 +6,18 @@ LABEL maintainer="Clayne Robison " ARG TF_WHL_URL # Optional parameters -ARG TF_BUILD_VERSION=r1.9 +ARG TF_BUILD_VERSION=r1.13 ARG PYTHON="python" ARG PYTHON_DEV="python-dev" ARG PIP="pip" # Pick up some TF dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN apt-get update && apt-get install -y --no-install-recommends --fix-missing \ + ${PYTHON} \ + ${PYTHON}-dev \ + ${PYTHON}-pip \ + ${PYTHON}-setuptools \ + ${PYTHON}-wheel \ build-essential \ curl \ libfreetype6-dev \ @@ -20,8 +25,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libpng-dev \ libzmq3-dev \ pkg-config \ - ${PYTHON} \ - ${PYTHON_DEV} \ rsync \ software-properties-common \ unzip \ @@ -29,9 +32,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ apt-get clean && \ rm -rf /var/lib/apt/lists/* -RUN curl -O https://bootstrap.pypa.io/get-pip.py && \ - ${PYTHON} get-pip.py && \ - rm get-pip.py RUN ${PIP} --no-cache-dir install \ Pillow \ @@ -48,13 +48,11 @@ RUN ${PIP} --no-cache-dir install \ && \ ${PYTHON} -m ipykernel.kernelspec + COPY ${TF_WHL_URL} / RUN ${PIP} install --no-cache-dir --force-reinstall /${TF_WHL_URL} && \ rm -rf /${TF_WHL_URL} -RUN if [ "${PYTHON}" = "python3" ]; then \ - ln -s -f /usr/bin/python3 /usr/bin/python; \ - fi # Set up our notebook config. COPY jupyter_notebook_config.py /root/.jupyter/ diff --git a/tensorflow/tools/docker/Dockerfile.mkl-horovod b/tensorflow/tools/docker/Dockerfile.mkl-horovod index 19dc45c62c..b0afd63727 100755 --- a/tensorflow/tools/docker/Dockerfile.mkl-horovod +++ b/tensorflow/tools/docker/Dockerfile.mkl-horovod @@ -6,36 +6,36 @@ LABEL maintainer="Cong Xu " ARG TF_WHL_URL # Optional parameters -ARG TF_BUILD_VERSION=r1.11 +ARG TF_BUILD_VERSION=r1.13 ARG PYTHON="python" ARG PYTHON_DEV="python-dev" ARG PIP="pip" # Pick up some TF dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ +# RUN apt-get update && apt-get install -y --no-install-recommends --fix-missing \ + ${PYTHON} \ + ${PYTHON}-dev \ + ${PYTHON}-pip \ + ${PYTHON}-setuptools \ + ${PYTHON}-wheel \ build-essential \ curl \ libfreetype6-dev \ libhdf5-serial-dev \ + libnuma-dev \ libpng-dev \ libzmq3-dev \ + openssh-client \ + openssh-server \ pkg-config \ - python \ - ${PYTHON_DEV} \ rsync \ software-properties-common \ unzip \ wget \ - libnuma-dev \ - openssh-client \ - openssh-server \ && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* -RUN curl -O https://bootstrap.pypa.io/get-pip.py && \ - python get-pip.py && \ - rm get-pip.py RUN ${PIP} --no-cache-dir install \ Pillow \ @@ -50,15 +50,13 @@ RUN ${PIP} --no-cache-dir install \ scipy \ sklearn \ && \ - python -m ipykernel.kernelspec + ${PYTHON} -m ipykernel.kernelspec + COPY ${TF_WHL_URL} / RUN ${PIP} install --no-cache-dir --force-reinstall /${TF_WHL_URL} && \ rm -rf /${TF_WHL_URL} -RUN if [ "${PYTHON}" = "python3" ]; then \ - ln -s -f /usr/bin/python3 /usr/bin/python; \ - fi # Set up our notebook config. COPY jupyter_notebook_config.py /root/.jupyter/ -- GitLab From 85ca0e9b815fc8438f1c10379c18393783021e15 Mon Sep 17 00:00:00 2001 From: "Joshua V. Dillon" Date: Wed, 2 Jan 2019 14:31:31 -0800 Subject: [PATCH 0102/2345] Remove tf.contrib.timeseries dependency on TF distributions. PiperOrigin-RevId: 227582617 --- .../timeseries/python/timeseries/BUILD | 3 +- .../timeseries/python/timeseries/ar_model.py | 19 ++++--------- .../python/timeseries/math_utils.py | 28 +++++++++++++++++++ .../timeseries/state_space_models/BUILD | 2 -- .../filtering_postprocessor.py | 8 ++---- .../state_space_models/kalman_filter.py | 9 +++--- 6 files changed, 43 insertions(+), 26 deletions(-) diff --git a/tensorflow/contrib/timeseries/python/timeseries/BUILD b/tensorflow/contrib/timeseries/python/timeseries/BUILD index 4b90b596b2..a0c3204d41 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/BUILD +++ b/tensorflow/contrib/timeseries/python/timeseries/BUILD @@ -361,9 +361,10 @@ py_library( srcs_version = "PY2AND3", deps = [ ":feature_keys", + ":math_utils", ":model", ":model_utils", - "//tensorflow/contrib/distributions:distributions_py", + "//tensorflow/contrib/rnn:rnn_py", "//tensorflow/python:array_ops", "//tensorflow/python:check_ops", "//tensorflow/python:constant_op", diff --git a/tensorflow/contrib/timeseries/python/timeseries/ar_model.py b/tensorflow/contrib/timeseries/python/timeseries/ar_model.py index bcadf4094e..a8d5e1a49d 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/ar_model.py +++ b/tensorflow/contrib/timeseries/python/timeseries/ar_model.py @@ -18,9 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib import distributions - from tensorflow.contrib.rnn.python.ops import lstm_ops +from tensorflow.contrib.timeseries.python.timeseries import math_utils from tensorflow.contrib.timeseries.python.timeseries import model from tensorflow.contrib.timeseries.python.timeseries import model_utils from tensorflow.contrib.timeseries.python.timeseries.feature_keys import PredictionFeatures @@ -462,8 +461,8 @@ class ARModel(model.TimeSeriesModel): if self.loss == ARModel.NORMAL_LIKELIHOOD_LOSS: covariance = prediction_ops["covariance"] sigma = math_ops.sqrt(gen_math_ops.maximum(covariance, 1e-5)) - normal = distributions.Normal(loc=targets, scale=sigma) - loss_op = -math_ops.reduce_sum(normal.log_prob(prediction)) + loss_op = -math_ops.reduce_sum( + math_utils.normal_log_prob(targets, sigma, prediction)) else: assert self.loss == ARModel.SQUARED_LOSS, self.loss loss_op = math_ops.reduce_sum(math_ops.square(prediction - targets)) @@ -965,16 +964,11 @@ class AnomalyMixtureARModel(ARModel): anomaly_variance = prediction_ops["anomaly_params"] anomaly_sigma = math_ops.sqrt( gen_math_ops.maximum(anomaly_variance, 1e-5)) - normal = distributions.Normal(loc=targets, scale=anomaly_sigma) - log_prob = normal.log_prob(prediction) + log_prob = math_utils.normal_log_prob(targets, anomaly_sigma, prediction) else: assert self._anomaly_distribution == AnomalyMixtureARModel.CAUCHY_ANOMALY anomaly_scale = prediction_ops["anomaly_params"] - cauchy = distributions.StudentT( - df=array_ops.ones([], dtype=anomaly_scale.dtype), - loc=targets, - scale=anomaly_scale) - log_prob = cauchy.log_prob(prediction) + log_prob = math_utils.cauchy_log_prob(targets, anomaly_scale, prediction) return log_prob def loss_op(self, targets, prediction_ops): @@ -983,8 +977,7 @@ class AnomalyMixtureARModel(ARModel): covariance = prediction_ops["covariance"] # Normal data log probability. sigma = math_ops.sqrt(gen_math_ops.maximum(covariance, 1e-5)) - normal1 = distributions.Normal(loc=targets, scale=sigma) - log_prob1 = normal1.log_prob(prediction) + log_prob1 = math_utils.normal_log_prob(targets, sigma, prediction) log_prob1 += math_ops.log(1 - self._anomaly_prior_probability) # Anomaly log probability. log_prob2 = self._anomaly_log_prob(targets, prediction_ops) diff --git a/tensorflow/contrib/timeseries/python/timeseries/math_utils.py b/tensorflow/contrib/timeseries/python/timeseries/math_utils.py index aab3306438..b7375e5055 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/math_utils.py +++ b/tensorflow/contrib/timeseries/python/timeseries/math_utils.py @@ -21,6 +21,8 @@ from __future__ import print_function import collections import math +import numpy as np + from tensorflow.contrib import lookup from tensorflow.contrib.layers.python.layers import layers @@ -43,6 +45,32 @@ from tensorflow.python.ops import variable_scope from tensorflow.python.util import nest +def normal_log_prob(loc, scale, x): + """Computes the Normal log pdf.""" + z = (x - loc) / scale + return -0.5 * (math_ops.square(z) + + np.log(2. * np.pi) + math_ops.log(scale)) + + +def cauchy_log_prob(loc, scale, x): + """Computes the Cauchy log pdf.""" + z = (x - loc) / scale + return (-np.log(np.pi) - math_ops.log(scale) - + math_ops.log1p(math_ops.square(z))) + + +def mvn_tril_log_prob(loc, scale_tril, x): + """Computes the MVN log pdf under tril scale. Doesn't handle batches.""" + x0 = x - loc + z = linalg_ops.matrix_triangular_solve( + scale_tril, x0[..., array_ops.newaxis])[..., 0] + log_det_cov = 2. * math_ops.reduce_sum(math_ops.log( + array_ops.matrix_diag_part(scale_tril)), axis=-1) + d = math_ops.cast(array_ops.shape(scale_tril)[-1], log_det_cov.dtype) + return -0.5 * (math_ops.reduce_sum(math_ops.square(z), axis=-1) + + d * np.log(2. * np.pi) + log_det_cov) + + def clip_covariance( covariance_matrix, maximum_variance_ratio, minimum_variance): """Enforce constraints on a covariance matrix to improve numerical stability. diff --git a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/BUILD b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/BUILD index 125750e763..cf5e749042 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/BUILD +++ b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/BUILD @@ -78,7 +78,6 @@ py_library( srcs = ["kalman_filter.py"], srcs_version = "PY2AND3", deps = [ - "//tensorflow/contrib/distributions:distributions_py", "//tensorflow/contrib/timeseries/python/timeseries:math_utils", "//tensorflow/python:array_ops", "//tensorflow/python:control_flow_ops", @@ -235,7 +234,6 @@ py_library( srcs = ["filtering_postprocessor.py"], srcs_version = "PY2AND3", deps = [ - "//tensorflow/contrib/distributions:distributions_py", "//tensorflow/contrib/timeseries/python/timeseries:math_utils", "//tensorflow/python:array_ops", "//tensorflow/python:check_ops", diff --git a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/filtering_postprocessor.py b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/filtering_postprocessor.py index e9e2ac0aaf..3fa2fbd9f7 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/filtering_postprocessor.py +++ b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/filtering_postprocessor.py @@ -22,8 +22,6 @@ import abc import six -from tensorflow.contrib import distributions - from tensorflow.contrib.timeseries.python.timeseries import math_utils from tensorflow.python.framework import dtypes @@ -91,10 +89,10 @@ def cauchy_alternative_to_gaussian(current_times, current_values, outputs): """ del current_times # unused cauchy_scale = math_utils.entropy_matched_cauchy_scale(outputs["covariance"]) - individual_log_pdfs = distributions.StudentT( - df=array_ops.ones([], dtype=current_values.dtype), + individual_log_pdfs = math_utils.cauchy_log_prob( loc=outputs["mean"], - scale=cauchy_scale).log_prob(current_values) + scale=cauchy_scale, + x=current_values) return math_ops.reduce_sum(individual_log_pdfs, axis=1) diff --git a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/kalman_filter.py b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/kalman_filter.py index a614386121..c0ec797bc5 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/kalman_filter.py +++ b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/kalman_filter.py @@ -18,8 +18,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib import distributions - from tensorflow.contrib.timeseries.python.timeseries import math_utils from tensorflow.python.framework import dtypes @@ -137,9 +135,10 @@ class KalmanFilter(object): with ops.control_dependencies([non_negative_assert]): observation_covariance_cholesky = linalg_ops.cholesky( symmetrized_observation_covariance) - log_prediction_prob = distributions.MultivariateNormalTriL( - predicted_observation, observation_covariance_cholesky).log_prob( - observation) + log_prediction_prob = math_utils.mvn_tril_log_prob( + loc=predicted_observation, + scale_tril=observation_covariance_cholesky, + x=observation) (posterior_state, posterior_state_var) = self.posterior_from_prior_state( prior_state=estimated_state, -- GitLab From 790a635a044c9b33103f1e02f57faf08c86eee36 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Wed, 2 Jan 2019 14:53:55 -0800 Subject: [PATCH 0103/2345] Stop Model.save_weights from printing a deprication warning (TF format) Also has it save relative paths in the checkpoint proto, which removes a behavior difference between it and CheckpointManager. PiperOrigin-RevId: 227586345 --- tensorflow/python/keras/engine/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py index 0837ce6780..8e130aef40 100644 --- a/tensorflow/python/keras/engine/network.py +++ b/tensorflow/python/keras/engine/network.py @@ -1382,9 +1382,10 @@ class Network(base_layer.Layer): % (optimizer,)) self._checkpointable_saver.save(filepath, session=session) # Record this checkpoint so it's visible from tf.train.latest_checkpoint. - checkpoint_management.update_checkpoint_state( + checkpoint_management.update_checkpoint_state_internal( save_dir=os.path.dirname(filepath), model_checkpoint_path=filepath, + save_relative_paths=True, all_model_checkpoint_paths=[filepath]) def load_weights(self, filepath, by_name=False): -- GitLab From a7a3bbfcbf53381631dded0e293031e93d056ebc Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Wed, 2 Jan 2019 15:00:14 -0800 Subject: [PATCH 0104/2345] [Grappler] Cleanup function/function_optimizer 1. Migrate to absl containers 2. Compute function signature hash without allocating temporary sorted containers PiperOrigin-RevId: 227587343 --- tensorflow/core/grappler/optimizers/BUILD | 2 + .../grappler/optimizers/function_optimizer.cc | 152 +++++++++--------- .../grappler/optimizers/meta_optimizer.cc | 19 +-- tensorflow/core/grappler/utils/BUILD | 4 + tensorflow/core/grappler/utils/functions.cc | 80 ++++----- tensorflow/core/grappler/utils/functions.h | 52 +++--- .../core/grappler/utils/functions_test.cc | 6 +- 7 files changed, 155 insertions(+), 160 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 3a1ab28dae..512c3b07b4 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -151,6 +151,8 @@ cc_library( "//tensorflow/core/grappler:utils", "//tensorflow/core/grappler/utils:functions", "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", ], diff --git a/tensorflow/core/grappler/optimizers/function_optimizer.cc b/tensorflow/core/grappler/optimizers/function_optimizer.cc index 73c950b3fc..d074676c3d 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer.cc @@ -15,10 +15,11 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/function_optimizer.h" -#include #include #include "absl/algorithm/container.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/strings/str_replace.h" #include "absl/strings/substitute.h" @@ -163,10 +164,10 @@ struct FunctionSpecializationSignature { string func_name; bool is_in_fetch_set; - gtl::FlatSet active_outputs; - std::unordered_map type_parameters; - std::unordered_map body_parameters; - std::unordered_map const_inputs; + absl::flat_hash_set active_outputs; + absl::flat_hash_map type_parameters; + absl::flat_hash_map body_parameters; + absl::flat_hash_map const_inputs; bool operator==(const FunctionSpecializationSignature& other) const { bool equals = func_name == other.func_name && @@ -189,48 +190,45 @@ struct FunctionSpecializationSignature { return true; } - // TODO(ezhulenev): Migrate to AbslHashValue. - // TODO(ezhulenev): Optimize performance by computing hashes of unordered - // values first, and then compute a hash of sorted hashes. - struct Hash { - uint64 operator()(FunctionSpecializationSignature const& s) const { - uint64 h = Hash64(s.func_name); - h = Hash64Combine(std::hash()(s.is_in_fetch_set), h); - - // Use std::set/std::map for deterministic iteration order. - - std::set active_outputs(s.active_outputs.begin(), - s.active_outputs.end()); - for (const auto& active_output : active_outputs) { - h = Hash64Combine(std::hash()(active_output), h); - } - - std::map types(s.type_parameters.begin(), - s.type_parameters.end()); - for (const auto& pair : types) { - AttrValue attr_value; - attr_value.set_type(pair.second); - h = Hash64Combine(Hash64(pair.first), h); - h = Hash64Combine(AttrValueHash(attr_value), h); - } - - std::map body(s.body_parameters.begin(), - s.body_parameters.end()); - for (const auto& pair : body) { - h = Hash64Combine(Hash64(pair.first), h); - h = Hash64Combine(FastAttrValueHash(pair.second), h); - } - - std::map inputs(s.const_inputs.begin(), - s.const_inputs.end()); - for (const auto& pair : inputs) { - h = Hash64Combine(std::hash()(pair.first), h); - h = Hash64Combine(Hash64(pair.second), h); - } - - return h; - } - }; + template + friend H AbslHashValue(H h, const FunctionSpecializationSignature& s) { + H base = H::combine(std::move(h), s.func_name, s.is_in_fetch_set); + + // First pre-compute hashes for all values in collections with + // non-deterministic iteration order. + std::vector hashes; + hashes.reserve(s.active_outputs.size() // + + s.type_parameters.size() * 2 // + + s.body_parameters.size() * 2 // + + s.const_inputs.size() * 2); + + absl::c_transform(s.active_outputs, std::back_inserter(hashes), + hash()); + + using TypeParam = std::pair; + absl::c_for_each(s.type_parameters, [&hashes](const TypeParam& type_param) { + AttrValue attr_value; + attr_value.set_type(type_param.second); + hashes.push_back(Hash64(type_param.first)); + hashes.push_back(AttrValueHash(attr_value)); + }); + + using BodyParam = std::pair; + absl::c_for_each(s.body_parameters, [&hashes](const BodyParam& body_param) { + hashes.push_back(Hash64(body_param.first)); + hashes.push_back(FastAttrValueHash(body_param.second)); + }); + + using ConstInput = std::pair; + absl::c_for_each(s.const_inputs, [&hashes](const ConstInput& const_input) { + hashes.push_back(hash()(const_input.first)); + hashes.push_back(Hash64(const_input.second)); + }); + + // Combine all pre-computed hashes in a deterministic order. + absl::c_sort(hashes); + return H::combine_contiguous(std::move(base), hashes.data(), hashes.size()); + } }; struct FunctionSpecialization { @@ -238,13 +236,13 @@ struct FunctionSpecialization { // True if the function caller node is in GrapplerItem fetch set. bool is_in_fetch_set; // Names of the tensors that were pushed down into the function body. - gtl::FlatSet const_inputs; + absl::flat_hash_set const_inputs; // Control dependencies of pushed down const inputs have to be attached to // function caller node. - gtl::FlatSet control_deps; + absl::flat_hash_set control_deps; // Output tensors (ports) that consumed by other nodes in the graph or in a // GrapplerItem fetch set. - gtl::FlatSet active_outputs; + absl::flat_hash_set active_outputs; // Mapping from original function output port to the output port of // specialized function. If function specialization changes the number of // function outputs it's required to update all node consumers. @@ -285,12 +283,13 @@ class FunctionOptimizerContext { return flr_; } - const gtl::FlatMap& + const absl::flat_hash_map& tensor_mapping() const { return tensor_mapping_; } - const gtl::FlatMap>& control_overrides() const { + const absl::flat_hash_map>& control_overrides() + const { return control_overrides_; } @@ -298,7 +297,9 @@ class FunctionOptimizerContext { const string& grappler_item_id() const { return grappler_item_id_; } - const gtl::FlatSet& fetch_tensors() const { return fetch_tensors_; } + const absl::flat_hash_set& fetch_tensors() const { + return fetch_tensors_; + } const DeviceSet* devices() const { // Create fake devices lazily only if we need a DeviceSet. @@ -365,7 +366,7 @@ class FunctionOptimizerContext { private: void InitializeTrulyConstNodes(const GrapplerItem& item) { - gtl::FlatSet feed_nodes; + absl::flat_hash_set feed_nodes; for (const auto& feed : item.feed) { feed_nodes.insert(NodeName(feed.first)); } @@ -411,7 +412,7 @@ class FunctionOptimizerContext { FunctionLibraryRuntime* flr_ = nullptr; // Fully defined names of the devices available to the GrapplerItem. - const gtl::FlatSet available_device_names_; + const absl::flat_hash_set available_device_names_; // List of available `FakedDevices` (lazily initialized, see devices()). mutable std::vector> available_devices_; @@ -421,16 +422,15 @@ class FunctionOptimizerContext { mutable DeviceSet available_device_set_; // Nodes that are Const and not in feed. - std::unordered_map truly_const_nodes_; + absl::flat_hash_map truly_const_nodes_; // Specialized functions. - std::unordered_map + absl::flat_hash_map specialized_functions_; // GrapplerItem.fetch is a vector of tensors. - gtl::FlatSet fetch_tensors_; // format: node_name:port - gtl::FlatSet fetch_nodes_; // format: node_name + absl::flat_hash_set fetch_tensors_; // format: node_name:port + absl::flat_hash_set fetch_nodes_; // format: node_name // After function inlining and specialization, the optimized graph might be in // invalid state, nodes can read from non-existing function call nodes that @@ -439,7 +439,7 @@ class FunctionOptimizerContext { // // Tensor mapping that has to be applied to the graph after all functions // optimizations (invalidated tensor id -> optimized graph tensor id). - gtl::FlatMap + absl::flat_hash_map tensor_mapping_; // When we inline a function into the optimized graph, we no longer have the @@ -448,7 +448,7 @@ class FunctionOptimizerContext { // to all side-effectful ops inside the function body. // // Invalidated function call node name -> Inlined side-effectful nodes - gtl::FlatMap> control_overrides_; + absl::flat_hash_map> control_overrides_; // Use graph view to find active outputs of the function caller nodes. GraphView graph_view_; @@ -472,10 +472,10 @@ const FunctionDef* FindFunctionCall(const FunctionOptimizerContext& ctx, return ctx.function_library().Find(node.op()); } -gtl::FlatSet GetActiveOutputs(const NodeDef& node, - const FunctionOptimizerContext& ctx, - int size_hint = 0) { - gtl::FlatSet active_outputs; +absl::flat_hash_set GetActiveOutputs(const NodeDef& node, + const FunctionOptimizerContext& ctx, + int size_hint = 0) { + absl::flat_hash_set active_outputs; active_outputs.reserve(static_cast(size_hint)); // 1. Output can be consumed by the other graph node. @@ -508,7 +508,7 @@ bool HasUnusedOutputs(const NodeDef& func_node, const FunctionDef& func, // number of output args is the same as number of possible function caller // node outputs. int num_outputs = func.signature().output_arg_size(); - const gtl::FlatSet active_outputs = + const absl::flat_hash_set active_outputs = GetActiveOutputs(func_node, ctx, /*size_hind*/ num_outputs); return active_outputs.size() != num_outputs; @@ -519,7 +519,7 @@ bool HasUnusedOutputs(const NodeDef& func_node, const FunctionDef& func, FunctionDefLibrary PruneFunctionLibrary(const FunctionLibraryDefinition& flib, const GraphDef& optimized_graph) { FunctionLibraryDefinition pruned_flib = - ReachableFunctionLibraryDefinition(flib, optimized_graph); + flib.ReachableDefinitions(optimized_graph); int pruned_functions = static_cast(pruned_flib.num_functions()) - static_cast(flib.num_functions()); @@ -534,8 +534,8 @@ FunctionDefLibrary PruneFunctionLibrary(const FunctionLibraryDefinition& flib, Status PushDownConstInputs(const NodeDef& func_node, const FunctionOptimizerContext& ctx, GrapplerFunctionItem* item, - gtl::FlatSet* const_inputs, - gtl::FlatSet* control_deps) { + absl::flat_hash_set* const_inputs, + absl::flat_hash_set* control_deps) { // Record node control dependencies in the control_deps set. const auto record_control_deps = [&](const NodeDef* const_input) { for (int i = const_input->input_size() - 1; i >= 0; --i) { @@ -585,7 +585,7 @@ void RemovePushedDownConstInputs(const FunctionSpecialization& specialization, // Attach control dependencies of pushed down const input to the caller node. if (!specialization.control_deps.empty()) { - gtl::FlatSet existing_control_deps; + absl::flat_hash_set existing_control_deps; for (const string& input : keep_inputs) { existing_control_deps.insert(AsControlDependency(NodeName(input))); @@ -797,8 +797,8 @@ Status SpecializeFunction(const NodeDef& func_node, const FunctionDef& func, // Push const inputs into the function body, and keep track of their control // dependencies. - gtl::FlatSet const_inputs; - gtl::FlatSet control_deps; + absl::flat_hash_set const_inputs; + absl::flat_hash_set control_deps; TF_RETURN_IF_ERROR(PushDownConstInputs(func_node, *ctx, &item, &const_inputs, &control_deps)); @@ -1005,7 +1005,7 @@ Status InlineDirectFunctionCall(const NodeDef& func_node, // Mapping from input placeholder name to function input position. int idx = 0; - std::unordered_map input_placeholders_idx; + absl::flat_hash_map input_placeholders_idx; for (const InputArgExpansion& input_arg : item.inputs()) { for (const string& placeholder : input_arg.placeholders) { input_placeholders_idx[placeholder] = idx++; @@ -1699,7 +1699,7 @@ Status FunctionOptimizer::Optimize(Cluster*, const GrapplerItem& item, if (!ctx.control_overrides().empty()) { for (NodeDef& node : *optimized_graph->mutable_node()) { // Keep track of new control inputs to the node. - gtl::FlatSet add_ctrl_inputs; + absl::flat_hash_set add_ctrl_inputs; // Remove all invalidated control inputs. for (int idx = 0; idx < node.input_size(); /* see below */) { diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer.cc b/tensorflow/core/grappler/optimizers/meta_optimizer.cc index 67699b093d..a84bb1d62f 100644 --- a/tensorflow/core/grappler/optimizers/meta_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/meta_optimizer.cc @@ -427,6 +427,14 @@ Status MetaOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, VLOG(1) << "Starting optimization for grappler item: " << item.id; optimization_results_.clear(); + // Constructs a FunctionLibraryDefinition with functions that are reachable + // from the nodes of the graph. + const auto minimized_flib = + [](const GraphDef& graph) -> FunctionLibraryDefinition { + return FunctionLibraryDefinition(OpRegistry::Global(), graph.library()) + .ReachableDefinitions(graph); + }; + // 0. Original graph might contain a huge function library, that is mostly // unused. This library copied over by each individual Grappler optimizer, // which adds a huge overhead. Before starting optimization passes we just @@ -436,11 +444,7 @@ Status MetaOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, GraphDef trimmed_graph; // do not copy graph with a potentially huge library *trimmed_graph.mutable_node() = item.graph.node(); *trimmed_graph.mutable_versions() = item.graph.versions(); - *trimmed_graph.mutable_library() = - grappler::ReachableFunctionLibraryDefinition( - FunctionLibraryDefinition(OpRegistry::Global(), item.graph.library()), - item.graph) - .ToProto(); + *trimmed_graph.mutable_library() = minimized_flib(item.graph).ToProto(); GrapplerItem trimmed_item = item.WithGraph(std::move(trimmed_graph)); @@ -472,10 +476,7 @@ Status MetaOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, } // 2. Optimize functions reachable from the optimized graph. - FunctionLibraryDefinition flib = ReachableFunctionLibraryDefinition( - FunctionLibraryDefinition(OpRegistry::Global(), - optimized_graph->library()), - *optimized_graph); + FunctionLibraryDefinition flib = minimized_flib(*optimized_graph); // Find functions for which we might need to compute a gradient at runtime. absl::flat_hash_set differentiable_functions; diff --git a/tensorflow/core/grappler/utils/BUILD b/tensorflow/core/grappler/utils/BUILD index cd69cf895c..89417f85c2 100644 --- a/tensorflow/core/grappler/utils/BUILD +++ b/tensorflow/core/grappler/utils/BUILD @@ -178,6 +178,9 @@ cc_library( "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler:utils", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", ], ) @@ -196,6 +199,7 @@ tf_cc_test( "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", + "@com_google_absl//absl/container:flat_hash_map", ], ) diff --git a/tensorflow/core/grappler/utils/functions.cc b/tensorflow/core/grappler/utils/functions.cc index f2894a942b..7c2180ae40 100644 --- a/tensorflow/core/grappler/utils/functions.cc +++ b/tensorflow/core/grappler/utils/functions.cc @@ -14,8 +14,9 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/grappler/utils/functions.h" -#include - +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/str_cat.h" #include "absl/strings/substitute.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/function.h" @@ -28,7 +29,6 @@ limitations under the License. #include "tensorflow/core/framework/versions.pb.h" #include "tensorflow/core/grappler/op_types.h" #include "tensorflow/core/grappler/utils.h" -#include "tensorflow/core/lib/gtl/map_util.h" #include "tensorflow/core/lib/strings/scanner.h" namespace tensorflow { @@ -76,16 +76,6 @@ Status ResolveFunctionBodyNodeAttrPlaceholders( } // namespace -FunctionLibraryDefinition ReachableFunctionLibraryDefinition( - const FunctionLibraryDefinition& flib, const GraphDef& graph) { - return flib.ReachableDefinitions(graph); -} - -FunctionLibraryDefinition ReachableFunctionLibraryDefinition( - const FunctionLibraryDefinition& flib, const FunctionDef& func) { - return flib.ReachableDefinitions(func); -} - void GrapplerFunctionConnectivity::RegisterInputArgExpansion( InputArgExpansion input_arg_expansion) { string input_name = input_arg_expansion.input_name; @@ -94,7 +84,7 @@ void GrapplerFunctionConnectivity::RegisterInputArgExpansion( for (int i = 0; i < placeholders.size(); ++i) { const string& placeholder = input_arg_expansion.placeholders[i]; input_arg_placeholders_.insert( - {placeholder, InputArgPlaceholder{input_name, /*input_position=*/i}}); + {placeholder, InputArgPlaceholder{input_name, /*input_index=*/i}}); } input_arg_expansions_.insert( {std::move(input_name), std::move(input_arg_expansion)}); @@ -193,7 +183,7 @@ Status GrapplerFunctionConnectivity::ExpandFunctionDefInput( // If position is not defined expand node output range for (int i = output_range.first; i < output_range.second; ++i) { graph_def_inputs->push_back( - i == 0 ? node_name : strings::StrCat(node_name, ":", i)); + i == 0 ? node_name : absl::StrCat(node_name, ":", i)); } } else { if (position > (output_range.second - output_range.first)) { @@ -203,7 +193,7 @@ Status GrapplerFunctionConnectivity::ExpandFunctionDefInput( } int pos = output_range.first + position; graph_def_inputs->push_back( - pos == 0 ? node_name : strings::StrCat(node_name, ":", pos)); + pos == 0 ? node_name : absl::StrCat(node_name, ":", pos)); } return Status::OK(); @@ -232,39 +222,39 @@ Status GrapplerFunctionConnectivity::ExpandNodeInputs( Status GrapplerFunctionConnectivity::AsFunctionDefInput( const string& graph_def_input, string* func_def_input) const { - using gtl::FindOrNull; - if (IsControlInput(graph_def_input)) { *func_def_input = graph_def_input; return Status::OK(); } - int position; - string node_name = ParseNodeName(graph_def_input, &position); - CHECK_GE(position, 0); + const TensorId tensor = ParseTensorName(graph_def_input); + DCHECK_GE(tensor.index(), 0); + + const absl::string_view node_name = tensor.node(); + const int index = tensor.index(); // Check if it's an input arg placeholder - if (position == 0) { - const InputArgPlaceholder* placeholder = - FindOrNull(input_arg_placeholders_, node_name); - if (placeholder != nullptr) { - *func_def_input = strings::StrCat(placeholder->input_name, ":", - placeholder->input_position); + if (tensor.index() == 0) { + const auto is_input_placeholder = input_arg_placeholders_.find(node_name); + if (is_input_placeholder != input_arg_placeholders_.end()) { + const InputArgPlaceholder& placeholder = is_input_placeholder->second; + *func_def_input = + absl::StrCat(placeholder.input_name, ":", placeholder.input_index); return Status::OK(); } } // It must be output from one of the function body nodes - const tensorflow::NameRangeMap* outputs_range_map = - FindOrNull(function_body_outputs_, node_name); - if (outputs_range_map != nullptr) { - for (const auto& el : *outputs_range_map) { + const auto is_body_output = function_body_outputs_.find(tensor.node()); + if (is_body_output != function_body_outputs_.end()) { + const tensorflow::NameRangeMap& outputs_range_map = is_body_output->second; + + for (const auto& el : outputs_range_map) { const auto& output_name = el.first; const auto& output_range = el.second; - if (position >= output_range.first && position < output_range.second) { - int pos = position - output_range.first; - *func_def_input = - strings::StrCat(node_name, ":", output_name, ":", pos); + if (index >= output_range.first && index < output_range.second) { + int pos = index - output_range.first; + *func_def_input = absl::StrCat(node_name, ":", output_name, ":", pos); return Status::OK(); } } @@ -426,7 +416,7 @@ bool IsParametrized(const FunctionDef& func) { Status InstantiationTypeParameters( const FunctionDef& func, const AttrSlice& func_instantiation_attr, - std::unordered_map* type_parameters) { + absl::flat_hash_map* type_parameters) { if (!type_parameters->empty()) { return errors::InvalidArgument("Type parameters output map must be empty"); } @@ -454,7 +444,7 @@ Status InstantiationTypeParameters( Status InstantiationBodyParameters( const FunctionDef& func, const AttrSlice& func_instantiation_attr, - std::unordered_map* body_parameters) { + absl::flat_hash_map* body_parameters) { if (!body_parameters->empty()) { return errors::InvalidArgument("Body parameters output map must be empty"); } @@ -514,8 +504,7 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, // Function body shares the library with the graph that instantiated it. We do // not need a full copy of the function library, just the reachable subset. - *function_body.mutable_library() = - ReachableFunctionLibraryDefinition(flib, func).ToProto(); + *function_body.mutable_library() = flib.ReachableDefinitions(func).ToProto(); VLOG(3) << absl::Substitute( "Deleted $0 unreachable functions from the Grappler function item " @@ -645,7 +634,7 @@ Status RegisterGrapplerFunctionConnectivity( return Status::OK(); } -Status ReplaceInputWithConst(const NodeDef& input_const, int input_position, +Status ReplaceInputWithConst(const NodeDef& input_const, int input_index, GrapplerFunctionItem* item) { if (!IsConstant(input_const)) { return errors::InvalidArgument("Input node ", input_const.name(), @@ -657,7 +646,7 @@ Status ReplaceInputWithConst(const NodeDef& input_const, int input_position, // Find input arg expansion and input placeholder position in it for the // given function input position. InputArgExpansion* input_arg_expansion = nullptr; - int placeholder_idx = input_position; + int placeholder_idx = input_index; for (InputArgExpansion& input : inputs) { if (placeholder_idx < input.placeholders.size()) { @@ -668,9 +657,8 @@ Status ReplaceInputWithConst(const NodeDef& input_const, int input_position, } if (input_arg_expansion == nullptr) { - return errors::InvalidArgument( - "Input placeholder not found: input_position=", input_position, - " function=", item->id); + return errors::InvalidArgument("Input placeholder not found: input_index=", + input_index, " function=", item->id); } // Delete placeholder from input expansion. @@ -699,7 +687,7 @@ Status ReplaceInputWithConst(const NodeDef& input_const, int input_position, return Status::OK(); } -Status RemoveUnusedOutputs(const gtl::FlatSet& active_outputs, +Status RemoveUnusedOutputs(const absl::flat_hash_set& active_outputs, GrapplerFunctionItem* item, std::vector>* output_mapping) { DCHECK(output_mapping->empty()); @@ -713,7 +701,7 @@ Status RemoveUnusedOutputs(const gtl::FlatSet& active_outputs, } } - gtl::FlatSet unused_output_args; + absl::flat_hash_set unused_output_args; const auto is_unused_output_arg = [&](const OutputArgExpansion& output) { return unused_output_args.find(&output) != unused_output_args.end(); diff --git a/tensorflow/core/grappler/utils/functions.h b/tensorflow/core/grappler/utils/functions.h index 038cf5f527..ce8a3e5ac7 100644 --- a/tensorflow/core/grappler/utils/functions.h +++ b/tensorflow/core/grappler/utils/functions.h @@ -18,7 +18,9 @@ limitations under the License. #include #include -#include + +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/function.pb.h" @@ -30,13 +32,6 @@ limitations under the License. namespace tensorflow { namespace grappler { -// Returns a copy of FunctionLibraryDefinition with subset of functions that are -// reachable from the nodes of the graph. -FunctionLibraryDefinition ReachableFunctionLibraryDefinition( - const FunctionLibraryDefinition& flib, const GraphDef& graph); -FunctionLibraryDefinition ReachableFunctionLibraryDefinition( - const FunctionLibraryDefinition& flib, const FunctionDef& func); - // Depending on the function instantiation attributes, input argument to the // function might be a single tensor, list of tensors of the same type, or a // list of tensors of different types. @@ -81,12 +76,12 @@ class GrapplerFunctionConnectivity { void RegisterFunctionBodyOutputs(const string& node_name, tensorflow::NameRangeMap&& outputs); - // Expand input encoded in FunctionDef format (name[:output][:position]) into + // Expands input encoded in FunctionDef format (name[:output][:position]) into // multiple inputs in GraphDef format (name[:position]). Status ExpandFunctionDefInput(const string& func_def_input, std::vector* graph_def_inputs) const; - // Update Node inputs from FunctionDef to GraphDef format. + // Updates Node inputs from FunctionDef to GraphDef format. Status ExpandNodeInputs(NodeDef* function_body_node) const; // When expanding inputs in function def format, single input might be @@ -96,29 +91,31 @@ class GrapplerFunctionConnectivity { // instantiation attributes and length of input args (and node def outputs) is // known. - // Map from GraphDef input format to FunctionDef input format using registered - // input arg expansion and function body outputs. + // Converts input name from GraphDef format (name[:position]) to the + // FunctionDef input format (name[:output][:position]) using registered input + // arg expansion and function body outputs. Status AsFunctionDefInput(const string& graph_def_input, string* func_def_input) const; - // Update Node inputs from GraphDef to FunctionDef format. + // Updates Node inputs from GraphDef to FunctionDef format. Status AsFunctionDefNode(NodeDef* function_body_node) const; private: // Mapping from input name to input arg expansion. - std::unordered_map input_arg_expansions_; + absl::flat_hash_map input_arg_expansions_; // Mapping from function body node name to output names range map. - std::unordered_map function_body_outputs_; + absl::flat_hash_map function_body_outputs_; + // For each placeholder added to the function instantiation graph, we keep a + // mapping back to the function input argument name and index. struct InputArgPlaceholder { - string input_name; // Name of the function input argument. - int input_position; // Index of a tensor in the function input argument - // expansion, it can be greater than `0` if input - // argument is a list of tensors (aka list(type)). + string input_name; // Name of the function input argument. + int input_index; // Index of a tensor in the function input argument + // expansion, it can be greater than `0` if input + // argument is a list of tensors (aka list(type)). }; - // Mapping from input arg placeholder to the function input tensor. - std::unordered_map input_arg_placeholders_; + absl::flat_hash_map input_arg_placeholders_; }; // Get Function type attributes using attributes of a node that instantiated @@ -172,7 +169,8 @@ class GrapplerFunctionItem : public GrapplerItem { friend Status ReplaceInputWithConst(const NodeDef&, int, GrapplerFunctionItem*); friend Status RemoveUnusedOutputs( - const gtl::FlatSet& active_outputs, GrapplerFunctionItem* item, + const absl::flat_hash_set& active_outputs, + GrapplerFunctionItem* item, std::vector>* output_mapping); GrapplerFunctionItem(string func_name, string description, @@ -191,7 +189,7 @@ class GrapplerFunctionItem : public GrapplerItem { std::set input_arg_placeholders_; - bool is_stateful_; + bool is_stateful_ = false; }; // Check if function input/output types are fully defined only at instantiation @@ -210,14 +208,14 @@ bool IsParametrized(const FunctionDef& func); // caller node. Return error if type can't be resolved. Status InstantiationTypeParameters( const FunctionDef& func, const AttrSlice& func_instantiation_attr, - std::unordered_map* type_parameters); + absl::flat_hash_map* type_parameters); // Resolve function instantiation body parameters (values for the function body // attr placeholders) from the attributes of the caller node. Return error if // type can't be resolved. Status InstantiationBodyParameters( const FunctionDef& func, const AttrSlice& func_instantiation_attr, - std::unordered_map* body_parameters); + absl::flat_hash_map* body_parameters); // Register GrapplerFunctionItem input arg expansion and function body outputs // in the GrapplerFunctionConnectivity. Use function library definition to @@ -227,7 +225,7 @@ Status RegisterGrapplerFunctionConnectivity( GrapplerFunctionConnectivity* connectivity); // Replace one of the function inputs with a constant. -Status ReplaceInputWithConst(const NodeDef& input_const, int input_position, +Status ReplaceInputWithConst(const NodeDef& input_const, int input_index, GrapplerFunctionItem* item); // Remove function output arguments that do not have any active outputs (output @@ -236,7 +234,7 @@ Status ReplaceInputWithConst(const NodeDef& input_const, int input_position, // potentially be connected to the same output argument (in case of tensor list // outputs). Add output mapping for all active outputs that changed it's output // position (std::pair). -Status RemoveUnusedOutputs(const gtl::FlatSet& active_outputs, +Status RemoveUnusedOutputs(const absl::flat_hash_set& active_outputs, GrapplerFunctionItem* item, std::vector>* output_mapping); diff --git a/tensorflow/core/grappler/utils/functions_test.cc b/tensorflow/core/grappler/utils/functions_test.cc index 5923850eca..29d6100d23 100644 --- a/tensorflow/core/grappler/utils/functions_test.cc +++ b/tensorflow/core/grappler/utils/functions_test.cc @@ -14,6 +14,8 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/grappler/utils/functions.h" + +#include "absl/container/flat_hash_map.h" #include "tensorflow/cc/ops/standard_ops.h" #include "tensorflow/core/framework/function_testlib.h" #include "tensorflow/core/framework/node_def.pb.h" @@ -77,7 +79,7 @@ TEST_F(FunctionsTest, InstantiationParameters) { func_instantiation_attr["B"].set_type(DT_INT32); func_instantiation_attr["C"].set_type(DT_DOUBLE); - std::unordered_map type_parameters; + absl::flat_hash_map type_parameters; TF_EXPECT_OK(InstantiationTypeParameters( func, AttrSlice(&func_instantiation_attr), &type_parameters)); @@ -86,7 +88,7 @@ TEST_F(FunctionsTest, InstantiationParameters) { EXPECT_EQ(DT_INT32, type_parameters["B"]); EXPECT_EQ(DT_DOUBLE, type_parameters["C"]); - std::unordered_map body_parameters; + absl::flat_hash_map body_parameters; TF_EXPECT_OK(InstantiationBodyParameters( func, AttrSlice(&func_instantiation_attr), &body_parameters)); -- GitLab From 26d8486baa67d2c545aa4384f9d666dc7b9ad530 Mon Sep 17 00:00:00 2001 From: Rachel Lim Date: Wed, 2 Jan 2019 15:11:44 -0800 Subject: [PATCH 0105/2345] documentation fix PiperOrigin-RevId: 227589407 --- tensorflow/python/data/experimental/ops/optimization_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/data/experimental/ops/optimization_options.py b/tensorflow/python/data/experimental/ops/optimization_options.py index 41a819d94b..eb95484ac5 100644 --- a/tensorflow/python/data/experimental/ops/optimization_options.py +++ b/tensorflow/python/data/experimental/ops/optimization_options.py @@ -33,7 +33,7 @@ class OptimizationOptions(options.OptionsBase): ```python options = tf.data.Options() options.experimental_optimization.map_vectorization = True - options.apply_default_optimizations = False + options.experimental_optimization.apply_default_optimizations = False dataset = dataset.with_options(options) ``` """ -- GitLab From 29bc2e98eaf1dd730574886ffe1b317e982323d4 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Wed, 2 Jan 2019 15:47:20 -0800 Subject: [PATCH 0106/2345] Validate that all variables under the model were created in the distribution strategy scope. PiperOrigin-RevId: 227595107 --- .../contrib/distribute/python/keras_test.py | 44 +++++++++++++++++++ tensorflow/python/keras/engine/training.py | 14 ++++++ 2 files changed, 58 insertions(+) diff --git a/tensorflow/contrib/distribute/python/keras_test.py b/tensorflow/contrib/distribute/python/keras_test.py index b99d11b923..ba5a5e6400 100644 --- a/tensorflow/contrib/distribute/python/keras_test.py +++ b/tensorflow/contrib/distribute/python/keras_test.py @@ -245,6 +245,18 @@ def all_strategy_combinations(): return strategy_minus_tpu_combinations() + tpu_strategy_combinations() +def all_strategy_combinations_minus_default(): + strategy_minus_default_combinations = combinations.combine( + distribution=[ + 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], + mode=['graph', 'eager']) + return strategy_minus_default_combinations + tpu_strategy_combinations() + + # TODO(priyag): Add v2 optimizers here. def strategy_and_optimizer_combinations(): return combinations.times( @@ -1149,5 +1161,37 @@ class TestDistributionStrategyWithNormalizationLayer( np.testing.assert_allclose(out.std(), 1.0, atol=1e-1) +class TestDistributionStrategyVariableValidation(test.TestCase, + parameterized.TestCase): + + @combinations.generate(all_strategy_combinations_minus_default()) + def test_layer_outside_scope(self, distribution): + with self.cached_session(): + with self.assertRaisesRegexp( + ValueError, 'was not created in the distribution strategy'): + x = keras.layers.Input(shape=(3,), name='input') + y = keras.layers.Dense(4, name='dense')(x) + with distribution.scope(): + model = keras.Model(x, y) + optimizer = gradient_descent.GradientDescentOptimizer(0.001) + loss = 'mse' + metrics = ['mae', keras.metrics.CategoricalAccuracy()] + model.compile(optimizer, loss, metrics=metrics) + + @combinations.generate(all_strategy_combinations_minus_default()) + def test_model_outside_scope(self, distribution): + with self.cached_session(): + with self.assertRaisesRegexp( + ValueError, 'was not created in the distribution strategy'): + x = keras.layers.Input(shape=(3,), name='input') + y = keras.layers.Dense(4, name='dense')(x) + model = keras.Model(x, y) + with distribution.scope(): + optimizer = gradient_descent.GradientDescentOptimizer(0.001) + loss = 'mse' + metrics = ['mae', keras.metrics.CategoricalAccuracy()] + model.compile(optimizer, loss, metrics=metrics) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index be3afafd3e..1dc3b81cfc 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -548,6 +548,20 @@ class Model(Network): trainable_weights = self.trainable_weights self._collected_trainable_weights = trainable_weights + # Validate all variables were correctly created in distribution scope. + if self._distribution_strategy and not self._compile_distribution: + for v in self.variables: + if v.distribute_strategy is not self._distribution_strategy: + raise ValueError( + 'Variable (%s) was not created in the distribution strategy ' + 'scope of (%s). It is most likely due to not all layers or ' + 'the model or optimizer being created outside the distribution ' + 'strategy scope. Try to make sure your code looks similar ' + 'to the following.\n' + 'with strategy.scope():\n' + ' model=_create_model()\n' + ' model.compile(...)'% (v, self._distribution_strategy)) + @property def metrics(self): """Returns the model's metrics added using `compile`, `add_metric` APIs.""" -- GitLab From 6af0243ba6842330d7bcce6499287f6e9a640c9f Mon Sep 17 00:00:00 2001 From: Penporn Koanantakool Date: Wed, 2 Jan 2019 15:52:17 -0800 Subject: [PATCH 0107/2345] Fix a typo in Near. Before this change, when the values are not 'Near', the program reports the address of b instead of the value of b[i]. PiperOrigin-RevId: 227595818 --- tensorflow/core/framework/tensor_testutil.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/framework/tensor_testutil.h b/tensorflow/core/framework/tensor_testutil.h index 3163002851..b58292b3b0 100644 --- a/tensorflow/core/framework/tensor_testutil.h +++ b/tensorflow/core/framework/tensor_testutil.h @@ -206,7 +206,7 @@ struct Expector { const T* b = y.flat().data(); for (int i = 0; i < size; ++i) { EXPECT_TRUE(Near(a[i], b[i], abs_err)) - << "a = " << a[i] << " b = " << b << " index = " << i; + << "a = " << a[i] << " b = " << b[i] << " index = " << i; } } }; -- GitLab From f0dde544f263013867e8c66c82db85e66c42d530 Mon Sep 17 00:00:00 2001 From: Michael Kuperstein Date: Wed, 2 Jan 2019 16:00:27 -0800 Subject: [PATCH 0108/2345] [XLA] Don't go out of bounds in Shape::dynamic_dimensions_. PiperOrigin-RevId: 227596997 --- tensorflow/compiler/xla/shape.cc | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/shape.cc b/tensorflow/compiler/xla/shape.cc index c04a916154..f05d816268 100644 --- a/tensorflow/compiler/xla/shape.cc +++ b/tensorflow/compiler/xla/shape.cc @@ -27,7 +27,19 @@ Shape::Shape(const ShapeProto& shape_proto) { for (const int64 dimension : shape_proto.dimensions()) { add_dimensions(dimension); } - for (int i = 0; i < shape_proto.is_dynamic_dimension_size(); i++) { + // A malformed proto may have different is_dynamic_dimension_size and + // dimensions_size. Since C++ is evil, and we have no good way of bailing out + // in a constructor, conservatively trim the is_dynamic_dimension size. + // TODO(b/120111794): Make this a hard error when we have a factory method + // instead of a constructor. + if (shape_proto.dimensions_size() != + shape_proto.is_dynamic_dimension_size()) { + LOG(ERROR) << "Malformed shape proto: number of is_dynamic_dimension " + "fields does not match number of dimension fields"; + } + int64 num_dynamic_dimension_fields = std::min( + shape_proto.dimensions_size(), shape_proto.is_dynamic_dimension_size()); + for (int i = 0; i < num_dynamic_dimension_fields; i++) { dynamic_dimensions_[i] = shape_proto.is_dynamic_dimension(i); } tuple_shapes_.reserve(shape_proto.tuple_shapes_size()); -- GitLab From 0b6177c2fa509d951d4086eab88fc506792cfe43 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 2 Jan 2019 16:08:42 -0800 Subject: [PATCH 0109/2345] Fix make_initializable_iterator comment PiperOrigin-RevId: 227598505 --- 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 1ba00a8e69..61210f3a7f 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -1756,8 +1756,8 @@ def make_initializable_iterator(dataset): RuntimeError: If eager execution is enabled. """ try: - # Call the defined `make_one_shot_iterator()` if there is one, because some - # datasets (e.g. for prefetching) override its behavior. + # Call the defined `make_initializable_iterator()` if there is one, because + # some datasets (e.g. for prefetching) override its behavior. return dataset.make_initializable_iterator() except AttributeError: return DatasetV1Adapter(dataset).make_initializable_iterator() -- GitLab From dd3adf935a800e25132b187d65aa62e97504f50c Mon Sep 17 00:00:00 2001 From: Kay Zhu Date: Wed, 2 Jan 2019 16:11:52 -0800 Subject: [PATCH 0110/2345] [XLA] Enable interpreter backend test by default for xla/tests without having to using additional backend tags "enable_for_xla_interpreter". PiperOrigin-RevId: 227598976 --- tensorflow/compiler/xla/client/lib/BUILD | 6 - .../compiler/xla/service/hlo_evaluator.cc | 5 + .../compiler/xla/service/hlo_evaluator.h | 23 +++ .../xla/service/hlo_evaluator_typed_visitor.h | 32 ++-- .../compiler/xla/service/interpreter/BUILD | 3 + .../xla/service/interpreter/compiler.cc | 6 + tensorflow/compiler/xla/tests/BUILD | 144 ++++-------------- .../xla/tests/array_elementwise_ops_test.cc | 13 ++ .../compiler/xla/tests/bfloat16_test.cc | 8 +- tensorflow/compiler/xla/tests/client_test.cc | 5 +- .../xla/tests/gather_operation_test.cc | 4 +- .../xla/tests/local_client_execute_test.cc | 6 +- 12 files changed, 115 insertions(+), 140 deletions(-) diff --git a/tensorflow/compiler/xla/client/lib/BUILD b/tensorflow/compiler/xla/client/lib/BUILD index 966a581981..d863ebfea1 100644 --- a/tensorflow/compiler/xla/client/lib/BUILD +++ b/tensorflow/compiler/xla/client/lib/BUILD @@ -93,7 +93,6 @@ cc_library( xla_test( name = "constants_test", srcs = ["constants_test.cc"], - tags = ["enable_for_xla_interpreter"], deps = [ ":constants", "//tensorflow/compiler/xla:test", @@ -147,7 +146,6 @@ cc_library( xla_test( name = "math_test", srcs = ["math_test.cc"], - tags = ["enable_for_xla_interpreter"], deps = [ ":math", "//tensorflow/compiler/xla:literal_util", @@ -181,7 +179,6 @@ cc_library( xla_test( name = "matrix_test", srcs = ["matrix_test.cc"], - tags = ["enable_for_xla_interpreter"], deps = [ ":matrix", ":slicing", @@ -295,7 +292,6 @@ cc_library( xla_test( name = "slicing_test", srcs = ["slicing_test.cc"], - tags = ["enable_for_xla_interpreter"], deps = [ ":slicing", "//tensorflow/compiler/xla:literal_util", @@ -324,7 +320,6 @@ cc_library( xla_test( name = "sorting_test", srcs = ["sorting_test.cc"], - tags = ["enable_for_xla_interpreter"], deps = [ ":sorting", "//tensorflow/compiler/xla:test", @@ -354,7 +349,6 @@ xla_test( srcs = ["quantize_test.cc"], # TODO(b/122119490): re-enable TAP after fixing. tags = [ - "enable_for_xla_interpreter", "notap", ], deps = [ diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.cc b/tensorflow/compiler/xla/service/hlo_evaluator.cc index 794b97a7e0..4f5484293c 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.cc +++ b/tensorflow/compiler/xla/service/hlo_evaluator.cc @@ -138,6 +138,11 @@ StatusOr Compare(const Shape& shape, HloOpcode opcode, } // namespace +// Note that unsupported types by the typed visitor does not necessarily imply +// the non-typed HloEvaluator (parent evaluator) would not support them either +// in the type-agnostic handler. For e.g., HandleGetTupleElement in the parent +// type-agnostic evaluator will be able to accept Tuple primitive type, whereas +// HloEvaluatorTypedVisitor cannot. HloEvaluator::HloEvaluator(int64 max_loop_iterations) : max_loop_iterations_(max_loop_iterations) { typed_visitors_[PRED] = diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.h b/tensorflow/compiler/xla/service/hlo_evaluator.h index 44d5e5c030..d8e3019576 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.h +++ b/tensorflow/compiler/xla/service/hlo_evaluator.h @@ -211,6 +211,29 @@ class HloEvaluator : public DfsHloVisitorWithDefault { Status HandleReduce(HloInstruction* reduce) override; + // Unsupported HLOs, note some of them (such as BatchNorm*) are typically + // expanded in a semantic-preserving way into other HLOs by adding exanpsion + // HLO pass to the HLO optimization pass during compilation, which can then be + // handled by the evaluator. + Status HandleBatchNormGrad(HloInstruction* batch_norm_grad) override { + return Unimplemented("BatchNormGrad HLO is unsupported by the evaluator."); + }; + Status HandleBatchNormInference( + HloInstruction* batch_norm_inference) override { + return Unimplemented( + "BatchNormInference HLO is unsupported by the evaluator."); + }; + Status HandleBatchNormTraining(HloInstruction* batch_norm_training) override { + return Unimplemented( + "BatchNormTraining HLO is unsupported by the evaluator."); + }; + Status HandleInfeed(HloInstruction* infeed) override { + return Unimplemented("Infeed HLO is unsupported by the evaluator."); + }; + Status HandleOutfeed(HloInstruction* outfeed) override { + return Unimplemented("Outfeed HLO is unsupported by the evaluator."); + }; + // Returns the already-evaluated literal result for the instruction. // A Constant instruction is considered evaluated and its literal will be // returned directly without looking up the cache. diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h index 6f3208be13..b1bbf49d4a 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h +++ b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h @@ -917,7 +917,11 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { Status HandleClamp(HloInstruction* clamp) { std::function clamp_op = [](ElementwiseT low, ElementwiseT value, ElementwiseT high) { - return std::fmin(high, std::fmax(value, low)); + if (std::isnan(low) || std::isnan(high)) { + return static_cast(NAN); + } + return static_cast( + std::fmin(high, std::fmax(value, low))); }; TF_ASSIGN_OR_RETURN( parent_->evaluated_[clamp], @@ -2664,12 +2668,13 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { return HandleReducePrecision(reduce_precision); } - template ::value || - std::is_same::value || - std::is_integral::value || - std::is_floating_point::value>::type* = nullptr> + template < + typename NativeT, + typename std::enable_if< + std::is_same::value || + std::is_same::value || + std::is_integral::value || is_complex_t::value || + std::is_floating_point::value>::type* = nullptr> Status HandleIota(HloInstruction* instruction) { auto* iota = Cast(instruction); const int64 iota_size = iota->shape().dimensions(iota->iota_dimension()); @@ -2700,12 +2705,13 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { return Status::OK(); } - template ::value || - std::is_same::value || - std::is_integral::value || - std::is_floating_point::value)>::type* = nullptr> + template < + typename NativeT, + typename std::enable_if< + !(std::is_same::value || + std::is_same::value || + std::is_integral::value || is_complex_t::value || + std::is_floating_point::value)>::type* = nullptr> Status HandleIota(HloInstruction* iota) { return UnsupportedTypeError(iota); } diff --git a/tensorflow/compiler/xla/service/interpreter/BUILD b/tensorflow/compiler/xla/service/interpreter/BUILD index a981d94a99..8999f38ccd 100644 --- a/tensorflow/compiler/xla/service/interpreter/BUILD +++ b/tensorflow/compiler/xla/service/interpreter/BUILD @@ -32,6 +32,7 @@ cc_library( "//tensorflow/compiler/xla:status_macros", "//tensorflow/compiler/xla:statusor", "//tensorflow/compiler/xla/service:algebraic_simplifier", + "//tensorflow/compiler/xla/service:batchnorm_expander", "//tensorflow/compiler/xla/service:compiler", "//tensorflow/compiler/xla/service:computation_placer", "//tensorflow/compiler/xla/service:executable", @@ -41,12 +42,14 @@ cc_library( "//tensorflow/compiler/xla/service:hlo_cost_analysis", "//tensorflow/compiler/xla/service:hlo_cse", "//tensorflow/compiler/xla/service:hlo_dce", + "//tensorflow/compiler/xla/service:hlo_get_dimension_size_rewriter", "//tensorflow/compiler/xla/service:hlo_module_config", "//tensorflow/compiler/xla/service:hlo_pass", "//tensorflow/compiler/xla/service:hlo_pass_pipeline", "//tensorflow/compiler/xla/service:hlo_subcomputation_unification", "//tensorflow/compiler/xla/service:layout_assignment", "//tensorflow/compiler/xla/service:map_inliner", + "//tensorflow/compiler/xla/service:reduce_precision_insertion", "//tensorflow/compiler/xla/service:reshape_mover", "//tensorflow/compiler/xla/service:while_loop_simplifier", "//tensorflow/core:lib", diff --git a/tensorflow/compiler/xla/service/interpreter/compiler.cc b/tensorflow/compiler/xla/service/interpreter/compiler.cc index d37ae94bf6..69f611a094 100644 --- a/tensorflow/compiler/xla/service/interpreter/compiler.cc +++ b/tensorflow/compiler/xla/service/interpreter/compiler.cc @@ -31,6 +31,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/interpreter/executable.h" #include "tensorflow/compiler/xla/service/layout_assignment.h" #include "tensorflow/compiler/xla/service/map_inliner.h" +#include "tensorflow/compiler/xla/service/reduce_precision_insertion.h" #include "tensorflow/compiler/xla/service/reshape_mover.h" #include "tensorflow/compiler/xla/service/while_loop_simplifier.h" #include "tensorflow/compiler/xla/status_macros.h" @@ -46,6 +47,11 @@ Status InterpreterCompiler::RunHloOptimization(HloModule* hlo_module) { pipeline.AddPass( hlo_module->mutable_entry_computation_layout(), LayoutAssignment::InstructionCanChangeLayout); + + ReducePrecisionInsertion::AddPasses( + &pipeline, hlo_module->config().debug_options(), + ReducePrecisionInsertion::PassTiming::BEFORE_OPTIMIZATION); + return pipeline.Run(hlo_module).status(); } diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index ee24d4d99c..578e716b59 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -276,9 +276,6 @@ cc_library( xla_test( name = "bad_rng_shape_validation_test", srcs = ["bad_rng_shape_validation_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:statusor", "//tensorflow/compiler/xla:test", @@ -344,9 +341,6 @@ xla_test( xla_test( name = "check_execution_arity_test", srcs = ["check_execution_arity_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_util", @@ -367,9 +361,6 @@ xla_test( xla_test( name = "query_inferred_shape_test", srcs = ["query_inferred_shape_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:statusor", @@ -387,9 +378,6 @@ xla_test( xla_test( name = "while_test", srcs = ["while_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_util", @@ -413,6 +401,10 @@ xla_test( xla_test( name = "xla_hlo_profile_test", srcs = ["xla_hlo_profile_test.cc"], + blacklisted_backends = [ + # Hlo profiles are not supported on the interpreter backend. + "interpreter", + ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:shape_util", @@ -436,9 +428,6 @@ xla_test( xla_test( name = "axpy_simple_test", srcs = ["axpy_simple_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla/client:local_client", "//tensorflow/compiler/xla/client:xla_builder", @@ -453,7 +442,6 @@ xla_test( xla_test( name = "map_test", srcs = ["map_test.cc"], - tags = ["enable_for_xla_interpreter"], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:literal", @@ -506,9 +494,6 @@ xla_test( xla_test( name = "pred_test", srcs = ["pred_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla/client:local_client", @@ -524,9 +509,6 @@ xla_test( xla_test( name = "select_test", srcs = ["select_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/compiler/xla/client:global_data", @@ -544,7 +526,6 @@ xla_test( xla_test( name = "conditional_test", srcs = ["conditional_test.cc"], - tags = ["enable_for_xla_interpreter"], deps = [ "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/compiler/xla/client:global_data", @@ -562,7 +543,6 @@ xla_test( xla_test( name = "unary_op_test", srcs = ["unary_op_test.cc"], - tags = ["enable_for_xla_interpreter"], deps = [ "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/compiler/xla/client:global_data", @@ -623,9 +603,6 @@ xla_test( xla_test( name = "deconstruct_tuple_test", srcs = ["deconstruct_tuple_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_util", @@ -648,7 +625,6 @@ xla_test( name = "array_elementwise_ops_test", srcs = ["array_elementwise_ops_test.cc"], shard_count = 25, - tags = ["enable_for_xla_interpreter"], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array3d", @@ -698,7 +674,6 @@ xla_test( xla_test( name = "reduce_precision_test", srcs = ["reduce_precision_test.cc"], - tags = ["enable_for_xla_interpreter"], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:literal", @@ -725,7 +700,6 @@ xla_test( srcs = ["dot_operation_test.cc"], shard_count = 20, tags = [ - "enable_for_xla_interpreter", "optonly", ], deps = [ @@ -806,9 +780,6 @@ xla_test( xla_test( name = "transpose_test", srcs = ["transpose_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:reference_util", @@ -828,9 +799,6 @@ xla_test( xla_test( name = "constants_test", srcs = ["constants_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array3d", @@ -951,6 +919,11 @@ xla_test( xla_test( name = "batch_normalization_test", srcs = ["batch_normalization_test.cc"], + blacklisted_backends = [ + # BatchNorm HLOs are not handled by the interpreter backend, and the + # BatchNorm expander is not run on the interpreter. + "interpreter", + ], shard_count = 40, deps = [ ":test_utils", @@ -1042,9 +1015,6 @@ xla_test( name = "slice_test", srcs = ["slice_test.cc"], shard_count = 40, - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:reference_util", @@ -1065,9 +1035,6 @@ xla_test( xla_test( name = "multidimensional_slice_test", srcs = ["multidimensional_slice_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array3d", @@ -1085,9 +1052,6 @@ xla_test( name = "dynamic_ops_test", timeout = "moderate", srcs = ["dynamic_ops_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:reference_util", @@ -1113,9 +1077,6 @@ xla_test( xla_test( name = "tuple_test", srcs = ["tuple_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:literal", @@ -1139,9 +1100,6 @@ xla_test( xla_test( name = "vector_ops_reduce_test", srcs = ["vector_ops_reduce_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array3d", @@ -1162,7 +1120,6 @@ xla_test( srcs = ["reduce_test.cc"], shard_count = 40, tags = [ - "enable_for_xla_interpreter", "optonly", ], deps = [ @@ -1229,7 +1186,6 @@ xla_test( srcs = [], shard_count = 20, tags = [ - "enable_for_xla_interpreter", "optonly", ], xla_test_library_deps = [":reduce_window_test_library"], @@ -1241,7 +1197,6 @@ xla_test( timeout = "long", srcs = ["select_and_scatter_test.cc"], tags = [ - "enable_for_xla_interpreter", "optonly", ], deps = [ @@ -1267,9 +1222,6 @@ xla_test( xla_test( name = "copy_test", srcs = ["copy_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ ":client_library_test_base", "//tensorflow/compiler/xla:array2d", @@ -1290,9 +1242,6 @@ xla_test( xla_test( name = "reduce_hlo_test", srcs = ["reduce_hlo_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla/service:hlo_parser", "//tensorflow/compiler/xla/tests:hlo_test_base", @@ -1306,9 +1255,6 @@ xla_test( xla_test( name = "token_hlo_test", srcs = ["token_hlo_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla/service:hlo_parser", "//tensorflow/compiler/xla/service:hlo_verifier", @@ -1323,9 +1269,6 @@ xla_test( xla_test( name = "call_test", srcs = ["call_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:literal_util", @@ -1368,9 +1311,6 @@ xla_test( xla_test( name = "binop_scaling_test", srcs = ["binop_scaling_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array4d", @@ -1388,9 +1328,6 @@ xla_test( xla_test( name = "broadcast_simple_test", srcs = ["broadcast_simple_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array4d", @@ -1410,9 +1347,6 @@ xla_test( xla_test( name = "pad_test", srcs = ["pad_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array4d", @@ -1434,9 +1368,6 @@ xla_test( xla_test( name = "fmax_test", srcs = ["fmax_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla/client:local_client", "//tensorflow/compiler/xla/client:xla_builder", @@ -1451,9 +1382,6 @@ xla_test( xla_test( name = "log_test", srcs = ["log_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla/client:local_client", "//tensorflow/compiler/xla/client:xla_builder", @@ -1468,9 +1396,6 @@ xla_test( xla_test( name = "matrix_ops_simple_test", srcs = ["matrix_ops_simple_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:literal", @@ -1497,6 +1422,10 @@ xla_test( xla_test( name = "prng_test", srcs = ["prng_test.cc"], + blacklisted_backends = [ + # TODO(b/122047800) support RNGs on the interpreter backend. + "interpreter", + ], deps = [ "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_util", @@ -1517,9 +1446,6 @@ xla_test( name = "reshape_test", srcs = ["reshape_test.cc"], shard_count = 30, - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array4d", @@ -1545,9 +1471,6 @@ xla_test( xla_test( name = "reverse_test", srcs = ["reverse_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array4d", @@ -1566,9 +1489,6 @@ xla_test( xla_test( name = "vector_ops_simple_test", srcs = ["vector_ops_simple_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array4d", "//tensorflow/compiler/xla:shape_util", @@ -1592,9 +1512,6 @@ xla_test( xla_test( name = "concat_test", srcs = ["concat_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:array3d", @@ -1615,9 +1532,6 @@ xla_test( xla_test( name = "convert_test", srcs = ["convert_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:xla_data_proto", @@ -1637,6 +1551,10 @@ xla_test( xla_test( name = "all_reduce_test", srcs = ["all_reduce_test.cc"], + blacklisted_backends = [ + # All reduce is not supported on the interpreter backend. + "interpreter", + ], deps = [ "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_util", @@ -1661,9 +1579,6 @@ xla_test( xla_test( name = "bitcast_convert_test", srcs = ["bitcast_convert_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:xla_data_proto", @@ -1703,9 +1618,6 @@ xla_test( xla_test( name = "floor_ceil_test", srcs = ["floor_ceil_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla/client:local_client", "//tensorflow/compiler/xla/client:xla_builder", @@ -1767,6 +1679,10 @@ xla_test( xla_test( name = "execution_profile_test", srcs = ["execution_profile_test.cc"], + blacklisted_backends = [ + # Execution profiles are not supported on the interpreter backend. + "interpreter", + ], deps = [ ":client_library_test_base", "//tensorflow/compiler/xla/client:global_data", @@ -1781,6 +1697,10 @@ xla_test( name = "execution_profile_test_with_xla_hlo_profile", srcs = ["execution_profile_test.cc"], args = ["--xla_hlo_profile"], + blacklisted_backends = [ + # Hlo profiles are not supported on the interpreter backend. + "interpreter", + ], deps = [ ":client_library_test_base", "//tensorflow/compiler/xla/client:global_data", @@ -1794,9 +1714,6 @@ xla_test( xla_test( name = "replay_test", srcs = ["replay_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:protobuf_util", @@ -1819,9 +1736,6 @@ xla_test( xla_test( name = "broadcast_test", srcs = ["broadcast_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_util", @@ -1883,9 +1797,6 @@ xla_test( xla_test( name = "fusion_test", srcs = ["fusion_test.cc"], - tags = [ - "enable_for_xla_interpreter", - ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:literal", @@ -2003,6 +1914,10 @@ xla_test( xla_test( name = "outfeed_in_nested_computation_test", srcs = ["outfeed_in_nested_computation_test.cc"], + blacklisted_backends = [ + # Outfeed ops are not supported on the interpreter backend. + "interpreter", + ], deps = [ "//tensorflow/compiler/xla/tests:local_client_test_base", "//tensorflow/compiler/xla/tests:xla_internal_test_main", @@ -2179,7 +2094,6 @@ xla_test( srcs = ["iota_test.cc"], shard_count = 30, tags = [ - "enable_for_xla_interpreter", # Require optimized builds, iota_test_cpu is very slow in fastbuild. "optonly", ], diff --git a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc index 915b456b52..e4cc8b4199 100644 --- a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc +++ b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc @@ -2047,6 +2047,19 @@ XLA_TEST_F(ArrayElementwiseOpTest, NonNanClampF32) { error_spec_); } +XLA_TEST_F(ArrayElementwiseOpTest, ClampF32) { + SetFastMathDisabled(true); + XlaBuilder builder(TestName()); + auto minimum = ConstantR1(&builder, {1.0f, -6.5f, 1.0f, 2.25f, NAN}); + auto argument = + ConstantR1(&builder, {2.0f, 10.0f, -5.0f, 1.0f, 10.0f}); + auto maximum = ConstantR1(&builder, {3.0f, 0.5f, 25.5f, NAN, 123.0f}); + Clamp(minimum, argument, maximum); + + ComputeAndCompareR1(&builder, {2.0f, 0.5f, 1.0f, NAN, NAN}, {}, + error_spec_); +} + XLA_TEST_F(ArrayElementwiseOpTest, ClampF32Scalar) { XlaBuilder builder(TestName()); auto minimum = ConstantR0(&builder, 0.0f); diff --git a/tensorflow/compiler/xla/tests/bfloat16_test.cc b/tensorflow/compiler/xla/tests/bfloat16_test.cc index e9728e636f..63e4811705 100644 --- a/tensorflow/compiler/xla/tests/bfloat16_test.cc +++ b/tensorflow/compiler/xla/tests/bfloat16_test.cc @@ -76,7 +76,9 @@ XLA_TEST_F(Bfloat16Test, NegateScalarF16) { error_spec_); } -XLA_TEST_F(Bfloat16Test, BatchNormTraining) { +// Disabled on interpreter since BatchNormExanper is not run by default on the +// intepreter backend. +XLA_TEST_F(Bfloat16Test, DISABLED_ON_INTERPRETER(BatchNormTraining)) { const int kFeatureIndex = 2; XlaBuilder builder(TestName()); @@ -110,7 +112,9 @@ XLA_TEST_F(Bfloat16Test, BatchNormTraining) { ComputeAndCompareTuple(&builder, expected, {}, ErrorSpec(0.01, 0.02)); } -XLA_TEST_F(Bfloat16Test, BatchNormGrad) { +// Disabled on interpreter since BatchNormExanper is not run by default on the +// intepreter backend. +XLA_TEST_F(Bfloat16Test, DISABLED_ON_INTERPRETER(BatchNormGrad)) { const int kFeatureIndex = 2; XlaBuilder builder(TestName()); diff --git a/tensorflow/compiler/xla/tests/client_test.cc b/tensorflow/compiler/xla/tests/client_test.cc index d5b3a4d14f..247328b730 100644 --- a/tensorflow/compiler/xla/tests/client_test.cc +++ b/tensorflow/compiler/xla/tests/client_test.cc @@ -109,7 +109,10 @@ XLA_TEST_F(ClientTest, ExecuteWithTupleLayout) { /*minor_to_major=*/{1, 0}))); } -XLA_TEST_F(ClientTest, DISABLED_ON_GPU(ExecuteParallel)) { +// Disabled for interpreter since ExecuteAsyncOnStream is not implemented on +// interpreter backend. +XLA_TEST_F(ClientTest, + DISABLED_ON_INTERPRETER(DISABLED_ON_GPU(ExecuteParallel))) { XlaComputation add_with_one_arg, mul_with_two_args, dot_with_one_arg; Shape shape = ShapeUtil::MakeShape(S32, {2, 2}); diff --git a/tensorflow/compiler/xla/tests/gather_operation_test.cc b/tensorflow/compiler/xla/tests/gather_operation_test.cc index daa89398a6..d65b67a535 100644 --- a/tensorflow/compiler/xla/tests/gather_operation_test.cc +++ b/tensorflow/compiler/xla/tests/gather_operation_test.cc @@ -600,7 +600,9 @@ ENTRY main { class GatherClientLibraryTest : public ClientLibraryTestBase {}; -XLA_TEST_F(GatherClientLibraryTest, DISABLED_ON_GPU(Basic)) { +// Disabled on interpreter since ExectuteAsyncOnStream is not supported. +XLA_TEST_F(GatherClientLibraryTest, + DISABLED_ON_INTERPRETER(DISABLED_ON_GPU(Basic))) { // We create this HLO, but using the XlaBuilder API. // // ENTRY main { diff --git a/tensorflow/compiler/xla/tests/local_client_execute_test.cc b/tensorflow/compiler/xla/tests/local_client_execute_test.cc index 6522c56325..96527886b7 100644 --- a/tensorflow/compiler/xla/tests/local_client_execute_test.cc +++ b/tensorflow/compiler/xla/tests/local_client_execute_test.cc @@ -842,7 +842,8 @@ XLA_TEST_F(LocalClientExecuteTest, ShapeBufferToLiteralConversion64bit) { LiteralUtil::CreateR0(123456789000LL)})); } -XLA_TEST_F(LocalClientExecuteTest, InfeedTest) { +// Disabled on interpreter backend since infeed HLO is unsupported. +XLA_TEST_F(LocalClientExecuteTest, DISABLED_ON_INTERPRETER(InfeedTest)) { XlaBuilder builder(TestName()); const Shape shape = ShapeUtil::MakeShape(F32, {3}); auto in = Infeed(&builder, shape); @@ -867,7 +868,8 @@ XLA_TEST_F(LocalClientExecuteTest, InfeedTest) { LiteralTestUtil::ExpectR1Equal({-4.0, 125.0, 45.0}, result); } -XLA_TEST_F(LocalClientExecuteTest, InfeedOutfeedTest) { +// Disabled on interpreter backend since infeed/outfeed HLOs are unsupported. +XLA_TEST_F(LocalClientExecuteTest, DISABLED_ON_INTERPRETER(InfeedOutfeedTest)) { XlaBuilder builder(TestName()); const Shape shape = ShapeUtil::MakeShape(F32, {3}); auto in = Infeed(&builder, shape); -- GitLab From 4435d81822bef10437b3ec7a73300db62a5a53ad Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Wed, 2 Jan 2019 16:16:35 -0800 Subject: [PATCH 0111/2345] Throw a more helpful error message when trying to export a subclassed Model Previously it said to call _set_inputs. More directly people should just specify signatures=... PiperOrigin-RevId: 227599645 --- tensorflow/python/saved_model/save_test.py | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tensorflow/python/saved_model/save_test.py b/tensorflow/python/saved_model/save_test.py index 4573495998..e643ff3dbf 100644 --- a/tensorflow/python/saved_model/save_test.py +++ b/tensorflow/python/saved_model/save_test.py @@ -31,6 +31,7 @@ from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_spec from tensorflow.python.framework import test_util +from tensorflow.python.keras.engine import training from tensorflow.python.keras.layers import core from tensorflow.python.lib.io import file_io from tensorflow.python.ops import lookup_ops @@ -306,6 +307,30 @@ class SaveTest(test.TestCase): self.assertNotIn("T", complex_node.attr) self.assertNotIn("Tout", complex_node.attr) + def test_subclassed_no_signature(self): + + class Subclassed(training.Model): + + def call(self, inputs): + return inputs * 2. + + save_dir = os.path.join(self.get_temp_dir(), "saved_model") + model = Subclassed() + with self.assertRaisesRegexp( + ValueError, "no @tf.function-decorated methods"): + save.save(model, save_dir) + + traced_call = def_function.function( + model.call, + input_signature=(tensor_spec.TensorSpec( + (None, None), + dtype=dtypes.float32),)) + save.save(model, save_dir, traced_call) + self.assertAllClose({"output_0": [[8., 10.], [10., 12.]]}, + _import_and_infer( + save_dir, + {"inputs": [[4., 5.], [5., 6.]]})) + class AssetTests(test.TestCase): -- GitLab From da7b71f67147ff4795c5c0168d1f225ba2b4b522 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 2 Jan 2019 16:32:13 -0800 Subject: [PATCH 0112/2345] Automated rollback of commit 7d2be9eef5f4b74da5b5c6f473ea2e6ad57398dc PiperOrigin-RevId: 227601883 --- .../lite/delegates/nnapi/nnapi_delegate.cc | 161 ++- tensorflow/lite/nnapi/BUILD | 12 - tensorflow/lite/nnapi/NeuralNetworksShim.cc | 137 -- tensorflow/lite/nnapi/NeuralNetworksShim.h | 1205 +++++++++-------- tensorflow/lite/nnapi/nnapi_lib_test.cc | 90 -- tensorflow/lite/nnapi_delegate.cc | 135 +- 6 files changed, 809 insertions(+), 931 deletions(-) delete mode 100644 tensorflow/lite/nnapi/NeuralNetworksShim.cc delete mode 100644 tensorflow/lite/nnapi/nnapi_lib_test.cc diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc index 9487d3331f..8af159e6fb 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc @@ -48,39 +48,57 @@ namespace { } while (0) namespace { +int32_t GetAndroidSdkVersion() { +#ifdef __ANDROID__ + const char* sdkProp = "ro.build.version.sdk"; + char sdkVersion[PROP_VALUE_MAX]; + int length = __system_property_get(sdkProp, sdkVersion); + if (length != 0) { + for (int i = 0; i < length; ++i) { + int digit = sdkVersion[i] - '0'; + if (digit < 0 || digit > 9) { + // Non-numeric SDK version, assume it's higher then expected; + return std::numeric_limits::max(); + } + } + return atoi(sdkVersion); + } +#endif // __ANDROID__ + return 0; +} + constexpr int32_t kMinSdkVersionForNNAPI = 27; constexpr int32_t kMinSdkVersionForNNAPI11 = 28; +static const int32_t kAndroidSdkVersion = GetAndroidSdkVersion(); + } // namespace // RAII NN API Model Destructor for use with std::unique_ptr struct NNFreeModel { void operator()(ANeuralNetworksModel* model) { - NnApiImplementation()->ANeuralNetworksModel_free(model); + ANeuralNetworksModel_free(model); } }; // RAII NN API Compilation Destructor for use with std::unique_ptr struct NNFreeCompilation { void operator()(ANeuralNetworksCompilation* model) { - NnApiImplementation()->ANeuralNetworksCompilation_free(model); + ANeuralNetworksCompilation_free(model); } }; // Manage NNAPI shared memory handle class NNMemory { public: + NNMemory(const char* name, size_t size) { #ifdef __ANDROID__ - NNMemory(const NnApi* nnapi, const char* name, size_t size) { - nnapi_ = nnapi; byte_size_ = size; - fd_ = nnapi_->ASharedMemory_create(name, size); + fd_ = ASharedMemory_create(name, size); data_ptr_ = reinterpret_cast( mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0)); - nnapi_->ANeuralNetworksMemory_createFromFd(size, PROT_READ | PROT_WRITE, - fd_, 0, &nn_memory_handle_); - } -#else - NNMemory(const NnApi* /*nnapi*/, const char* /*name*/, size_t /*size*/) {} + ANeuralNetworksMemory_createFromFd(size, PROT_READ | PROT_WRITE, fd_, 0, + &nn_memory_handle_); #endif + } ~NNMemory() { #ifdef __ANDROID__ @@ -88,7 +106,7 @@ class NNMemory { munmap(data_ptr_, byte_size_); } if (nn_memory_handle_) { - nnapi_->ANeuralNetworksMemory_free(nn_memory_handle_); + ANeuralNetworksMemory_free(nn_memory_handle_); } if (fd_ > 0) close(fd_); #endif @@ -99,7 +117,6 @@ class NNMemory { private: #ifdef __ANDROID__ - const NnApi* nnapi_; int fd_ = 0; size_t byte_size_ = 0; #endif @@ -149,10 +166,9 @@ class OperandMapping { // operands for both tensors and parameters, and TFLite separates the two. class NNAPIOpBuilder { public: - NNAPIOpBuilder(const NnApi* nnapi, TfLiteContext* context, - OperandMapping* tensor_mapping, ANeuralNetworksModel* nn_model) - : nnapi_(nnapi), - context_(context), + NNAPIOpBuilder(TfLiteContext* context, OperandMapping* tensor_mapping, + ANeuralNetworksModel* nn_model) + : context_(context), operand_mapping_(tensor_mapping), nn_model_(nn_model) {} @@ -208,8 +224,7 @@ class NNAPIOpBuilder { .dimensionCount = dimension_count, .dimensions = dims.data()}; RETURN_TFLITE_ERROR_IF_NN_ERROR( - context_, - nnapi_->ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); + context_, ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); int ann_operand = operand_mapping_->add_new_non_tensor_operand(); augmented_outputs_.push_back(ann_operand); return kTfLiteOk; @@ -226,8 +241,7 @@ class NNAPIOpBuilder { reinterpret_cast(tensor->dims->data), tensor->params.scale, tensor->params.zero_point}; RETURN_TFLITE_ERROR_IF_NN_ERROR( - context_, - nnapi_->ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); + context_, ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); augmented_outputs_.push_back(ann_index); *ann_tensor_index_out = ann_index; @@ -284,14 +298,13 @@ class NNAPIOpBuilder { nn_type, static_cast(tensor->dims->size), reinterpret_cast(tensor->dims->data), scale, zeroPoint}; RETURN_TFLITE_ERROR_IF_NN_ERROR( - context_, - nnapi_->ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); + context_, ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); if (tensor->allocation_type == kTfLiteMmapRo) { // TODO(b/80630405): Use NNAPIAllocation. RETURN_TFLITE_ERROR_IF_NN_ERROR( context_, - nnapi_->ANeuralNetworksModel_setOperandValue( + ANeuralNetworksModel_setOperandValue( nn_model_, ann_tensor_index, tensor->data.raw, tensor->bytes)); } @@ -304,7 +317,7 @@ class NNAPIOpBuilder { // Actually add a NN API operation RETURN_TFLITE_ERROR_IF_NN_ERROR( context_, - nnapi_->ANeuralNetworksModel_addOperation( + ANeuralNetworksModel_addOperation( nn_model_, type, static_cast(augmented_inputs_.size()), augmented_inputs_.data(), static_cast(augmented_outputs_.size()), @@ -319,12 +332,11 @@ class NNAPIOpBuilder { TfLiteStatus AddScalarOperand(T value, int32_t nn_type) { ANeuralNetworksOperandType operand_type{.type = nn_type}; RETURN_TFLITE_ERROR_IF_NN_ERROR( - context_, - nnapi_->ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); + context_, ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); int ann_operand = operand_mapping_->add_new_non_tensor_operand(); RETURN_TFLITE_ERROR_IF_NN_ERROR( - context_, nnapi_->ANeuralNetworksModel_setOperandValue( - nn_model_, ann_operand, &value, sizeof(T))); + context_, ANeuralNetworksModel_setOperandValue(nn_model_, ann_operand, + &value, sizeof(T))); augmented_inputs_.push_back(ann_operand); return kTfLiteOk; } @@ -335,19 +347,15 @@ class NNAPIOpBuilder { ANeuralNetworksOperandType operand_type{ .type = nn_type, .dimensionCount = 1, .dimensions = &num_values}; RETURN_TFLITE_ERROR_IF_NN_ERROR( - context_, - nnapi_->ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); + context_, ANeuralNetworksModel_addOperand(nn_model_, &operand_type)); int ann_operand = operand_mapping_->add_new_non_tensor_operand(); RETURN_TFLITE_ERROR_IF_NN_ERROR( - context_, nnapi_->ANeuralNetworksModel_setOperandValue( + context_, ANeuralNetworksModel_setOperandValue( nn_model_, ann_operand, values, sizeof(T) * num_values)); augmented_inputs_.push_back(ann_operand); return kTfLiteOk; } - // Access to NNAPI. - const NnApi* const nnapi_; - // TfLiteContext for error handling. TfLiteContext* const context_; @@ -383,7 +391,7 @@ ANeuralNetworksOperationType BasicMappingFn( // The kernel that represents the node sub set of TF Lite being run on NN API. class NNAPIDelegateKernel { public: - NNAPIDelegateKernel() { nnapi_ = NnApiImplementation(); } + NNAPIDelegateKernel() = default; typedef ANeuralNetworksOperationType (*MappingFn)( const NNAPIOpMappingArgs& mapping_args); @@ -392,7 +400,7 @@ class NNAPIDelegateKernel { // when called. You can use this function to see if a node is supported // (i.e. that MappingFn is not nullptr). static MappingFn Map(TfLiteContext* context, int builtin_code, int version, - int android_sdk_version, TfLiteNode* node) { + TfLiteNode* node) { switch (builtin_code) { case kTfLiteBuiltinAdd: if (version == 1) { @@ -511,7 +519,7 @@ class NNAPIDelegateKernel { } break; case kTfLiteBuiltinSqueeze: - if (version == 1 && android_sdk_version >= kMinSdkVersionForNNAPI11) { + if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11) { return [](const NNAPIOpMappingArgs& mapping_args) -> ANeuralNetworksOperationType { auto builtin = reinterpret_cast( @@ -627,7 +635,7 @@ class NNAPIDelegateKernel { } break; case kTfLiteBuiltinSub: - if (version == 1 && android_sdk_version >= kMinSdkVersionForNNAPI11 && + if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11 && context->tensors[node->inputs->data[0]].type == kTfLiteFloat32) { // NNAPI only support float sub. return [](const NNAPIOpMappingArgs& mapping_args) @@ -640,7 +648,7 @@ class NNAPIDelegateKernel { } break; case kTfLiteBuiltinDiv: - if (version == 1 && android_sdk_version >= kMinSdkVersionForNNAPI11 && + if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11 && context->tensors[node->inputs->data[0]].type == kTfLiteFloat32) { // NNAPI only support float div. return [](const NNAPIOpMappingArgs& mapping_args) @@ -653,7 +661,7 @@ class NNAPIDelegateKernel { } break; case kTfLiteBuiltinPad: - if (version == 1 && android_sdk_version >= kMinSdkVersionForNNAPI11 && + if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11 && node->inputs->size == 2 && context->tensors[node->inputs->data[0]].type == kTfLiteFloat32) { // NNAPI does not support specifying the padding value. @@ -663,12 +671,12 @@ class NNAPIDelegateKernel { } break; case kTfLiteBuiltinSpaceToBatchNd: - if (version == 1 && android_sdk_version >= kMinSdkVersionForNNAPI11) { + if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11) { return BasicMappingFn; } break; case kTfLiteBuiltinStridedSlice: - if (version == 1 && android_sdk_version >= kMinSdkVersionForNNAPI11) { + if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11) { return [](const NNAPIOpMappingArgs& mapping_args) -> ANeuralNetworksOperationType { auto builtin = reinterpret_cast( @@ -686,7 +694,7 @@ class NNAPIDelegateKernel { // dimensions. // TODO(b/110888333): Support dynamically-sized tensors in delegates. if ((version == 1) && - (android_sdk_version >= kMinSdkVersionForNNAPI11) && + (kAndroidSdkVersion >= kMinSdkVersionForNNAPI11) && (node->inputs->size > 1) && (context->tensors[node->inputs->data[1]].allocation_type == kTfLiteMmapRo)) { @@ -784,7 +792,7 @@ class NNAPIDelegateKernel { break; case kTfLiteBuiltinMean: // NNAPI does not support generating a scalar as output for MEAN. - if (version == 1 && android_sdk_version >= kMinSdkVersionForNNAPI11 && + if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11 && context->tensors[node->inputs->data[0]].type == kTfLiteFloat32 && context->tensors[node->outputs->data[0]].dims->size > 0) { return [](const NNAPIOpMappingArgs& mapping_args) @@ -828,8 +836,8 @@ class NNAPIDelegateKernel { if (!nn_model_) { ANeuralNetworksModel* model; - RETURN_TFLITE_ERROR_IF_NN_ERROR( - context, nnapi_->ANeuralNetworksModel_create(&model)); + RETURN_TFLITE_ERROR_IF_NN_ERROR(context, + ANeuralNetworksModel_create(&model)); nn_model_.reset(model); TF_LITE_ENSURE_STATUS( @@ -839,10 +847,10 @@ class NNAPIDelegateKernel { if (!nn_compilation_) { ANeuralNetworksCompilation* compilation; RETURN_TFLITE_ERROR_IF_NN_ERROR( - context, nnapi_->ANeuralNetworksCompilation_create(nn_model_.get(), - &compilation)); + context, + ANeuralNetworksCompilation_create(nn_model_.get(), &compilation)); RETURN_TFLITE_ERROR_IF_NN_ERROR( - context, nnapi_->ANeuralNetworksCompilation_finish(compilation)); + context, ANeuralNetworksCompilation_finish(compilation)); nn_compilation_.reset(compilation); } return kTfLiteOk; @@ -851,8 +859,8 @@ class NNAPIDelegateKernel { TfLiteStatus Invoke(TfLiteContext* context, TfLiteNode* node) { ANeuralNetworksExecution* execution = nullptr; RETURN_TFLITE_ERROR_IF_NN_ERROR( - context, nnapi_->ANeuralNetworksExecution_create(nn_compilation_.get(), - &execution)); + context, + ANeuralNetworksExecution_create(nn_compilation_.get(), &execution)); // Set the input tensor buffers. Note: we access tflite tensors using // absolute indices but NN api indices inputs by relative indices. @@ -872,7 +880,7 @@ class NNAPIDelegateKernel { tensor->data.raw, tensor->bytes); RETURN_TFLITE_ERROR_IF_NN_ERROR( context, - nnapi_->ANeuralNetworksExecution_setInputFromMemory( + ANeuralNetworksExecution_setInputFromMemory( execution, relative_input_index, nullptr, nn_input_memory_->get_handle(), input_offset, tensor->bytes)); input_offset += tensor->bytes; @@ -887,7 +895,7 @@ class NNAPIDelegateKernel { TfLiteTensor* tensor = &context->tensors[output_index]; RETURN_TFLITE_ERROR_IF_NN_ERROR( context, - nnapi_->ANeuralNetworksExecution_setOutputFromMemory( + ANeuralNetworksExecution_setOutputFromMemory( execution, relative_output_index, nullptr, nn_output_memory_->get_handle(), output_offset, tensor->bytes)); output_offset += tensor->bytes; @@ -903,7 +911,7 @@ class NNAPIDelegateKernel { // reading and writing into the same buffer during a invocation. // TODO(110369471): using double shared buffer to minimize the copies. RETURN_TFLITE_ERROR_IF_NN_ERROR( - context, nnapi_->ANeuralNetworksExecution_setOutput( + context, ANeuralNetworksExecution_setOutput( execution, relative_output_index, nullptr, tensor->data.raw, tensor->bytes)); relative_output_index++; @@ -911,12 +919,10 @@ class NNAPIDelegateKernel { // Invoke ANN in blocking fashion. ANeuralNetworksEvent* event = nullptr; RETURN_TFLITE_ERROR_IF_NN_ERROR( - context, - nnapi_->ANeuralNetworksExecution_startCompute(execution, &event)); - RETURN_TFLITE_ERROR_IF_NN_ERROR(context, - nnapi_->ANeuralNetworksEvent_wait(event)); - nnapi_->ANeuralNetworksEvent_free(event); - nnapi_->ANeuralNetworksExecution_free(execution); + context, ANeuralNetworksExecution_startCompute(execution, &event)); + RETURN_TFLITE_ERROR_IF_NN_ERROR(context, ANeuralNetworksEvent_wait(event)); + ANeuralNetworksEvent_free(event); + ANeuralNetworksExecution_free(execution); // copy results from shared memory to the destination. output_offset = 0; @@ -931,8 +937,6 @@ class NNAPIDelegateKernel { } private: - // Access to NNApi. - const NnApi* nnapi_; // ANN API state. std::unique_ptr nn_model_; std::unique_ptr @@ -953,7 +957,7 @@ class NNAPIDelegateKernel { // The operand builder allows creating a single op. We create it at this // reduced power position rather than in the for loop to avoid reallocating // the vectors. - NNAPIOpBuilder builder(nnapi_, context, &operand_mapping_, nn_model_.get()); + NNAPIOpBuilder builder(context, &operand_mapping_, nn_model_.get()); // Add Tensors // allocate outside to avoid realloc for (auto node_index : nodes_) { @@ -976,10 +980,9 @@ class NNAPIDelegateKernel { } } // Get op type and operands - int nn_op_type = Map( - context, reg->builtin_code, reg->version, nnapi_->android_sdk_version, - node)({context, &builder, node, &model_state_outputs_, - &model_state_tfl_inputs_}); + int nn_op_type = Map(context, reg->builtin_code, reg->version, node)( + {context, &builder, node, &model_state_outputs_, + &model_state_tfl_inputs_}); // Map outputs to NN API tensor indices. for (auto output_index : TfLiteIntArrayView(node->outputs)) { TF_LITE_ENSURE_STATUS(builder.AddTensorOutput(output_index)); @@ -1025,27 +1028,25 @@ class NNAPIDelegateKernel { // Tell ANN to declare inputs/outputs RETURN_TFLITE_ERROR_IF_NN_ERROR( - context, nnapi_->ANeuralNetworksModel_identifyInputsAndOutputs( + context, ANeuralNetworksModel_identifyInputsAndOutputs( nn_model_.get(), inputs.size(), inputs.data(), outputs.size(), outputs.data())); // Set relaxed computation mode for fp32 if possible. - if (nnapi_->android_sdk_version >= kMinSdkVersionForNNAPI11) { + if (kAndroidSdkVersion >= kMinSdkVersionForNNAPI11) { RETURN_TFLITE_ERROR_IF_NN_ERROR( - context, - nnapi_->ANeuralNetworksModel_relaxComputationFloat32toFloat16( - nn_model_.get(), context->allow_fp32_relax_to_fp16)); + context, ANeuralNetworksModel_relaxComputationFloat32toFloat16( + nn_model_.get(), context->allow_fp32_relax_to_fp16)); } // Finalize the model RETURN_TFLITE_ERROR_IF_NN_ERROR( - context, nnapi_->ANeuralNetworksModel_finish(nn_model_.get())); + context, ANeuralNetworksModel_finish(nn_model_.get())); // Create shared memory pool for inputs and outputs. - nn_input_memory_.reset( - new NNMemory(nnapi_, "input_pool", total_input_byte_size)); + nn_input_memory_.reset(new NNMemory("input_pool", total_input_byte_size)); nn_output_memory_.reset( - new NNMemory(nnapi_, "output_pool", total_output_byte_size)); + new NNMemory("output_pool", total_output_byte_size)); return kTfLiteOk; } @@ -1061,9 +1062,7 @@ TfLiteDelegate* NnApiDelegate() { .Prepare = [](TfLiteContext* context, TfLiteDelegate* delegate) -> TfLiteStatus { // Do not check nodes_ if NN API is unavailable. - const NnApi* nnapi = NnApiImplementation(); - if (nnapi->android_sdk_version < kMinSdkVersionForNNAPI || - !nnapi->nnapi_exists) { + if (kAndroidSdkVersion < kMinSdkVersionForNNAPI || !NNAPIExists()) { return kTfLiteOk; } @@ -1076,7 +1075,6 @@ TfLiteDelegate* NnApiDelegate() { TfLiteIntArray* plan; TF_LITE_ENSURE_STATUS(context->GetExecutionPlan(context, &plan)); - int android_sdk_version = NnApiImplementation()->android_sdk_version; // Check for every node if it is supported // TODO(b/80625235): Fix this to do more careful checking of versioning. for (int node_index : TfLiteIntArrayView(plan)) { @@ -1085,8 +1083,7 @@ TfLiteDelegate* NnApiDelegate() { TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration( context, node_index, &node, ®istration)); if (NNAPIDelegateKernel::Map(context, registration->builtin_code, - registration->version, - android_sdk_version, node)) { + registration->version, node)) { supported_nodes.push_back(node_index); } } diff --git a/tensorflow/lite/nnapi/BUILD b/tensorflow/lite/nnapi/BUILD index 390c3730cb..467a2b7a7b 100644 --- a/tensorflow/lite/nnapi/BUILD +++ b/tensorflow/lite/nnapi/BUILD @@ -6,20 +6,8 @@ package(default_visibility = [ cc_library( name = "nnapi_lib", - srcs = [ - "NeuralNetworksShim.cc", - ], hdrs = [ "NeuralNetworksShim.h", ], linkopts = ["-ldl"], ) - -cc_test( - name = "nnapi_lib_test", - srcs = ["nnapi_lib_test.cc"], - deps = [ - "//tensorflow/lite/nnapi:nnapi_lib", - "@com_google_googletest//:gtest_main", - ], -) diff --git a/tensorflow/lite/nnapi/NeuralNetworksShim.cc b/tensorflow/lite/nnapi/NeuralNetworksShim.cc deleted file mode 100644 index c063525cae..0000000000 --- a/tensorflow/lite/nnapi/NeuralNetworksShim.cc +++ /dev/null @@ -1,137 +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/lite/nnapi/NeuralNetworksShim.h" - -#include - -#ifdef __ANDROID__ -#include -#include -#include -#include -#endif - -#define NNAPI_LOG(format, ...) fprintf(stderr, format "\n", __VA_ARGS__); - -namespace { - -#ifdef __ANDROID__ -int32_t GetAndroidSdkVersion() { - const char* sdkProp = "ro.build.version.sdk"; - char sdkVersion[PROP_VALUE_MAX]; - int length = __system_property_get(sdkProp, sdkVersion); - if (length != 0) { - int32_t result = 0; - for (int i = 0; i < length; ++i) { - int digit = sdkVersion[i] - '0'; - if (digit < 0 || digit > 9) { - // Non-numeric SDK version, assume it's higher than expected; - return 0xffff; - } - result = result * 10 + digit; - } - return result; - } - return 0; -} - -void* LoadFunction(void* handle, const char* name) { - if (handle == nullptr) { - return nullptr; - } - void* fn = dlsym(handle, name); - if (fn == nullptr) { - NNAPI_LOG("nnapi error: unable to open function %s", name); - } - return fn; -} - -#define LOAD_FUNCTION(handle, name) \ - nnapi.name = reinterpret_cast(LoadFunction(handle, #name)); - -#else - -#define LOAD_FUNCTION(handle, name) nnapi.name = nullptr; - -#endif - -const NnApi LoadNnApi() { - NnApi nnapi = {}; - -#ifdef __ANDROID__ - void* libneuralnetworks = nullptr; - void* libandroid = nullptr; - nnapi.android_sdk_version = GetAndroidSdkVersion(); - if (nnapi.android_sdk_version < 27) { - NNAPI_LOG("nnapi error: requires android sdk version to be at least %d", - 27); - } else { - // TODO: change RTLD_LOCAL? Assumes there can be multiple instances of nn - // api RT - libneuralnetworks = dlopen("libneuralnetworks.so", RTLD_LAZY | RTLD_LOCAL); - if (libneuralnetworks == nullptr) { - NNAPI_LOG("nnapi error: unable to open library %s", - "libneuralnetworks.so"); - } - libandroid = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL); - if (libneuralnetworks == nullptr) { - NNAPI_LOG("nnapi error: unable to open library %s", "libandroid.so"); - } - } - nnapi.nnapi_exists = libneuralnetworks != nullptr; -#else - nnapi.nnapi_exists = false; - nnapi.android_sdk_version = 0; -#endif - - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksMemory_createFromFd); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksMemory_free); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_create); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_free); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_finish); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_addOperand); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_setOperandValue); - LOAD_FUNCTION(libneuralnetworks, - ANeuralNetworksModel_setOperandValueFromMemory); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_addOperation); - LOAD_FUNCTION(libneuralnetworks, - ANeuralNetworksModel_identifyInputsAndOutputs); - LOAD_FUNCTION(libneuralnetworks, - ANeuralNetworksModel_relaxComputationFloat32toFloat16); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksCompilation_create); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksCompilation_free); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksCompilation_setPreference); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksCompilation_finish); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_create); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_free); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_setInput); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_setInputFromMemory); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_setOutput); - LOAD_FUNCTION(libneuralnetworks, - ANeuralNetworksExecution_setOutputFromMemory); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_startCompute); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksEvent_wait); - LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksEvent_free); - LOAD_FUNCTION(libandroid, ASharedMemory_create); - - return nnapi; -} - -} // namespace - -const NnApi* NnApiImplementation() { - static const NnApi nnapi = LoadNnApi(); - return &nnapi; -} diff --git a/tensorflow/lite/nnapi/NeuralNetworksShim.h b/tensorflow/lite/nnapi/NeuralNetworksShim.h index 3fd869e4af..c39502f4ac 100644 --- a/tensorflow/lite/nnapi/NeuralNetworksShim.h +++ b/tensorflow/lite/nnapi/NeuralNetworksShim.h @@ -15,10 +15,69 @@ limitations under the License. #ifndef TENSORFLOW_LITE_NNAPI_NEURALNETWORKSSHIM_H_ #define TENSORFLOW_LITE_NNAPI_NEURALNETWORKSSHIM_H_ +#include #include #include #include +// helpers + +#define NNAPI_LOG(format, ...) fprintf(stderr, format "\n", __VA_ARGS__); +#define LOAD_FUNCTION(name) \ + static name##_fn fn = reinterpret_cast(loadFunction(#name)); +#define EXECUTE_FUNCTION(...) \ + if (fn != nullptr) { \ + fn(__VA_ARGS__); \ + } +#define EXECUTE_FUNCTION_RETURN(...) return fn != nullptr ? fn(__VA_ARGS__) : 0; + +inline void* loadLibrary(const char* name) { + // TODO: change RTLD_LOCAL? Assumes there can be multiple instances of nn + // api RT + void* handle = nullptr; +#ifdef __ANDROID__ + handle = dlopen(name, RTLD_LAZY | RTLD_LOCAL); + if (handle == nullptr) { + NNAPI_LOG("nnapi error: unable to open library %s", name); + } +#endif + return handle; +} + +typedef int (*ASharedMemory_create_fn)(const char* name, size_t size); + +// ASharedMemory_create was added in Android 8.0, so safe to use with NNAPI +// which was added in 8.1. +inline int ASharedMemory_create(const char* name, size_t size) { + static void* handle = loadLibrary("libandroid.so"); + static ASharedMemory_create_fn fn = + handle != nullptr ? reinterpret_cast( + dlsym(handle, "ASharedMemory_create")) + : nullptr; + return fn(name, size); +} + +inline void* getLibraryHandle() { + static void* handle = loadLibrary("libneuralnetworks.so"); + return handle; +} + +inline void* loadFunction(const char* name) { + void* fn = nullptr; + if (getLibraryHandle() != nullptr) { + fn = dlsym(getLibraryHandle(), name); + } + if (fn == nullptr) { + NNAPI_LOG("nnapi error: unable to open function %s", name); + } + return fn; +} + +inline bool NNAPIExists() { + static bool nnapi_is_available = getLibraryHandle(); + return nnapi_is_available; +} + // NN api types based on NNAPI header file // https://developer.android.com/ndk/reference/group/neural-networks @@ -348,564 +407,606 @@ typedef int (*ANeuralNetworksEvent_wait_fn)(ANeuralNetworksEvent* event); typedef void (*ANeuralNetworksEvent_free_fn)(ANeuralNetworksEvent* event); -typedef int (*ASharedMemory_create_fn)(const char* name, size_t size); +/** + * Creates a shared memory object from a file descriptor. + * + * The shared memory is backed by a file descriptor via mmap. + * See {@link ANeuralNetworksMemory} for a description on how to use + * this shared memory. + * + * @param size The requested size in bytes. + * Must not be larger than the file size. + * @param prot The desired memory protection for the mapping. + * It is either PROT_NONE or the bitwise OR of one or + * more of the following flags: PROT_READ, PROT_WRITE. + * @param fd The requested file descriptor. + * The file descriptor has to be mmap-able. The file + * descriptor will be duplicated. + * @param offset The offset to the beginning of the file of the area to map. + * The offset has to be aligned to a page size. + * @param memory The memory object to be created. + * Set to NULL if unsuccessful. + * + * @return ANEURALNETWORKS_NO_ERROR if the request completed normally. + */ +inline int ANeuralNetworksMemory_createFromFd(size_t size, int protect, int fd, + size_t offset, + ANeuralNetworksMemory** memory) { + LOAD_FUNCTION(ANeuralNetworksMemory_createFromFd); + EXECUTE_FUNCTION_RETURN(size, protect, fd, offset, memory); +} -struct NnApi { - bool nnapi_exists; - int32_t android_sdk_version; - - /** - * Creates a shared memory object from a file descriptor. - * - * The shared memory is backed by a file descriptor via mmap. - * See {@link ANeuralNetworksMemory} for a description on how to use - * this shared memory. - * - * @param size The requested size in bytes. - * Must not be larger than the file size. - * @param prot The desired memory protection for the mapping. - * It is either PROT_NONE or the bitwise OR of one or - * more of the following flags: PROT_READ, PROT_WRITE. - * @param fd The requested file descriptor. - * The file descriptor has to be mmap-able. The file - * descriptor will be duplicated. - * @param offset The offset to the beginning of the file of the area to map. - * The offset has to be aligned to a page size. - * @param memory The memory object to be created. - * Set to NULL if unsuccessful. - * - * @return ANEURALNETWORKS_NO_ERROR if the request completed normally. - */ - int (*ANeuralNetworksMemory_createFromFd)(size_t size, int protect, int fd, - size_t offset, - ANeuralNetworksMemory** memory); - - /** - * Delete a memory object. - * - * Destroys the object used by the run time to keep track of the memory. - * This will free the underlying actual memory if no other code has open - * handles to this memory. - * - * @param memory The memory object to be freed. - */ - void (*ANeuralNetworksMemory_free)(ANeuralNetworksMemory* memory); - - /** - * Create an empty {@link ANeuralNetworksModel}. - * - *

This only creates the object. Computation is performed once - * {@link ANeuralNetworksExecution_startCompute} is invoked. - * - * The model should be constructed with calls to - * {@link ANeuralNetworksModel_addOperation} and - * {@link ANeuralNetworksModel_addOperand} - * - *

{@link ANeuralNetworksModel_finish} should be called once the model - * has been fully constructed.

- * - *

{@link ANeuralNetworksModel_free} should be called once the model - * is no longer needed.

- * - * @param model The {@link ANeuralNetworksModel} to be created. - * Set to NULL if unsuccessful. - * - * @return ANEURALNETWORKS_NO_ERROR if successful. - */ - int (*ANeuralNetworksModel_create)(ANeuralNetworksModel** model); - - /** - * Destroy a model. - * - * The model need not have been finished by a call to - * {@link ANeuralNetworksModel_finish}. - * - * See {@link ANeuralNetworksModel} for information on multithreaded usage. - * - * @param model The model to be destroyed. Passing NULL is acceptable and - * results in no operation. - */ - void (*ANeuralNetworksModel_free)(ANeuralNetworksModel* model); - - /** - * Indicate that we have finished modifying a model. Required before - * calling {@link ANeuralNetworksCompilation_compile}. - * - * An application is responsible to make sure that no other thread uses - * the model at the same time. - * - * See {@link ANeuralNetworksModel} for information on multithreaded usage. - * - * @param model The model to be finished. - * - * @return ANEURALNETWORKS_NO_ERROR if successful. - */ - int (*ANeuralNetworksModel_finish)(ANeuralNetworksModel* model); - - /** - * Add an operand to a model. - * - * The order in which the operands are added is important. The first one added - * to a model will have the index value 0, the second 1, etc. These indexes - * are used as operand identifiers in - * {@link ANeuralNetworksModel_addOperation}, - * {@link ANeuralNetworksExecution_setInput}, - * {@link ANeuralNetworksExecution_setInputFromMemory}, - * {@link ANeuralNetworksExecution_setOutput}, - * {@link ANeuralNetworksExecution_setOutputFromMemory} and - * {@link ANeuralNetworksExecution_setOperandValue}. - * - * To build a model that can accommodate inputs of various sizes, as you may - * want to do for a CNN, set the size of the dimensions that will vary at run - * time to 0. If you do so, provide the full dimensions when calling - * {@link ANeuralNetworksExecution_setInput} or {@link - * ANeuralNetworksExecution_setInputFromMemory}. - * - * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has - * been called will return an error. - * - * See {@link ANeuralNetworksModel} for information on multithreaded usage. - * - * @param model The model to be modified. - * @param type The {@link ANeuralNetworksOperandType} that describes the shape - * of the operand. - * - * @return ANEURALNETWORKS_NO_ERROR if successful. - */ - int (*ANeuralNetworksModel_addOperand)( - ANeuralNetworksModel* model, const ANeuralNetworksOperandType* type); - - /** - * Sets an operand to a constant value. - * - * For scalar values, the content of buffer is copied into the model. - * - * For tensor values, a pointer to the buffer is stored within the model. - * The application is responsible for not changing the content of this region - * until all executions using this model have completed. As the data may - * be copied during processing, modifying the data after this call yields - * undefined results. - * - * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has - * been called will return an error. - * - * See {@link ANeuralNetworksModel} for information on multithreaded usage. - * - * @param model The model to be modified. - * @param index The index of the model operand we're setting. - * @param buffer A pointer to the data to use. - * @param length The size in bytes of the data value. - * - * @return ANEURALNETWORKS_NO_ERROR if successful. - */ - int (*ANeuralNetworksModel_setOperandValue)(ANeuralNetworksModel* model, - int32_t index, const void* buffer, - size_t length); - - /** - * Sets an operand to a value stored in a memory object. - * - * The content of the memory is not copied. A reference to that memory is - * stored inside the model. The application is responsible for not changing - * the content of the memory region until all executions using this model have - * completed. - * As the data may be copied during processing, modifying the data after this - * call yields undefined results. - * - * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has - * been called will return an error. - * - * See {@link ANeuralNetworksModel} for information on multithreaded usage. - * - * @param model The model to be modified. - * @param index The index of the model operand we're setting. - * @param buffer A pointer to the data to use. - * @param memory The memory containing the data. - * @param offset This specifies the location of the data within the memory. - * The offset is in bytes from the start of memory. - * @param length The size in bytes of the data value. - * - * @return ANEURALNETWORKS_NO_ERROR if successful. - */ - int (*ANeuralNetworksModel_setOperandValueFromMemory)( - ANeuralNetworksModel* model, int32_t index, - const ANeuralNetworksMemory* memory, size_t offset, size_t length); - - /** - * Add an operation to a model. - * - * @param model The model to be modified. - * @param type The type of the operation. - * @param inputCount The number of entries in the inputs array. - * @param inputs An array of indexes identifying each operand. - * @param outputCount The number of entries in the outputs array. - * @param outputs An array of indexes identifying each operand. - * - * The operands specified by inputs and outputs must have been - * previously added by calls to {@link ANeuralNetworksModel_addOperand}. - * - * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has - * been called will return an error. - * - * See {@link ANeuralNetworksModel} for information on multithreaded usage. - * - * @return ANEURALNETWORKS_NO_ERROR if successful. - */ - int (*ANeuralNetworksModel_addOperation)(ANeuralNetworksModel* model, - ANeuralNetworksOperationType type, - uint32_t inputCount, - const uint32_t* inputs, - uint32_t outputCount, - const uint32_t* outputs); - - /** - * Specifies which operands will be the model's inputs and outputs. - * - * An operand cannot be used for both input and output. Doing so will - * return an error. - * - * @param model The model to be modified. - * @param inputCount The number of entries in the inputs array. - * @param inputs An array of indexes identifying the input operands. - * @param outputCount The number of entries in the outputs array. - * @param outputs An array of indexes identifying the output operands. - * - * The operands specified by inputs and outputs must have been - * previously added by calls to {@link ANeuralNetworksModel_addOperand}. - * - * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has - * been called will return an error. - * - * See {@link ANeuralNetworksModel} for information on multithreaded usage. - * - */ - int (*ANeuralNetworksModel_identifyInputsAndOutputs)( - ANeuralNetworksModel* model, uint32_t inputCount, const uint32_t* inputs, - uint32_t outputCount, const uint32_t* outputs); - - /** - * Specifies whether {@link ANEURALNETWORKS_TENSOR_FLOAT32} is allowed to be - * calculated with range and/or precision as low as that of the - * IEEE 754 16-bit floating-point format. By default, - * {@link ANEURALNETWORKS_TENSOR_FLOAT32} must be calculated using at least - * the range and precision of the IEEE 754 32-bit floating-point format. - * - * @param model The model to be modified. - * @param allow 'true' indicates {@link ANEURALNETWORKS_TENSOR_FLOAT32} may be - * calculated with range and/or precision as low as that of the - * IEEE 754 16-bit floating point format. 'false' indicates - * {@link ANEURALNETWORKS_TENSOR_FLOAT32} must be calculated - * using at least the range and precision of the IEEE 754 32-bit - * floating point format. - * - * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has - * been called will return an error. - * - * Available since API level 28. - * - * See {@link ANeuralNetworksModel} for information on multithreaded usage. - */ - int (*ANeuralNetworksModel_relaxComputationFloat32toFloat16)( - ANeuralNetworksModel* model, bool allow); - - /** - * Create a {@link ANeuralNetworksCompilation} to compile the given model. - * This only creates the object. Compilation is only performed once - * {@link ANeuralNetworksCompilation_start} is invoked. - * - *

The provided model must outlive the compilation.

- * - * The model must already have been finished by a call to - * {@link ANeuralNetworksModel_finish}. - * - * See {@link ANeuralNetworksCompilation} for information on multithreaded - * usage. - * - * @param model The {@link ANeuralNetworksModel} to be compiled. - * @param compilation The newly created object or NULL if unsuccessful. - * - * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA - * if the model is invalid. - */ - int (*ANeuralNetworksCompilation_create)( - ANeuralNetworksModel* model, ANeuralNetworksCompilation** compilation); - - /** - * Destroy a compilation. - * - *

If called on a compilation for which - * {@link ANeuralNetworksCompilation_start} has been called, the - * function will return immediately but will mark the compilation to be - * deleted once the compilation completes. The - * {@link ANeuralNetworksCompilation_wait} will return ERROR_DELETED. - * - * See {@link ANeuralNetworksCompilation} for information on multithreaded - * usage. - * - * @param compilation The compilation to be destroyed. Passing NULL is - * acceptable and results in no operation. - */ - void (*ANeuralNetworksCompilation_free)( - ANeuralNetworksCompilation* compilation); - - /** - * Sets the execution preference. - * - *

Provides guidance to the runtime when trade-offs are possible.

- * - * See {@link ANeuralNetworksCompilation} for information on multithreaded - * usage. - * - * @param compilation The compilation to be modified. - * @param preference Either {@link PREFER_LOW_POWER}, - * {@link PREFER_SINGLE_FAST_ANSWER}, or - * {@link PREFER_SUSTAINED_SPEED}. - * - * @return ANEURALNETWORKS_NO_ERROR if successful. - */ - int (*ANeuralNetworksCompilation_setPreference)( - ANeuralNetworksCompilation* compilation, int32_t preference); - - /** - * Waits until the compilation completes. - * - * More than one thread can wait on a compilation. When the compilation - * completes, all threads will be released. - * - * See {@link ANeuralNetworksCompilation} for information on multithreaded - * usage. - * - * @return ANEURALNETWORKS_NO_ERROR if the compilation completed normally. - */ - int (*ANeuralNetworksCompilation_finish)( - ANeuralNetworksCompilation* compilation); - - /** - * Create a {@link ANeuralNetworksExecution} to apply the given compilation. - * This only creates the object. Computation is only performed once - * {@link ANeuralNetworksExecution_startCompute} is invoked. - * - *

The provided compilation must outlive the execution.

- * - * See {@link ANeuralNetworksExecution} for information on multithreaded - * usage. - * - * @param compilation The {@link ANeuralNetworksCompilation} to be evaluated. - * @param execution The newly created object or NULL if unsuccessful. - * - * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA - * if the compilation is invalid. - */ - int (*ANeuralNetworksExecution_create)( - ANeuralNetworksCompilation* compilation, - ANeuralNetworksExecution** execution); - - /** - * Destroy an execution. - * - *

If called on an execution for which - * {@link ANeuralNetworksExecution_startCompute} has been called, the - * function will return immediately but will mark the execution to be deleted - * once the computation completes. The {link ANeuralNetworksExecution_wait} - * will return ANEURALNETWORKS_ERROR_DELETED. - * - * See {@link ANeuralNetworksExecution} for information on multithreaded - * usage. - * - * @param execution The execution to be destroyed. Passing NULL is acceptable - * and results in no operation. - */ - void (*ANeuralNetworksExecution_free)(ANeuralNetworksExecution* execution); - - /** - * Associate a user buffer with an input of the model of the - * {@link ANeuralNetworksExecution}. - * - *

The provided buffer must outlive the execution.

- * - * See {@link ANeuralNetworksExecution} for information on multithreaded - * usage. - * - * @param execution The execution to be modified. - * @param index The index of the input argument we are setting. It is - * an index into the lists passed to - * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is - * not the index associated with {@link - * ANeuralNetworksModel_addOperand}. - * @param type The type of the operand. This should be used to specify the - * dimensions that were set to 0 when the operand was added to the - * model. All other properties of the type must be the same as - * specified in the model. If the type is the same as specified - * when the model was built, NULL can be passed. - * @param buffer The buffer containing the data. - * @param length The length in bytes of the buffer. - * - * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if - * the name is not recognized or the buffer is too small for the input. - */ - int (*ANeuralNetworksExecution_setInput)( - ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, const void* buffer, - size_t length); - - /** - * Associate part of a memory object with an input of the model of the - * {@link ANeuralNetworksExecution}. - * - *

The provided memory must outlive the execution.

- * - * See {@link ANeuralNetworksExecution} for information on multithreaded - * usage. - * - * @param execution The execution to be modified. - * @param index The index of the input argument we are setting. It is - * an index into the lists passed to - * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is - * not the index associated with {@link - * ANeuralNetworksModel_addOperand}. - * @param type The type of the operand. This can be used to specify the - * dimensions that were set to 0 when the operand was added to the - * model. All other values must be the same as specified in the - * model. If the type is the same as specified when the model - * was built, NULL can be passed. - * @param memory The memory containing the data. - * @param offset This specifies the location of the data within the memory. - * The offset is in bytes from the start of memory. - * @param length The size in bytes of the data value. - * - * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if - * the name is not recognized or the buffer is too small for the input. - */ - int (*ANeuralNetworksExecution_setInputFromMemory)( - ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, - const ANeuralNetworksMemory* memory, size_t offset, size_t length); - - /** - * Associate a user buffer with an output of the model of the - * {@link ANeuralNetworksExecution}. - * - *

The provided buffer must outlive the execution.

- * - * See {@link ANeuralNetworksExecution} for information on multithreaded - * usage. - * - * @param execution The execution to be modified. - * @param index The index of the output argument we are setting. It is - * an index into the lists passed to - * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is - * not the index associated with {@link - * ANeuralNetworksModel_addOperand}. - * @param type The type of the operand. This can be used to specify the - * dimensions that were set to 0 when the operand was added to the - * model. All other values must be the same as specified in the - * model. If the type is the same as specified when the model - * was built, NULL can be passed. - * @param buffer The buffer where the data is to be written. - * @param length The length in bytes of the buffer. - * - * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if - * the name is not recognized or the buffer is too small for the output. - */ - int (*ANeuralNetworksExecution_setOutput)( - ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, void* buffer, size_t length); - - /** - * Associate part of a memory object with an output of the model of the - * {@link ANeuralNetworksExecution}. - * - *

The provided memory must outlive the execution.

- * - * See {@link ANeuralNetworksExecution} for information on multithreaded - * usage. - * - * @param execution The execution to be modified. - * @param index The index of the output argument we are setting. It is - * an index into the lists passed to - * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is - * not the index associated with {@link - * ANeuralNetworksModel_addOperand}. - * @param type The type of the operand. This can be used to specify the - * dimensions that were set to 0 when the operand was added to the - * model. All other values must be the same as specified in the - * model. If the type is the same as specified when the model - * was built, NULL can be passed. - * @param memory The memory where the data is to be stored. - * @param offset This specifies the location of the data within the memory. - * The offset is in bytes from the start of memory. - * @param length The length in bytes of the data value. - * - * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if - * the name is not recognized or the buffer is too small for the output. - */ - int (*ANeuralNetworksExecution_setOutputFromMemory)( - ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, - const ANeuralNetworksMemory* memory, size_t offset, size_t length); - - /** - * Schedule evaluation of the execution. - * - *

Schedules evaluation of the execution. Once the model has been - * applied and the outputs are ready to be consumed, the execution will be - * signaled. Use {@link ANeuralNetworksExecution_wait} to wait for that - * signal. - *

- * - * Multiple executions can be scheduled and evaluated concurrently, and - * compilations can be performed concurrently with executions. The runtime - * makes no guarantee on the ordering of the completion of compilations and - * executions. If it's important to the application, the application should - * enforce the ordering by using {@link ANeuralNetworksCompilation_wait} and - * {@link ANeuralNetworksExecution_wait}. - * - * ANeuralNetworksExecution_wait must be called to recuperate the resources - * used by the execution. - * - * See {@link ANeuralNetworksExecution} for information on multithreaded - * usage. - * - * @param execution The execution to be scheduled and executed. - * - * @return ANEURALNETWORKS_NO_ERROR if successful. - */ - int (*ANeuralNetworksExecution_startCompute)( - ANeuralNetworksExecution* execution, ANeuralNetworksEvent** event); - - /** - * Waits until the execution completes. - * - * More than one thread can wait on an event. When the execution completes, - * all threads will be released. - * - * See {@link ANeuralNetworksExecution} for information on multithreaded - * usage. - * - * @return ANEURALNETWORKS_NO_ERROR if the execution completed normally. - */ - int (*ANeuralNetworksEvent_wait)(ANeuralNetworksEvent* event); +/** + * Delete a memory object. + * + * Destroys the object used by the run time to keep track of the memory. + * This will free the underlying actual memory if no other code has open + * handles to this memory. + * + * @param memory The memory object to be freed. + */ +inline void ANeuralNetworksMemory_free(ANeuralNetworksMemory* memory) { + LOAD_FUNCTION(ANeuralNetworksMemory_free); + EXECUTE_FUNCTION(memory); +} - /** - * Destroys the event. - * - * See {@link ANeuralNetworksExecution} for information on multithreaded - * usage. - */ - void (*ANeuralNetworksEvent_free)(ANeuralNetworksEvent* event); +/** + * Create an empty {@link ANeuralNetworksModel}. + * + *

This only creates the object. Computation is performed once + * {@link ANeuralNetworksExecution_startCompute} is invoked. + * + * The model should be constructed with calls to + * {@link ANeuralNetworksModel_addOperation} and + * {@link ANeuralNetworksModel_addOperand} + * + *

{@link ANeuralNetworksModel_finish} should be called once the model + * has been fully constructed.

+ * + *

{@link ANeuralNetworksModel_free} should be called once the model + * is no longer needed.

+ * + * @param model The {@link ANeuralNetworksModel} to be created. + * Set to NULL if unsuccessful. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +inline int ANeuralNetworksModel_create(ANeuralNetworksModel** model) { + LOAD_FUNCTION(ANeuralNetworksModel_create); + EXECUTE_FUNCTION_RETURN(model); +} - // ASharedMemory_create was added in Android 8.0, so safe to use with NNAPI - // which was added in 8.1. - int (*ASharedMemory_create)(const char* name, size_t size); +/** + * Destroy a model. + * + * The model need not have been finished by a call to + * {@link ANeuralNetworksModel_finish}. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be destroyed. Passing NULL is acceptable and + * results in no operation. + */ +inline void ANeuralNetworksModel_free(ANeuralNetworksModel* model) { + LOAD_FUNCTION(ANeuralNetworksModel_free); + EXECUTE_FUNCTION(model); +} - /**/ -}; +/** + * Indicate that we have finished modifying a model. Required before + * calling {@link ANeuralNetworksCompilation_compile}. + * + * An application is responsible to make sure that no other thread uses + * the model at the same time. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be finished. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +inline int ANeuralNetworksModel_finish(ANeuralNetworksModel* model) { + LOAD_FUNCTION(ANeuralNetworksModel_finish); + EXECUTE_FUNCTION_RETURN(model); +} + +/** + * Add an operand to a model. + * + * The order in which the operands are added is important. The first one added + * to a model will have the index value 0, the second 1, etc. These indexes are + * used as operand identifiers in {@link ANeuralNetworksModel_addOperation}, + * {@link ANeuralNetworksExecution_setInput}, + * {@link ANeuralNetworksExecution_setInputFromMemory}, + * {@link ANeuralNetworksExecution_setOutput}, + * {@link ANeuralNetworksExecution_setOutputFromMemory} and + * {@link ANeuralNetworksExecution_setOperandValue}. + * + * To build a model that can accommodate inputs of various sizes, as you may + * want to do for a CNN, set the size of the dimensions that will vary at run + * time to 0. If you do so, provide the full dimensions when calling + * {@link ANeuralNetworksExecution_setInput} or {@link + * ANeuralNetworksExecution_setInputFromMemory}. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has + * been called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be modified. + * @param type The {@link ANeuralNetworksOperandType} that describes the shape + * of the operand. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +inline int ANeuralNetworksModel_addOperand( + ANeuralNetworksModel* model, const ANeuralNetworksOperandType* type) { + LOAD_FUNCTION(ANeuralNetworksModel_addOperand); + EXECUTE_FUNCTION_RETURN(model, type); +} + +/** + * Sets an operand to a constant value. + * + * For scalar values, the content of buffer is copied into the model. + * + * For tensor values, a pointer to the buffer is stored within the model. + * The application is responsible for not changing the content of this region + * until all executions using this model have completed. As the data may + * be copied during processing, modifying the data after this call yields + * undefined results. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has + * been called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be modified. + * @param index The index of the model operand we're setting. + * @param buffer A pointer to the data to use. + * @param length The size in bytes of the data value. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +inline int ANeuralNetworksModel_setOperandValue(ANeuralNetworksModel* model, + int32_t index, + const void* buffer, + size_t length) { + LOAD_FUNCTION(ANeuralNetworksModel_setOperandValue); + EXECUTE_FUNCTION_RETURN(model, index, buffer, length); +} /** - * Load the NNAPI implementation from the shared libraries. - * The NnApi structure is filled with all the pointers. If one function doesn't - * exist, a null pointer is stored. + * Sets an operand to a value stored in a memory object. + * + * The content of the memory is not copied. A reference to that memory is stored + * inside the model. The application is responsible for not changing the content + * of the memory region until all executions using this model have completed. + * As the data may be copied during processing, modifying the data after this + * call yields undefined results. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has + * been called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be modified. + * @param index The index of the model operand we're setting. + * @param buffer A pointer to the data to use. + * @param memory The memory containing the data. + * @param offset This specifies the location of the data within the memory. + * The offset is in bytes from the start of memory. + * @param length The size in bytes of the data value. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. */ -const NnApi* NnApiImplementation(); +inline int ANeuralNetworksModel_setOperandValueFromMemory( + ANeuralNetworksModel* model, int32_t index, + const ANeuralNetworksMemory* memory, size_t offset, size_t length) { + LOAD_FUNCTION(ANeuralNetworksModel_setOperandValueFromMemory); + EXECUTE_FUNCTION_RETURN(model, index, memory, offset, length); +} + +/** + * Add an operation to a model. + * + * @param model The model to be modified. + * @param type The type of the operation. + * @param inputCount The number of entries in the inputs array. + * @param inputs An array of indexes identifying each operand. + * @param outputCount The number of entries in the outputs array. + * @param outputs An array of indexes identifying each operand. + * + * The operands specified by inputs and outputs must have been + * previously added by calls to {@link ANeuralNetworksModel_addOperand}. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has + * been called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +inline int ANeuralNetworksModel_addOperation(ANeuralNetworksModel* model, + ANeuralNetworksOperationType type, + uint32_t inputCount, + const uint32_t* inputs, + uint32_t outputCount, + const uint32_t* outputs) { + LOAD_FUNCTION(ANeuralNetworksModel_addOperation); + EXECUTE_FUNCTION_RETURN(model, type, inputCount, inputs, outputCount, + outputs); +} + +/** + * Specifies which operands will be the model's inputs and outputs. + * + * An operand cannot be used for both input and output. Doing so will + * return an error. + * + * @param model The model to be modified. + * @param inputCount The number of entries in the inputs array. + * @param inputs An array of indexes identifying the input operands. + * @param outputCount The number of entries in the outputs array. + * @param outputs An array of indexes identifying the output operands. + * + * The operands specified by inputs and outputs must have been + * previously added by calls to {@link ANeuralNetworksModel_addOperand}. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has + * been called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + */ +inline int ANeuralNetworksModel_identifyInputsAndOutputs( + ANeuralNetworksModel* model, uint32_t inputCount, const uint32_t* inputs, + uint32_t outputCount, const uint32_t* outputs) { + LOAD_FUNCTION(ANeuralNetworksModel_identifyInputsAndOutputs); + EXECUTE_FUNCTION_RETURN(model, inputCount, inputs, outputCount, outputs); +} + +/** + * Specifies whether {@link ANEURALNETWORKS_TENSOR_FLOAT32} is allowed to be + * calculated with range and/or precision as low as that of the IEEE 754 16-bit + * floating-point format. By default, {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * must be calculated using at least the range and precision of the IEEE 754 + * 32-bit floating-point format. + * + * @param model The model to be modified. + * @param allow 'true' indicates {@link ANEURALNETWORKS_TENSOR_FLOAT32} may be + * calculated with range and/or precision as low as that of the + * IEEE 754 16-bit floating point format. 'false' indicates + * {@link ANEURALNETWORKS_TENSOR_FLOAT32} must be calculated using + * at least the range and precision of the IEEE 754 32-bit floating + * point format. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has + * been called will return an error. + * + * Available since API level 28. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + */ +inline int ANeuralNetworksModel_relaxComputationFloat32toFloat16( + ANeuralNetworksModel* model, bool allow) { + LOAD_FUNCTION(ANeuralNetworksModel_relaxComputationFloat32toFloat16); + EXECUTE_FUNCTION_RETURN(model, allow); +} + +/** + * Create a {@link ANeuralNetworksCompilation} to compile the given model. + * This only creates the object. Compilation is only performed once + * {@link ANeuralNetworksCompilation_start} is invoked. + * + *

The provided model must outlive the compilation.

+ * + * The model must already have been finished by a call to + * {@link ANeuralNetworksModel_finish}. + * + * See {@link ANeuralNetworksCompilation} for information on multithreaded + * usage. + * + * @param model The {@link ANeuralNetworksModel} to be compiled. + * @param compilation The newly created object or NULL if unsuccessful. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA + * if the model is invalid. + */ +inline int ANeuralNetworksCompilation_create( + ANeuralNetworksModel* model, ANeuralNetworksCompilation** compilation) { + LOAD_FUNCTION(ANeuralNetworksCompilation_create); + EXECUTE_FUNCTION_RETURN(model, compilation); +} + +/** + * Destroy a compilation. + * + *

If called on a compilation for which + * {@link ANeuralNetworksCompilation_start} has been called, the + * function will return immediately but will mark the compilation to be deleted + * once the compilation completes. The {@link ANeuralNetworksCompilation_wait} + * will return ERROR_DELETED. + * + * See {@link ANeuralNetworksCompilation} for information on multithreaded + * usage. + * + * @param compilation The compilation to be destroyed. Passing NULL is + * acceptable and results in no operation. + */ +inline void ANeuralNetworksCompilation_free( + ANeuralNetworksCompilation* compilation) { + LOAD_FUNCTION(ANeuralNetworksCompilation_free); + EXECUTE_FUNCTION(compilation); +} + +/** + * Sets the execution preference. + * + *

Provides guidance to the runtime when trade-offs are possible.

+ * + * See {@link ANeuralNetworksCompilation} for information on multithreaded + * usage. + * + * @param compilation The compilation to be modified. + * @param preference Either {@link PREFER_LOW_POWER}, + * {@link PREFER_SINGLE_FAST_ANSWER}, or + * {@link PREFER_SUSTAINED_SPEED}. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +inline int ANeuralNetworksCompilation_setPreference( + ANeuralNetworksCompilation* compilation, int32_t preference) { + LOAD_FUNCTION(ANeuralNetworksCompilation_setPreference); + EXECUTE_FUNCTION_RETURN(compilation, preference); +} + +/** + * Waits until the compilation completes. + * + * More than one thread can wait on a compilation. When the compilation + * completes, all threads will be released. + * + * See {@link ANeuralNetworksCompilation} for information on multithreaded + * usage. + * + * @return ANEURALNETWORKS_NO_ERROR if the compilation completed normally. + */ +inline int ANeuralNetworksCompilation_finish( + ANeuralNetworksCompilation* compilation) { + LOAD_FUNCTION(ANeuralNetworksCompilation_finish); + EXECUTE_FUNCTION_RETURN(compilation); +} +/** + * Create a {@link ANeuralNetworksExecution} to apply the given compilation. + * This only creates the object. Computation is only performed once + * {@link ANeuralNetworksExecution_startCompute} is invoked. + * + *

The provided compilation must outlive the execution.

+ * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param compilation The {@link ANeuralNetworksCompilation} to be evaluated. + * @param execution The newly created object or NULL if unsuccessful. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA + * if the compilation is invalid. + */ +inline int ANeuralNetworksExecution_create( + ANeuralNetworksCompilation* compilation, + ANeuralNetworksExecution** execution) { + LOAD_FUNCTION(ANeuralNetworksExecution_create); + EXECUTE_FUNCTION_RETURN(compilation, execution); +} + +/** + * Destroy an execution. + * + *

If called on an execution for which + * {@link ANeuralNetworksExecution_startCompute} has been called, the + * function will return immediately but will mark the execution to be deleted + * once the computation completes. The {link ANeuralNetworksExecution_wait} + * will return ANEURALNETWORKS_ERROR_DELETED. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be destroyed. Passing NULL is acceptable + * and results in no operation. + */ +inline void ANeuralNetworksExecution_free(ANeuralNetworksExecution* execution) { + LOAD_FUNCTION(ANeuralNetworksExecution_free); + EXECUTE_FUNCTION(execution); +} + +/** + * Associate a user buffer with an input of the model of the + * {@link ANeuralNetworksExecution}. + * + *

The provided buffer must outlive the execution.

+ * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be modified. + * @param index The index of the input argument we are setting. It is + * an index into the lists passed to + * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not + * the index associated with {@link + * ANeuralNetworksModel_addOperand}. + * @param type The type of the operand. This should be used to specify the + * dimensions that were set to 0 when the operand was added to the + * model. All other properties of the type must be the same as + * specified in the model. If the type is the same as specified + * when the model was built, NULL can be passed. + * @param buffer The buffer containing the data. + * @param length The length in bytes of the buffer. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if + * the name is not recognized or the buffer is too small for the input. + */ +inline int ANeuralNetworksExecution_setInput( + ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, const void* buffer, size_t length) { + LOAD_FUNCTION(ANeuralNetworksExecution_setInput); + EXECUTE_FUNCTION_RETURN(execution, index, type, buffer, length); +} + +/** + * Associate part of a memory object with an input of the model of the + * {@link ANeuralNetworksExecution}. + * + *

The provided memory must outlive the execution.

+ * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be modified. + * @param index The index of the input argument we are setting. It is + * an index into the lists passed to + * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not + * the index associated with {@link + * ANeuralNetworksModel_addOperand}. + * @param type The type of the operand. This can be used to specify the + * dimensions that were set to 0 when the operand was added to the + * model. All other values must be the same as specified in the + * model. If the type is the same as specified when the model + * was built, NULL can be passed. + * @param memory The memory containing the data. + * @param offset This specifies the location of the data within the memory. + * The offset is in bytes from the start of memory. + * @param length The size in bytes of the data value. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if + * the name is not recognized or the buffer is too small for the input. + */ +inline int ANeuralNetworksExecution_setInputFromMemory( + ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, const ANeuralNetworksMemory* memory, + size_t offset, size_t length) { + LOAD_FUNCTION(ANeuralNetworksExecution_setInputFromMemory); + EXECUTE_FUNCTION_RETURN(execution, index, type, memory, offset, length); +} + +/** + * Associate a user buffer with an output of the model of the + * {@link ANeuralNetworksExecution}. + * + *

The provided buffer must outlive the execution.

+ * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be modified. + * @param index The index of the output argument we are setting. It is + * an index into the lists passed to + * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not + * the index associated with {@link + * ANeuralNetworksModel_addOperand}. + * @param type The type of the operand. This can be used to specify the + * dimensions that were set to 0 when the operand was added to the + * model. All other values must be the same as specified in the + * model. If the type is the same as specified when the model + * was built, NULL can be passed. + * @param buffer The buffer where the data is to be written. + * @param length The length in bytes of the buffer. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if + * the name is not recognized or the buffer is too small for the output. + */ +inline int ANeuralNetworksExecution_setOutput( + ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, void* buffer, size_t length) { + LOAD_FUNCTION(ANeuralNetworksExecution_setOutput); + EXECUTE_FUNCTION_RETURN(execution, index, type, buffer, length); +} + +/** + * Associate part of a memory object with an output of the model of the + * {@link ANeuralNetworksExecution}. + * + *

The provided memory must outlive the execution.

+ * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be modified. + * @param index The index of the output argument we are setting. It is + * an index into the lists passed to + * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not + * the index associated with {@link + * ANeuralNetworksModel_addOperand}. + * @param type The type of the operand. This can be used to specify the + * dimensions that were set to 0 when the operand was added to the + * model. All other values must be the same as specified in the + * model. If the type is the same as specified when the model + * was built, NULL can be passed. + * @param memory The memory where the data is to be stored. + * @param offset This specifies the location of the data within the memory. + * The offset is in bytes from the start of memory. + * @param length The length in bytes of the data value. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if + * the name is not recognized or the buffer is too small for the output. + */ +inline int ANeuralNetworksExecution_setOutputFromMemory( + ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, const ANeuralNetworksMemory* memory, + size_t offset, size_t length) { + LOAD_FUNCTION(ANeuralNetworksExecution_setOutputFromMemory); + EXECUTE_FUNCTION_RETURN(execution, index, type, memory, offset, length); +} + +/** + * Schedule evaluation of the execution. + * + *

Schedules evaluation of the execution. Once the model has been + * applied and the outputs are ready to be consumed, the execution will be + * signaled. Use {@link ANeuralNetworksExecution_wait} to wait for that signal. + *

+ * + * Multiple executions can be scheduled and evaluated concurrently, and + * compilations can be performed concurrently with executions. The runtime makes + * no guarantee on the ordering of the completion of compilations and + * executions. If it's important to the application, the application should + * enforce the ordering by using {@link ANeuralNetworksCompilation_wait} and + * {@link ANeuralNetworksExecution_wait}. + * + * ANeuralNetworksExecution_wait must be called to recuperate the resources used + * by the execution. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be scheduled and executed. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +inline int ANeuralNetworksExecution_startCompute( + ANeuralNetworksExecution* execution, ANeuralNetworksEvent** event) { + LOAD_FUNCTION(ANeuralNetworksExecution_startCompute); + EXECUTE_FUNCTION_RETURN(execution, event); +} + +/** + * Waits until the execution completes. + * + * More than one thread can wait on an event. When the execution completes, + * all threads will be released. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @return ANEURALNETWORKS_NO_ERROR if the execution completed normally. + */ +inline int ANeuralNetworksEvent_wait(ANeuralNetworksEvent* event) { + LOAD_FUNCTION(ANeuralNetworksEvent_wait); + EXECUTE_FUNCTION_RETURN(event); +} + +/** + * Destroys the event. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + */ +inline void ANeuralNetworksEvent_free(ANeuralNetworksEvent* event) { + LOAD_FUNCTION(ANeuralNetworksEvent_free); + EXECUTE_FUNCTION(event); +} + +/**/ #endif // TENSORFLOW_LITE_NNAPI_NEURALNETWORKSSHIM_H_ diff --git a/tensorflow/lite/nnapi/nnapi_lib_test.cc b/tensorflow/lite/nnapi/nnapi_lib_test.cc deleted file mode 100644 index aaf0ffaa8e..0000000000 --- a/tensorflow/lite/nnapi/nnapi_lib_test.cc +++ /dev/null @@ -1,90 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#include -#include "tensorflow/lite/nnapi/NeuralNetworksShim.h" - -namespace { - -TEST(NnapiLibTest, NnApiImplementation) { - const NnApi* nnapi = NnApiImplementation(); - EXPECT_NE(nnapi, nullptr); -#ifdef __ANDROID__ - EXPECT_TRUE(nnapi->nnapi_exists); - EXPECT_GT(nnapi->android_sdk_version, 0); - EXPECT_NE(nnapi->ANeuralNetworksMemory_createFromFd, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksMemory_free, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksModel_create, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksModel_free, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksModel_finish, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksModel_addOperand, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksModel_setOperandValue, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksModel_setOperandValueFromMemory, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksModel_addOperation, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksModel_identifyInputsAndOutputs, nullptr); - if (nnapi->android_sdk_version >= 28) { - // relaxComputationFloat32toFloat16 only available with Android 9.0 (P). - EXPECT_NE(nnapi->ANeuralNetworksModel_relaxComputationFloat32toFloat16, - nullptr); - } else { - EXPECT_EQ(nnapi->ANeuralNetworksModel_relaxComputationFloat32toFloat16, - nullptr); - } - EXPECT_NE(nnapi->ANeuralNetworksCompilation_create, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksCompilation_free, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksCompilation_setPreference, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksCompilation_finish, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksExecution_create, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksExecution_free, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksExecution_setInput, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksExecution_setInputFromMemory, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksExecution_setOutput, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksExecution_setOutputFromMemory, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksExecution_startCompute, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksEvent_wait, nullptr); - EXPECT_NE(nnapi->ANeuralNetworksEvent_free, nullptr); - EXPECT_NE(nnapi->ASharedMemory_create, nullptr); -#else - EXPECT_FALSE(nnapi->nnapi_exists); - EXPECT_EQ(nnapi->android_sdk_version, 0); - EXPECT_EQ(nnapi->ANeuralNetworksMemory_createFromFd, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksMemory_free, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksModel_create, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksModel_free, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksModel_finish, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksModel_addOperand, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksModel_setOperandValue, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksModel_setOperandValueFromMemory, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksModel_addOperation, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksModel_identifyInputsAndOutputs, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksModel_relaxComputationFloat32toFloat16, - nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksCompilation_create, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksCompilation_free, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksCompilation_setPreference, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksCompilation_finish, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksExecution_create, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksExecution_free, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksExecution_setInput, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksExecution_setInputFromMemory, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksExecution_setOutput, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksExecution_setOutputFromMemory, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksExecution_startCompute, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksEvent_wait, nullptr); - EXPECT_EQ(nnapi->ANeuralNetworksEvent_free, nullptr); - EXPECT_EQ(nnapi->ASharedMemory_create, nullptr); -#endif -} - -} // namespace diff --git a/tensorflow/lite/nnapi_delegate.cc b/tensorflow/lite/nnapi_delegate.cc index 44b9930acc..dc8e81cde7 100644 --- a/tensorflow/lite/nnapi_delegate.cc +++ b/tensorflow/lite/nnapi_delegate.cc @@ -84,27 +84,56 @@ void logError(const char* format, ...) { static const int64_t kOperandIdNotSet = -1; static const int64_t kOperandNotNeeded = -2; +namespace { + +int32_t GetAndroidSdkVersion() { +#ifdef __ANDROID__ + const char* sdkProp = "ro.build.version.sdk"; + char sdkVersion[PROP_VALUE_MAX]; + int length = __system_property_get(sdkProp, sdkVersion); + if (length != 0) { + for (int i = 0; i < length; ++i) { + int digit = sdkVersion[i] - '0'; + if (digit < 0 || digit > 9) { + // Non-numeric SDK version, assume it's higher then expected; + return 0xFFFF; + } + } + return atoi(sdkVersion); + } + FATAL("No %s prop", sdkProp); +#endif // __ANDROID__ + return 0; +} + +int32_t GetAndroidSdkVersionCached() { + static int32_t androidSdkVersion = GetAndroidSdkVersion(); + return androidSdkVersion; +} + +} // namespace + NNAPIAllocation::NNAPIAllocation(const char* filename, ErrorReporter* error_reporter) : MMAPAllocation(filename, error_reporter) { if (mmapped_buffer_ != MAP_FAILED) - CHECK_NN(NnApiImplementation()->ANeuralNetworksMemory_createFromFd( - buffer_size_bytes_, PROT_READ, mmap_fd_, 0, &handle_)); + CHECK_NN(ANeuralNetworksMemory_createFromFd(buffer_size_bytes_, PROT_READ, + mmap_fd_, 0, &handle_)); } NNAPIAllocation::~NNAPIAllocation() { if (handle_) { - NnApiImplementation()->ANeuralNetworksMemory_free(handle_); + ANeuralNetworksMemory_free(handle_); } } NNAPIDelegate::~NNAPIDelegate() { if (nn_compiled_model_) { - NnApiImplementation()->ANeuralNetworksCompilation_free(nn_compiled_model_); + ANeuralNetworksCompilation_free(nn_compiled_model_); nn_compiled_model_ = nullptr; } if (nn_model_) { - NnApiImplementation()->ANeuralNetworksModel_free(nn_model_); + ANeuralNetworksModel_free(nn_model_); nn_model_ = nullptr; // TODO(aselle): Is this thread-safe and callable multiple times? } @@ -116,7 +145,6 @@ TfLiteStatus addTensorOperands(tflite::Subgraph* subgraph, ANeuralNetworksModel* nn_model, uint32_t* no_of_operands_added, std::vector* nnapi_ids) { - const NnApi* nnapi = NnApiImplementation(); uint32_t next_id = 0; for (size_t i = 0; i < subgraph->tensors_size(); i++) { // Skip temporaries and RNN back-edges. @@ -170,24 +198,24 @@ TfLiteStatus addTensorOperands(tflite::Subgraph* subgraph, nn_type, static_cast(tensor->dims->size), reinterpret_cast(tensor->dims->data), scale, zeroPoint}; RETURN_ERROR_IF_NN_FAILED( - nnapi->ANeuralNetworksModel_addOperand(nn_model, &operand_type)); + ANeuralNetworksModel_addOperand(nn_model, &operand_type)); // TODO(aselle): Based on Michael's suggestion, limiting this to read // only memory if (tensor->allocation_type == kTfLiteMmapRo) { if (const NNAPIAllocation* alloc = dynamic_cast( static_cast(tensor->allocation))) { RETURN_ERROR_IF_NN_FAILED( - nnapi->ANeuralNetworksModel_setOperandValueFromMemory( + ANeuralNetworksModel_setOperandValueFromMemory( nn_model, next_id, alloc->memory(), alloc->offset(tensor->data.raw), tensor->bytes)); } else { - RETURN_ERROR_IF_NN_FAILED(nnapi->ANeuralNetworksModel_setOperandValue( + RETURN_ERROR_IF_NN_FAILED(ANeuralNetworksModel_setOperandValue( nn_model, next_id, tensor->data.raw, tensor->bytes)); } } else if (tensor->bytes == 0) { // These size 0 tensors are optional tensors reserved. - RETURN_ERROR_IF_NN_FAILED(nnapi->ANeuralNetworksModel_setOperandValue( - nn_model, next_id, nullptr, 0)); + RETURN_ERROR_IF_NN_FAILED( + ANeuralNetworksModel_setOperandValue(nn_model, next_id, nullptr, 0)); } ++next_id; @@ -216,7 +244,6 @@ TfLiteStatus AddOpsAndParams( uint32_t next_id, std::vector* model_state_inputs, std::vector* model_state_outputs, const std::vector& tensor_id_to_nnapi_id) { - const NnApi* nnapi = NnApiImplementation(); for (size_t i = 0; i < subgraph->nodes_size(); i++) { const auto* node_and_registration = subgraph->node_and_registration(i); const TfLiteNode& node = node_and_registration->first; @@ -231,21 +258,21 @@ TfLiteStatus AddOpsAndParams( MapAndAddTensorIds(node.outputs->data, node.outputs->size, &augmented_outputs, tensor_id_to_nnapi_id); - auto add_scalar_int32 = [nnapi, &nn_model, &augmented_inputs, + auto add_scalar_int32 = [&nn_model, &augmented_inputs, &next_id](int value) { ANeuralNetworksOperandType operand_type{.type = ANEURALNETWORKS_INT32}; - CHECK_NN(nnapi->ANeuralNetworksModel_addOperand(nn_model, &operand_type)) - CHECK_NN(nnapi->ANeuralNetworksModel_setOperandValue( - nn_model, next_id, &value, sizeof(int32_t))) + CHECK_NN(ANeuralNetworksModel_addOperand(nn_model, &operand_type)) + CHECK_NN(ANeuralNetworksModel_setOperandValue(nn_model, next_id, &value, + sizeof(int32_t))) augmented_inputs.push_back(next_id++); }; - auto add_scalar_float32 = [nnapi, &nn_model, &augmented_inputs, + auto add_scalar_float32 = [&nn_model, &augmented_inputs, &next_id](float value) { ANeuralNetworksOperandType operand_type{.type = ANEURALNETWORKS_FLOAT32}; - CHECK_NN(nnapi->ANeuralNetworksModel_addOperand(nn_model, &operand_type)) - CHECK_NN(nnapi->ANeuralNetworksModel_setOperandValue( - nn_model, next_id, &value, sizeof(float))) + CHECK_NN(ANeuralNetworksModel_addOperand(nn_model, &operand_type)) + CHECK_NN(ANeuralNetworksModel_setOperandValue(nn_model, next_id, &value, + sizeof(float))) augmented_inputs.push_back(next_id++); }; @@ -254,8 +281,8 @@ TfLiteStatus AddOpsAndParams( .type = ANEURALNETWORKS_TENSOR_INT32, .dimensionCount = 1, .dimensions = &num_values}; - CHECK_NN(nnapi->ANeuralNetworksModel_addOperand(nn_model, &operand_type)) - CHECK_NN(nnapi->ANeuralNetworksModel_setOperandValue( + CHECK_NN(ANeuralNetworksModel_addOperand(nn_model, &operand_type)) + CHECK_NN(ANeuralNetworksModel_setOperandValue( nn_model, next_id, values, sizeof(int32_t) * num_values)); augmented_inputs.push_back(next_id++); }; @@ -264,16 +291,15 @@ TfLiteStatus AddOpsAndParams( // For each state_out tensor, a corresponding state_in operand needs to be // created for NNAPI. auto duplicate_state_tensor_float32 = - [nnapi, subgraph, &nn_model, &next_id, &augmented_inputs, - &model_state_inputs, &model_state_outputs](int tensor_id) { + [subgraph, &nn_model, &next_id, &augmented_inputs, &model_state_inputs, + &model_state_outputs](int tensor_id) { const TfLiteTensor* tensor = subgraph->tensor(tensor_id); ANeuralNetworksOperandType operand_type{ ANEURALNETWORKS_TENSOR_FLOAT32, static_cast(tensor->dims->size), reinterpret_cast(tensor->dims->data), tensor->params.scale, tensor->params.zero_point}; - CHECK_NN( - nnapi->ANeuralNetworksModel_addOperand(nn_model, &operand_type)); + CHECK_NN(ANeuralNetworksModel_addOperand(nn_model, &operand_type)); augmented_inputs.push_back(next_id); model_state_inputs->push_back(next_id); model_state_outputs->push_back(tensor_id); @@ -362,7 +388,7 @@ TfLiteStatus AddOpsAndParams( }; // LSTM in NNAPI requires scratch tensor as an output operand. - auto add_lstm_scratch_tensor_float32 = [nnapi, subgraph, &node, &nn_model, + auto add_lstm_scratch_tensor_float32 = [subgraph, &node, &nn_model, &next_id, &augmented_outputs]() { if (node.temporaries->size == 0) return; int scratch_buffer_index = node.temporaries->data[0]; @@ -372,7 +398,7 @@ TfLiteStatus AddOpsAndParams( static_cast(tensor->dims->size), reinterpret_cast(tensor->dims->data), tensor->params.scale, tensor->params.zero_point}; - CHECK_NN(nnapi->ANeuralNetworksModel_addOperand(nn_model, &operand_type)); + CHECK_NN(ANeuralNetworksModel_addOperand(nn_model, &operand_type)); augmented_outputs.insert(augmented_outputs.begin(), next_id++); }; @@ -401,16 +427,15 @@ TfLiteStatus AddOpsAndParams( }; // Handle optional input tensors. - auto add_optional_tensors = [nnapi, &nn_model, &augmented_inputs, + auto add_optional_tensors = [&nn_model, &augmented_inputs, &next_id](int nn_type) { for (size_t idx = 0; idx < augmented_inputs.size(); idx++) { if (augmented_inputs[idx] == kOptionalTensor) { const std::vector dim = {0, 0}; ANeuralNetworksOperandType operand_type{nn_type, 2, dim.data(), 0, 0}; - CHECK_NN( - nnapi->ANeuralNetworksModel_addOperand(nn_model, &operand_type)) - CHECK_NN(nnapi->ANeuralNetworksModel_setOperandValue( - nn_model, next_id, nullptr, 0)) + CHECK_NN(ANeuralNetworksModel_addOperand(nn_model, &operand_type)) + CHECK_NN(ANeuralNetworksModel_setOperandValue(nn_model, next_id, + nullptr, 0)) augmented_inputs[idx] = next_id++; } } @@ -671,13 +696,13 @@ TfLiteStatus AddOpsAndParams( break; } - if (nnapi_version == 11 && nnapi->android_sdk_version < 28) { + if (nnapi_version == 11 && GetAndroidSdkVersionCached() < 28) { logError("Op %d needs NNAPI1.1", builtin); return kTfLiteError; } // Add the operation. - RETURN_ERROR_IF_NN_FAILED(nnapi->ANeuralNetworksModel_addOperation( + RETURN_ERROR_IF_NN_FAILED(ANeuralNetworksModel_addOperation( nn_model, nn_op_type, static_cast(augmented_inputs.size()), augmented_inputs.data(), static_cast(augmented_outputs.size()), @@ -689,10 +714,9 @@ TfLiteStatus AddOpsAndParams( TfLiteStatus NNAPIDelegate::BuildGraph(Subgraph* subgraph) { if (nn_model_ && nn_compiled_model_) return model_status_; - const NnApi* nnapi = NnApiImplementation(); // TODO(aselle): This is not correct. need to handle resize invalidation. if (!nn_model_) { - CHECK_NN(nnapi->ANeuralNetworksModel_create(&nn_model_)); + CHECK_NN(ANeuralNetworksModel_create(&nn_model_)); // Find which tensors should be added to NNAPI. TFLite has temporaries // and RNN back-edges which are are not valid for NNAPI. We look through all @@ -739,22 +763,21 @@ TfLiteStatus NNAPIDelegate::BuildGraph(Subgraph* subgraph) { model_states_outputs_.size(), &augmented_outputs, tensor_id_to_nnapi_id); - CHECK_NN(nnapi->ANeuralNetworksModel_identifyInputsAndOutputs( + CHECK_NN(ANeuralNetworksModel_identifyInputsAndOutputs( nn_model_, static_cast(augmented_inputs.size()), reinterpret_cast(augmented_inputs.data()), static_cast(augmented_outputs.size()), reinterpret_cast(augmented_outputs.data()))); - if (nnapi->android_sdk_version >= 28) { - CHECK_NN(nnapi->ANeuralNetworksModel_relaxComputationFloat32toFloat16( + if (GetAndroidSdkVersionCached() >= 28) { + CHECK_NN(ANeuralNetworksModel_relaxComputationFloat32toFloat16( nn_model_, subgraph->GetAllowFp16PrecisionForFp32())); } - CHECK_NN(nnapi->ANeuralNetworksModel_finish(nn_model_)); + CHECK_NN(ANeuralNetworksModel_finish(nn_model_)); } if (!nn_compiled_model_) { - CHECK_NN(nnapi->ANeuralNetworksCompilation_create(nn_model_, - &nn_compiled_model_)); - CHECK_NN(nnapi->ANeuralNetworksCompilation_finish(nn_compiled_model_)); + CHECK_NN(ANeuralNetworksCompilation_create(nn_model_, &nn_compiled_model_)); + CHECK_NN(ANeuralNetworksCompilation_finish(nn_compiled_model_)); } return kTfLiteOk; } @@ -770,10 +793,8 @@ TfLiteStatus NNAPIDelegate::Invoke(Subgraph* subgraph) { return model_status_; } - const NnApi* nnapi = NnApiImplementation(); ANeuralNetworksExecution* execution = nullptr; - CHECK_NN( - nnapi->ANeuralNetworksExecution_create(nn_compiled_model_, &execution)); + CHECK_NN(ANeuralNetworksExecution_create(nn_compiled_model_, &execution)); // Currently perform deep copy of input buffer for (size_t i = 0; i < subgraph->inputs().size(); i++) { @@ -781,7 +802,7 @@ TfLiteStatus NNAPIDelegate::Invoke(Subgraph* subgraph) { // TODO(aselle): Is this what we want or do we want input instead? // TODO(aselle): This should be called setInputValue maybe to be cons. TfLiteTensor* tensor = subgraph->tensor(input); - CHECK_NN(nnapi->ANeuralNetworksExecution_setInput( + CHECK_NN(ANeuralNetworksExecution_setInput( execution, i, nullptr, tensor->data.raw, tensor->bytes)); } @@ -789,7 +810,7 @@ TfLiteStatus NNAPIDelegate::Invoke(Subgraph* subgraph) { for (size_t i = 0; i < subgraph->outputs().size(); i++) { int output = subgraph->outputs()[i]; TfLiteTensor* tensor = subgraph->tensor(output); - CHECK_NN(nnapi->ANeuralNetworksExecution_setOutput( + CHECK_NN(ANeuralNetworksExecution_setOutput( execution, i, nullptr, tensor->data.raw, tensor->bytes)); } @@ -801,21 +822,21 @@ TfLiteStatus NNAPIDelegate::Invoke(Subgraph* subgraph) { // Here we are using a deep copy for state_in tensors so that we are not // reading and writing into the same buffer during a invocation. // TODO(miaowang): using double shared buffer to minimize the copies. - CHECK_NN(nnapi->ANeuralNetworksExecution_setInput( + CHECK_NN(ANeuralNetworksExecution_setInput( execution, i + subgraph->inputs().size(), nullptr, tensor->data.raw, tensor->bytes)); // Tell NNAPI where to output the state_out. - CHECK_NN(nnapi->ANeuralNetworksExecution_setOutput( + CHECK_NN(ANeuralNetworksExecution_setOutput( execution, i + subgraph->outputs().size(), nullptr, tensor->data.raw, tensor->bytes)); } // Currently use blocking compute. ANeuralNetworksEvent* event = nullptr; - CHECK_NN(nnapi->ANeuralNetworksExecution_startCompute(execution, &event)); - CHECK_NN(nnapi->ANeuralNetworksEvent_wait(event)); - nnapi->ANeuralNetworksEvent_free(event); - nnapi->ANeuralNetworksExecution_free(execution); + CHECK_NN(ANeuralNetworksExecution_startCompute(execution, &event)); + CHECK_NN(ANeuralNetworksEvent_wait(event)); + ANeuralNetworksEvent_free(event); + ANeuralNetworksExecution_free(execution); #if 0 printf("From the NN API:\n"); @@ -833,8 +854,6 @@ TfLiteStatus NNAPIDelegate::Invoke(Subgraph* subgraph) { return kTfLiteOk; } -bool NNAPIDelegate::IsSupported() { - return NnApiImplementation()->nnapi_exists; -} +bool NNAPIDelegate::IsSupported() { return NNAPIExists(); } } // namespace tflite -- GitLab From a460c9525d416e442c245c87ccb6da9032cc5fd6 Mon Sep 17 00:00:00 2001 From: Scott Zhu Date: Wed, 2 Jan 2019 16:58:33 -0800 Subject: [PATCH 0113/2345] Fix masking generate logic in sequence model. sequence model was incorrectly using the layer output instead of input to generate mask, and compute_mask of the layer usually does not have shape check. PiperOrigin-RevId: 227605298 --- tensorflow/python/keras/engine/sequential.py | 9 +- tensorflow/python/keras/layers/gru_test.py | 112 ++++++++-------- tensorflow/python/keras/layers/lstm_test.py | 8 +- .../python/keras/layers/simplernn_test.py | 8 +- .../python/keras/layers/unified_gru_test.py | 64 +++++---- .../python/keras/layers/unified_lstm_test.py | 125 +++++++++--------- 6 files changed, 153 insertions(+), 173 deletions(-) diff --git a/tensorflow/python/keras/engine/sequential.py b/tensorflow/python/keras/engine/sequential.py index dffd3c8a69..3627091198 100644 --- a/tensorflow/python/keras/engine/sequential.py +++ b/tensorflow/python/keras/engine/sequential.py @@ -262,16 +262,17 @@ class Sequential(Model): with ops.name_scope(layer._name_scope()): layer._maybe_build(x) layer.built = True + if layer.supports_masking: + mask = layer.compute_mask(x, mask) + else: + mask = None + if context.executing_eagerly(): x = layer(x, **kwargs) elif layer.dynamic: x = layer._symbolic_call(x) else: x = layer.call(x, **kwargs) - if layer.supports_masking: - mask = layer.compute_mask(x, mask) - else: - mask = None if not context.executing_eagerly(): x._keras_mask = mask return x, mask diff --git a/tensorflow/python/keras/layers/gru_test.py b/tensorflow/python/keras/layers/gru_test.py index 52a7944d60..e2b65de661 100644 --- a/tensorflow/python/keras/layers/gru_test.py +++ b/tensorflow/python/keras/layers/gru_test.py @@ -117,63 +117,6 @@ class GRULayerTest(keras_parameterized.TestCase): run_eagerly=testing_utils.should_run_eagerly()) model.fit(inputs, targets, epochs=1, batch_size=2, verbose=1) - -@tf_test_util.run_all_in_graph_and_eager_modes -class GRULayerGenericTest(test.TestCase): - - def test_constraints_GRU(self): - embedding_dim = 4 - layer_class = keras.layers.GRU - k_constraint = keras.constraints.max_norm(0.01) - r_constraint = keras.constraints.max_norm(0.01) - b_constraint = keras.constraints.max_norm(0.01) - layer = layer_class( - 5, - return_sequences=False, - weights=None, - input_shape=(None, embedding_dim), - kernel_constraint=k_constraint, - recurrent_constraint=r_constraint, - bias_constraint=b_constraint) - layer.build((None, None, embedding_dim)) - self.assertEqual(layer.cell.kernel.constraint, k_constraint) - self.assertEqual(layer.cell.recurrent_kernel.constraint, r_constraint) - self.assertEqual(layer.cell.bias.constraint, b_constraint) - - def test_from_config_GRU(self): - layer_class = keras.layers.GRU - for stateful in (False, True): - l1 = layer_class(units=1, stateful=stateful) - l2 = layer_class.from_config(l1.get_config()) - assert l1.get_config() == l2.get_config() - - def test_regularizers_GRU(self): - embedding_dim = 4 - layer_class = keras.layers.GRU - layer = layer_class( - 5, - return_sequences=False, - weights=None, - input_shape=(None, embedding_dim), - kernel_regularizer=keras.regularizers.l1(0.01), - recurrent_regularizer=keras.regularizers.l1(0.01), - bias_regularizer='l2', - activity_regularizer='l1') - layer.build((None, None, 2)) - self.assertEqual(len(layer.losses), 3) - - x = keras.backend.variable(np.ones((2, 3, 2))) - layer(x) - if context.executing_eagerly(): - self.assertEqual(len(layer.losses), 4) - else: - self.assertEqual(len(layer.get_losses_for(x)), 1) - - -class GRULayerV1OnlyTest(test.TestCase): - - @tf_test_util.run_v1_only('b/120941292') - @tf_test_util.run_in_graph_and_eager_modes def test_statefulness_GRU(self): num_samples = 2 timesteps = 3 @@ -192,7 +135,8 @@ class GRULayerV1OnlyTest(test.TestCase): layer = layer_class( units, return_sequences=False, stateful=True, weights=None) model.add(layer) - model.compile(optimizer='sgd', loss='mse') + model.compile(optimizer='sgd', loss='mse', + run_eagerly=testing_utils.should_run_eagerly()) out1 = model.predict(np.ones((num_samples, timesteps))) self.assertEqual(out1.shape, (num_samples, units)) @@ -237,5 +181,57 @@ class GRULayerV1OnlyTest(test.TestCase): np.testing.assert_allclose(out7, out6, atol=1e-5) +@tf_test_util.run_all_in_graph_and_eager_modes +class GRULayerGenericTest(test.TestCase): + + def test_constraints_GRU(self): + embedding_dim = 4 + layer_class = keras.layers.GRU + k_constraint = keras.constraints.max_norm(0.01) + r_constraint = keras.constraints.max_norm(0.01) + b_constraint = keras.constraints.max_norm(0.01) + layer = layer_class( + 5, + return_sequences=False, + weights=None, + input_shape=(None, embedding_dim), + kernel_constraint=k_constraint, + recurrent_constraint=r_constraint, + bias_constraint=b_constraint) + layer.build((None, None, embedding_dim)) + self.assertEqual(layer.cell.kernel.constraint, k_constraint) + self.assertEqual(layer.cell.recurrent_kernel.constraint, r_constraint) + self.assertEqual(layer.cell.bias.constraint, b_constraint) + + def test_from_config_GRU(self): + layer_class = keras.layers.GRU + for stateful in (False, True): + l1 = layer_class(units=1, stateful=stateful) + l2 = layer_class.from_config(l1.get_config()) + assert l1.get_config() == l2.get_config() + + def test_regularizers_GRU(self): + embedding_dim = 4 + layer_class = keras.layers.GRU + layer = layer_class( + 5, + return_sequences=False, + weights=None, + input_shape=(None, embedding_dim), + kernel_regularizer=keras.regularizers.l1(0.01), + recurrent_regularizer=keras.regularizers.l1(0.01), + bias_regularizer='l2', + activity_regularizer='l1') + layer.build((None, None, 2)) + self.assertEqual(len(layer.losses), 3) + + x = keras.backend.variable(np.ones((2, 3, 2))) + layer(x) + if context.executing_eagerly(): + self.assertEqual(len(layer.losses), 4) + else: + self.assertEqual(len(layer.get_losses_for(x)), 1) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/layers/lstm_test.py b/tensorflow/python/keras/layers/lstm_test.py index e8c55438c9..38c0177e39 100644 --- a/tensorflow/python/keras/layers/lstm_test.py +++ b/tensorflow/python/keras/layers/lstm_test.py @@ -23,7 +23,6 @@ import numpy as np from tensorflow.python import keras from tensorflow.python.eager import context -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 @@ -340,11 +339,6 @@ class LSTMLayerTest(keras_parameterized.TestCase): else: self.assertEqual(len(layer.get_losses_for(x)), 1) - -class LSTMLayerV1OnlyTest(test.TestCase): - - @test_util.run_v1_only('b/120941292') - @test_util.run_in_graph_and_eager_modes def test_statefulness_LSTM(self): num_samples = 2 timesteps = 3 @@ -363,7 +357,7 @@ class LSTMLayerV1OnlyTest(test.TestCase): units, return_sequences=False, stateful=True, weights=None) model.add(layer) model.compile(optimizer=gradient_descent.GradientDescentOptimizer(0.01), - loss='mse') + loss='mse', run_eagerly=testing_utils.should_run_eagerly()) out1 = model.predict(np.ones((num_samples, timesteps))) self.assertEqual(out1.shape, (num_samples, units)) diff --git a/tensorflow/python/keras/layers/simplernn_test.py b/tensorflow/python/keras/layers/simplernn_test.py index 390ae789e1..0e599dade9 100644 --- a/tensorflow/python/keras/layers/simplernn_test.py +++ b/tensorflow/python/keras/layers/simplernn_test.py @@ -22,7 +22,6 @@ import numpy as np from tensorflow.python import keras from tensorflow.python.eager import context -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 @@ -141,11 +140,6 @@ class SimpleRNNLayerTest(keras_parameterized.TestCase): else: self.assertEqual(len(layer.get_losses_for(x)), 1) - -class SimpleRNNLayerV1OnlyTest(test.TestCase): - - @test_util.run_v1_only('b/120941292') - @test_util.run_in_graph_and_eager_modes def test_statefulness_SimpleRNN(self): num_samples = 2 timesteps = 3 @@ -164,7 +158,7 @@ class SimpleRNNLayerV1OnlyTest(test.TestCase): units, return_sequences=False, stateful=True, weights=None) model.add(layer) model.compile(optimizer=gradient_descent.GradientDescentOptimizer(0.01), - loss='mse') + loss='mse', run_eagerly=testing_utils.should_run_eagerly()) out1 = model.predict(np.ones((num_samples, timesteps))) self.assertEqual(out1.shape, (num_samples, units)) diff --git a/tensorflow/python/keras/layers/unified_gru_test.py b/tensorflow/python/keras/layers/unified_gru_test.py index 244ffdb8b6..11322764ac 100644 --- a/tensorflow/python/keras/layers/unified_gru_test.py +++ b/tensorflow/python/keras/layers/unified_gru_test.py @@ -402,39 +402,6 @@ class UnifiedGRUTest(keras_parameterized.TestCase): else: self.assertEqual(len(layer.get_losses_for(x)), 1) - -class GRULayerGradientTapeTest(test.TestCase): - - @test_util.run_in_graph_and_eager_modes(config=_config) - def test_in_tape(self): - if not context.executing_eagerly(): - self.skipTest('bloo') - time_steps = 10 - embedding_size = 11 - gru_unit_size = 12 - - gru = keras.layers.UnifiedGRU(gru_unit_size, - return_sequences=True, - return_state=True, - recurrent_activation='sigmoid', - recurrent_initializer='glorot_uniform') - - x = random_ops.random_uniform([1, time_steps, embedding_size]) - y = random_ops.random_uniform([1, gru_unit_size]) - - with backprop.GradientTape() as tape: - hidden_state = array_ops.zeros([1, gru_unit_size], dtype=dtypes.float32) - _, state = gru(x, initial_state=hidden_state) - - loss = math_ops.reduce_mean(math_ops.square(state - y)) - - tape.gradient(loss, gru.variables) - - -class GRULayerV1OnlyTest(test.TestCase, parameterized.TestCase): - - @test_util.run_v1_only('b/120941292') - @test_util.run_in_graph_and_eager_modes(config=_config) def test_statefulness_GRU(self): num_samples = 2 timesteps = 3 @@ -452,7 +419,8 @@ class GRULayerV1OnlyTest(test.TestCase, parameterized.TestCase): layer = layer_class( units, return_sequences=False, stateful=True, weights=None) model.add(layer) - model.compile(optimizer='sgd', loss='mse') + model.compile(optimizer=gradient_descent.GradientDescentOptimizer(0.01), + loss='mse', run_eagerly=testing_utils.should_run_eagerly()) out1 = model.predict(np.ones((num_samples, timesteps))) self.assertEqual(out1.shape, (num_samples, units)) @@ -497,6 +465,34 @@ class GRULayerV1OnlyTest(test.TestCase, parameterized.TestCase): np.testing.assert_allclose(out7, out6, atol=1e-5) +class GRULayerGradientTapeTest(test.TestCase): + + @test_util.run_in_graph_and_eager_modes(config=_config) + def test_in_tape(self): + if not context.executing_eagerly(): + self.skipTest('bloo') + time_steps = 10 + embedding_size = 11 + gru_unit_size = 12 + + gru = keras.layers.UnifiedGRU(gru_unit_size, + return_sequences=True, + return_state=True, + recurrent_activation='sigmoid', + recurrent_initializer='glorot_uniform') + + x = random_ops.random_uniform([1, time_steps, embedding_size]) + y = random_ops.random_uniform([1, gru_unit_size]) + + with backprop.GradientTape() as tape: + hidden_state = array_ops.zeros([1, gru_unit_size], dtype=dtypes.float32) + _, state = gru(x, initial_state=hidden_state) + + loss = math_ops.reduce_mean(math_ops.square(state - y)) + + tape.gradient(loss, gru.variables) + + class GRULayerGraphOnlyTest(test.TestCase): # Need session for test diff --git a/tensorflow/python/keras/layers/unified_lstm_test.py b/tensorflow/python/keras/layers/unified_lstm_test.py index 811b8d128e..375894b166 100644 --- a/tensorflow/python/keras/layers/unified_lstm_test.py +++ b/tensorflow/python/keras/layers/unified_lstm_test.py @@ -571,6 +571,68 @@ class UnifiedLSTMTest(keras_parameterized.TestCase): else: self.assertEqual(len(layer.get_losses_for(x)), 1) + def test_statefulness_LSTM(self): + num_samples = 2 + timesteps = 3 + embedding_dim = 4 + units = 2 + layer_class = keras.layers.UnifiedLSTM + model = keras.models.Sequential() + model.add( + keras.layers.Embedding( + 4, + embedding_dim, + mask_zero=True, + input_length=timesteps, + batch_input_shape=(num_samples, timesteps))) + layer = layer_class( + units, return_sequences=False, stateful=True, weights=None) + model.add(layer) + model.compile(optimizer=gradient_descent.GradientDescentOptimizer(0.01), + loss='mse', run_eagerly=testing_utils.should_run_eagerly()) + out1 = model.predict(np.ones((num_samples, timesteps))) + self.assertEqual(out1.shape, (num_samples, units)) + + # train once so that the states change + model.train_on_batch( + np.ones((num_samples, timesteps)), np.ones((num_samples, units))) + out2 = model.predict(np.ones((num_samples, timesteps))) + + # if the state is not reset, output should be different + self.assertNotEqual(out1.max(), out2.max()) + + # check that output changes after states are reset + # (even though the model itself didn't change) + layer.reset_states() + out3 = model.predict(np.ones((num_samples, timesteps))) + self.assertNotEqual(out2.max(), out3.max()) + + # check that container-level reset_states() works + model.reset_states() + out4 = model.predict(np.ones((num_samples, timesteps))) + self.assertAllClose(out3, out4, atol=1e-5) + + # check that the call to `predict` updated the states + out5 = model.predict(np.ones((num_samples, timesteps))) + self.assertNotEqual(out4.max(), out5.max()) + + # Check masking + layer.reset_states() + + left_padded_input = np.ones((num_samples, timesteps)) + left_padded_input[0, :1] = 0 + left_padded_input[1, :2] = 0 + out6 = model.predict(left_padded_input) + + layer.reset_states() + + right_padded_input = np.ones((num_samples, timesteps)) + right_padded_input[0, -1:] = 0 + right_padded_input[1, -2:] = 0 + out7 = model.predict(right_padded_input) + + self.assertAllClose(out7, out6, atol=1e-5) + class LSTMLayerGraphOnlyTest(test.TestCase): @@ -697,69 +759,6 @@ class LSTMLayerV1OnlyTest(test.TestCase, parameterized.TestCase): }, input_shape=(num_samples, timesteps, embedding_dim)) - @test_util.run_in_graph_and_eager_modes(config=_config) - def test_statefulness_LSTM(self): - num_samples = 2 - timesteps = 3 - embedding_dim = 4 - units = 2 - layer_class = keras.layers.UnifiedLSTM - model = keras.models.Sequential() - model.add( - keras.layers.Embedding( - 4, - embedding_dim, - mask_zero=True, - input_length=timesteps, - batch_input_shape=(num_samples, timesteps))) - layer = layer_class( - units, return_sequences=False, stateful=True, weights=None) - model.add(layer) - model.compile( - optimizer=gradient_descent.GradientDescentOptimizer(0.01), loss='mse') - out1 = model.predict(np.ones((num_samples, timesteps))) - self.assertEqual(out1.shape, (num_samples, units)) - - # train once so that the states change - model.train_on_batch( - np.ones((num_samples, timesteps)), np.ones((num_samples, units))) - out2 = model.predict(np.ones((num_samples, timesteps))) - - # if the state is not reset, output should be different - self.assertNotEqual(out1.max(), out2.max()) - - # check that output changes after states are reset - # (even though the model itself didn't change) - layer.reset_states() - out3 = model.predict(np.ones((num_samples, timesteps))) - self.assertNotEqual(out2.max(), out3.max()) - - # check that container-level reset_states() works - model.reset_states() - out4 = model.predict(np.ones((num_samples, timesteps))) - self.assertAllClose(out3, out4, atol=1e-5) - - # check that the call to `predict` updated the states - out5 = model.predict(np.ones((num_samples, timesteps))) - self.assertNotEqual(out4.max(), out5.max()) - - # Check masking - layer.reset_states() - - left_padded_input = np.ones((num_samples, timesteps)) - left_padded_input[0, :1] = 0 - left_padded_input[1, :2] = 0 - out6 = model.predict(left_padded_input) - - layer.reset_states() - - right_padded_input = np.ones((num_samples, timesteps)) - right_padded_input[0, -1:] = 0 - right_padded_input[1, -2:] = 0 - out7 = model.predict(right_padded_input) - - self.assertAllClose(out7, out6, atol=1e-5) - class UnifiedLSTMPerformanceTest(test.Benchmark): -- GitLab From 563ff22e7f6c9431072d2d9af6e876b3b4843920 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 2 Jan 2019 17:06:04 -0800 Subject: [PATCH 0114/2345] Fix bug where sample weights were being ignored in the tf.distribute.Strategy / numpy input case. PiperOrigin-RevId: 227606473 --- tensorflow/python/keras/engine/training.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index 1dc3b81cfc..8263467c37 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -2191,7 +2191,6 @@ class Model(Network): else: x = dataset_ops.Dataset.from_tensor_slices((var_x, var_y)) - x = dataset_ops.Dataset.from_tensor_slices((var_x, var_y)) if shuffle: # 1024 is a good buffer size since it is much larger than the average # batch size provided by the user and provides sufficient randomness. -- GitLab From b38e065943271461bd9afce7ae8f1bd00c3d6bf9 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 2 Jan 2019 17:34:44 -0800 Subject: [PATCH 0115/2345] use packages@tensorflow.org as author email for python package PiperOrigin-RevId: 227609414 --- tensorflow/lite/tools/pip_package/setup.py | 2 +- tensorflow/tools/pip_package/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/tools/pip_package/setup.py b/tensorflow/lite/tools/pip_package/setup.py index 64d62ee1f2..82f9ac5b8f 100644 --- a/tensorflow/lite/tools/pip_package/setup.py +++ b/tensorflow/lite/tools/pip_package/setup.py @@ -136,7 +136,7 @@ setup( long_description='\n'.join(DOCLINES[2:]), url='https://www.tensorflow.org/lite/', author='Google Inc.', - author_email='opensource@google.com', + author_email='packages@tensorflow.org', license='Apache 2.0', include_package_data=True, keywords='tflite tensorflow tensor machine learning', diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index 3927540cc7..9567e0aff7 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -248,7 +248,7 @@ setup( url='https://www.tensorflow.org/', download_url='https://github.com/tensorflow/tensorflow/tags', author='Google Inc.', - author_email='opensource@google.com', + author_email='packages@tensorflow.org', # Contained modules and scripts. packages=find_packages(), entry_points={ -- GitLab From 894278658bf6da29831371670862688f46153be0 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Wed, 2 Jan 2019 17:56:16 -0800 Subject: [PATCH 0116/2345] Add top k categorical accuracy and sparse top k categorical accuracy v2 metrics. PiperOrigin-RevId: 227611439 --- tensorflow/python/keras/metrics.py | 78 +++++++++++++++++++++++++ tensorflow/python/keras/metrics_test.py | 76 ++++++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index 6b81151d5c..73c116a745 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -590,6 +590,84 @@ class SparseCategoricalAccuracy(MeanMetricWrapper): return super(SparseCategoricalAccuracy, cls).from_config(config) +class TopKCategoricalAccuracy(MeanMetricWrapper): + """Computes how often targets are in the top `K` predictions. + + Usage: + + ```python + m = tf.keras.metrics.TopKCategoricalAccuracy() + m.update_state([[0, 0, 1], [0, 1, 0]], [[0.1, 0.9, 0.8], [0.05, 0.95, 0]]) + print('Final result: ', m.result().numpy()) # Final result: 1.0 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile('sgd', metrics=[tf.keras.metrics.TopKCategoricalAccuracy()]) + ``` + """ + + def __init__(self, k=5, name='top_k_categorical_accuracy', dtype=None): + """Creates a `TopKCategoricalAccuracy` instance. + + Args: + k: (Optional) Number of top elements to look at for computing accuracy. + Defaults to 5. + name: (Optional) string name of the metric instance. + dtype: (Optional) data type of the metric result. + """ + super(TopKCategoricalAccuracy, self).__init__( + top_k_categorical_accuracy, name, dtype=dtype, k=k) + + @classmethod + def from_config(cls, config): + if 'fn' in config: + config.pop('fn') + return super(TopKCategoricalAccuracy, cls).from_config(config) + + +class SparseTopKCategoricalAccuracy(MeanMetricWrapper): + """Computes how often integer targets are in the top `K` predictions. + + Usage: + + ```python + m = tf.keras.metrics.SparseTopKCategoricalAccuracy() + m.update_state([2, 1], [[0.1, 0.9, 0.8], [0.05, 0.95, 0]]) + print('Final result: ', m.result().numpy()) # Final result: 1.0 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile( + 'sgd', + metrics=[tf.keras.metrics.SparseTopKCategoricalAccuracy()]) + ``` + """ + + def __init__(self, k=5, name='sparse_top_k_categorical_accuracy', dtype=None): + """Creates a `SparseTopKCategoricalAccuracy` instance. + + Args: + k: (Optional) Number of top elements to look at for computing accuracy. + Defaults to 5. + name: (Optional) string name of the metric instance. + dtype: (Optional) data type of the metric result. + """ + super(SparseTopKCategoricalAccuracy, self).__init__( + sparse_top_k_categorical_accuracy, name, dtype=dtype, k=k) + + @classmethod + def from_config(cls, config): + if 'fn' in config: + config.pop('fn') + return super(SparseTopKCategoricalAccuracy, cls).from_config(config) + + class _ConfusionMatrixConditionCount(Metric): """Calculates the number of the given confusion matrix condition.""" diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index 8f8befd413..fb635e7055 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -1391,6 +1391,82 @@ class RootMeanSquaredErrorTest(test.TestCase): self.assertAllClose(math.sqrt(13), self.evaluate(result), atol=1e-3) +@test_util.run_all_in_graph_and_eager_modes +class TopKCategoricalAccuracyTest(test.TestCase): + + def test_config(self): + a_obj = metrics.TopKCategoricalAccuracy(name='topkca', dtype=dtypes.int32) + self.assertEqual(a_obj.name, 'topkca') + self.assertEqual(a_obj._dtype, dtypes.int32) + + a_obj2 = metrics.TopKCategoricalAccuracy.from_config(a_obj.get_config()) + self.assertEqual(a_obj2.name, 'topkca') + self.assertEqual(a_obj2._dtype, dtypes.int32) + + def test_correctness(self): + a_obj = metrics.TopKCategoricalAccuracy() + self.evaluate(variables.variables_initializer(a_obj.variables)) + y_true = constant_op.constant([[0, 0, 1], [0, 1, 0]]) + y_pred = constant_op.constant([[0.1, 0.9, 0.8], [0.05, 0.95, 0]]) + + result = a_obj(y_true, y_pred) + self.assertEqual(1, self.evaluate(result)) # both the samples match + + # With `k` < 5. + a_obj = metrics.TopKCategoricalAccuracy(k=1) + self.evaluate(variables.variables_initializer(a_obj.variables)) + result = a_obj(y_true, y_pred) + self.assertEqual(0.5, self.evaluate(result)) # only sample #2 matches + + # With `k` > 5. + y_true = constant_op.constant([[0, 0, 1, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0]]) + y_pred = constant_op.constant([[0.5, 0.9, 0.1, 0.7, 0.6, 0.5, 0.4], + [0.05, 0.95, 0, 0, 0, 0, 0]]) + a_obj = metrics.TopKCategoricalAccuracy(k=6) + self.evaluate(variables.variables_initializer(a_obj.variables)) + result = a_obj(y_true, y_pred) + self.assertEqual(0.5, self.evaluate(result)) # only 1 sample matches. + + +@test_util.run_all_in_graph_and_eager_modes +class SparseTopKCategoricalAccuracyTest(test.TestCase): + + def test_config(self): + a_obj = metrics.SparseTopKCategoricalAccuracy( + name='stopkca', dtype=dtypes.int32) + self.assertEqual(a_obj.name, 'stopkca') + self.assertEqual(a_obj._dtype, dtypes.int32) + + a_obj2 = metrics.SparseTopKCategoricalAccuracy.from_config( + a_obj.get_config()) + self.assertEqual(a_obj2.name, 'stopkca') + self.assertEqual(a_obj2._dtype, dtypes.int32) + + def test_correctness(self): + a_obj = metrics.SparseTopKCategoricalAccuracy() + self.evaluate(variables.variables_initializer(a_obj.variables)) + y_true = constant_op.constant([2, 1]) + y_pred = constant_op.constant([[0.1, 0.9, 0.8], [0.05, 0.95, 0]]) + + result = a_obj(y_true, y_pred) + self.assertEqual(1, self.evaluate(result)) # both the samples match + + # With `k` < 5. + a_obj = metrics.SparseTopKCategoricalAccuracy(k=1) + self.evaluate(variables.variables_initializer(a_obj.variables)) + result = a_obj(y_true, y_pred) + self.assertEqual(0.5, self.evaluate(result)) # only sample #2 matches + + # With `k` > 5. + y_pred = constant_op.constant([[0.5, 0.9, 0.1, 0.7, 0.6, 0.5, 0.4], + [0.05, 0.95, 0, 0, 0, 0, 0]]) + a_obj = metrics.SparseTopKCategoricalAccuracy(k=6) + self.evaluate(variables.variables_initializer(a_obj.variables)) + result = a_obj(y_true, y_pred) + self.assertEqual(0.5, self.evaluate(result)) # only 1 sample matches. + + def _get_model(compile_metrics): model_layers = [ layers.Dense(3, activation='relu', kernel_initializer='ones'), -- GitLab From a8bf983c60f3684220996cd518d2b86e31dd6e1e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 2 Jan 2019 21:49:05 -0800 Subject: [PATCH 0117/2345] use packages@tensorflow.org as author email for python package PiperOrigin-RevId: 227630041 --- tensorflow/contrib/tpu/profiler/pip_package/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/tpu/profiler/pip_package/setup.py b/tensorflow/contrib/tpu/profiler/pip_package/setup.py index f27ae38e04..807cf26fe9 100644 --- a/tensorflow/contrib/tpu/profiler/pip_package/setup.py +++ b/tensorflow/contrib/tpu/profiler/pip_package/setup.py @@ -33,7 +33,7 @@ setup( long_description='Tools for capture TPU profile', url='https://www.tensorflow.org/tfrc/', author='Google Inc.', - author_email='opensource@google.com', + author_email='packages@tensorflow.org', packages=['cloud_tpu_profiler'], package_data={ 'cloud_tpu_profiler': ['data/*'], -- GitLab From 7e71784d707cee4e14689b55a59bb1e23ed1a3f5 Mon Sep 17 00:00:00 2001 From: Heungsub Lee Date: Thu, 3 Jan 2019 17:12:21 +0900 Subject: [PATCH 0118/2345] Fix quotation typo around "task" --- tensorflow/contrib/distribute/python/mirrored_strategy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy.py b/tensorflow/contrib/distribute/python/mirrored_strategy.py index db8fd98307..e635190b50 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy.py @@ -48,7 +48,7 @@ class MirroredStrategy(distribute_lib.DistributionStrategy): distributed environment. There are several important concepts for distributed TensorFlow, e.g. - `client`, `job`, 'task', `cluster`, `in-graph replication` and + `client`, `job`, `task`, `cluster`, `in-graph replication` and 'synchronous training' and they have already been defined in the [TensorFlow's documentation](https://www.tensorflow.org/deploy/distributed). The distribution strategy inherits these concepts as well and in addition to -- GitLab From df0bd2904e2ace75c53e0e7663a2f9cff23f261d Mon Sep 17 00:00:00 2001 From: Kay Zhu Date: Thu, 3 Jan 2019 00:30:28 -0800 Subject: [PATCH 0119/2345] [XLA] Support RNG in HloEvaluator. PiperOrigin-RevId: 227642508 --- .../xla/service/hlo_constant_folding.cc | 5 +- .../compiler/xla/service/hlo_evaluator.cc | 8 ++ .../compiler/xla/service/hlo_evaluator.h | 5 + .../xla/service/hlo_evaluator_typed_visitor.h | 99 ++++++++++++++++++- tensorflow/compiler/xla/tests/BUILD | 4 - tensorflow/compiler/xla/tests/prng_test.cc | 41 +++++++- 6 files changed, 153 insertions(+), 9 deletions(-) diff --git a/tensorflow/compiler/xla/service/hlo_constant_folding.cc b/tensorflow/compiler/xla/service/hlo_constant_folding.cc index c58d00ff50..799cc9fd91 100644 --- a/tensorflow/compiler/xla/service/hlo_constant_folding.cc +++ b/tensorflow/compiler/xla/service/hlo_constant_folding.cc @@ -52,7 +52,7 @@ StatusOr HloConstantFolding::Run(HloModule* module) { computation->root_instruction() != instruction) { continue; } - // Skip Constant, Parameter, Tuple, AfterAll operation. + // Skip Constant, Parameter, Tuple, AfterAll, Rng operations. // Tuple constants are not directly supported by any backends, hence // folding Tuple is not useful and would in fact be expanded back into // kTuple by Algebraic Simplifier. @@ -62,7 +62,8 @@ StatusOr HloConstantFolding::Run(HloModule* module) { if (instruction->opcode() == HloOpcode::kParameter || instruction->opcode() == HloOpcode::kConstant || instruction->opcode() == HloOpcode::kTuple || - instruction->opcode() == HloOpcode::kAfterAll) { + instruction->opcode() == HloOpcode::kAfterAll || + instruction->opcode() == HloOpcode::kRng) { continue; } diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.cc b/tensorflow/compiler/xla/service/hlo_evaluator.cc index 4f5484293c..e897d09e1b 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.cc +++ b/tensorflow/compiler/xla/service/hlo_evaluator.cc @@ -233,6 +233,14 @@ StatusOr HloEvaluator::Evaluate( for (const auto& literal_ptr : arg_literals) { arg_literals_.push_back(&*literal_ptr); } + if (computation.parent()->config().seed()) { + seed_ = computation.parent()->config().seed(); + } else { + std::random_device rd; + seed_ = rd(); + } + + engine_ = std::minstd_rand0(seed_); TF_RETURN_IF_ERROR(computation.Accept(this)); return GetEvaluatedLiteralFor(computation.root_instruction()).Clone(); diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.h b/tensorflow/compiler/xla/service/hlo_evaluator.h index d8e3019576..4fc5d10aed 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.h +++ b/tensorflow/compiler/xla/service/hlo_evaluator.h @@ -290,6 +290,11 @@ class HloEvaluator : public DfsHloVisitorWithDefault { // Max loop iterations to execute with no maximum if negative. int64 max_loop_iterations_; + // Module-level seed handle. + uint64 seed_; + // RNG engine. + std::minstd_rand0 engine_; + TF_DISALLOW_COPY_AND_ASSIGN(HloEvaluator); }; diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h index b1bbf49d4a..cf3509d0aa 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h +++ b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h @@ -2653,7 +2653,7 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { template ::value>::type* = nullptr> Status HandleReducePrecision(HloInstruction* reduce_precision) { - return InvalidArgument("Double not supported for reduce precision"); + return InvalidArgument("Double is not supported for reduce precision"); } template < @@ -2719,6 +2719,103 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { return HandleIota(iota); } + template ::value || + std::is_floating_point::value)>::type* = nullptr> + Status HandleRng(HloInstruction* random) { + return UnsupportedTypeError(random); + } + template ::value)>::type* = nullptr> + Status HandleRng(HloInstruction* random) { + RandomDistribution distribution = random->random_distribution(); + const auto result_shape = random->shape(); + Literal result(result_shape); + + switch (distribution) { + case RNG_UNIFORM: { + const Literal& low = + parent_->GetEvaluatedLiteralFor(random->operand(0)); + const Literal& high = + parent_->GetEvaluatedLiteralFor(random->operand(1)); + + std::uniform_real_distribution generator( + low.Get({}), high.Get({})); + + TF_RETURN_IF_ERROR( + result.Populate([&](absl::Span /*indexes*/) { + return generator(parent_->engine_); + })); + break; + } + case RNG_NORMAL: { + const Literal& mean = + parent_->GetEvaluatedLiteralFor(random->operand(0)); + const Literal& stddev = + parent_->GetEvaluatedLiteralFor(random->operand(1)); + + std::normal_distribution generator(mean.Get({}), + stddev.Get({})); + + TF_RETURN_IF_ERROR( + result.Populate([&](absl::Span /*indexes*/) { + return generator(parent_->engine_); + })); + break; + } + default: + return UnimplementedStrCat("The distribution ", + RandomDistribution_Name(distribution), + " is not implemented."); + } + parent_->evaluated_[random] = std::move(result); + return Status::OK(); + } + template ::value)>::type* = + nullptr> + Status HandleRng(HloInstruction* random) { + RandomDistribution distribution = random->random_distribution(); + const auto result_shape = random->shape(); + Literal result(result_shape); + + switch (distribution) { + case RNG_UNIFORM: { + const Literal& low = + parent_->GetEvaluatedLiteralFor(random->operand(0)); + const Literal& high = + parent_->GetEvaluatedLiteralFor(random->operand(1)); + + // Note std::uniform_int_distribution assumes interval is closed, i.e., + // [low, high], but we want [low, high) instead. Hence high-1 is used as + // the upper range. + std::uniform_int_distribution generator( + low.Get({}), high.Get({}) - 1); + + TF_RETURN_IF_ERROR( + result.Populate([&](absl::Span /*indexes*/) { + return static_cast(generator(parent_->engine_)); + })); + break; + } + case RNG_NORMAL: { + return Unimplemented( + "Normal distribution is not supported for integral types."); + } + default: + return UnimplementedStrCat("The distribution ", + RandomDistribution_Name(distribution), + " is not implemented."); + } + parent_->evaluated_[random] = std::move(result); + return Status::OK(); + } + Status HandleRng(HloInstruction* random) override { + return HandleRng(random); + } + private: // Creates a vector of multipliers which can be used to create a linear index // into shape. diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 578e716b59..2c11ba4283 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -1422,10 +1422,6 @@ xla_test( xla_test( name = "prng_test", srcs = ["prng_test.cc"], - blacklisted_backends = [ - # TODO(b/122047800) support RNGs on the interpreter backend. - "interpreter", - ], deps = [ "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_util", diff --git a/tensorflow/compiler/xla/tests/prng_test.cc b/tensorflow/compiler/xla/tests/prng_test.cc index 8f2c26f0ee..e49bcf26bd 100644 --- a/tensorflow/compiler/xla/tests/prng_test.cc +++ b/tensorflow/compiler/xla/tests/prng_test.cc @@ -80,7 +80,9 @@ XLA_TEST_F(PrngTest, LargeU01) { UniformTest(0, 1, {0x100, 0x100}); } XLA_TEST_F(PrngTest, TwelveValuesU524) { UniformTest(5, 24, {12}); } // TODO(b/71543667): Fix Rng ops on LLVM backends. -XLA_TEST_F(PrngTest, DISABLED_ON_GPU(DISABLED_ON_CPU(ScalarBF16Tests))) { +// TODO(b/122047800): Interpreter does not support BF16 for RNG ops. +XLA_TEST_F(PrngTest, DISABLED_ON_INTERPRETER( + DISABLED_ON_GPU(DISABLED_ON_CPU(ScalarBF16Tests)))) { for (int64 seed = 0; seed < 100; ++seed) { // The largest negative number smaller than zero in bf16 that's not // denormalized. @@ -103,7 +105,9 @@ XLA_TEST_F(PrngTest, DISABLED_ON_GPU(DISABLED_ON_CPU(ScalarBF16Tests))) { } // TODO(b/71543667): Fix Rng ops on LLVM backends. -XLA_TEST_F(PrngTest, DISABLED_ON_GPU(DISABLED_ON_CPU(ScalarBF16CountTests))) { +// TODO(b/122047800): Interpreter does not support BF16 for RNG ops. +XLA_TEST_F(PrngTest, DISABLED_ON_INTERPRETER(DISABLED_ON_GPU( + DISABLED_ON_CPU(ScalarBF16CountTests)))) { // There are 3 BF16 values in the range of [32.25, 33): 32.25, 32.5, 32.75, // they should get similar counts. bfloat16 low = static_cast(32.25); @@ -276,6 +280,39 @@ XLA_TEST_F(PrngTest, PassInGlobalRngSeed) { EXPECT_FALSE(LiteralTestUtil::Equal(result5, result6)); } +// This test verifies that the two RNG instructions with the same parameters in +// the same HloComputation produces different values. +XLA_TEST_F(PrngTest, DifferentValuesForIdenticalRngNodesInSameComputation) { + // Build a U[0,1) computation. + auto build_computation = [this]() { + XlaBuilder builder(TestName()); + auto a = RngUniform(ConstantR0(&builder, 0), + ConstantR0(&builder, 100), + ShapeUtil::MakeShape(S32, {10})); + auto b = RngUniform(ConstantR0(&builder, 0), + ConstantR0(&builder, 100), + ShapeUtil::MakeShape(S32, {10})); + Tuple(&builder, {a, b}); + return builder.Build(); + }; + + ExecutionOptions execution_options = execution_options_; + execution_options.set_seed(42); + + Literal result_tuple; + { + TF_ASSERT_OK_AND_ASSIGN(auto computation, build_computation()); + TF_ASSERT_OK_AND_ASSIGN( + result_tuple, client_->ExecuteAndTransfer(computation, /*arguments=*/{}, + &execution_options)); + } + + auto results = result_tuple.DecomposeTuple(); + ASSERT_EQ(results.size(), 2); + + EXPECT_FALSE(LiteralTestUtil::Equal(results[0], results[1])); +} + XLA_TEST_F(PrngTest, TenValuesN01) { XlaBuilder builder(TestName()); RngNormal(ConstantR0(&builder, 0), ConstantR0(&builder, 1), -- GitLab From 37326d1497de6de3ea32a1b121f99b2e50ac2dbc Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 01:02:58 -0800 Subject: [PATCH 0120/2345] compat: Update forward compatibility horizon to 2019-01-03 PiperOrigin-RevId: 227646086 --- 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 949bb0dc0c..c60c42ee65 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, 1, 2) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 3) @tf_export("compat.forward_compatible") -- GitLab From 61fde9e1e3c5d995aa20f7bf2781ba60db5bf246 Mon Sep 17 00:00:00 2001 From: Heungsub Lee Date: Thu, 3 Jan 2019 18:26:13 +0900 Subject: [PATCH 0121/2345] Single quote to backtick for consistency --- tensorflow/contrib/distribute/python/mirrored_strategy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy.py b/tensorflow/contrib/distribute/python/mirrored_strategy.py index e635190b50..e3ab2bf19e 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy.py @@ -49,7 +49,7 @@ class MirroredStrategy(distribute_lib.DistributionStrategy): There are several important concepts for distributed TensorFlow, e.g. `client`, `job`, `task`, `cluster`, `in-graph replication` and - 'synchronous training' and they have already been defined in the + `synchronous training` and they have already been defined in the [TensorFlow's documentation](https://www.tensorflow.org/deploy/distributed). The distribution strategy inherits these concepts as well and in addition to that we also clarify several more concepts: -- GitLab From eb54349cb4274ab797917a9e0699decec0b9794c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 03:11:44 -0800 Subject: [PATCH 0122/2345] Remove NCCL from RBE dockers. NCCL is built from source now. PiperOrigin-RevId: 227661008 --- .../Dockerfile.rbe.cuda10.0-cudnn7-ubuntu14.04 | 9 --------- .../ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 | 9 --------- tensorflow/tools/docker/Dockerfile.devel-gpu | 10 ---------- 3 files changed, 28 deletions(-) diff --git a/tensorflow/tools/ci_build/Dockerfile.rbe.cuda10.0-cudnn7-ubuntu14.04 b/tensorflow/tools/ci_build/Dockerfile.rbe.cuda10.0-cudnn7-ubuntu14.04 index 553fbb2860..d08d31d913 100644 --- a/tensorflow/tools/ci_build/Dockerfile.rbe.cuda10.0-cudnn7-ubuntu14.04 +++ b/tensorflow/tools/ci_build/Dockerfile.rbe.cuda10.0-cudnn7-ubuntu14.04 @@ -19,7 +19,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates ENV CUDA_VERSION 10.0.130 ENV CUDA_PKG_VERSION 10-0=$CUDA_VERSION-1 ENV CUDNN_VERSION 7.3.1.20 -ENV NCCL_VERSION 2.3.5 ENV TENSORRT_VERSION 5.0.2 ENV NVIDIA_DRIVER_CAPABILITIES compute,utility ENV NVIDIA_REQUIRE_CUDA "cuda>=10.0,driver>=410" @@ -48,26 +47,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libcudnn7=$CUDNN_VERSION-1+cuda10.0 \ libcudnn7=$CUDNN_VERSION-1+cuda10.0 \ libcudnn7-dev=$CUDNN_VERSION-1+cuda10.0 \ - libnccl2=$NCCL_VERSION-2+cuda10.0 \ - libnccl-dev=$NCCL_VERSION-2+cuda10.0 \ nvinfer-runtime-trt-repo-ubuntu1604-$TENSORRT_VERSION-ga-cuda10.0 && \ apt-get update && apt-get install -y --no-install-recommends \ libnvinfer5=$TENSORRT_VERSION-1+cuda10.0 \ libnvinfer-dev=$TENSORRT_VERSION-1+cuda10.0 && \ ln -s cuda-10.0 /usr/local/cuda && \ apt-mark hold libcudnn7 && \ - apt-mark hold libnccl2 && \ rm -rf /var/lib/apt/lists/* # TODO(b/110903506): Provide a link to the SONAME of libcuda.so. # https://github.com/NVIDIA/nvidia-docker/issues/775 RUN ln -s libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 -# TODO(klimek): Once the TODO in tensorflow's configure.py to correctly find -# libnccl is resolved, delete this block. -RUN ln -s /usr/lib/x86_64-linux-gnu/libnccl.so /usr/lib/libnccl.so \ - && ln -s /usr/lib/x86_64-linux-gnu/libnccl.so /usr/lib/libnccl.so.2 - # Install a newer version of libstdc++, as new clang versions do not work # with the stock ubuntu 14.04 libstdc++. RUN apt-get update && \ diff --git a/tensorflow/tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 b/tensorflow/tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 index 60a23e1edb..9d5d51447c 100644 --- a/tensorflow/tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 +++ b/tensorflow/tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 @@ -25,7 +25,6 @@ ENV PATH /usr/local/nvidia/bin:/usr/local/cuda/bin:${PATH} ENV NVIDIA_VISIBLE_DEVICES all ENV NVIDIA_DRIVER_CAPABILITIES compute,utility ENV NVIDIA_REQUIRE_CUDA "cuda>=9.0" -ENV NCCL_VERSION 2.2.13 ENV TENSORRT_VERSION 5.0.2 ENV CUDNN_VERSION 7.1.4.18 @@ -45,14 +44,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ cuda-cudart-$CUDA_PKG_VERSION \ cuda-libraries-$CUDA_PKG_VERSION \ cuda-cublas-9-0=9.0.176.4-1 \ - libnccl2=$NCCL_VERSION-1+cuda9.0 \ cuda-libraries-dev-$CUDA_PKG_VERSION \ cuda-nvml-dev-$CUDA_PKG_VERSION \ cuda-minimal-build-$CUDA_PKG_VERSION \ cuda-command-line-tools-$CUDA_PKG_VERSION \ cuda-core-9-0=9.0.176.3-1 \ cuda-cublas-dev-9-0=9.0.176.4-1 \ - libnccl-dev=$NCCL_VERSION-1+cuda9.0 \ libcudnn7-dev=$CUDNN_VERSION-1+cuda9.0 \ libcudnn7=$CUDNN_VERSION-1+cuda9.0 \ nvinfer-runtime-trt-repo-ubuntu1604-$TENSORRT_VERSION-ga-cuda9.0 && \ @@ -60,7 +57,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libnvinfer5=$TENSORRT_VERSION-1+cuda9.0 \ libnvinfer-dev=$TENSORRT_VERSION-1+cuda9.0 && \ ln -s cuda-9.0 /usr/local/cuda && \ - apt-mark hold libnccl2 && \ apt-mark hold libcudnn7 libcudnn7-dev && \ rm -rf /var/lib/apt/lists/* @@ -71,11 +67,6 @@ RUN echo "/usr/local/nvidia/lib" >> /etc/ld.so.conf.d/nvidia.conf && \ # https://github.com/NVIDIA/nvidia-docker/issues/775 RUN ln -s libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 -# TODO(klimek): Once the TODO in tensorflow's configure.py to correctly find -# libnccl is resolved, delete this block. -RUN ln -s /usr/lib/x86_64-linux-gnu/libnccl.so /usr/lib/libnccl.so \ - && ln -s /usr/lib/x86_64-linux-gnu/libnccl.so /usr/lib/libnccl.so.2 - # Install a newer version of libstdc++, as new clang versions do not work # with the stock ubuntu 14.04 libstdc++. RUN apt-get update && \ diff --git a/tensorflow/tools/docker/Dockerfile.devel-gpu b/tensorflow/tools/docker/Dockerfile.devel-gpu index 1ad359ddcc..e085ee7170 100644 --- a/tensorflow/tools/docker/Dockerfile.devel-gpu +++ b/tensorflow/tools/docker/Dockerfile.devel-gpu @@ -15,8 +15,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git \ libcudnn7=7.2.1.38-1+cuda9.0 \ libcudnn7-dev=7.2.1.38-1+cuda9.0 \ - libnccl2=2.2.13-1+cuda9.0 \ - libnccl-dev=2.2.13-1+cuda9.0 \ libcurl3-dev \ libfreetype6-dev \ libhdf5-serial-dev \ @@ -41,11 +39,6 @@ RUN apt-get update && \ apt-get install libnvinfer4=4.1.2-1+cuda9.0 && \ apt-get install libnvinfer-dev=4.1.2-1+cuda9.0 -# Link NCCL libray and header where the build script expects them. -RUN mkdir /usr/local/cuda-9.0/lib && \ - ln -s /usr/lib/x86_64-linux-gnu/libnccl.so.2 /usr/local/cuda/lib/libnccl.so.2 && \ - ln -s /usr/include/nccl.h /usr/local/cuda/include/nccl.h - RUN curl -fSsL -O https://bootstrap.pypa.io/get-pip.py && \ python get-pip.py && \ rm get-pip.py @@ -111,9 +104,6 @@ ENV TF_CUDA_COMPUTE_CAPABILITIES=3.5,5.2,6.0,6.1,7.0 ENV TF_CUDA_VERSION=9.0 ENV TF_CUDNN_VERSION=7 -# NCCL 2.x -ENV TF_NCCL_VERSION=2 - RUN ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 && \ LD_LIBRARY_PATH=/usr/local/cuda/lib64/stubs:${LD_LIBRARY_PATH} \ tensorflow/tools/ci_build/builds/configured GPU \ -- GitLab From 31cf2b97f0f3b1a90d26a56016c4b984f62dea3e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 06:51:42 -0800 Subject: [PATCH 0123/2345] Add all unary operations to list of autograph-supported ops. PiperOrigin-RevId: 227681912 --- .../converters/logical_expressions.py | 44 ++++++------- .../converters/logical_expressions_test.py | 7 ++ .../python/autograph/operators/__init__.py | 2 + .../python/autograph/operators/logical.py | 65 +++++-------------- 4 files changed, 46 insertions(+), 72 deletions(-) diff --git a/tensorflow/python/autograph/converters/logical_expressions.py b/tensorflow/python/autograph/converters/logical_expressions.py index dfcaafdc9e..ea9740a22e 100644 --- a/tensorflow/python/autograph/converters/logical_expressions.py +++ b/tensorflow/python/autograph/converters/logical_expressions.py @@ -38,29 +38,29 @@ from tensorflow.python.autograph.pyct import templates SAFE_BOOLEAN_OPERAND = 'SAFE_BOOLEAN_OPERAND' +OP_MAPPING = { + gast.And: 'ag__.and_', + gast.Eq: 'ag__.eq', + gast.NotEq: 'ag__.not_eq', + gast.Lt: 'ag__.lt', + gast.LtE: 'ag__.lt_e', + gast.Gt: 'ag__.gt', + gast.GtE: 'ag__.gt_e', + gast.Is: 'ag__.is_', + gast.IsNot: 'ag__.is_not', + gast.In: 'ag__.in_', + gast.Not: 'ag__.not_', + gast.NotIn: 'ag__.not_in', + gast.Or: 'ag__.or_', + gast.UAdd: 'ag__.u_add', + gast.USub: 'ag__.u_sub', + gast.Invert: 'ag__.invert', +} + + class LogicalExpressionTransformer(converter.Base): """Converts logical expressions to corresponding TF calls.""" - def __init__(self, ctx): - super(LogicalExpressionTransformer, self).__init__(ctx) - # TODO(mdan): For completeness and consistency, overload everything. - self.op_mapping = { - gast.And: 'ag__.and_', - gast.Eq: 'ag__.eq', - gast.NotEq: 'ag__.not_eq', - gast.Lt: 'ag__.lt', - gast.LtE: 'ag__.lt_e', - gast.Gt: 'ag__.gt', - gast.GtE: 'ag__.gt_e', - gast.Is: 'ag__.is_', - gast.IsNot: 'ag__.is_not', - gast.In: 'ag__.in_', - gast.Not: 'ag__.not_', - gast.NotIn: 'ag__.not_in', - gast.Or: 'ag__.or_', - gast.USub: 'ag__.u_sub', - } - def _expect_simple_symbol(self, operand): if isinstance(operand, gast.Name): return @@ -74,11 +74,11 @@ class LogicalExpressionTransformer(converter.Base): def _has_matching_func(self, operator): op_type = type(operator) - return op_type in self.op_mapping + return op_type in OP_MAPPING def _matching_func(self, operator): op_type = type(operator) - return self.op_mapping[op_type] + return OP_MAPPING[op_type] def _as_function(self, func_name, args, args_as_lambda=False): if args_as_lambda: diff --git a/tensorflow/python/autograph/converters/logical_expressions_test.py b/tensorflow/python/autograph/converters/logical_expressions_test.py index 687412750e..67ccd1fb47 100644 --- a/tensorflow/python/autograph/converters/logical_expressions_test.py +++ b/tensorflow/python/autograph/converters/logical_expressions_test.py @@ -77,6 +77,13 @@ class LogicalExpressionTest(converter_testing.TestCase): with self.converted(test_fn, logical_expressions, {}) as result: self.assertTrue(result.test_fn('a', ('a',))) + def test_unary_ops(self): + def test_fn(a): + return ~a, -a, +a + + with self.converted(test_fn, logical_expressions, {}) as result: + self.assertEqual(result.test_fn(1), (-2, -1, 1)) + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/autograph/operators/__init__.py b/tensorflow/python/autograph/operators/__init__.py index 7a580fe324..35f8028c29 100644 --- a/tensorflow/python/autograph/operators/__init__.py +++ b/tensorflow/python/autograph/operators/__init__.py @@ -52,6 +52,7 @@ from tensorflow.python.autograph.operators.logical import eq from tensorflow.python.autograph.operators.logical import gt from tensorflow.python.autograph.operators.logical import gt_e from tensorflow.python.autograph.operators.logical import in_ +from tensorflow.python.autograph.operators.logical import invert from tensorflow.python.autograph.operators.logical import is_ from tensorflow.python.autograph.operators.logical import is_not from tensorflow.python.autograph.operators.logical import lt @@ -60,6 +61,7 @@ from tensorflow.python.autograph.operators.logical import not_ from tensorflow.python.autograph.operators.logical import not_eq from tensorflow.python.autograph.operators.logical import not_in from tensorflow.python.autograph.operators.logical import or_ +from tensorflow.python.autograph.operators.logical import u_add from tensorflow.python.autograph.operators.logical import u_sub from tensorflow.python.autograph.operators.py_builtins import float_ from tensorflow.python.autograph.operators.py_builtins import int_ diff --git a/tensorflow/python/autograph/operators/logical.py b/tensorflow/python/autograph/operators/logical.py index 569db5b91b..dadb0daf1a 100644 --- a/tensorflow/python/autograph/operators/logical.py +++ b/tensorflow/python/autograph/operators/logical.py @@ -18,6 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import operator + from tensorflow.python.framework import tensor_util from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import gen_math_ops @@ -35,7 +37,7 @@ def and_(a, b): a_val = a() if tensor_util.is_tensor(a_val): return _tf_lazy_and(a_val, b) - return _py_lazy_and(a_val, b) + return a_val and b() def _tf_lazy_and(cond, b): @@ -44,17 +46,12 @@ def _tf_lazy_and(cond, b): return control_flow_ops.cond(cond, b, lambda: cond) -def _py_lazy_and(cond, b): - """Lazy-eval equivalent of "and" in Python.""" - return cond and b() - - def or_(a, b): """Functional form of "or". Uses lazy evaluation semantics.""" a_val = a() if tensor_util.is_tensor(a_val): return _tf_lazy_or(a_val, b) - return _py_lazy_or(a_val, b) + return a_val or b() def _tf_lazy_or(cond, b): @@ -63,16 +60,11 @@ def _tf_lazy_or(cond, b): return control_flow_ops.cond(cond, lambda: cond, b) -def _py_lazy_or(cond, b): - """Lazy-eval equivalent of "or" in Python.""" - return cond or b() - - def eq(a, b): """Functional form of "equal".""" if tensor_util.is_tensor(a) or tensor_util.is_tensor(b): return _tf_equal(a, b) - return _py_equal(a, b) + return a == b def _tf_equal(a, b): @@ -80,11 +72,6 @@ def _tf_equal(a, b): return gen_math_ops.equal(a, b) -def _py_equal(a, b): - """Overload of "equal" that falls back to Python's default implementation.""" - return a == b - - def not_eq(a, b): """Functional form of "not-equal".""" return not_(eq(a, b)) @@ -92,25 +79,8 @@ def not_eq(a, b): # Default implementation for the remainings. - -def gt(a, b): - """Functional form of "less-than".""" - return a > b - - -def gt_e(a, b): - """Functional form of "less-than".""" - return a >= b - - -def is_(a, b): - """Functional form of "less-than".""" - return a is b - - -def is_not(a, b): - """Functional form of "less-than".""" - return a is not b +is_ = operator.is_ +is_not = operator.is_not def in_(a, b): @@ -119,21 +89,16 @@ def in_(a, b): return a in b -def lt(a, b): - """Functional form of "less-than".""" - return a < b - - -def lt_e(a, b): - """Functional form of "less-than".""" - return a <= b - - def not_in(a, b): """Functional form of "less-than".""" return a not in b +gt = operator.gt +gt_e = operator.ge +lt = operator.lt +lt_e = operator.le + -def u_sub(a): - """Functional form of "unary-sub".""" - return -a +u_add = operator.pos +u_sub = operator.neg +invert = operator.invert -- GitLab From dfad4e97b2d00df768e9e8df16179a6f7ceda43c Mon Sep 17 00:00:00 2001 From: James Keeling Date: Thu, 3 Jan 2019 09:08:14 -0800 Subject: [PATCH 0124/2345] Fix odd number of backticks in docstring. This was causing the documentation to render incorrectly. PiperOrigin-RevId: 227698008 --- tensorflow/python/ops/linalg/linear_operator_kronecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/linalg/linear_operator_kronecker.py b/tensorflow/python/ops/linalg/linear_operator_kronecker.py index f7e785caa5..005b9b429b 100644 --- a/tensorflow/python/ops/linalg/linear_operator_kronecker.py +++ b/tensorflow/python/ops/linalg/linear_operator_kronecker.py @@ -71,7 +71,7 @@ class LinearOperatorKronecker(linear_operator.LinearOperator): `op1 x op2 x .. opJ` (we omit parentheses as the Kronecker product is associative). - If `opj` has shape `batch_shape_j` + [M_j, N_j`, then the composed operator + If `opj` has shape `batch_shape_j + [M_j, N_j]`, then the composed operator will have shape equal to `broadcast_batch_shape + [prod M_j, prod N_j]`, where the product is over all operators. -- GitLab From d04b09d65653bb78ad3b14448bf9ef867d9041d4 Mon Sep 17 00:00:00 2001 From: Blake Hechtman Date: Thu, 3 Jan 2019 09:19:36 -0800 Subject: [PATCH 0125/2345] [XLA] Propagate the layout of transposes and reshapes that add one sized dimensions in a depth first order. PiperOrigin-RevId: 227699358 --- .../compiler/xla/service/layout_assignment.cc | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tensorflow/compiler/xla/service/layout_assignment.cc b/tensorflow/compiler/xla/service/layout_assignment.cc index 1c4ac178f0..ebf7a88fae 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.cc +++ b/tensorflow/compiler/xla/service/layout_assignment.cc @@ -1236,6 +1236,23 @@ Status LayoutAssignment::PropagateUseConstraintToDefs( }); } +namespace { +// A transpose or a reshape that only changes trivial dimensions have meaningful +// layouts that are valuable to propagate in a depthfirst manner to avoid +// unassinged layouts in the graph. +bool InstructionShouldPropagateDepthFirst(const HloInstruction& hlo) { + switch (hlo.opcode()) { + case HloOpcode::kReshape: + return std::get<0>(hlo.ReshapeMerelyInsertsOrDeletes1SizedDimensions()); + case HloOpcode::kTranspose: + return true; + default: + return false; + } +} + +} // namespace + Status LayoutAssignment::PropagateOperandConstraint( const OperandLayoutConstraint& operand_constraint, LayoutConstraints* constraints) { @@ -1370,7 +1387,7 @@ Status LayoutAssignment::PropagateOperandConstraint( TF_RETURN_IF_ERROR(constraints->SetBufferLayout( *layout, *buffer, /*mandatory=*/user->opcode() == HloOpcode::kReduce, - /*dfs=*/false)); + /*dfs=*/InstructionShouldPropagateDepthFirst(*user))); } } return Status::OK(); @@ -1420,11 +1437,9 @@ Status LayoutAssignment::PropagateBufferConstraintToOperands( ChooseOperandLayoutFromOutputLayout(buffer_constraint.layout(), instruction, operand_no); if (operand_layout != nullptr) { - // Do not propagate operand constraints of transposes and reshapes, it - // tends to create really bad layouts. TF_RETURN_IF_ERROR(constraints->SetArrayOperandLayout( *operand_layout, instruction, operand_no, /*mandatory=*/false, - /*dfs=*/false)); + /*dfs=*/InstructionShouldPropagateDepthFirst(*instruction))); } } else { VLOG(6) << "Operand already has a constraint " -- GitLab From a5b70b7cb46e18846ba8875a13da08e7444c5136 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Thu, 3 Jan 2019 09:20:12 -0800 Subject: [PATCH 0126/2345] Fix for an optimizer.get_config() exception when exporting a SavedModel Tests tf.saved_model.save() with Optimizerv2 instead of v1 PiperOrigin-RevId: 227699428 --- tensorflow/python/keras/backend.py | 5 +++++ tensorflow/python/saved_model/save_test.py | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/keras/backend.py b/tensorflow/python/keras/backend.py index 42d94e77a0..6693243a68 100644 --- a/tensorflow/python/keras/backend.py +++ b/tensorflow/python/keras/backend.py @@ -2794,6 +2794,11 @@ def get_value(x): """ if context.executing_eagerly(): return x.numpy() + elif not getattr(x, '_in_graph_mode', True): + # This is a variable which was created in an eager context, but is being + # evaluated from a Graph. + with context.eager_mode(): + return x.numpy() elif ops.inside_function(): raise RuntimeError('Cannot get value inside Tensorflow graph function.') return x.eval(session=get_session()) diff --git a/tensorflow/python/saved_model/save_test.py b/tensorflow/python/saved_model/save_test.py index e643ff3dbf..533b954190 100644 --- a/tensorflow/python/saved_model/save_test.py +++ b/tensorflow/python/saved_model/save_test.py @@ -41,7 +41,7 @@ from tensorflow.python.saved_model import loader from tensorflow.python.saved_model import save from tensorflow.python.saved_model import signature_constants from tensorflow.python.saved_model import tag_constants -from tensorflow.python.training import adam +from tensorflow.python.keras.optimizer_v2 import adam from tensorflow.python.training.checkpointable import tracking from tensorflow.python.training.checkpointable import util @@ -50,7 +50,7 @@ class _ModelWithOptimizer(util.Checkpoint): def __init__(self): self.dense = core.Dense(1) - self.optimizer = adam.AdamOptimizer(0.01) + self.optimizer = adam.Adam(0.01) @def_function.function( input_signature=(tensor_spec.TensorSpec([None, 2], dtypes.float32), @@ -401,7 +401,7 @@ class _ModelWithOptimizerUsingDefun(util.Checkpoint): def __init__(self): self.dense = core.Dense(1) - self.optimizer = adam.AdamOptimizer(0.01) + self.optimizer = adam.Adam(0.01) # Using defun due to control flow v2 cycles, b/121159261. def_function uses # conds to gate variable initialization and so triggers cond reference cycles, -- GitLab From 78acbc6ee7fa557504599d7847bcd96a9a977d40 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Thu, 3 Jan 2019 09:21:25 -0800 Subject: [PATCH 0127/2345] TFTS: Remove flaky assertions PiperOrigin-RevId: 227699591 --- tensorflow/contrib/timeseries/examples/predict_test.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tensorflow/contrib/timeseries/examples/predict_test.py b/tensorflow/contrib/timeseries/examples/predict_test.py index 678fd71cd8..b353f85cb5 100644 --- a/tensorflow/contrib/timeseries/examples/predict_test.py +++ b/tensorflow/contrib/timeseries/examples/predict_test.py @@ -43,10 +43,6 @@ class PeriodTrendExampleTest(test.TestCase): self.assertAllEqual([700], mean.shape) self.assertAllEqual([700], upper_limit.shape) self.assertAllEqual([700], lower_limit.shape) - # Check that variance hasn't blown up too much. This is a relatively good - # indication that training was successful. - self.assertLess(upper_limit[-1] - lower_limit[-1], - 1.5 * (upper_limit[0] - lower_limit[0])) def test_ar(self): (times, observed, all_times, mean, @@ -55,7 +51,6 @@ class PeriodTrendExampleTest(test.TestCase): self.assertAllEqual(all_times.shape, mean.shape) self.assertAllEqual(all_times.shape, upper_limit.shape) self.assertAllEqual(all_times.shape, lower_limit.shape) - self.assertLess((upper_limit - lower_limit).mean(), 4.) if __name__ == "__main__": -- GitLab From c2ec7217a8b122d6a561da5d7d96b94ede9b7684 Mon Sep 17 00:00:00 2001 From: Jianwei Xie Date: Thu, 3 Jan 2019 09:53:37 -0800 Subject: [PATCH 0128/2345] Add model saving in Resnet50 code. In addition, 1. Optimized the weights saving logic in Keras for TPU. 2. Fixed the bug in optimizer for TPU mirrored variable. PiperOrigin-RevId: 227704010 --- tensorflow/python/keras/engine/saving.py | 11 ++++++++++- tensorflow/python/keras/optimizer_v2/BUILD | 1 + tensorflow/python/keras/optimizer_v2/optimizer_v2.py | 4 +++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/keras/engine/saving.py b/tensorflow/python/keras/engine/saving.py index 91eba0acab..0604a389a9 100644 --- a/tensorflow/python/keras/engine/saving.py +++ b/tensorflow/python/keras/engine/saving.py @@ -25,6 +25,7 @@ import os import numpy as np from six.moves import zip # pylint: disable=redefined-builtin +from tensorflow.python.framework import ops from tensorflow.python.keras import backend as K from tensorflow.python.keras import optimizers from tensorflow.python.keras.utils import conv_utils @@ -736,9 +737,17 @@ def save_weights_to_hdf5_group(f, layers): f.attrs['backend'] = K.backend().encode('utf8') f.attrs['keras_version'] = str(keras_version).encode('utf8') + # On TPUs, modifying the graph between session.runs() triggers some expensive + # recompilation overhead. To avoid this, we build up the full set of tensors + # to save before fetching weights, thus only modifying the graph once. + layer_weights_dict = {} + for layer in layers: + layer_weights_dict[layer.name] = [ops.convert_to_tensor(w) + for w in layer.weights] + for layer in layers: g = f.create_group(layer.name) - symbolic_weights = layer.weights + symbolic_weights = layer_weights_dict[layer.name] weight_values = K.batch_get_value(symbolic_weights) weight_names = [] for i, (w, val) in enumerate(zip(symbolic_weights, weight_values)): diff --git a/tensorflow/python/keras/optimizer_v2/BUILD b/tensorflow/python/keras/optimizer_v2/BUILD index 05f6e5ac48..964cf5fcba 100644 --- a/tensorflow/python/keras/optimizer_v2/BUILD +++ b/tensorflow/python/keras/optimizer_v2/BUILD @@ -34,6 +34,7 @@ py_library( "//tensorflow/python:variable_scope", "//tensorflow/python:variables", "//tensorflow/python/distribute:reduce_util", + "//tensorflow/python/distribute:values", ], ) diff --git a/tensorflow/python/keras/optimizer_v2/optimizer_v2.py b/tensorflow/python/keras/optimizer_v2/optimizer_v2.py index b740b2f05b..98f87a41ae 100644 --- a/tensorflow/python/keras/optimizer_v2/optimizer_v2.py +++ b/tensorflow/python/keras/optimizer_v2/optimizer_v2.py @@ -28,6 +28,7 @@ import six from tensorflow.python.distribute import distribute_lib from tensorflow.python.distribute import distribution_strategy_context as distribute_ctx from tensorflow.python.distribute import reduce_util as ds_reduce_util +from tensorflow.python.distribute import values as distributed_values from tensorflow.python.eager import backprop from tensorflow.python.eager import context from tensorflow.python.framework import dtypes @@ -576,7 +577,8 @@ class OptimizerV2(checkpointable.CheckpointableBase): value = self._get_hyper(hyperparameter_name) if callable(value): return value() - if isinstance(value, (ops.Tensor, tf_variables.Variable)): + if isinstance(value, (ops.Tensor, tf_variables.Variable, + distributed_values.TPUMirroredVariable)): return backend.get_value(value) return value -- GitLab From 3e2408a52822439b04de0a0a070a200c8f15509b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 10:34:07 -0800 Subject: [PATCH 0129/2345] Modify NNAPI delegate SVDF test to use 0 bias. PiperOrigin-RevId: 227710985 --- tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc index 2da14cc1de..5da052eb42 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc @@ -2003,7 +2003,9 @@ class BaseSVDFOpModel : public SingleOpModelWithNNAPI { input_ = AddInput(TensorType_FLOAT32); weights_feature_ = AddInput(weights_feature_type); weights_time_ = AddInput(weights_time_type); - bias_ = AddNullInput(); + // TODO(b/121383394) : figure out why optional bias causes TFLite segfault + // when using NNAPI delegate. + bias_ = AddInput(TensorType_FLOAT32); const int num_filters = units * rank; activation_state_ = AddInput( TensorData{TensorType_FLOAT32, {batches, memory_size * num_filters}}, @@ -2019,6 +2021,8 @@ class BaseSVDFOpModel : public SingleOpModelWithNNAPI { {units_}, // bias tensor {batches, memory_size * num_filters} // activation_state tensor }); + // TODO(b/121383394) : remove once the optional bias bug is fixed. + PopulateTensor(bias_, std::vector(units_)); } // Populates the weights_feature tensor. -- GitLab From 140f0a3cde3696ca5128322cffda178563c8d04f Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Thu, 3 Jan 2019 10:35:02 -0800 Subject: [PATCH 0130/2345] Finish migration to the new scope based API for using distribution strategy. PiperOrigin-RevId: 227711127 --- tensorflow/contrib/distribute/README.md | 30 ++++++++----------- .../distribute/python/examples/keras_mnist.py | 19 ++++++------ 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/tensorflow/contrib/distribute/README.md b/tensorflow/contrib/distribute/README.md index 8a8dc159ad..dbcaf8185f 100644 --- a/tensorflow/contrib/distribute/README.md +++ b/tensorflow/contrib/distribute/README.md @@ -43,28 +43,19 @@ the workers. Let's see how to scale to multiple GPUs on one machine using `MirroredStrategy` with [tf.keras] (https://www.tensorflow.org/guide/keras). -Take a very simple model consisting of a single layer: +Let's define a simple input dataset for training this model. Note that currently we require using +[`tf.data.Dataset`](https://www.tensorflow.org/api_docs/python/tf/data/Dataset) +with `DistributionStrategy`. ```python import tensorflow as tf from tensorflow import keras -inputs = tf.keras.layers.Input(shape=(1,)) -predictions = tf.keras.layers.Dense(1)(inputs) -model = tf.keras.models.Model(inputs=inputs, outputs=predictions) -``` - -Let's also define a simple input dataset for training this model. Note that currently we require using -[`tf.data.Dataset`](https://www.tensorflow.org/api_docs/python/tf/data/Dataset) -with `DistributionStrategy`. - -```python features = tf.data.Dataset.from_tensors([1.]).repeat(10000).batch(10) labels = tf.data.Dataset.from_tensors([1.]).repeat(10000).batch(10) train_dataset = tf.data.Dataset.zip((features, labels)) ``` - To distribute this Keras model on multiple GPUs using `MirroredStrategy` we first instantiate a `MirroredStrategy` object. @@ -72,14 +63,17 @@ first instantiate a `MirroredStrategy` object. distribution = tf.contrib.distribute.MirroredStrategy() ``` -We then compile the Keras model and pass the `MirroredStrategy` object in the -`distribute` argument (apart from other usual arguments like `loss` and -`optimizer`). +Take a very simple model consisting of a single layer. We need to create and compile +the model under the distribution strategy scope. ```python -model.compile(loss='mean_squared_error', - optimizer=tf.train.GradientDescentOptimizer(learning_rate=0.2), - distribute=distribution) +with distribution.scope(): + inputs = tf.keras.layers.Input(shape=(1,)) + predictions = tf.keras.layers.Dense(1)(inputs) + model = tf.keras.models.Model(inputs=inputs, outputs=predictions) + + model.compile(loss='mean_squared_error', + optimizer=tf.train.GradientDescentOptimizer(learning_rate=0.2)) ``` To train the model we call Keras `fit` API using the input dataset that we diff --git a/tensorflow/contrib/distribute/python/examples/keras_mnist.py b/tensorflow/contrib/distribute/python/examples/keras_mnist.py index 60fda99664..1ce91ecaf2 100644 --- a/tensorflow/contrib/distribute/python/examples/keras_mnist.py +++ b/tensorflow/contrib/distribute/python/examples/keras_mnist.py @@ -109,22 +109,21 @@ def main(_): tf.enable_eager_execution() train_ds, eval_ds, input_shape = get_input_datasets() - model = get_model(input_shape) # Instantiate the MirroredStrategy object. If we don't specify `num_gpus` or # the `devices` argument then all the GPUs available on the machine are used. # TODO(priyag): Use `tf.distribute.MirroredStrategy` once available. strategy = mirrored_strategy.MirroredStrategy(['/gpu:0', '/cpu:0']) - optimizer = rmsprop.RMSProp(learning_rate=0.001) - - # Compile the model by passing the distribution strategy object to the - # `distribute` argument. `fit`, `evaluate` and `predict` will be distributed - # based on the strategy instantiated. - model.compile(loss=tf.keras.losses.categorical_crossentropy, - optimizer=optimizer, - metrics=['accuracy'], - distribute=strategy) + # Create and compile the model under Distribution strategy scope. + # `fit`, `evaluate` and `predict` will be distributed based on the strategy + # model was compiled with. + with strategy.scope(): + model = get_model(input_shape) + optimizer = rmsprop.RMSProp(learning_rate=0.001) + model.compile(loss=tf.keras.losses.categorical_crossentropy, + optimizer=optimizer, + metrics=['accuracy']) # Train the model with the train dataset. model.fit(x=train_ds, epochs=20, steps_per_epoch=468) -- GitLab From 64596a68235709e7f3246d0b120a4146521a998b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 10:37:35 -0800 Subject: [PATCH 0131/2345] No public changes. PiperOrigin-RevId: 227711548 --- tensorflow/lite/experimental/swift/BUILD | 100 +++++ tensorflow/lite/experimental/swift/LICENSE | 202 ++++++++++ tensorflow/lite/experimental/swift/README.md | 54 +++ .../swift/Sources/Interpreter.swift | 265 ++++++++++++++ .../swift/Sources/InterpreterError.swift | 99 +++++ .../swift/Sources/InterpreterOptions.swift | 29 ++ .../experimental/swift/Sources/Model.swift | 40 ++ .../Sources/QuantizationParameters.swift | 38 ++ .../experimental/swift/Sources/Tensor.swift | 138 +++++++ .../Configs/TensorFlowLite.tulsigen | 57 +++ .../project.tulsiconf | 14 + .../project.pbxproj | 345 ++++++++++++++++++ .../TensorFlowLiteApp/AppDelegate.swift | 24 ++ .../Array+TensorFlowLite.swift | 22 ++ .../AppIcon.appiconset/Contents.json | 98 +++++ .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 44 +++ .../Base.lproj/Main.storyboard | 95 +++++ .../Data+TensorFlowLite.swift | 13 + .../TensorFlowLiteApp/Info.plist | 46 +++ .../TensorFlowLiteApp/ViewController.swift | 299 +++++++++++++++ .../swift/Tests/InterpreterOptionsTests.swift | 54 +++ .../swift/Tests/InterpreterTests.swift | 315 ++++++++++++++++ .../experimental/swift/Tests/ModelTests.swift | 59 +++ .../Tests/QuantizationParametersTests.swift | 43 +++ .../swift/Tests/TensorTests.swift | 83 +++++ .../tools/pip_package/pip_smoke_test.py | 36 +- 27 files changed, 2603 insertions(+), 15 deletions(-) create mode 100644 tensorflow/lite/experimental/swift/BUILD create mode 100644 tensorflow/lite/experimental/swift/LICENSE create mode 100644 tensorflow/lite/experimental/swift/README.md create mode 100644 tensorflow/lite/experimental/swift/Sources/Interpreter.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/InterpreterError.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/Model.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/Tensor.swift create mode 100644 tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen create mode 100644 tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/ModelTests.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/TensorTests.swift diff --git a/tensorflow/lite/experimental/swift/BUILD b/tensorflow/lite/experimental/swift/BUILD new file mode 100644 index 0000000000..3bd288a1e1 --- /dev/null +++ b/tensorflow/lite/experimental/swift/BUILD @@ -0,0 +1,100 @@ +# TensorFlow Lite for Swift. + +package(default_visibility = ["//visibility:private"]) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_unit_test") +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +MINIMUM_OS_VERSION = "9.0" + +SWIFT_COPTS = [ + "-wmo", +] + +swift_library( + name = "TensorFlowLite", + srcs = glob(["Sources/*.swift"]), + copts = SWIFT_COPTS, + module_name = "TensorFlowLite", + tags = ["manual"], + deps = [ + "//tensorflow/lite/experimental/c:c_api", + ], +) + +ios_unit_test( + name = "TensorFlowLiteTests", + size = "small", + minimum_os_version = MINIMUM_OS_VERSION, + tags = [ + "manual", + # DISABLED: Following sanitizer tests are not supported by iOS test targets. + "noasan", + "nomsan", + "notsan", + ], + deps = [":TensorFlowLiteTestsLib"], +) + +swift_library( + name = "TensorFlowLiteTestsLib", + testonly = 1, + srcs = glob(["Tests/*.swift"]), + copts = SWIFT_COPTS, + tags = ["manual"], + deps = [ + ":TensorFlowLite", + ":TestResources", + ], +) + +objc_library( + name = "TestResources", + resources = [ + "//tensorflow/lite:testdata/add.bin", + "//tensorflow/lite:testdata/add_quantized.bin", + "//tensorflow/lite:testdata/multi_add.bin", + ], + tags = ["manual"], +) + +ios_application( + name = "TensorFlowLiteApp", + app_icons = glob(["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/**"]), + bundle_id = "com.tensorflow.lite.swift.TensorFlowLite", + families = [ + "ipad", + "iphone", + ], + infoplists = ["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist"], + launch_storyboard = "TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard", + minimum_os_version = MINIMUM_OS_VERSION, + sdk_frameworks = [ + "CoreGraphics", + ], + tags = ["manual"], + deps = [":TensorFlowLiteAppLib"], +) + +swift_library( + name = "TensorFlowLiteAppLib", + srcs = glob(["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/*.swift"]), + tags = ["manual"], + deps = [ + ":TensorFlowLite", + ":TensorFlowLiteAppResources", + ], +) + +objc_library( + name = "TensorFlowLiteAppResources", + storyboards = glob([ + "TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/*.storyboard", + ]), + tags = ["manual"], + deps = [":TestResources"], +) diff --git a/tensorflow/lite/experimental/swift/LICENSE b/tensorflow/lite/experimental/swift/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/tensorflow/lite/experimental/swift/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tensorflow/lite/experimental/swift/README.md b/tensorflow/lite/experimental/swift/README.md new file mode 100644 index 0000000000..deb16c1e91 --- /dev/null +++ b/tensorflow/lite/experimental/swift/README.md @@ -0,0 +1,54 @@ +# TensorFlow Lite for Swift + +[TensorFlow Lite](https://www.tensorflow.org/lite/) is TensorFlow's lightweight +solution for Swift developers. It enables low-latency inference of on-device +machine learning models with a small binary size and fast performance supporting +hardware acceleration. + +## Getting Started + +### Bazel + +In your `BUILD` file, add the `TensorFlowLite` dependency: + +```python +swift_library( + # ... + deps = [ + "//tensorflow/lite/swift:TensorFlowLite", + ], +) +``` + +In your Swift files, import the module: + +```swift +import TensorFlowLite +``` + +### Tulsi + +Open the `TensorFlowLite.tulsiproj` using the [TulsiApp](https://github.com/bazelbuild/tulsi) or by +running the [`generate_xcodeproj.sh`](https://github.com/bazelbuild/tulsi/blob/master/src/tools/generate_xcodeproj.sh) +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 +``` diff --git a/tensorflow/lite/experimental/swift/Sources/Interpreter.swift b/tensorflow/lite/experimental/swift/Sources/Interpreter.swift new file mode 100644 index 0000000000..a14b5966b1 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/Interpreter.swift @@ -0,0 +1,265 @@ +// Copyright 2018 Google Inc. 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 Foundation +import TensorFlowLiteCAPI + +/// A TensorFlow Lite interpreter that performs inference from a given model. +public final class Interpreter { + + /// The `TFL_Interpreter` C pointer type represented as an `UnsafePointer`. + private typealias CInterpreter = OpaquePointer + + /// Total number of input tensors associated with the model. + public var inputTensorCount: Int { + return Int(TFL_InterpreterGetInputTensorCount(cInterpreter)) + } + + /// Total number of output tensors associated with the model. + public var outputTensorCount: Int { + return Int(TFL_InterpreterGetOutputTensorCount(cInterpreter)) + } + + /// The underlying `TFL_Interpreter` C pointer. + private var cInterpreter: CInterpreter? + + /// Creates a new model interpreter instance. + /// + /// - Parameters: + /// - modelPath: Local file path to a TensorFlow Lite model. + /// - options: Custom configurations for the interpreter. The default is `nil` indicating that + /// interpreter will determine the configuration options. + /// - Throws: An error if the model could not be loaded or the interpreter could not be created. + public init(modelPath: String, options: InterpreterOptions? = nil) throws { + guard let model = Model(filePath: modelPath) else { throw InterpreterError.failedToLoadModel } + + let cInterpreterOptions: OpaquePointer? = try options.map { options in + guard let cOptions = TFL_NewInterpreterOptions() else { + throw InterpreterError.failedToCreateInterpreter + } + if let threadCount = options.threadCount, threadCount > 0 { + TFL_InterpreterOptionsSetNumThreads(cOptions, Int32(threadCount)) + } + if options.isErrorLoggingEnabled { + TFL_InterpreterOptionsSetErrorReporter( + cOptions, + { (_, format, arguments) in + guard let cFormat = format, + let message = String(cFormat: cFormat, arguments: arguments) + else { + return + } + print(String(describing: InterpreterError.tensorFlowLiteError(message))) + }, + nil + ) + } + return cOptions + } + defer { TFL_DeleteInterpreterOptions(cInterpreterOptions) } + + guard let cInterpreter = TFL_NewInterpreter(model.cModel, cInterpreterOptions) else { + throw InterpreterError.failedToCreateInterpreter + } + self.cInterpreter = cInterpreter + } + + deinit { + TFL_DeleteInterpreter(cInterpreter) + } + + /// Invokes the interpreter to perform inference from the loaded graph. + /// + /// - Throws: An error if the model was not ready because tensors were not allocated. + public func invoke() throws { + guard TFL_InterpreterInvoke(cInterpreter) == kTfLiteOk else { + // TODO(b/117510052): Determine which error to throw. + throw InterpreterError.allocateTensorsRequired + } + } + + /// Returns the input tensor at the given index. + /// + /// - Parameters: + /// - index: The index for the input tensor. + /// - Throws: An error if the index is invalid or the tensors have not been allocated. + /// - Returns: The input tensor at the given index. + public func input(at index: Int) throws -> Tensor { + let maxIndex = inputTensorCount - 1 + guard case 0...maxIndex = index else { + throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) + } + guard let cTensor = TFL_InterpreterGetInputTensor(cInterpreter, Int32(index)), + let bytes = TFL_TensorData(cTensor), + let nameCString = TFL_TensorName(cTensor) + else { + throw InterpreterError.allocateTensorsRequired + } + guard let dataType = TensorDataType(type: TFL_TensorType(cTensor)) else { + throw InterpreterError.invalidTensorDataType + } + + let name = String(cString: nameCString) + let rank = TFL_TensorNumDims(cTensor) + let dimensions = (0.. Tensor { + let maxIndex = outputTensorCount - 1 + guard case 0...maxIndex = index else { + throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) + } + guard let cTensor = TFL_InterpreterGetOutputTensor(cInterpreter, Int32(index)), + let bytes = TFL_TensorData(cTensor), + let nameCString = TFL_TensorName(cTensor) + else { + // TODO(b/117510052): Determine which error to throw. + throw InterpreterError.invokeInterpreterRequired + } + guard let dataType = TensorDataType(type: TFL_TensorType(cTensor)) else { + throw InterpreterError.invalidTensorDataType + } + + let name = String(cString: nameCString) + let rank = TFL_TensorNumDims(cTensor) + let dimensions = (0.. Tensor { + let maxIndex = inputTensorCount - 1 + guard case 0...maxIndex = index else { + throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) + } + guard let cTensor = TFL_InterpreterGetInputTensor(cInterpreter, Int32(index)) else { + throw InterpreterError.allocateTensorsRequired + } + + let byteCount = TFL_TensorByteSize(cTensor) + guard data.count == byteCount else { + throw InterpreterError.invalidTensorDataCount(provided: data.count, required: byteCount) + } + + let status = data.withUnsafeBytes { TFL_TensorCopyFromBuffer(cTensor, $0, data.count) } + guard status == kTfLiteOk else { throw InterpreterError.failedToCopyDataToInputTensor } + return try input(at: index) + } + + /// Allocates memory for all input tensors based on their `TensorShape`s. + /// + /// - Note: This is a relatively expensive operation and should only be called after creating the + /// interpreter and/or resizing any input tensors. + /// - Throws: An error if memory could not be allocated for the input tensors. + public func allocateTensors() throws { + guard TFL_InterpreterAllocateTensors(cInterpreter) == kTfLiteOk else { + throw InterpreterError.failedToAllocateTensors + } + } +} + +// MARK: - Extensions + +extension String { + /// Returns a new `String` initialized by using the given format C array as a template into which + /// the remaining argument values are substituted according to the user’s default locale. + /// + /// - Note: Returns `nil` if a new `String` could not be constructed from the given values. + /// - Parameters: + /// - cFormat: The format C array as a template for substituting values. + /// - arguments: A C pointer to a `va_list` of arguments to substitute into `cFormat`. + init?(cFormat: UnsafePointer, arguments: CVaListPointer) { + var buffer: UnsafeMutablePointer? + guard vasprintf(&buffer, cFormat, arguments) != 0, let cString = buffer else { return nil } + self.init(validatingUTF8: cString) + } +} diff --git a/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift b/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift new file mode 100644 index 0000000000..5de58b997a --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift @@ -0,0 +1,99 @@ +// Copyright 2018 Google Inc. 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 Foundation + +/// TensorFlow Lite interpreter errors. +public enum InterpreterError: Error { + case invalidTensorIndex(index: Int, maxIndex: Int) + case invalidTensorDataCount(provided: Int, required: Int) + case invalidTensorDataType + case failedToLoadModel + case failedToCreateInterpreter + case failedToResizeInputTensor(index: Int) + case failedToCopyDataToInputTensor + case failedToAllocateTensors + case allocateTensorsRequired + case invokeInterpreterRequired + case tensorFlowLiteError(String) +} + +// MARK: - Extensions + +extension InterpreterError: LocalizedError { + /// Localized description of the interpreter error. + public var errorDescription: String? { + switch self { + case .invalidTensorIndex(let index, let maxIndex): + return "Invalid tensor index \(index), max index is \(maxIndex)." + case .invalidTensorDataCount(let providedCount, let requiredCount): + return "Provided data count \(providedCount) must match the required count \(requiredCount)." + case .invalidTensorDataType: + return "Tensor data type is unsupported or could not be determined because of a model error." + case .failedToLoadModel: + return "Failed to load the given model." + case .failedToCreateInterpreter: + return "Failed to create the interpreter." + case .failedToResizeInputTensor(let index): + return "Failed to resize input tesnor at index \(index)." + case .failedToCopyDataToInputTensor: + return "Failed to copy data to input tensor." + case .failedToAllocateTensors: + return "Failed to allocate memory for input tensors." + case .allocateTensorsRequired: + return "Must call allocateTensors()." + case .invokeInterpreterRequired: + return "Must call invoke()." + case .tensorFlowLiteError(let message): + return "TensorFlow Lite Error: \(message)" + } + } +} + +extension InterpreterError: CustomStringConvertible { + /// Textual representation of the TensorFlow Lite interpreter error. + public var description: String { + return errorDescription ?? "Unknown error." + } +} + +#if swift(>=4.2) +extension InterpreterError: Equatable {} +#else +extension InterpreterError: Equatable { + public static func == (lhs: InterpreterError, rhs: InterpreterError) -> Bool { + switch (lhs, rhs) { + case (.invalidTensorDataType, .invalidTensorDataType), + (.failedToLoadModel, .failedToLoadModel), + (.failedToCreateInterpreter, .failedToCreateInterpreter), + (.failedToAllocateTensors, .failedToAllocateTensors), + (.allocateTensorsRequired, .allocateTensorsRequired), + (.invokeInterpreterRequired, .invokeInterpreterRequired): + return true + case (.invalidTensorIndex(let lhsIndex, let lhsMaxIndex), + .invalidTensorIndex(let rhsIndex, let rhsMaxIndex)): + return lhsIndex == rhsIndex && lhsMaxIndex == rhsMaxIndex + case (.invalidTensorDataCount(let lhsProvidedCount, let lhsRequiredCount), + .invalidTensorDataCount(let rhsProvidedCount, let rhsRequiredCount)): + return lhsProvidedCount == rhsProvidedCount && lhsRequiredCount == rhsRequiredCount + case (.failedToResizeInputTensor(let lhsIndex), .failedToResizeInputTensor(let rhsIndex)): + return lhsIndex == rhsIndex + case (.tensorFlowLiteError(let lhsMessage), .tensorFlowLiteError(let rhsMessage)): + return lhsMessage == rhsMessage + default: + return false + } + } +} +#endif // swift(>=4.2) diff --git a/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift b/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift new file mode 100644 index 0000000000..2365fd7ade --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift @@ -0,0 +1,29 @@ +// Copyright 2018 Google Inc. 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 Foundation + +/// Custom configuration options for a TensorFlow Lite interpreter. +public struct InterpreterOptions: Equatable { + + /// Maximum number of CPU threads that the interpreter should run on. Default is `nil` which + /// indicates that the `Interpreter` will decide the number of threads to use. + public var threadCount: Int? = nil + + /// Whether error logging to the console is enabled. The default is `false`. + public var isErrorLoggingEnabled = false + + /// Creates a new instance of interpreter options. + public init() {} +} diff --git a/tensorflow/lite/experimental/swift/Sources/Model.swift b/tensorflow/lite/experimental/swift/Sources/Model.swift new file mode 100644 index 0000000000..e8c49ff1ae --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/Model.swift @@ -0,0 +1,40 @@ +// Copyright 2018 Google Inc. 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 Foundation +import TensorFlowLiteCAPI + +/// A TensorFlow Lite model used by the 'Interpreter` to perform inference. +final class Model { + + /// The `TFL_Model` C pointer type represented as an `UnsafePointer`. + typealias CModel = OpaquePointer + + /// The underlying `TFL_Model` C pointer. + let cModel: CModel? + + /// Creates a new model instance. + /// + /// - Precondition: Initialization can fail if the given `filePath` is invalid. + /// - Parameters: + /// - filePath: Local file path to a TensorFlow Lite model. + init?(filePath: String) { + guard !filePath.isEmpty, let cModel = TFL_NewModelFromFile(filePath) else { return nil } + self.cModel = cModel + } + + deinit { + TFL_DeleteModel(cModel) + } +} diff --git a/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift b/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift new file mode 100644 index 0000000000..f367875644 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift @@ -0,0 +1,38 @@ +// Copyright 2018 Google Inc. 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 Foundation + +/// Parameters that determine the mapping of quantized values to real values. Quantized values can +/// be mapped to float values using the following conversion: +/// `realValue = scale * (quantizedValue - zeroPoint)`. +public struct QuantizationParameters { + + /// Difference between real values corresponding to consecutive quantized values differing by 1. + /// For example, the range of quantized values for `UInt8` data type is [0, 255]. + public let scale: Float + + /// Quantized value that corresponds to the real 0 value. + public let zeroPoint: Int + + /// Creates a new quantization parameters instance. + /// + /// - Parameters: + /// - scale: Scale value for asymmetric quantization. + /// - zeroPoint: Zero point for asymmetric quantization. + init(scale: Float, zeroPoint: Int) { + self.scale = scale + self.zeroPoint = zeroPoint + } +} diff --git a/tensorflow/lite/experimental/swift/Sources/Tensor.swift b/tensorflow/lite/experimental/swift/Sources/Tensor.swift new file mode 100644 index 0000000000..b738d87549 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/Tensor.swift @@ -0,0 +1,138 @@ +// Copyright 2018 Google Inc. 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 Foundation +import TensorFlowLiteCAPI + +/// An input or output tensor in a TensorFlow Lite graph. +public struct Tensor { + + /// Name of the tensor. + public let name: String + + /// Data type of the tensor. + public let dataType: TensorDataType + + /// Shape of the tensor. + public let shape: TensorShape + + /// Data in the input or output tensor. + public let data: Data + + /// Quantization parameters for the tensor if using a quantized model. + public let quantizationParameters: QuantizationParameters? + + /// Creates a new input or output tensor instance. + /// + /// - Parameters: + /// - name: Name of the tensor. + /// - dataType: Data type of the tensor. + /// - data: Data in the input tensor. + /// - quantizationParameters Quantization parameters for the tensor if using a quantized model. + /// The default is `nil`. + init( + name: String, + dataType: TensorDataType, + shape: TensorShape, + data: Data, + quantizationParameters: QuantizationParameters? = nil + ) { + self.name = name + self.dataType = dataType + self.shape = shape + self.data = data + self.quantizationParameters = quantizationParameters + } +} + +/// Supported TensorFlow Lite tensor data types. +public enum TensorDataType: Equatable { + /// 32-bit single precision floating point tensor data type. + case float32 + /// 8-bit unsigned integer tensor data type. + case uInt8 + /// 16-bit signed integer tensor data type. + case int16 + /// 32-bit signed integer tensor data type. + case int32 + /// 64-bit signed integer tensor data type. + case int64 + /// Boolean tensor data type. + case bool + + /// Creates a new tensor data type from the given `TFL_Type` or `nil` if the data type is + /// unsupported or could not be determined because there was an error. + /// + /// - Parameter type: A data type supported by a tensor. + init?(type: TFL_Type) { + switch type { + case kTfLiteFloat32: + self = .float32 + case kTfLiteUInt8: + self = .uInt8 + case kTfLiteInt16: + self = .int16 + case kTfLiteInt32: + self = .int32 + case kTfLiteInt64: + self = .int64 + case kTfLiteBool: + self = .bool + case kTfLiteNoType: + fallthrough + default: + return nil + } + } +} + +/// The shape of a TensorFlow Lite tensor. +public struct TensorShape { + + /// The number of dimensions of the tensor. + public let rank: Int + + /// Array of dimensions for the tensor. + public let dimensions: [Int] + + /// Array of `Int32` dimensions for the tensor. + var int32Dimensions: [Int32] { return dimensions.map(Int32.init) } + + /// Creates a new tensor shape instance with the given array of dimensions. + /// + /// - Parameters: + /// - dimensions: Dimensions for the tensor. + public init(_ dimensions: [Int]) { + self.rank = dimensions.count + self.dimensions = dimensions + } + + /// Creates a new tensor shape instance with the given elements representing the dimensions. + /// + /// - Parameters: + /// - elements: Dimensions for the tensor. + public init(_ elements: Int...) { + self.init(elements) + } +} + +extension TensorShape: ExpressibleByArrayLiteral { + /// Creates a new tensor shape instance with the given array literal representing the dimensions. + /// + /// - Parameters: + /// - arrayLiteral: Dimensions for the tensor. + public init(arrayLiteral: Int...) { + self.init(arrayLiteral) + } +} diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen new file mode 100644 index 0000000000..4010fab49e --- /dev/null +++ b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen @@ -0,0 +1,57 @@ +{ + "sourceFilters" : [ + "third_party/tensorflow/lite/experimental/c", + "third_party/tensorflow/lite/experimental/swift", + "third_party/tensorflow/lite/experimental/swift/Sources", + "third_party/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp", + "third_party/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj", + "third_party/tensorflow/lite/experimental/swift/Tests", + ], + "buildTargets" : [ + "//third_party/tensorflow/lite/experimental/swift:TensorFlowLite", + "//third_party/tensorflow/lite/experimental/swift:TensorFlowLiteApp", + "//third_party/tensorflow/lite/experimental/swift:TensorFlowLiteTests", + ], + "projectName" : "TensorFlowLite", + "optionSet" : { + "LaunchActionPreActionScript" : { + "p" : "$(inherited)" + }, + "BazelBuildStartupOptionsRelease" : { + "p" : "$(inherited)" + }, + "BazelBuildOptionsRelease" : { + "p" : "$(inherited)" + }, + "BazelBuildOptionsDebug" : { + "p" : "$(inherited)" + }, + "EnvironmentVariables" : { + "p" : "$(inherited)" + }, + "BuildActionPreActionScript" : { + "p" : "$(inherited)" + }, + "CommandlineArguments" : { + "p" : "$(inherited)" + }, + "TestActionPreActionScript" : { + "p" : "$(inherited)" + }, + "BazelBuildStartupOptionsDebug" : { + "p" : "$(inherited)" + }, + "BuildActionPostActionScript" : { + "p" : "$(inherited)" + }, + "TestActionPostActionScript" : { + "p" : "$(inherited)" + }, + "LaunchActionPostActionScript" : { + "p" : "$(inherited)" + } + }, + "additionalFilePaths" : [ + "third_party/tensorflow/lite/experimental/swift/BUILD" + ] +} diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf new file mode 100644 index 0000000000..14cff94453 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf @@ -0,0 +1,14 @@ +{ + "configDefaults" : { + "optionSet" : { + "ProjectPrioritizesSwift" : { + "p" : "YES" + } + } + }, + "projectName" : "TensorFlowLite", + "packages" : [ + "third_party/tensorflow/lite/experimental/swift" + ], + "workspaceRoot" : "../../../../../.." +} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..fbbf9a1de2 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj @@ -0,0 +1,345 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 4A7304B421500B8400C90B21 /* Data+TensorFlowLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */; }; + 4AA72B732146ED64006C3AEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA72B722146ED64006C3AEF /* AppDelegate.swift */; }; + 4AA72B752146ED64006C3AEF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA72B742146ED64006C3AEF /* ViewController.swift */; }; + 4AA72B782146ED64006C3AEF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B762146ED64006C3AEF /* Main.storyboard */; }; + 4AA72B7A2146ED66006C3AEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B792146ED66006C3AEF /* Assets.xcassets */; }; + 4AA72B7D2146ED66006C3AEF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */; }; + 4ADDE0CE2176600E00FF07A2 /* Array+TensorFlowLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+TensorFlowLite.swift"; sourceTree = ""; }; + 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TensorFlowLiteApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AA72B722146ED64006C3AEF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 4AA72B742146ED64006C3AEF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 4AA72B772146ED64006C3AEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 4AA72B792146ED66006C3AEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 4AA72B7C2146ED66006C3AEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 4AA72B7E2146ED66006C3AEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+TensorFlowLite.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4AA72B6C2146ED64006C3AEF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4AA72B662146ED64006C3AEF = { + isa = PBXGroup; + children = ( + 4AA72B712146ED64006C3AEF /* TensorFlowLiteApp */, + 4AA72B702146ED64006C3AEF /* Products */, + ); + sourceTree = ""; + }; + 4AA72B702146ED64006C3AEF /* Products */ = { + isa = PBXGroup; + children = ( + 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 4AA72B712146ED64006C3AEF /* TensorFlowLiteApp */ = { + isa = PBXGroup; + children = ( + 4AA72B722146ED64006C3AEF /* AppDelegate.swift */, + 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */, + 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */, + 4AA72B742146ED64006C3AEF /* ViewController.swift */, + 4AA72B762146ED64006C3AEF /* Main.storyboard */, + 4AA72B792146ED66006C3AEF /* Assets.xcassets */, + 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */, + 4AA72B7E2146ED66006C3AEF /* Info.plist */, + ); + path = TensorFlowLiteApp; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4AA72B6E2146ED64006C3AEF /* TensorFlowLiteApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4AA72B812146ED66006C3AEF /* Build configuration list for PBXNativeTarget "TensorFlowLiteApp" */; + buildPhases = ( + 4AA72B6B2146ED64006C3AEF /* Sources */, + 4AA72B6C2146ED64006C3AEF /* Frameworks */, + 4AA72B6D2146ED64006C3AEF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TensorFlowLiteApp; + productName = TensorFlowLiteApp; + productReference = 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4AA72B672146ED64006C3AEF /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0940; + LastUpgradeCheck = 0940; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 4AA72B6E2146ED64006C3AEF = { + CreatedOnToolsVersion = 9.4.1; + }; + }; + }; + buildConfigurationList = 4AA72B6A2146ED64006C3AEF /* Build configuration list for PBXProject "TensorFlowLiteApp" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4AA72B662146ED64006C3AEF; + productRefGroup = 4AA72B702146ED64006C3AEF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4AA72B6E2146ED64006C3AEF /* TensorFlowLiteApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4AA72B6D2146ED64006C3AEF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AA72B7D2146ED66006C3AEF /* LaunchScreen.storyboard in Resources */, + 4AA72B7A2146ED66006C3AEF /* Assets.xcassets in Resources */, + 4AA72B782146ED64006C3AEF /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4AA72B6B2146ED64006C3AEF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AA72B732146ED64006C3AEF /* AppDelegate.swift in Sources */, + 4ADDE0CE2176600E00FF07A2 /* Array+TensorFlowLite.swift in Sources */, + 4A7304B421500B8400C90B21 /* Data+TensorFlowLite.swift in Sources */, + 4AA72B752146ED64006C3AEF /* ViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 4AA72B762146ED64006C3AEF /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 4AA72B772146ED64006C3AEF /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 4AA72B7C2146ED66006C3AEF /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 4AA72B7F2146ED66006C3AEF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 4AA72B802146ED66006C3AEF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 4AA72B822146ED66006C3AEF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = TensorFlowLiteApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tensorflow.lite.swift.TensorFlowLite; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4AA72B832146ED66006C3AEF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = TensorFlowLiteApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tensorflow.lite.swift.TensorFlowLite; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4AA72B6A2146ED64006C3AEF /* Build configuration list for PBXProject "TensorFlowLiteApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AA72B7F2146ED66006C3AEF /* Debug */, + 4AA72B802146ED66006C3AEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4AA72B812146ED66006C3AEF /* Build configuration list for PBXNativeTarget "TensorFlowLiteApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AA72B822146ED66006C3AEF /* Debug */, + 4AA72B832146ED66006C3AEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4AA72B672146ED64006C3AEF /* Project object */; +} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift new file mode 100644 index 0000000000..ffa90a06ad --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift @@ -0,0 +1,24 @@ +import UIKit + +@UIApplicationMain + +final class AppDelegate: UIResponder, UIApplicationDelegate { + + /// The main window of the app. + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + return true + } +} + +// MARK: - Extensions + +#if !swift(>=4.2) +extension UIApplication { + typealias LaunchOptionsKey = UIApplicationLaunchOptionsKey +} +#endif // !swift(>=4.2) diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift new file mode 100644 index 0000000000..56df1ce659 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift @@ -0,0 +1,22 @@ +import Foundation + +extension Array { + /// Creates a new array from the bytes of the given unsafe data. + /// + /// - Warning: The array's `Element` type must be trivial in that it can be copied bit for bit + /// with no indirection or reference-counting operations; otherwise, copying the raw bytes in + /// the `unsafeData`'s buffer to a new array returns an unsafe copy. + /// - Note: Returns `nil` if `unsafeData.count` is not a multiple of + /// `MemoryLayout.stride`. + /// - Parameter unsafeData: The data containing the bytes to turn into an array. + init?(unsafeData: Data) { + guard unsafeData.count % MemoryLayout.stride == 0 else { return nil } + let elements = unsafeData.withUnsafeBytes { + UnsafeBufferPointer( + start: $0, + count: unsafeData.count / MemoryLayout.stride + ) + } + self.init(elements) + } +} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..a07a1321be --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..d0e91d63c4 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift new file mode 100644 index 0000000000..bc8a70c848 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift @@ -0,0 +1,13 @@ +import Foundation + +extension Data { + /// Creates a new buffer by copying the buffer pointer of the given array. + /// + /// - Warning: The given array's element type `T` must be trivial in that it can be copied bit + /// for bit with no indirection or reference-counting operations; otherwise, reinterpreting + /// data from the resulting buffer has undefined behavior. + /// - Parameter array: An array with elements of type `T`. + init(copyingBufferOf array: [T]) { + self = array.withUnsafeBufferPointer(Data.init) + } +} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist new file mode 100644 index 0000000000..3ca3875f04 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist @@ -0,0 +1,46 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 0.0.1 + LSRequiresIPhoneOS + + NSCameraUsageDescription + NSCameraUsageDescription + NSPhotoLibraryUsageDescription + Select a photo to detect objects in. + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift new file mode 100644 index 0000000000..73c74fd19c --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift @@ -0,0 +1,299 @@ +import TensorFlowLite +import UIKit + +class ViewController: UIViewController { + + // MARK: - Properties + + /// TensorFlowLite interpreter object for performing inference from a given model. + private var interpreter: Interpreter? + + /// Serial dispatch queue for managing `Interpreter` calls. + private let interpreterQueue = DispatchQueue( + label: Constant.dispatchQueueLabel, + qos: .userInitiated + ) + + /// The currently selected model. + private var currentModel: Model { + guard let currentModel = Model(rawValue: modelControl.selectedSegmentIndex) else { + preconditionFailure("Invalid model for selected segment index.") + } + return currentModel + } + + /// A description of the current model. + private var modelDescription: String { + guard let interpreter = interpreter else { return "" } + let inputCount = interpreter.inputTensorCount + let outputCount = interpreter.outputTensorCount + let inputTensors = (0.. String = { + guard let results = [Float32](unsafeData: outputTensor.data) else { return "No results." } + return resultsText + results.description + } + self.updateResultsText(results()) + } catch let error { + self.updateResultsText( + "Failed to invoke the interpreter with error: \(error.localizedDescription)" + ) + return + } + } + } + + private func invokeAddQuantized() { + interpreterQueue.async { + guard let interpreter = self.interpreter else { + self.updateResultsText(Constant.nilInterpreterErrorMessage) + return + } + do { + try interpreter.resizeInput(at: 0, to: [2]) + try interpreter.allocateTensors() + let input: [UInt8] = [1, 3] + let resultsText = self.modelDescription + "\n\n" + + "Performing 2 add operations on quantized input \(input.description) equals: " + self.updateResultsText(resultsText) + let data = Data(input) + try interpreter.copy(data, toInputAt: 0) + try interpreter.invoke() + let outputTensor = try interpreter.output(at: 0) + let results: () -> String = { + guard let quantizationParameters = outputTensor.quantizationParameters else { + return "No results." + } + let quantizedResults = [UInt8](outputTensor.data) + let dequantizedResults = quantizedResults.map { + quantizationParameters.scale * Float(Int($0) - quantizationParameters.zeroPoint) + } + return resultsText + quantizedResults.description + + ", dequantized results: " + dequantizedResults.description + } + self.updateResultsText(results()) + } catch let error { + self.updateResultsText( + "Failed to invoke the interpreter with error: \(error.localizedDescription)" + ) + return + } + } + } + + private func invokeMultiAdd() { + interpreterQueue.async { + guard let interpreter = self.interpreter else { + self.updateResultsText(Constant.nilInterpreterErrorMessage) + return + } + do { + let shape = TensorShape(2) + try (0.. [Float32] in + let input = [Float32(index + 1), Float32(index + 2)] + let data = Data(copyingBufferOf: input) + try interpreter.copy(data, toInputAt: index) + return input + } + let resultsText = self.modelDescription + "\n\n" + + "Performing 3 add operations on inputs \(inputs.description) equals: " + self.updateResultsText(resultsText) + try interpreter.invoke() + let results = try (0.. [Float32] in + let tensor = try interpreter.output(at: index) + return [Float32](unsafeData: tensor.data) ?? [] + } + self.updateResultsText(resultsText + results.description) + } catch let error { + self.updateResultsText( + "Failed to invoke the interpreter with error: \(error.localizedDescription)" + ) + return + } + } + } + + private func updateResultsText(_ text: String? = nil) { + safeDispatchOnMain { self.resultsTextView.text = text } + } +} + +// MARK: - Constants + +private enum Constant { + static let dispatchQueueLabel = "TensorFlowLiteInterpreterQueue" + static let nilInterpreterErrorMessage = + "Failed to invoke the interpreter because the interpreter was nil." +} + +/// Models that can be loaded by the TensorFlow Lite `Interpreter`. +private enum Model: Int, CustomStringConvertible { + /// A float model that performs two add operations on one input tensor and returns the result in + /// one output tensor. + case add = 0 + /// A quantized model that performs two add operations on one input tensor and returns the result + /// in one output tensor. + case addQuantized = 1 + /// A float model that performs three add operations on four input tensors and returns the results + /// in 2 output tensors. + case multiAdd = 2 + + var fileInfo: (name: String, extension: String) { + switch self { + case .add: + return Add.fileInfo + case .addQuantized: + return AddQuantized.fileInfo + case .multiAdd: + return MultiAdd.fileInfo + } + } + + // MARK: - CustomStringConvertible + + var description: String { + switch self { + case .add: + return Add.name + case .addQuantized: + return AddQuantized.name + case .multiAdd: + return MultiAdd.name + } + } +} + +/// Values for the `Add` model. +private enum Add { + static let name = "Add" + static let fileInfo = (name: "add", extension: "bin") +} + +/// Values for the `AddQuantized` model. +private enum AddQuantized { + static let name = "AddQuantized" + static let fileInfo = (name: "add_quantized", extension: "bin") +} + +/// Values for the `MultiAdd` model. +private enum MultiAdd { + static let name = "MultiAdd" + static let fileInfo = (name: "multi_add", extension: "bin") +} + +// MARK: - Fileprivate + +/// Safely dispatches the given block on the main queue. If the current thread is `main`, the block +/// is executed synchronously; otherwise, the block is executed asynchronously on the main thread. +fileprivate func safeDispatchOnMain(_ block: @escaping () -> Void) { + if Thread.isMainThread { block(); return } + DispatchQueue.main.async { block() } +} diff --git a/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift b/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift new file mode 100644 index 0000000000..54b4f59b28 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift @@ -0,0 +1,54 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class InterpreterOptionsTests: XCTestCase { + + func testInterpreterOptions_InitWithDefaultValues() { + let options = InterpreterOptions() + XCTAssertNil(options.threadCount) + XCTAssertFalse(options.isErrorLoggingEnabled) + } + + func testInterpreterOptions_InitWithCustomValues() { + var options = InterpreterOptions() + options.threadCount = 2 + XCTAssertEqual(options.threadCount, 2) + options.isErrorLoggingEnabled = true + XCTAssertTrue(options.isErrorLoggingEnabled) + } + + func testInterpreterOptions_Equatable() { + var options1 = InterpreterOptions() + var options2 = InterpreterOptions() + XCTAssertEqual(options1, options2) + + options1.threadCount = 2 + options2.threadCount = 2 + XCTAssertEqual(options1, options2) + + options2.threadCount = 3 + XCTAssertNotEqual(options1, options2) + options2.threadCount = 2 + + options1.isErrorLoggingEnabled = true + options2.isErrorLoggingEnabled = true + XCTAssertEqual(options1, options2) + + options2.isErrorLoggingEnabled = false + XCTAssertNotEqual(options1, options2) + } +} diff --git a/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift b/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift new file mode 100644 index 0000000000..e98da5f951 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift @@ -0,0 +1,315 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class InterpreterTests: XCTestCase { + + var interpreter: Interpreter! + + override func setUp() { + super.setUp() + + interpreter = try! Interpreter(modelPath: AddModel.path) + } + + override func tearDown() { + interpreter = nil + + super.tearDown() + } + + func testInterpreter_InitWithModelPath() { + XCTAssertNoThrow(try Interpreter(modelPath: AddModel.path)) + } + + func testInterpreter_Init_ThrowsFailedToLoadModel() { + XCTAssertThrowsError(try Interpreter(modelPath: "/invalid/path")) { error in + self.assertEqualErrors(actual: error, expected: .failedToLoadModel) + } + } + + func testInterpreter_InitWithModelPathAndOptions() { + var options = InterpreterOptions() + options.threadCount = 2 + XCTAssertNoThrow(try Interpreter(modelPath: AddModel.path, options: options)) + } + + func testInterpreter_InputTensorCount() { + XCTAssertEqual(interpreter.inputTensorCount, AddModel.inputTensorCount) + } + + func testInterpreter_OutputTensorCount() { + XCTAssertEqual(interpreter.outputTensorCount, AddModel.outputTensorCount) + } + + func testInterpreter_Invoke() throws { + try interpreter.allocateTensors() + XCTAssertNoThrow(try interpreter.invoke()) + } + + func testInterpreter_Invoke_ThrowsAllocateTensorsRequired_ModelNotReady() { + XCTAssertThrowsError(try interpreter.invoke()) { error in + self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired) + } + } + + func testInterpreter_InputTensorAtIndex() throws { + try setUpAddModelInputTensor() + let inputTensor = try interpreter.input(at: AddModel.validIndex) + XCTAssertEqual(inputTensor, AddModel.inputTensor) + } + + func testInterpreter_InputTensorAtIndex_QuantizedModel() throws { + interpreter = try Interpreter(modelPath: AddQuantizedModel.path) + try setUpAddQuantizedModelInputTensor() + let inputTensor = try interpreter.input(at: AddQuantizedModel.inputOutputIndex) + XCTAssertEqual(inputTensor, AddQuantizedModel.inputTensor) + } + + func testInterpreter_InputTensorAtIndex_ThrowsInvalidIndex() throws { + try interpreter.allocateTensors() + XCTAssertThrowsError(try interpreter.input(at: AddModel.invalidIndex)) { error in + let maxIndex = AddModel.inputTensorCount - 1 + self.assertEqualErrors( + actual: error, + expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) + ) + } + } + + func testInterpreter_InputTensorAtIndex_ThrowsAllocateTensorsRequired() { + XCTAssertThrowsError(try interpreter.input(at: AddModel.validIndex)) { error in + self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired) + } + } + + func testInterpreter_OutputTensorAtIndex() throws { + try setUpAddModelInputTensor() + try interpreter.invoke() + let outputTensor = try interpreter.output(at: AddModel.validIndex) + XCTAssertEqual(outputTensor, AddModel.outputTensor) + let expectedResults = [Float32](unsafeData: outputTensor.data) + XCTAssertEqual(expectedResults, AddModel.results) + } + + func testInterpreter_OutputTensorAtIndex_QuantizedModel() throws { + interpreter = try Interpreter(modelPath: AddQuantizedModel.path) + try setUpAddQuantizedModelInputTensor() + try interpreter.invoke() + let outputTensor = try interpreter.output(at: AddQuantizedModel.inputOutputIndex) + XCTAssertEqual(outputTensor, AddQuantizedModel.outputTensor) + let expectedResults = [UInt8](outputTensor.data) + XCTAssertEqual(expectedResults, AddQuantizedModel.results) + } + + func testInterpreter_OutputTensorAtIndex_ThrowsInvalidIndex() throws { + try interpreter.allocateTensors() + try interpreter.invoke() + XCTAssertThrowsError(try interpreter.output(at: AddModel.invalidIndex)) { error in + let maxIndex = AddModel.outputTensorCount - 1 + self.assertEqualErrors( + actual: error, + expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) + ) + } + } + + func testInterpreter_OutputTensorAtIndex_ThrowsInvokeInterpreterRequired() { + XCTAssertThrowsError(try interpreter.output(at: AddModel.validIndex)) { error in + self.assertEqualErrors(actual: error, expected: .invokeInterpreterRequired) + } + } + + func testInterpreter_ResizeInputTensorAtIndexToShape() { + XCTAssertNoThrow(try interpreter.resizeInput(at: AddModel.validIndex, to: [2, 2, 3])) + XCTAssertNoThrow(try interpreter.allocateTensors()) + } + + func testInterpreter_ResizeInputTensorAtIndexToShape_ThrowsInvalidIndex() { + XCTAssertThrowsError(try interpreter.resizeInput( + at: AddModel.invalidIndex, + to: [2, 2, 3] + )) { error in + let maxIndex = AddModel.inputTensorCount - 1 + self.assertEqualErrors( + actual: error, + expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) + ) + } + } + + func testInterpreter_CopyDataToInputTensorAtIndex() throws { + try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) + try interpreter.allocateTensors() + let inputTensor = try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex) + XCTAssertEqual(inputTensor.data, AddModel.inputData) + } + + func testInterpreter_CopyDataToInputTensorAtIndex_ThrowsInvalidIndex() { + XCTAssertThrowsError(try interpreter.copy( + AddModel.inputData, + toInputAt: AddModel.invalidIndex + )) { error in + let maxIndex = AddModel.inputTensorCount - 1 + self.assertEqualErrors( + actual: error, + expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) + ) + } + } + + func testInterpreter_CopyDataToInputTensorAtIndex_ThrowsInvalidDataCount() throws { + try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) + try interpreter.allocateTensors() + let invalidData = Data(count: AddModel.dataCount - 1) + XCTAssertThrowsError(try interpreter.copy( + invalidData, + toInputAt: AddModel.validIndex + )) { error in + self.assertEqualErrors( + actual: error, + expected: .invalidTensorDataCount(provided: invalidData.count, required: AddModel.dataCount) + ) + } + } + + func testInterpreter_AllocateTensors() { + XCTAssertNoThrow(try interpreter.allocateTensors()) + } + + // MARK: - Private + + private func setUpAddModelInputTensor() throws { + precondition(interpreter != nil) + try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) + try interpreter.allocateTensors() + try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex) + } + + private func setUpAddQuantizedModelInputTensor() throws { + precondition(interpreter != nil) + try interpreter.resizeInput(at: AddQuantizedModel.inputOutputIndex, to: AddQuantizedModel.shape) + try interpreter.allocateTensors() + try interpreter.copy(AddQuantizedModel.inputData, toInputAt: AddQuantizedModel.inputOutputIndex) + } + + private func assertEqualErrors(actual: Error, expected: InterpreterError) { + guard let actual = actual as? InterpreterError else { + XCTFail("Actual error should be of type InterpreterError.") + return + } + XCTAssertEqual(actual, expected) + } +} + +// MARK: - Constants + +/// Values for the `add.bin` model. +private enum AddModel { + static let info = (name: "add", extension: "bin") + static let inputTensorCount = 1 + static let outputTensorCount = 1 + static let invalidIndex = 1 + static let validIndex = 0 + static let shape: TensorShape = [2] + static let dataCount = inputData.count + static let inputData = Data(copyingBufferOf: [Float32(1.0), Float32(3.0)]) + static let outputData = Data(copyingBufferOf: [Float32(3.0), Float32(9.0)]) + static let results = [Float32(3.0), Float32(9.0)] + + static let inputTensor = Tensor( + name: "input", + dataType: .float32, + shape: shape, + data: inputData + ) + static let outputTensor = Tensor( + name: "output", + dataType: .float32, + shape: shape, + data: outputData + ) + + static var path: String = { + let bundle = Bundle(for: InterpreterTests.self) + guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" } + return path + }() +} + +/// Values for the `add_quantized.bin` model. +private enum AddQuantizedModel { + static let info = (name: "add_quantized", extension: "bin") + static let inputOutputIndex = 0 + static let shape: TensorShape = [2] + static let inputData = Data([1, 3]) + static let outputData = Data([3, 9]) + static let quantizationParameters = QuantizationParameters(scale: 0.003922, zeroPoint: 0) + static let results: [UInt8] = [3, 9] + + static let inputTensor = Tensor( + name: "input", + dataType: .uInt8, + shape: shape, + data: inputData, + quantizationParameters: quantizationParameters + ) + static let outputTensor = Tensor( + name: "output", + dataType: .uInt8, + shape: shape, + data: outputData, + quantizationParameters: quantizationParameters + ) + + static var path: String = { + let bundle = Bundle(for: InterpreterTests.self) + guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" } + return path + }() +} + +// MARK: - Extensions + +extension Array { + /// Creates a new array from the bytes of the given unsafe data. + /// + /// - Note: Returns `nil` if `unsafeData.count` is not a multiple of + /// `MemoryLayout.stride`. + /// - Parameter unsafeData: The data containing the bytes to turn into an array. + init?(unsafeData: Data) { + guard unsafeData.count % MemoryLayout.stride == 0 else { return nil } + let elements = unsafeData.withUnsafeBytes { + UnsafeBufferPointer( + start: $0, + count: unsafeData.count / MemoryLayout.stride + ) + } + self.init(elements) + } +} + +extension Data { + /// Creates a new buffer by copying the buffer pointer of the given array. + /// + /// - Warning: The given array's element type `T` must be trivial in that it can be copied bit + /// for bit with no indirection or reference-counting operations; otherwise, reinterpreting + /// data from the resulting buffer has undefined behavior. + /// - Parameter array: An array with elements of type `T`. + init(copyingBufferOf array: [T]) { + self = array.withUnsafeBufferPointer(Data.init) + } +} diff --git a/tensorflow/lite/experimental/swift/Tests/ModelTests.swift b/tensorflow/lite/experimental/swift/Tests/ModelTests.swift new file mode 100644 index 0000000000..025db18906 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/ModelTests.swift @@ -0,0 +1,59 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class ModelTests: XCTestCase { + + var modelPath: String! + + override func setUp() { + super.setUp() + + let bundle = Bundle(for: type(of: self)) + guard let modelPath = bundle.path( + forResource: Constant.modelInfo.name, + ofType: Constant.modelInfo.extension) + else { + XCTFail("Failed to get the model file path.") + return + } + self.modelPath = modelPath + } + + override func tearDown() { + modelPath = nil + + super.tearDown() + } + + func testModel_InitWithFilePath() { + XCTAssertNotNil(Model(filePath: modelPath)) + } + + func testModel_InitWithEmptyFilePath_FailsInitialization() { + XCTAssertNil(Model(filePath: "")) + } + + func testModel_InitWithInvalidFilePath_FailsInitialization() { + XCTAssertNil(Model(filePath: "invalid/path")) + } +} + +// MARK: - Constants + +private enum Constant { + static let modelInfo = (name: "add", extension: "bin") +} diff --git a/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift b/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift new file mode 100644 index 0000000000..65648c2698 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift @@ -0,0 +1,43 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class QuantizationParametersTests: XCTestCase { + + func testQuantizationParameters_InitWithCustomValues() { + let parameters = QuantizationParameters(scale: 0.5, zeroPoint: 1) + XCTAssertEqual(parameters.scale, 0.5) + XCTAssertEqual(parameters.zeroPoint, 1) + } + + func testQuantizationParameters_Equatable() { + let parameters1 = QuantizationParameters(scale: 0.5, zeroPoint: 1) + let parameters2 = QuantizationParameters(scale: 0.5, zeroPoint: 1) + XCTAssertEqual(parameters1, parameters2) + + let parameters3 = QuantizationParameters(scale: 0.4, zeroPoint: 1) + XCTAssertNotEqual(parameters1, parameters3) + XCTAssertNotEqual(parameters2, parameters3) + } +} + +// MARK: - Extensions + +extension QuantizationParameters: Equatable { + public static func == (lhs: QuantizationParameters, rhs: QuantizationParameters) -> Bool { + return lhs.scale == rhs.scale && lhs.zeroPoint == rhs.zeroPoint + } +} diff --git a/tensorflow/lite/experimental/swift/Tests/TensorTests.swift b/tensorflow/lite/experimental/swift/Tests/TensorTests.swift new file mode 100644 index 0000000000..4540043a16 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/TensorTests.swift @@ -0,0 +1,83 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class TensorTests: XCTestCase { + + // MARK: - Tensor + + func testTensor_Init() { + let name = "InputTensor" + let dataType: TensorDataType = .uInt8 + let shape = TensorShape(Constant.dimensions) + guard let data = name.data(using: .utf8) else { XCTFail("Data should not be nil."); return } + let quantizationParameters = QuantizationParameters(scale: 0.5, zeroPoint: 1) + let inputTensor = Tensor( + name: name, + dataType: dataType, + shape: shape, + data: data, + quantizationParameters: quantizationParameters + ) + XCTAssertEqual(inputTensor.name, name) + XCTAssertEqual(inputTensor.dataType, dataType) + XCTAssertEqual(inputTensor.shape, shape) + XCTAssertEqual(inputTensor.data, data) + XCTAssertEqual(inputTensor.quantizationParameters, quantizationParameters) + } + + // MARK: - TensorShape + + func testTensorShape_InitWithArray() { + let shape = TensorShape(Constant.dimensions) + XCTAssertEqual(shape.rank, Constant.dimensions.count) + XCTAssertEqual(shape.dimensions, Constant.dimensions) + } + + func testTensorShape_InitWithElements() { + let shape = TensorShape(2, 2, 3) + XCTAssertEqual(shape.rank, Constant.dimensions.count) + XCTAssertEqual(shape.dimensions, Constant.dimensions) + } + + func testTensorShape_InitWithArrayLiteral() { + let shape: TensorShape = [2, 2, 3] + XCTAssertEqual(shape.rank, Constant.dimensions.count) + XCTAssertEqual(shape.dimensions, Constant.dimensions) + } +} + +// MARK: - Constants + +private enum Constant { + /// Array of 2 arrays of 2 arrays of 3 numbers: [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]]. + static let dimensions = [2, 2, 3] +} + +// MARK: - Extensions + +extension TensorShape: Equatable { + public static func == (lhs: TensorShape, rhs: TensorShape) -> Bool { + return lhs.rank == rhs.rank && lhs.dimensions == rhs.dimensions + } +} + +extension Tensor: Equatable { + public static func == (lhs: Tensor, rhs: Tensor) -> Bool { + return lhs.name == rhs.name && lhs.dataType == rhs.dataType && lhs.shape == rhs.shape && + lhs.data == rhs.data && lhs.quantizationParameters == rhs.quantizationParameters + } +} diff --git a/tensorflow/tools/pip_package/pip_smoke_test.py b/tensorflow/tools/pip_package/pip_smoke_test.py index 51d010c9e1..952c71c615 100644 --- a/tensorflow/tools/pip_package/pip_smoke_test.py +++ b/tensorflow/tools/pip_package/pip_smoke_test.py @@ -30,14 +30,19 @@ os.chdir(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))) PIP_PACKAGE_QUERY_EXPRESSION = ( "deps(//tensorflow/tools/pip_package:build_pip_package)") +# List of file paths containing BUILD files that should not be included for the +# pip smoke test. +BUILD_BLACKLIST = [ + "tensorflow/lite/examples/android", + "tensorflow/lite/experimental/swift", +] def GetBuild(dir_base): """Get the list of BUILD file all targets recursively startind at dir_base.""" items = [] for root, _, files in os.walk(dir_base): for name in files: - if (name == "BUILD" and - root.find("tensorflow/lite/examples/android") == -1): + if (name == "BUILD" and root not in BUILD_BLACKLIST): items.append("//" + root + ":all") return items @@ -67,9 +72,9 @@ def BuildPyTestDependencies(): PYTHON_TARGETS, PY_TEST_QUERY_EXPRESSION = BuildPyTestDependencies() -# Hard-coded blacklist of files if not included in pip package # TODO(amitpatankar): Clean up blacklist. -BLACKLIST = [ +# List of dependencies that should not included in the pip package. +DEPENDENCY_BLACKLIST = [ "//tensorflow/python:extra_py_tests_deps", "//tensorflow/cc/saved_model:saved_model_half_plus_two", "//tensorflow:no_tensorflow_py_deps", @@ -82,9 +87,7 @@ BLACKLIST = [ "//tensorflow/core/kernels/cloud:bigquery_reader_ops", "//tensorflow/python/feature_column:vocabulary_testdata", "//tensorflow/python:framework/test_file_system.so", - # contrib - "//tensorflow/contrib/session_bundle:session_bundle_half_plus_two", - "//tensorflow/contrib/keras:testing_utils", + # lite "//tensorflow/lite/experimental/examples/lstm:tflite_lstm", "//tensorflow/lite/experimental/examples/lstm:tflite_lstm.py", "//tensorflow/lite/experimental/examples/lstm:unidirectional_sequence_lstm_test", # pylint:disable=line-too-long @@ -93,6 +96,9 @@ BLACKLIST = [ "//tensorflow/lite/python:interpreter_test", "//tensorflow/lite/python:interpreter.py", "//tensorflow/lite/python:interpreter_test.py", + # contrib + "//tensorflow/contrib/session_bundle:session_bundle_half_plus_two", + "//tensorflow/contrib/keras:testing_utils", "//tensorflow/contrib/ffmpeg:test_data", "//tensorflow/contrib/fused_conv:fused_conv2d_bias_activation_op_test_base", "//tensorflow/contrib/hadoop:test_data", @@ -149,8 +155,8 @@ def main(): # File extensions and endings to ignore ignore_extensions = ["_test", "_test.py", "_test_gpu", "_test_gpu.py"] - ignored_files = 0 - blacklisted_files = len(BLACKLIST) + ignored_files_count = 0 + blacklisted_dependencies_count = len(DEPENDENCY_BLACKLIST) # Compare dependencies for dependency in tf_py_test_dependencies_list: if dependency and dependency.startswith("//tensorflow"): @@ -158,16 +164,16 @@ def main(): # Ignore extensions if any(dependency.endswith(ext) for ext in ignore_extensions): ignore = True - ignored_files += 1 + ignored_files_count += 1 - # Check if the dependency is in the pip package, the blacklist, or - # should be ignored because of its file extension + # Check if the dependency is in the pip package, the dependency blacklist, + # or should be ignored because of its file extension. if not (ignore or dependency in pip_package_dependencies_list or - dependency in BLACKLIST): + dependency in DEPENDENCY_BLACKLIST): missing_dependencies.append(dependency) - print("Ignored files: %d" % ignored_files) - print("Blacklisted files: %d" % blacklisted_files) + print("Ignored files count: %d" % ignored_files_count) + print("Blacklisted dependencies count: %d" % blacklisted_dependencies_count) if missing_dependencies: print("Missing the following dependencies from pip_packages:") for missing_dependency in missing_dependencies: -- GitLab From e5b4b8daf1db2f49173a0eace291172cc9b1e0d3 Mon Sep 17 00:00:00 2001 From: Ben Zinberg Date: Thu, 3 Jan 2019 10:37:47 -0800 Subject: [PATCH 0132/2345] Minor correction to the docs: `tf.Dimension(None)` does not `==` itself. PiperOrigin-RevId: 227711581 --- tensorflow/python/framework/tensor_shape.py | 49 +++++++++++---------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/tensorflow/python/framework/tensor_shape.py b/tensorflow/python/framework/tensor_shape.py index a4c626c64c..a7537bb5f1 100644 --- a/tensorflow/python/framework/tensor_shape.py +++ b/tensorflow/python/framework/tensor_shape.py @@ -271,10 +271,11 @@ class Dimension(object): Dimensions are combined as follows: ```python - tf.Dimension(n) .merge_with(tf.Dimension(n)) == tf.Dimension(n) - tf.Dimension(n) .merge_with(tf.Dimension(None)) == tf.Dimension(n) - tf.Dimension(None).merge_with(tf.Dimension(n)) == tf.Dimension(n) - tf.Dimension(None).merge_with(tf.Dimension(None)) == tf.Dimension(None) + tf.Dimension(n) .merge_with(tf.Dimension(n)) == tf.Dimension(n) + tf.Dimension(n) .merge_with(tf.Dimension(None)) == tf.Dimension(n) + tf.Dimension(None).merge_with(tf.Dimension(n)) == tf.Dimension(n) + # equivalent to tf.Dimension(None) + tf.Dimension(None).merge_with(tf.Dimension(None)) # raises ValueError for n != m tf.Dimension(n) .merge_with(tf.Dimension(m)) @@ -304,10 +305,10 @@ class Dimension(object): Dimensions are summed as follows: ```python - tf.Dimension(m) + tf.Dimension(n) == tf.Dimension(m + n) - tf.Dimension(m) + tf.Dimension(None) == tf.Dimension(None) - tf.Dimension(None) + tf.Dimension(n) == tf.Dimension(None) - tf.Dimension(None) + tf.Dimension(None) == tf.Dimension(None) + tf.Dimension(m) + tf.Dimension(n) == tf.Dimension(m + n) + tf.Dimension(m) + tf.Dimension(None) # equiv. to tf.Dimension(None) + tf.Dimension(None) + tf.Dimension(n) # equiv. to tf.Dimension(None) + tf.Dimension(None) + tf.Dimension(None) # equiv. to tf.Dimension(None) ``` Args: @@ -339,10 +340,10 @@ class Dimension(object): Dimensions are subtracted as follows: ```python - tf.Dimension(m) - tf.Dimension(n) == tf.Dimension(m - n) - tf.Dimension(m) - tf.Dimension(None) == tf.Dimension(None) - tf.Dimension(None) - tf.Dimension(n) == tf.Dimension(None) - tf.Dimension(None) - tf.Dimension(None) == tf.Dimension(None) + tf.Dimension(m) - tf.Dimension(n) == tf.Dimension(m - n) + tf.Dimension(m) - tf.Dimension(None) # equiv. to tf.Dimension(None) + tf.Dimension(None) - tf.Dimension(n) # equiv. to tf.Dimension(None) + tf.Dimension(None) - tf.Dimension(None) # equiv. to tf.Dimension(None) ``` Args: @@ -378,10 +379,10 @@ class Dimension(object): Dimensions are summed as follows: ```python - tf.Dimension(m) * tf.Dimension(n) == tf.Dimension(m * n) - tf.Dimension(m) * tf.Dimension(None) == tf.Dimension(None) - tf.Dimension(None) * tf.Dimension(n) == tf.Dimension(None) - tf.Dimension(None) * tf.Dimension(None) == tf.Dimension(None) + tf.Dimension(m) * tf.Dimension(n) == tf.Dimension(m * n) + tf.Dimension(m) * tf.Dimension(None) # equiv. to tf.Dimension(None) + tf.Dimension(None) * tf.Dimension(n) # equiv. to tf.Dimension(None) + tf.Dimension(None) * tf.Dimension(None) # equiv. to tf.Dimension(None) ``` Args: @@ -417,10 +418,10 @@ class Dimension(object): Dimensions are divided as follows: ```python - tf.Dimension(m) // tf.Dimension(n) == tf.Dimension(m // n) - tf.Dimension(m) // tf.Dimension(None) == tf.Dimension(None) - tf.Dimension(None) // tf.Dimension(n) == tf.Dimension(None) - tf.Dimension(None) // tf.Dimension(None) == tf.Dimension(None) + tf.Dimension(m) // tf.Dimension(n) == tf.Dimension(m // n) + tf.Dimension(m) // tf.Dimension(None) # equiv. to tf.Dimension(None) + tf.Dimension(None) // tf.Dimension(n) # equiv. to tf.Dimension(None) + tf.Dimension(None) // tf.Dimension(None) # equiv. to tf.Dimension(None) ``` Args: @@ -475,10 +476,10 @@ class Dimension(object): Dimension moduli are computed as follows: ```python - tf.Dimension(m) % tf.Dimension(n) == tf.Dimension(m % n) - tf.Dimension(m) % tf.Dimension(None) == tf.Dimension(None) - tf.Dimension(None) % tf.Dimension(n) == tf.Dimension(None) - tf.Dimension(None) % tf.Dimension(None) == tf.Dimension(None) + tf.Dimension(m) % tf.Dimension(n) == tf.Dimension(m % n) + tf.Dimension(m) % tf.Dimension(None) # equiv. to tf.Dimension(None) + tf.Dimension(None) % tf.Dimension(n) # equiv. to tf.Dimension(None) + tf.Dimension(None) % tf.Dimension(None) # equiv. to tf.Dimension(None) ``` Args: -- GitLab From 0d969989e0e4fbddb8c57eadf8f4c72d975fbd6b Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Thu, 3 Jan 2019 10:41:14 -0800 Subject: [PATCH 0133/2345] Automated rollback of commit 4d9173668fd3ba410532efd901467312b7418752 PiperOrigin-RevId: 227712180 --- tensorflow/cc/BUILD | 1 + tensorflow/cc/saved_model/BUILD | 2 + tensorflow/compiler/tf2xla/BUILD | 1 + tensorflow/core/BUILD | 3 +- tensorflow/core/kernels/BUILD | 83 +------------------------------- tensorflow/tensorflow.bzl | 2 + 6 files changed, 10 insertions(+), 82 deletions(-) diff --git a/tensorflow/cc/BUILD b/tensorflow/cc/BUILD index a09becc49b..cf6d6050fa 100644 --- a/tensorflow/cc/BUILD +++ b/tensorflow/cc/BUILD @@ -150,6 +150,7 @@ cc_library_with_android_deps( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:ops", "//tensorflow/core:protos_all_cc", ], ) diff --git a/tensorflow/cc/saved_model/BUILD b/tensorflow/cc/saved_model/BUILD index 52345a376c..836678d364 100644 --- a/tensorflow/cc/saved_model/BUILD +++ b/tensorflow/cc/saved_model/BUILD @@ -81,6 +81,7 @@ cc_library( ] + if_not_mobile([ "//tensorflow/core:core_cpu", "//tensorflow/core:lib", + "//tensorflow/core:ops", "//tensorflow/core:protos_all_cc", "//tensorflow/core:tensorflow", ]) + if_android([ @@ -100,6 +101,7 @@ cc_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:ops", "//tensorflow/core:protos_all_cc", "//tensorflow/core/util/tensor_bundle:naming", # mobile not supported yet diff --git a/tensorflow/compiler/tf2xla/BUILD b/tensorflow/compiler/tf2xla/BUILD index d8123e956f..dfb6b57161 100644 --- a/tensorflow/compiler/tf2xla/BUILD +++ b/tensorflow/compiler/tf2xla/BUILD @@ -281,6 +281,7 @@ tf_cc_test( "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", "//tensorflow/core:test_main", + "//tensorflow/core:testlib", ], ) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 1afac057d9..f9552d5965 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -1526,6 +1526,7 @@ cc_library( ":framework_internal", ":lib", ":lib_internal", + ":ops", ":protos_all_cc", ":shape_inference_testutil", ":tensor_testutil", @@ -3072,6 +3073,7 @@ tf_cuda_library( ":lib", ":lib_internal", ":metrics", + ":ops", ":proto_text", ":protos_all_cc", "//tensorflow/core/debug:debug_graph_utils", @@ -3854,7 +3856,6 @@ tf_cc_test( "ops/cudnn_rnn_ops_test.cc", ], deps = [ - ":cudnn_rnn_ops", "//tensorflow/core", "//tensorflow/core:framework", "//tensorflow/core:lib", diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index c72d04d3af..fd1df63c95 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -157,7 +157,6 @@ tf_kernel_library( name = "collective_ops", prefix = "collective_ops", deps = [ - "//tensorflow/core:collective_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", @@ -222,7 +221,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:nn_ops_op_lib", "//third_party/eigen3", ], alwayslink = 1, @@ -313,7 +311,6 @@ tf_kernel_library( "//tensorflow/core/nccl:nccl_lib", "//tensorflow/core:framework", "//tensorflow/core:gpu_headers_lib", - "//tensorflow/core:nccl_ops_op_lib", ]), ) @@ -459,7 +456,6 @@ cc_library( name = "batch_kernels", srcs = ["batch_kernels.cc"], deps = [ - "//tensorflow/core:batch_ops_op_lib", "//tensorflow/core:framework_headers_lib", "//tensorflow/core:protos_all_cc", "//tensorflow/core/kernels:concat_lib_hdrs", @@ -686,7 +682,6 @@ ARRAY_DEPS = [ ":ops_util", ":transpose_functor", "//tensorflow/core:array_grad", - "//tensorflow/core:array_ops_op_lib", "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", @@ -715,7 +710,6 @@ tf_kernel_library( deps = [ "//tensorflow/core:framework_headers_lib", "//tensorflow/core:lib", - "//tensorflow/core:set_ops_op_lib", "//third_party/eigen3", ], ) @@ -1079,7 +1073,6 @@ tf_kernel_library( srcs = ["ragged_gather_op.cc"], deps = [ "//tensorflow/core:framework", - "//tensorflow/core:ragged_array_ops_op_lib", ], ) @@ -1090,7 +1083,6 @@ tf_cc_test( deps = [ ":ragged_gather_op", "//tensorflow/core:framework", - "//tensorflow/core:ragged_array_ops_op_lib", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", @@ -1103,7 +1095,6 @@ tf_kernel_library( srcs = ["ragged_range_op.cc"], deps = [ "//tensorflow/core:framework", - "//tensorflow/core:ragged_math_ops_op_lib", ], ) @@ -1113,7 +1104,6 @@ tf_cc_test( deps = [ ":ragged_range_op", "//tensorflow/core:framework", - "//tensorflow/core:ragged_math_ops_op_lib", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", @@ -1126,7 +1116,6 @@ tf_kernel_library( srcs = ["ragged_tensor_to_sparse_kernel.cc"], deps = [ "//tensorflow/core:framework", - "//tensorflow/core:ragged_conversion_ops_op_lib", ], ) @@ -1138,7 +1127,6 @@ tf_cc_test( ":ragged_tensor_to_sparse_kernel", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:ragged_conversion_ops_op_lib", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", @@ -1152,7 +1140,6 @@ tf_kernel_library( visibility = ["//visibility:public"], deps = [ ":gpu_util_hdrs", - "//tensorflow/core:cudnn_rnn_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -1229,7 +1216,6 @@ tf_cuda_cc_test( "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:math_ops_op_lib", "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", "//tensorflow/core:test_main", @@ -1754,7 +1740,6 @@ tf_kernel_library( prefix = "candidate_sampler_ops", deps = [ ":range_sampler", - "//tensorflow/core:candidate_sampling_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", ], @@ -1788,7 +1773,6 @@ tf_kernel_library( name = "control_flow_ops", prefix = "control_flow_ops", deps = [ - "//tensorflow/core:control_flow_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", ], @@ -1800,7 +1784,6 @@ tf_kernel_library( deps = [ ":bounds_check", ":ops_util", - "//tensorflow/core:ctc_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core/util/ctc:ctc_beam_search_lib", @@ -1881,7 +1864,6 @@ DATA_FLOW_DEPS = [ ":typed_queue", "//third_party/eigen3", "//tensorflow/core:core_cpu", - "//tensorflow/core:data_flow_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -1946,7 +1928,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:scoped_allocator_ops_op_lib", ], ) @@ -1965,7 +1946,6 @@ tf_cuda_cc_test( "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:math_ops_op_lib", "//tensorflow/core:proto_text", "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", @@ -2013,7 +1993,6 @@ tf_kernel_library( DYNAMIC_DEPS = [ ":bounds_check", "//tensorflow/core:core_cpu", - "//tensorflow/core:data_flow_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -2046,7 +2025,6 @@ LOOKUP_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:lookup_ops_op_lib", ] tf_kernel_library( @@ -2075,7 +2053,6 @@ tf_kernel_library( deps = [ ":lookup_table_init_op", ":lookup_table_op", - "//tensorflow/core:checkpoint_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//third_party/eigen3", @@ -2086,7 +2063,6 @@ tf_kernel_library( name = "load_and_remap_matrix_op", srcs = ["load_and_remap_matrix_op.cc"], deps = [ - "//tensorflow/core:checkpoint_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -2233,7 +2209,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:resource_variable_ops_op_lib", "@com_google_absl//absl/strings", ], ) @@ -2250,7 +2225,6 @@ tf_kernel_library( ":concat_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:list_ops_op_lib", "//third_party/eigen3", ], ) @@ -2261,7 +2235,6 @@ tf_kernel_library( deps = [ "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:user_ops_op_lib", ], ) @@ -2284,7 +2257,6 @@ tf_kernel_library( "//tensorflow/core:core_cpu", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", - "//tensorflow/core:functional_ops_op_lib", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//third_party/eigen3", @@ -2297,7 +2269,6 @@ tf_kernel_library( deps = [ "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", - "//tensorflow/core:functional_ops_op_lib", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", "//tensorflow/core/grappler:grappler_item", @@ -2340,7 +2311,6 @@ IMAGE_DEPS = [ "//third_party/eigen3", "//tensorflow/core:framework", "//tensorflow/core:gif_internal", - "//tensorflow/core:image_ops_op_lib", "//tensorflow/core:jpeg_internal", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -2680,7 +2650,6 @@ cc_library( IO_DEPS = [ ":ops_util", "//tensorflow/core:framework", - "//tensorflow/core:io_ops_op_lib", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", @@ -2724,7 +2693,6 @@ SAVE_RESTORE_DEPS = [ ":bounds_check_lib", ":save_restore_tensor", "//tensorflow/core:framework", - "//tensorflow/core:io_ops_op_lib", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", @@ -2843,7 +2811,6 @@ LINALG_DEPS = [ "//third_party/eigen3", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:linalg_ops_op_lib", ] + if_cuda([ ":cuda_solvers", ":transpose_functor", @@ -2987,7 +2954,6 @@ LOGGING_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:logging_ops_op_lib", "//tensorflow/core:protos_all_cc", ] @@ -3059,7 +3025,6 @@ tf_kernel_library( ":bounds_check", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:manip_ops_op_lib", "//third_party/eigen3", ], ) @@ -3091,7 +3056,6 @@ MATH_DEPS = [ "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:math_grad", - "//tensorflow/core:math_ops_op_lib", "//third_party/eigen3", ] @@ -3202,7 +3166,7 @@ tf_kernel_library( tf_kernel_library( name = "cwise_op", prefix = "cwise_op", - deps = MATH_DEPS + ["//tensorflow/core:bitwise_ops_op_lib"], + deps = MATH_DEPS, ) tf_kernel_library( @@ -3221,7 +3185,6 @@ tf_kernel_library( name = "fft_ops", prefix = "fft_ops", deps = MATH_DEPS + [ - "//tensorflow/core:spectral_ops_op_lib", ] + if_cuda([ "//tensorflow/core/platform/default/build_config:cufft_plugin", ]), @@ -3420,10 +3383,7 @@ tf_cuda_cc_test( ":quantized_ops", "//tensorflow/cc:cc_ops", "//tensorflow/cc:client_session", - "//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", @@ -3681,7 +3641,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:nn_ops_op_lib", ] + select({ ":xsmm_convolutions": [ "@libxsmm_archive//:xsmm_avx", @@ -3703,7 +3662,6 @@ tf_kernel_library( "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:nn_ops_op_lib", ] + if_cuda([ "@cub_archive//:cub", "@local_config_cuda//cuda:cudnn_header", @@ -3723,7 +3681,6 @@ tf_kernel_library( "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:nn_ops_op_lib", ] + if_cuda([ "@local_config_cuda//cuda:cudnn_header", ]), @@ -3771,9 +3728,8 @@ NN_DEPS = [ "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:nn_grad", - "//tensorflow/core:nn_ops_op_lib", "//third_party/eigen3", -] + if_mkl(["//tensorflow/core:mkl_nn_ops_op_lib"]) +] tf_kernel_library( name = "batch_norm_op", @@ -3893,7 +3849,6 @@ tf_kernel_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:nn_grad", - "//tensorflow/core:nn_ops_op_lib", ] + if_cuda(["@cub_archive//:cub"]), ) @@ -4001,7 +3956,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:nn_ops_op_lib", "//tensorflow/core:stream_executor", "//third_party/eigen3", ], @@ -4045,7 +3999,6 @@ tf_kernel_library( "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:nn_ops_op_lib", "//third_party/eigen3", ], ) @@ -4127,7 +4080,6 @@ cc_library( PARSING_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:parsing_ops_op_lib", "//tensorflow/core:proto_text", "//tensorflow/core:protos_all_cc", ] @@ -4196,7 +4148,6 @@ RANDOM_OPS_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:random_ops_op_lib", ] tf_kernel_library( @@ -4235,7 +4186,6 @@ tf_kernel_library( ":random_op", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:stateless_random_ops_op_lib", ], ) @@ -4250,8 +4200,6 @@ cc_library( REQUIRED_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:no_op_op_lib", - "//tensorflow/core:sendrecv_ops_op_lib", ] tf_kernel_library( @@ -4312,7 +4260,6 @@ cc_library( SPARSE_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:sparse_ops_op_lib", ] tf_kernel_library( @@ -4557,7 +4504,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:sdca_ops_op_lib", "//third_party/eigen3", "@farmhash_archive//:farmhash", ], @@ -4597,7 +4543,6 @@ STATE_DEPS = [ "//third_party/eigen3", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:state_ops_op_lib", ] + if_sycl(["//tensorflow/core:sycl_runtime"]) tf_kernel_library( @@ -4735,7 +4680,6 @@ STRING_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:string_ops_op_lib", ] tf_kernel_library( @@ -4886,7 +4830,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:string_ops_op_lib", "//third_party/eigen3", "//third_party/icu/data:conversion_data", "@icu//:common", @@ -4908,7 +4851,6 @@ tf_kernel_library( ":variable_ops", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:training_ops_op_lib", "//third_party/eigen3", ], ) @@ -4967,7 +4909,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:random_ops_op_lib", ], ) @@ -4995,7 +4936,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:random_ops_op_lib", ], ) @@ -5904,12 +5844,9 @@ tf_kernel_library( ":ops_util", ":pooling_ops", ":quantization_utils", - "//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/eigen3", "@gemmlowp", ], @@ -6479,7 +6416,6 @@ tf_kernel_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", - "//tensorflow/core:remote_fused_graph_ops_op_lib", ], ) @@ -6634,8 +6570,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(), ) @@ -6685,8 +6619,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(), ) @@ -6705,8 +6637,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(), ) @@ -6720,8 +6650,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", "//third_party/eigen3", ] + mkl_deps(), ) @@ -6736,8 +6664,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", "//third_party/eigen3", ] + mkl_deps(), ) @@ -6878,7 +6804,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", - "//tensorflow/core:summary_ops_op_lib", "//tensorflow/core/lib/db:sqlite", ] + if_not_v2([ "//tensorflow/contrib/tensorboard/db:schema", @@ -6893,7 +6818,6 @@ tf_kernel_library( "decode_proto_op.cc", ], deps = [ - "//tensorflow/core:decode_proto_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core/util/proto:decode", @@ -6907,7 +6831,6 @@ tf_kernel_library( name = "encode_proto_op", srcs = ["encode_proto_op.cc"], deps = [ - "//tensorflow/core:encode_proto_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core/util/proto:descriptors", @@ -6925,7 +6848,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:rpc_ops_op_lib", "//tensorflow/core/util/rpc:call_container", "//tensorflow/core/util/rpc:rpc_factory", "//tensorflow/core/util/rpc:rpc_factory_registry", @@ -6938,7 +6860,6 @@ tf_kernel_library( srcs = ["unicode_script_op.cc"], deps = [ "//tensorflow/core:framework", - "//tensorflow/core:string_ops_op_lib", "@icu//:common", ], ) diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index 1024b686eb..b9069768e8 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -590,6 +590,7 @@ def tf_gen_op_wrappers_cc( clean_dep("//tensorflow/core:core_cpu"), clean_dep("//tensorflow/core:framework"), clean_dep("//tensorflow/core:lib"), + clean_dep("//tensorflow/core:ops"), clean_dep("//tensorflow/core:protos_all_cc"), ]) + if_android([ clean_dep("//tensorflow/core:android_tensorflow_lib"), @@ -606,6 +607,7 @@ def tf_gen_op_wrappers_cc( clean_dep("//tensorflow/core:core_cpu"), clean_dep("//tensorflow/core:framework"), clean_dep("//tensorflow/core:lib"), + clean_dep("//tensorflow/core:ops"), clean_dep("//tensorflow/core:protos_all_cc"), ]) + if_android([ clean_dep("//tensorflow/core:android_tensorflow_lib"), -- GitLab From f6fa3515db311b666dd4bd529f9b8b87575ce978 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Thu, 3 Jan 2019 10:44:43 -0800 Subject: [PATCH 0134/2345] Silence a noisy __del__ Popped up sometimes, looking something like: Exception ignored in: > Traceback (most recent call last): File "[...]tensorflow/python/client/session.py", line 738, in __del__ TypeError: 'NoneType' object is not callable Which I'm guessing is TF_DeleteSession being None. PiperOrigin-RevId: 227712751 --- tensorflow/python/client/session.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/client/session.py b/tensorflow/python/client/session.py index 87a200ed33..b97eb884b3 100644 --- a/tensorflow/python/client/session.py +++ b/tensorflow/python/client/session.py @@ -736,10 +736,11 @@ class BaseSession(SessionInterface): if self._session is not None: try: tf_session.TF_DeleteSession(self._session) - except AttributeError: - # At shutdown, `c_api_util` or `tf_session` may have been garbage - # collected, causing the above method calls to fail. In this case, - # silently leak since the program is about to terminate anyway. + except (AttributeError, TypeError): + # At shutdown, `c_api_util`, `tf_session`, or + # `tf_session.TF_DeleteSession` may have been garbage collected, causing + # the above method calls to fail. In this case, silently leak since the + # program is about to terminate anyway. pass self._session = None -- GitLab From e518527f10048b10956c7192de1411c36a3a1938 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 11:59:51 -0800 Subject: [PATCH 0135/2345] Spectral Normalization implementation in TFGAN PiperOrigin-RevId: 227725854 --- tensorflow/contrib/gan/BUILD | 43 +++ .../contrib/gan/python/features/__init__.py | 3 + .../features/python/spectral_normalization.py | 32 ++ .../python/spectral_normalization_impl.py | 315 ++++++++++++++++ .../python/spectral_normalization_test.py | 354 ++++++++++++++++++ 5 files changed, 747 insertions(+) create mode 100644 tensorflow/contrib/gan/python/features/python/spectral_normalization.py create mode 100644 tensorflow/contrib/gan/python/features/python/spectral_normalization_impl.py create mode 100644 tensorflow/contrib/gan/python/features/python/spectral_normalization_test.py diff --git a/tensorflow/contrib/gan/BUILD b/tensorflow/contrib/gan/BUILD index 97184dabb0..0626875b76 100644 --- a/tensorflow/contrib/gan/BUILD +++ b/tensorflow/contrib/gan/BUILD @@ -132,6 +132,7 @@ py_library( ":clip_weights", ":conditioning_utils", ":random_tensor_pool", + ":spectral_normalization", ":virtual_batchnorm", "//tensorflow/python:util", ], @@ -676,3 +677,45 @@ py_test( "//third_party/py/numpy", ], ) + +py_library( + name = "spectral_normalization", + srcs = [ + "python/features/python/spectral_normalization.py", + "python/features/python/spectral_normalization_impl.py", + ], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:dtypes", + "//tensorflow/python:framework_ops", + "//tensorflow/python:init_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:standard_ops", + "//tensorflow/python:variable_scope", + "//tensorflow/python/keras:engine", + ], +) + +py_test( + name = "spectral_normalization_test", + srcs = ["python/features/python/spectral_normalization_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":spectral_normalization", + "//tensorflow/contrib/layers:layers_py", + "//tensorflow/contrib/slim", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:dtypes", + "//tensorflow/python:framework_ops", + "//tensorflow/python:init_ops", + "//tensorflow/python:layers", + "//tensorflow/python:linalg_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", + "//tensorflow/python/keras:layers", + "//third_party/py/numpy", + ], +) diff --git a/tensorflow/contrib/gan/python/features/__init__.py b/tensorflow/contrib/gan/python/features/__init__.py index 4816daf760..410c3a0205 100644 --- a/tensorflow/contrib/gan/python/features/__init__.py +++ b/tensorflow/contrib/gan/python/features/__init__.py @@ -27,11 +27,13 @@ from __future__ import print_function from tensorflow.contrib.gan.python.features.python import clip_weights from tensorflow.contrib.gan.python.features.python import conditioning_utils from tensorflow.contrib.gan.python.features.python import random_tensor_pool +from tensorflow.contrib.gan.python.features.python import spectral_normalization from tensorflow.contrib.gan.python.features.python import virtual_batchnorm from tensorflow.contrib.gan.python.features.python.clip_weights import * from tensorflow.contrib.gan.python.features.python.conditioning_utils import * from tensorflow.contrib.gan.python.features.python.random_tensor_pool import * +from tensorflow.contrib.gan.python.features.python.spectral_normalization import * from tensorflow.contrib.gan.python.features.python.virtual_batchnorm import * # pylint: enable=unused-import,wildcard-import @@ -40,5 +42,6 @@ from tensorflow.python.util.all_util import remove_undocumented _allowed_symbols = clip_weights.__all__ _allowed_symbols += conditioning_utils.__all__ _allowed_symbols += random_tensor_pool.__all__ +_allowed_symbols += spectral_normalization.__all__ _allowed_symbols += virtual_batchnorm.__all__ remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/gan/python/features/python/spectral_normalization.py b/tensorflow/contrib/gan/python/features/python/spectral_normalization.py new file mode 100644 index 0000000000..54d3d0a218 --- /dev/null +++ b/tensorflow/contrib/gan/python/features/python/spectral_normalization.py @@ -0,0 +1,32 @@ +# 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. +# ============================================================================== +"""Keras-like layers and utilities that implement Spectral Normalization. + +Based on "Spectral Normalization for Generative Adversarial Networks" by Miyato, +et al in ICLR 2018. https://openreview.net/pdf?id=B1QRgziT- +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.gan.python.features.python import spectral_normalization_impl +# pylint: disable=wildcard-import +from tensorflow.contrib.gan.python.features.python.spectral_normalization_impl import * +# pylint: enable=wildcard-import +from tensorflow.python.util.all_util import remove_undocumented + +__all__ = spectral_normalization_impl.__all__ +remove_undocumented(__name__, __all__) diff --git a/tensorflow/contrib/gan/python/features/python/spectral_normalization_impl.py b/tensorflow/contrib/gan/python/features/python/spectral_normalization_impl.py new file mode 100644 index 0000000000..0cc653f0a7 --- /dev/null +++ b/tensorflow/contrib/gan/python/features/python/spectral_normalization_impl.py @@ -0,0 +1,315 @@ +# 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. +# ============================================================================== +"""Keras-like layers and utilities that implement Spectral Normalization. + +Based on "Spectral Normalization for Generative Adversarial Networks" by Miyato, +et al in ICLR 2018. https://openreview.net/pdf?id=B1QRgziT- +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import contextlib +import numbers +import re + +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.keras.engine import base_layer_utils as keras_base_layer_utils +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import init_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import nn +from tensorflow.python.ops import variable_scope +from tensorflow.python.platform import tf_logging as logging + +__all__ = [ + 'compute_spectral_norm', 'spectral_normalize', 'spectral_norm_regularizer', + 'spectral_normalization_custom_getter', 'keras_spectral_normalization' +] + +# tf.bfloat16 should work, but tf.matmul converts those to tf.float32 which then +# can't directly be assigned back to the tf.bfloat16 variable. +_OK_DTYPES_FOR_SPECTRAL_NORM = (dtypes.float16, dtypes.float32, dtypes.float64) +_PERSISTED_U_VARIABLE_SUFFIX = 'spectral_norm_u' + + +def compute_spectral_norm(w_tensor, power_iteration_rounds=1, name=None): + """Estimates the largest singular value in the weight tensor. + + 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. + name: An optional scope name. + + Returns: + The largest singular value (the spectral norm) of w. + """ + with variable_scope.variable_scope(name, 'spectral_norm'): + # The paper says to flatten convnet kernel weights from + # (C_out, C_in, KH, KW) to (C_out, C_in * KH * KW). But TensorFlow's Conv2D + # kernel weight shape is (KH, KW, C_in, C_out), so it should be reshaped to + # (KH * KW * C_in, C_out), and similarly for other layers that put output + # channels as last dimension. + # n.b. this means that w here is equivalent to w.T in the paper. + w = array_ops.reshape(w_tensor, (-1, w_tensor.get_shape()[-1])) + + # Persisted approximation of first left singular vector of matrix `w`. + u_var = variable_scope.get_variable( + _PERSISTED_U_VARIABLE_SUFFIX, + shape=(w.shape[0], 1), + dtype=w.dtype, + initializer=init_ops.random_normal_initializer(), + trainable=False) + u = u_var + + # Use power iteration method to approximate spectral norm. + for _ in range(power_iteration_rounds): + # `v` approximates the first right singular vector of matrix `w`. + v = nn.l2_normalize(math_ops.matmul(array_ops.transpose(w), u)) + u = nn.l2_normalize(math_ops.matmul(w, v)) + + # Update persisted approximation. + with ops.control_dependencies([u_var.assign(u, name='update_u')]): + u = array_ops.identity(u) + + u = array_ops.stop_gradient(u) + v = array_ops.stop_gradient(v) + + # Largest singular value of `w`. + spectral_norm = math_ops.matmul( + math_ops.matmul(array_ops.transpose(u), w), v) + spectral_norm.shape.assert_is_fully_defined() + spectral_norm.shape.assert_is_compatible_with([1, 1]) + + return spectral_norm[0][0] + + +def spectral_normalize(w, power_iteration_rounds=1, name=None): + """Normalizes a weight matrix by its spectral norm. + + 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. + name: An optional scope name. + + Returns: + A normalized weight matrix tensor. + """ + with variable_scope.variable_scope(name, 'spectral_normalize'): + w_normalized = w / compute_spectral_norm( + w, power_iteration_rounds=power_iteration_rounds) + return array_ops.reshape(w_normalized, w.get_shape()) + + +def spectral_norm_regularizer(scale, power_iteration_rounds=1, scope=None): + """Returns a functions that can be used to apply spectral norm regularization. + + Small spectral norms enforce a small Lipschitz constant, which is necessary + for Wasserstein GANs. + + 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. + scope: An optional scope name. + + Returns: + A function with the signature `sn(weights)` that applies spectral norm + regularization. + + Raises: + ValueError: If scale is negative or if scale is not a float. + """ + if isinstance(scale, numbers.Integral): + raise ValueError('scale cannot be an integer: %s' % scale) + if isinstance(scale, numbers.Real): + if scale < 0.0: + raise ValueError( + 'Setting a scale less than 0 on a regularizer: %g' % scale) + if scale == 0.0: + logging.info('Scale of 0 disables regularizer.') + return lambda _: None + + def sn(weights, name=None): + """Applies spectral norm regularization to weights.""" + with ops.name_scope(scope, 'SpectralNormRegularizer', [weights]) as name: + scale_t = ops.convert_to_tensor( + scale, dtype=weights.dtype.base_dtype, name='scale') + return math_ops.multiply( + scale_t, + compute_spectral_norm( + weights, power_iteration_rounds=power_iteration_rounds), + name=name) + + return sn + + +def _default_name_filter(name): + """A filter function to identify common names of weight variables. + + Args: + name: The variable name. + + Returns: + Whether `name` is a standard name for a weight/kernel variables used in the + Keras, tf.layers, tf.contrib.layers or tf.contrib.slim libraries. + """ + match = re.match(r'(.*\/)?(depthwise_|pointwise_)?(weights|kernel)$', name) + return match is not None + + +def spectral_normalization_custom_getter(name_filter=_default_name_filter, + power_iteration_rounds=1): + """Custom getter that performs Spectral Normalization on a weight tensor. + + Specifically it divides the weight tensor by its largest singular value. This + is intended to stabilize GAN training, by making the discriminator satisfy a + local 1-Lipschitz constraint. + + Based on [Spectral Normalization for Generative Adversarial Networks][sn-gan]. + + [sn-gan]: https://openreview.net/forum?id=B1QRgziT- + + To reproduce an SN-GAN, apply this custom_getter to every weight tensor of + your discriminator. The last dimension of the weight tensor must be the number + of output channels. + + Apply this to layers by supplying this as the `custom_getter` of a + `tf.variable_scope`. For example: + + with tf.variable_scope('discriminator', + custom_getter=spectral_norm_getter()): + net = discriminator_fn(net) + + IMPORTANT: Keras does not respect the custom_getter supplied by the + VariableScope, so Keras users should use `keras_spectral_normalization` + instead of (or in addition to) this approach. + + It is important to carefully select to which weights you want to apply + Spectral Normalization. In general you want to normalize the kernels of + convolution and dense layers, but you do not want to normalize biases. You + also want to avoid normalizing batch normalization (and similar) variables, + but in general such layers play poorly with Spectral Normalization, since the + gamma can cancel out the normalization in other layers. By default we supply a + filter that matches the kernel variable names of the dense and convolution + layers of the tf.layers, tf.contrib.layers, tf.keras and tf.contrib.slim + libraries. If you are using anything else you'll need a custom `name_filter`. + + This custom getter internally creates a variable used to compute the spectral + norm by power iteration. It will update every time the variable is accessed, + which means the normalized discriminator weights may change slightly whilst + training the generator. Whilst unusual, this matches how the paper's authors + implement it, and in general additional rounds of power iteration can't hurt. + + Args: + 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 + true spectral norm. + + Returns: + A custom getter function that applies Spectral Normalization to all + Variables whose names match `name_filter`. + + Raises: + ValueError: If name_filter is not callable. + """ + if not callable(name_filter): + raise ValueError('name_filter must be callable') + + def _internal_getter(getter, name, *args, **kwargs): + """A custom getter function that applies Spectral Normalization. + + Args: + getter: The true getter to call. + name: Name of new/existing variable, in the same format as + tf.get_variable. + *args: Other positional arguments, in the same format as tf.get_variable. + **kwargs: Keyword arguments, in the same format as tf.get_variable. + + Returns: + The return value of `getter(name, *args, **kwargs)`, spectrally + normalized. + + Raises: + ValueError: If used incorrectly, or if `dtype` is not supported. + """ + if not name_filter(name): + return getter(name, *args, **kwargs) + + if name.endswith(_PERSISTED_U_VARIABLE_SUFFIX): + raise ValueError( + 'Cannot apply Spectral Normalization to internal variables created ' + 'for Spectral Normalization. Tried to normalized variable [%s]' % + name) + + if kwargs['dtype'] not in _OK_DTYPES_FOR_SPECTRAL_NORM: + raise ValueError('Disallowed data type {}'.format(kwargs['dtype'])) + + # This layer's weight Variable/PartitionedVariable. + w_tensor = getter(name, *args, **kwargs) + + if len(w_tensor.get_shape()) < 2: + raise ValueError( + 'Spectral norm can only be applied to multi-dimensional tensors') + + return spectral_normalize( + w_tensor, + power_iteration_rounds=power_iteration_rounds, + name=(name + '/spectral_normalize')) + + return _internal_getter + + +@contextlib.contextmanager +def keras_spectral_normalization(name_filter=_default_name_filter, + power_iteration_rounds=1): + """A context manager that enables Spectral Normalization for Keras. + + Keras doesn't respect the `custom_getter` in the VariableScope, so this is a + bit of a hack to make things work. + + Usage: + with keras_spectral_normalization(): + net = discriminator_fn(net) + + Args: + 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 + true spectral norm. + + Yields: + A context manager that wraps the standard Keras variable creation method + with the `spectral_normalization_custom_getter`. + """ + original_make_variable = keras_base_layer_utils.make_variable + sn_getter = spectral_normalization_custom_getter( + name_filter=name_filter, power_iteration_rounds=power_iteration_rounds) + + def make_variable_wrapper(name, *args, **kwargs): + return sn_getter(original_make_variable, name, *args, **kwargs) + + keras_base_layer_utils.make_variable = make_variable_wrapper + + yield + + keras_base_layer_utils.make_variable = original_make_variable diff --git a/tensorflow/contrib/gan/python/features/python/spectral_normalization_test.py b/tensorflow/contrib/gan/python/features/python/spectral_normalization_test.py new file mode 100644 index 0000000000..4ea21f70ec --- /dev/null +++ b/tensorflow/contrib/gan/python/features/python/spectral_normalization_test.py @@ -0,0 +1,354 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for features.spectral_normalization.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib import slim +from tensorflow.contrib.gan.python.features.python import spectral_normalization_impl as spectral_normalization +from tensorflow.contrib.layers.python.layers import layers as contrib_layers +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.keras.layers import convolutional as keras_convolutional +from tensorflow.python.keras.layers import core as keras_core +from tensorflow.python.layers import convolutional as layers_convolutional +from tensorflow.python.layers import core as layers_core +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import init_ops +from tensorflow.python.ops import linalg_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + + +class SpectralNormalizationTest(test.TestCase): + + def testComputeSpectralNorm(self): + weights = variable_scope.get_variable( + 'w', dtype=dtypes.float32, shape=[2, 3, 50, 100]) + weights = math_ops.multiply(weights, 10.0) + s = linalg_ops.svd( + array_ops.reshape(weights, [-1, weights.shape[-1]]), compute_uv=False) + true_sn = s[..., 0] + estimated_sn = spectral_normalization.compute_spectral_norm(weights) + + with self.cached_session() as sess: + sess.run(variables.global_variables_initializer()) + np_true_sn = sess.run(true_sn) + for i in range(50): + est = sess.run(estimated_sn) + if i < 1: + np_est_1 = est + if i < 4: + np_est_5 = est + if i < 9: + np_est_10 = est + np_est_50 = est + + # Check that the estimate improves with more iterations. + self.assertAlmostEqual(np_true_sn, np_est_50, 0) + self.assertGreater( + abs(np_true_sn - np_est_10), abs(np_true_sn - np_est_50)) + self.assertGreater( + abs(np_true_sn - np_est_5), abs(np_true_sn - np_est_10)) + self.assertGreater(abs(np_true_sn - np_est_1), abs(np_true_sn - np_est_5)) + + def testSpectralNormalize(self): + weights = variable_scope.get_variable( + 'w', dtype=dtypes.float32, shape=[2, 3, 50, 100]) + weights = math_ops.multiply(weights, 10.0) + normalized_weights = spectral_normalization.spectral_normalize( + weights, power_iteration_rounds=1) + + unnormalized_sigma = linalg_ops.svd( + array_ops.reshape(weights, [-1, weights.shape[-1]]), + compute_uv=False)[..., 0] + normalized_sigma = linalg_ops.svd( + array_ops.reshape(normalized_weights, [-1, weights.shape[-1]]), + compute_uv=False)[..., 0] + + with self.cached_session() as sess: + sess.run(variables.global_variables_initializer()) + s0 = sess.run(unnormalized_sigma) + + for i in range(50): + sigma = sess.run(normalized_sigma) + if i < 1: + s1 = sigma + if i < 5: + s5 = sigma + if i < 10: + s10 = sigma + s50 = sigma + + self.assertAlmostEqual(1., s50, 0) + self.assertGreater(abs(s10 - 1.), abs(s50 - 1.)) + self.assertGreater(abs(s5 - 1.), abs(s10 - 1.)) + self.assertGreater(abs(s1 - 1.), abs(s5 - 1.)) + self.assertGreater(abs(s0 - 1.), abs(s1 - 1.)) + + def _testLayerHelper(self, build_layer_fn, w_shape, b_shape, is_keras=False): + x = array_ops.placeholder(dtypes.float32, shape=[2, 10, 10, 3]) + + w_initial = np.random.randn(*w_shape) * 10 + w_initializer = init_ops.constant_initializer(w_initial) + b_initial = np.random.randn(*b_shape) + b_initializer = init_ops.constant_initializer(b_initial) + + if is_keras: + context_manager = spectral_normalization.keras_spectral_normalization() + else: + getter = spectral_normalization.spectral_normalization_custom_getter() + context_manager = variable_scope.variable_scope('', custom_getter=getter) + + with context_manager: + (net, + expected_normalized_vars, expected_not_normalized_vars) = build_layer_fn( + x, w_initializer, b_initializer) + + x_data = np.random.rand(*x.shape) + + with self.cached_session() as sess: + sess.run(variables.global_variables_initializer()) + + # Before running a forward pass we still expect the variables values to + # differ from the initial value because of the normalizer. + w_befores = [] + for name, var in expected_normalized_vars.items(): + w_before = sess.run(var) + w_befores.append(w_before) + self.assertFalse( + np.allclose(w_initial, w_before), + msg=('%s appears not to be normalized. Before: %s After: %s' % + (name, w_initial, w_before))) + + # Not true for the unnormalized variables. + for name, var in expected_not_normalized_vars.items(): + b_before = sess.run(var) + self.assertTrue( + np.allclose(b_initial, b_before), + msg=('%s appears to be unexpectedly normalized. ' + 'Before: %s After: %s' % (name, b_initial, b_before))) + + # Run a bunch of forward passes. + for _ in range(1000): + _ = sess.run(net, feed_dict={x: x_data}) + + # We expect this to have improved the estimate of the spectral norm, + # which should have changed the variable values and brought them close + # to the true Spectral Normalized values. + _, s, _ = np.linalg.svd(w_initial.reshape([-1, 3])) + exactly_normalized = w_initial / s[0] + for w_before, (name, var) in zip(w_befores, + expected_normalized_vars.items()): + w_after = sess.run(var) + self.assertFalse( + np.allclose(w_before, w_after, rtol=1e-8, atol=1e-8), + msg=('%s did not improve over many iterations. ' + 'Before: %s After: %s' % (name, w_before, w_after))) + self.assertAllClose( + exactly_normalized, + w_after, + rtol=1e-4, + atol=1e-4, + msg=('Estimate of spectral norm for %s was innacurate. ' + 'Normalized matrices do not match.' + 'Estimate: %s Actual: %s' % (name, w_after, + exactly_normalized))) + + def testConv2D_Layers(self): + + def build_layer_fn(x, w_initializer, b_initializer): + layer = layers_convolutional.Conv2D( + filters=3, + kernel_size=3, + padding='same', + kernel_initializer=w_initializer, + bias_initializer=b_initializer) + net = layer.apply(x) + expected_normalized_vars = {'tf.layers.Conv2d.kernel': layer.kernel} + expected_not_normalized_vars = {'tf.layers.Conv2d.bias': layer.bias} + + return net, expected_normalized_vars, expected_not_normalized_vars + + self._testLayerHelper(build_layer_fn, (3, 3, 3, 3), (3,)) + + def testConv2D_ContribLayers(self): + + def build_layer_fn(x, w_initializer, b_initializer): + var_collection = { + 'weights': ['CONTRIB_LAYERS_CONV2D_WEIGHTS'], + 'biases': ['CONTRIB_LAYERS_CONV2D_BIASES'] + } + net = contrib_layers.conv2d( + x, + 3, + 3, + weights_initializer=w_initializer, + biases_initializer=b_initializer, + variables_collections=var_collection) + weight_vars = ops.get_collection('CONTRIB_LAYERS_CONV2D_WEIGHTS') + self.assertEquals(1, len(weight_vars)) + bias_vars = ops.get_collection('CONTRIB_LAYERS_CONV2D_BIASES') + self.assertEquals(1, len(bias_vars)) + expected_normalized_vars = { + 'contrib.layers.conv2d.weights': weight_vars[0] + } + expected_not_normalized_vars = { + 'contrib.layers.conv2d.bias': bias_vars[0] + } + + return net, expected_normalized_vars, expected_not_normalized_vars + + self._testLayerHelper(build_layer_fn, (3, 3, 3, 3), (3,)) + + def testConv2D_Slim(self): + + def build_layer_fn(x, w_initializer, b_initializer): + var_collection = { + 'weights': ['SLIM_CONV2D_WEIGHTS'], + 'biases': ['SLIM_CONV2D_BIASES'] + } + net = slim.conv2d( + x, + 3, + 3, + weights_initializer=w_initializer, + biases_initializer=b_initializer, + variables_collections=var_collection) + weight_vars = ops.get_collection('SLIM_CONV2D_WEIGHTS') + self.assertEquals(1, len(weight_vars)) + bias_vars = ops.get_collection('SLIM_CONV2D_BIASES') + self.assertEquals(1, len(bias_vars)) + expected_normalized_vars = {'slim.conv2d.weights': weight_vars[0]} + expected_not_normalized_vars = {'slim.conv2d.bias': bias_vars[0]} + + return net, expected_normalized_vars, expected_not_normalized_vars + + self._testLayerHelper(build_layer_fn, (3, 3, 3, 3), (3,)) + + def testConv2D_Keras(self): + + def build_layer_fn(x, w_initializer, b_initializer): + layer = keras_convolutional.Conv2D( + filters=3, + kernel_size=3, + padding='same', + kernel_initializer=w_initializer, + bias_initializer=b_initializer) + net = layer.apply(x) + expected_normalized_vars = {'keras.layers.Conv2d.kernel': layer.kernel} + expected_not_normalized_vars = {'keras.layers.Conv2d.bias': layer.bias} + + return net, expected_normalized_vars, expected_not_normalized_vars + + self._testLayerHelper(build_layer_fn, (3, 3, 3, 3), (3,), is_keras=True) + + def testFC_Layers(self): + + def build_layer_fn(x, w_initializer, b_initializer): + x = layers_core.Flatten()(x) + layer = layers_core.Dense( + units=3, + kernel_initializer=w_initializer, + bias_initializer=b_initializer) + net = layer.apply(x) + expected_normalized_vars = {'tf.layers.Dense.kernel': layer.kernel} + expected_not_normalized_vars = {'tf.layers.Dense.bias': layer.bias} + + return net, expected_normalized_vars, expected_not_normalized_vars + + self._testLayerHelper(build_layer_fn, (300, 3), (3,)) + + def testFC_ContribLayers(self): + + def build_layer_fn(x, w_initializer, b_initializer): + var_collection = { + 'weights': ['CONTRIB_LAYERS_FC_WEIGHTS'], + 'biases': ['CONTRIB_LAYERS_FC_BIASES'] + } + x = contrib_layers.flatten(x) + net = contrib_layers.fully_connected( + x, + 3, + weights_initializer=w_initializer, + biases_initializer=b_initializer, + variables_collections=var_collection) + weight_vars = ops.get_collection('CONTRIB_LAYERS_FC_WEIGHTS') + self.assertEquals(1, len(weight_vars)) + bias_vars = ops.get_collection('CONTRIB_LAYERS_FC_BIASES') + self.assertEquals(1, len(bias_vars)) + expected_normalized_vars = { + 'contrib.layers.fully_connected.weights': weight_vars[0] + } + expected_not_normalized_vars = { + 'contrib.layers.fully_connected.bias': bias_vars[0] + } + + return net, expected_normalized_vars, expected_not_normalized_vars + + self._testLayerHelper(build_layer_fn, (300, 3), (3,)) + + def testFC_Slim(self): + + def build_layer_fn(x, w_initializer, b_initializer): + var_collection = { + 'weights': ['SLIM_FC_WEIGHTS'], + 'biases': ['SLIM_FC_BIASES'] + } + x = slim.flatten(x) + net = slim.fully_connected( + x, + 3, + weights_initializer=w_initializer, + biases_initializer=b_initializer, + variables_collections=var_collection) + weight_vars = ops.get_collection('SLIM_FC_WEIGHTS') + self.assertEquals(1, len(weight_vars)) + bias_vars = ops.get_collection('SLIM_FC_BIASES') + self.assertEquals(1, len(bias_vars)) + expected_normalized_vars = { + 'slim.fully_connected.weights': weight_vars[0] + } + expected_not_normalized_vars = {'slim.fully_connected.bias': bias_vars[0]} + + return net, expected_normalized_vars, expected_not_normalized_vars + + self._testLayerHelper(build_layer_fn, (300, 3), (3,)) + + def testFC_Keras(self): + + def build_layer_fn(x, w_initializer, b_initializer): + x = keras_core.Flatten()(x) + layer = keras_core.Dense( + units=3, + kernel_initializer=w_initializer, + bias_initializer=b_initializer) + net = layer.apply(x) + expected_normalized_vars = {'keras.layers.Dense.kernel': layer.kernel} + expected_not_normalized_vars = {'keras.layers.Dense.bias': layer.bias} + + return net, expected_normalized_vars, expected_not_normalized_vars + + self._testLayerHelper(build_layer_fn, (300, 3), (3,), is_keras=True) + + +if __name__ == '__main__': + test.main() -- GitLab From 41c30afbfb6a752f09f9b26db807b1a1ac494e8a Mon Sep 17 00:00:00 2001 From: Karim Nosir Date: Thu, 3 Jan 2019 12:07:50 -0800 Subject: [PATCH 0136/2345] Enable kernel tests for ios. PiperOrigin-RevId: 227727388 --- tensorflow/core/BUILD | 9 +- tensorflow/lite/kernels/BUILD | 163 ------------------ .../lite/kernels/batch_to_space_nd_test.cc | 2 + tensorflow/lite/kernels/pad_test.cc | 8 + tensorflow/lite/kernels/reshape_test.cc | 10 ++ .../lite/kernels/space_to_batch_nd_test.cc | 4 + .../lite/kernels/space_to_depth_test.cc | 2 + tensorflow/lite/kernels/strided_slice_test.cc | 2 + tensorflow/lite/kernels/transpose_test.cc | 4 + 9 files changed, 39 insertions(+), 165 deletions(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index f9552d5965..6b32e1fead 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -2348,7 +2348,12 @@ cc_library( cc_library( name = "tflite_portable_logging", - srcs = [], + srcs = [ + ] + if_ios([ + "platform/default/logging.cc", + "platform/env_time.cc", + "platform/posix/env_time.cc", + ]), hdrs = [ "lib/bfloat16/bfloat16.h", "platform/default/integral_types.h", @@ -2357,7 +2362,7 @@ cc_library( "platform/macros.h", "platform/platform.h", "platform/types.h", - ] + if_windows(["platform/windows/integral_types.h"]), + ] + if_windows(["platform/windows/integral_types.h"]) + if_ios(["platform/env_time.h"]), copts = tf_copts(), linkopts = ["-ldl"], deps = [ diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD index 5cc06c7a63..9abc33d297 100644 --- a/tensorflow/lite/kernels/BUILD +++ b/tensorflow/lite/kernels/BUILD @@ -25,9 +25,6 @@ tf_cc_test( name = "optional_tensor_test", size = "small", srcs = ["optional_tensor_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -122,9 +119,6 @@ tf_cc_test( name = "kernel_util_test", size = "small", srcs = ["kernel_util_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":kernel_util", "//tensorflow/lite/testing:util", @@ -136,9 +130,6 @@ tf_cc_test( name = "test_util_test", size = "small", srcs = ["test_util_test.cc"], - tags = [ - "tflite_not_portable_ios", # TODO(b/117786830) - ], deps = [ ":test_util", "//tensorflow/lite/testing:util", @@ -304,9 +295,6 @@ tf_cc_test( name = "audio_spectrogram_test", size = "small", srcs = ["audio_spectrogram_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -320,9 +308,6 @@ tf_cc_test( name = "mfcc_test", size = "small", srcs = ["mfcc_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -336,9 +321,6 @@ tf_cc_test( name = "detection_postprocess_test", size = "small", srcs = ["detection_postprocess_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -352,9 +334,6 @@ tf_cc_test( name = "relu1_test", size = "small", srcs = ["relu1_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -368,9 +347,6 @@ tf_cc_test( name = "sparse_output_fully_connected_test", size = "small", srcs = ["sparse_output_fully_connected_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -384,7 +360,6 @@ tf_cc_test( name = "activations_test", size = "small", srcs = ["activations_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -397,7 +372,6 @@ tf_cc_test( name = "add_test", size = "small", srcs = ["add_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -410,9 +384,6 @@ tf_cc_test( name = "arg_min_max_test", size = "small", srcs = ["arg_min_max_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -425,9 +396,6 @@ tf_cc_test( name = "div_test", size = "small", srcs = ["div_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -440,9 +408,6 @@ tf_cc_test( name = "sub_test", size = "small", srcs = ["sub_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -455,9 +420,6 @@ tf_cc_test( name = "transpose_test", size = "small", srcs = ["transpose_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -472,9 +434,6 @@ tf_cc_test( name = "space_to_batch_nd_test", size = "small", srcs = ["space_to_batch_nd_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -487,9 +446,6 @@ tf_cc_test( name = "batch_to_space_nd_test", size = "small", srcs = ["batch_to_space_nd_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -502,9 +458,6 @@ tf_cc_test( name = "cast_test", size = "small", srcs = ["cast_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -517,7 +470,6 @@ tf_cc_test( name = "concatenation_test", size = "small", srcs = ["concatenation_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -530,7 +482,6 @@ tf_cc_test( name = "conv_test", size = "small", srcs = ["conv_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -544,7 +495,6 @@ tf_cc_test( name = "depthwise_conv_test", size = "small", srcs = ["depthwise_conv_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -558,9 +508,6 @@ tf_cc_test( name = "dequantize_test", size = "small", srcs = ["dequantize_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -574,7 +521,6 @@ tf_cc_test( name = "basic_rnn_test", size = "small", srcs = ["basic_rnn_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -587,9 +533,6 @@ tf_cc_test( name = "bidirectional_sequence_lstm_test", size = "small", srcs = ["bidirectional_sequence_lstm_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -603,9 +546,6 @@ tf_cc_test( name = "floor_test", size = "small", srcs = ["floor_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -618,9 +558,6 @@ tf_cc_test( name = "elementwise_test", size = "small", srcs = ["elementwise_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -633,9 +570,6 @@ tf_cc_test( name = "unidirectional_sequence_lstm_test", size = "small", srcs = ["unidirectional_sequence_lstm_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -663,9 +597,6 @@ tf_cc_test( name = "unidirectional_sequence_rnn_test", size = "small", srcs = ["unidirectional_sequence_rnn_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -678,7 +609,6 @@ tf_cc_test( name = "l2norm_test", size = "small", srcs = ["l2norm_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -691,9 +621,6 @@ tf_cc_test( name = "exp_test", size = "small", srcs = ["exp_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -706,9 +633,6 @@ tf_cc_test( name = "fake_quant_test", size = "small", srcs = ["fake_quant_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -721,9 +645,6 @@ tf_cc_test( name = "maximum_minimum_test", size = "small", srcs = ["maximum_minimum_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -736,9 +657,6 @@ tf_cc_test( name = "reduce_test", size = "small", srcs = ["reduce_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -751,7 +669,6 @@ tf_cc_test( name = "mul_test", size = "small", srcs = ["mul_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -764,9 +681,6 @@ tf_cc_test( name = "pad_test", size = "small", srcs = ["pad_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -779,7 +693,6 @@ tf_cc_test( name = "reshape_test", size = "small", srcs = ["reshape_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -792,9 +705,6 @@ tf_cc_test( name = "gather_test", size = "small", srcs = ["gather_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -808,9 +718,6 @@ tf_cc_test( name = "topk_v2_test", size = "small", srcs = ["topk_v2_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -824,7 +731,6 @@ tf_cc_test( name = "resize_bilinear_test", size = "small", srcs = ["resize_bilinear_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -837,7 +743,6 @@ tf_cc_test( name = "resize_nearest_neighbor_test", size = "small", srcs = ["resize_nearest_neighbor_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -850,7 +755,6 @@ tf_cc_test( name = "svdf_test", size = "small", srcs = ["svdf_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -863,7 +767,6 @@ tf_cc_test( name = "embedding_lookup_test", size = "small", srcs = ["embedding_lookup_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -876,7 +779,6 @@ tf_cc_test( name = "embedding_lookup_sparse_test", size = "small", srcs = ["embedding_lookup_sparse_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -889,7 +791,6 @@ tf_cc_test( name = "fully_connected_test", size = "small", srcs = ["fully_connected_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -904,7 +805,6 @@ tf_cc_test( name = "local_response_norm_test", size = "small", srcs = ["local_response_norm_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -917,7 +817,6 @@ tf_cc_test( name = "pooling_test", size = "small", srcs = ["pooling_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -930,7 +829,6 @@ tf_cc_test( name = "softmax_test", size = "small", srcs = ["softmax_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -944,9 +842,6 @@ tf_cc_test( name = "log_softmax_test", size = "small", srcs = ["log_softmax_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -960,7 +855,6 @@ tf_cc_test( name = "lsh_projection_test", size = "small", srcs = ["lsh_projection_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -973,7 +867,6 @@ tf_cc_test( name = "hashtable_lookup_test", size = "small", srcs = ["hashtable_lookup_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -987,7 +880,6 @@ tf_cc_test( name = "layer_norm_lstm_test", size = "small", srcs = ["layer_norm_lstm_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1001,7 +893,6 @@ tf_cc_test( name = "lstm_test", size = "small", srcs = ["lstm_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1014,7 +905,6 @@ tf_cc_test( name = "skip_gram_test", size = "small", srcs = ["skip_gram_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1028,7 +918,6 @@ tf_cc_test( name = "space_to_depth_test", size = "small", srcs = ["space_to_depth_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1041,9 +930,6 @@ tf_cc_test( name = "split_test", size = "small", srcs = ["split_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1056,9 +942,6 @@ tf_cc_test( name = "split_v_test", size = "small", srcs = ["split_v_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1071,9 +954,6 @@ tf_cc_test( name = "squeeze_test", size = "small", srcs = ["squeeze_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1086,9 +966,6 @@ tf_cc_test( name = "strided_slice_test", size = "small", srcs = ["strided_slice_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1101,9 +978,6 @@ tf_cc_test( name = "tile_test", size = "small", srcs = ["tile_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1119,9 +993,6 @@ tf_cc_test( srcs = [ "comparisons_test.cc", ], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1134,9 +1005,6 @@ tf_cc_test( name = "neg_test", size = "small", srcs = ["neg_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1151,9 +1019,6 @@ tf_cc_test( srcs = [ "select_test.cc", ], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1168,9 +1033,6 @@ tf_cc_test( srcs = [ "slice_test.cc", ], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1183,9 +1045,6 @@ tf_cc_test( name = "transpose_conv_test", size = "small", srcs = ["transpose_conv_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1199,9 +1058,6 @@ tf_cc_test( name = "expand_dims_test", size = "small", srcs = ["expand_dims_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1215,9 +1071,6 @@ tf_cc_test( name = "sparse_to_dense_test", size = "small", srcs = ["sparse_to_dense_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1231,9 +1084,6 @@ tf_cc_test( name = "shape_test", size = "small", srcs = ["shape_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1247,9 +1097,6 @@ tf_cc_test( name = "pow_test", size = "small", srcs = ["pow_test.cc"], - tags = [ - "tflite_not_portable_ios", - ], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1263,7 +1110,6 @@ tf_cc_test( name = "pack_test", size = "small", srcs = ["pack_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1277,7 +1123,6 @@ tf_cc_test( name = "one_hot_test", size = "small", srcs = ["one_hot_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1290,7 +1135,6 @@ tf_cc_test( name = "logical_test", size = "small", srcs = ["logical_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1304,7 +1148,6 @@ tf_cc_test( name = "unpack_test", size = "small", srcs = ["unpack_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:builtin_op_data", @@ -1318,7 +1161,6 @@ tf_cc_test( name = "floor_div_test", size = "small", srcs = ["floor_div_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:builtin_op_data", @@ -1332,7 +1174,6 @@ tf_cc_test( name = "zeros_like_test", size = "small", srcs = ["zeros_like_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:builtin_op_data", @@ -1346,7 +1187,6 @@ tf_cc_test( name = "floor_mod_test", size = "small", srcs = ["floor_mod_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:builtin_op_data", @@ -1360,7 +1200,6 @@ tf_cc_test( name = "range_test", size = "small", srcs = ["range_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:builtin_op_data", @@ -1374,7 +1213,6 @@ tf_cc_test( name = "squared_difference_test", size = "small", srcs = ["squared_difference_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", @@ -1387,7 +1225,6 @@ tf_cc_test( name = "fill_test", size = "small", srcs = ["fill_test.cc"], - tags = ["tflite_not_portable_ios"], deps = [ ":builtin_ops", "//tensorflow/lite:framework", diff --git a/tensorflow/lite/kernels/batch_to_space_nd_test.cc b/tensorflow/lite/kernels/batch_to_space_nd_test.cc index a3e06d4c89..f330895599 100644 --- a/tensorflow/lite/kernels/batch_to_space_nd_test.cc +++ b/tensorflow/lite/kernels/batch_to_space_nd_test.cc @@ -114,6 +114,7 @@ TEST(BatchToSpaceNDOpTest, SimpleDynamicTest) { 4, 8, 11, 15, 12, 16})); } +#ifdef GTEST_HAS_DEATH_TEST TEST(BatchToSpaceNDOpTest, InvalidShapeTest) { EXPECT_DEATH(BatchToSpaceNDOpConstModel({3, 2, 2, 1}, {2, 2}, {0, 0, 0, 0}), "Cannot allocate tensors"); @@ -131,6 +132,7 @@ TEST(BatchToSpaceNDOpTest, InvalidCropsDynamicTest) { m.SetCrops({0, 0, -1, 0}); EXPECT_DEATH(m.Invoke(), "crops.2. >= 0 was not true."); } +#endif } // namespace } // namespace tflite diff --git a/tensorflow/lite/kernels/pad_test.cc b/tensorflow/lite/kernels/pad_test.cc index 415a285c70..3caa4065dc 100644 --- a/tensorflow/lite/kernels/pad_test.cc +++ b/tensorflow/lite/kernels/pad_test.cc @@ -175,6 +175,7 @@ class PadOpDynamicModel : public PadOpModel { } }; +#ifdef GTEST_HAS_DEATH_TEST TEST(PadOpTest, TooManyDimensions) { EXPECT_DEATH( PadOpConstModel({TensorType_FLOAT32, {1, 2, 3, 4, 5, 6, 7, 8, 9}}, {9, 2}, @@ -195,6 +196,7 @@ TEST(PadOpTest, InvalidPadValue) { {0, 0, 1, -1, 2, -1, 0, 0}, {TensorType_FLOAT32}), "Pad value has to be greater than equal to 0."); } +#endif TEST(PadOpTest, SimpleConstTest) { // Padding is represented as four 2-D lists representing above padding and @@ -306,6 +308,7 @@ class QuantizedPadOpTest : public ::testing::Test { } }; +#ifdef GTEST_HAS_DEATH_TEST TEST_F(QuantizedPadOpTest, ZeroNotInQuantizationRange) { // The test_util and actual quantization code currently ensure that the range // must include zero, but if that ever changes, this test will catch it. @@ -314,6 +317,7 @@ TEST_F(QuantizedPadOpTest, ZeroNotInQuantizationRange) { {TensorType_UINT8, {}, 1.0, 2.0}), ".*Check failed: f_min <= 0.*"); } +#endif TEST_F(QuantizedPadOpTest, SimpleConstTest) { // Padding is represented as four 2-D lists representing above padding and @@ -371,6 +375,7 @@ TEST_F(QuantizedPadOpTest, AdvancedDynamicTest) { EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1})); } +#ifdef GTEST_HAS_DEATH_TEST TEST(PadV2OpTest, TooManyDimensions) { EXPECT_DEATH(PadV2OpConstModel( {TensorType_FLOAT32, {1, 2, 3, 4, 5, 6, 7, 8, 9}}, {9, 2}, @@ -392,6 +397,7 @@ TEST(PadV2OpTest, InvalidPadValue) { {TensorType_FLOAT32}), "Pad value has to be greater than equal to 0."); } +#endif TEST(PadV2OpTest, SimpleConstTest) { // Padding is represented as four 2-D lists representing above padding and @@ -495,6 +501,7 @@ class QuantizedPadV2OpTest : public ::testing::Test { } }; +#ifdef GTEST_HAS_DEATH_TEST TEST_F(QuantizedPadV2OpTest, ZeroNotInQuantizationRange) { // The test_util and actual quantization code currently ensure that the range // must include zero, but if that ever changes, this test will catch it. @@ -504,6 +511,7 @@ TEST_F(QuantizedPadV2OpTest, ZeroNotInQuantizationRange) { {TensorType_UINT8, {}, 1.0, 2.0}), ".*Check failed: f_min <= 0.*"); } +#endif TEST_F(QuantizedPadV2OpTest, SimpleConstTest) { // Padding is represented as four 2-D lists representing above padding and diff --git a/tensorflow/lite/kernels/reshape_test.cc b/tensorflow/lite/kernels/reshape_test.cc index 00bbbef57e..f98f3eb9ae 100644 --- a/tensorflow/lite/kernels/reshape_test.cc +++ b/tensorflow/lite/kernels/reshape_test.cc @@ -123,6 +123,7 @@ class ReshapeOpModel : public SingleOpModel { int output_; }; +#ifdef GTEST_HAS_DEATH_TEST TEST_P(ReshapeOpTest, MismatchedDimensions) { if (GetParam() == kAsTensor) { ReshapeOpModel m({1, 2, 4, 1}, {2}, {2, 1}, GetParam()); @@ -133,12 +134,15 @@ TEST_P(ReshapeOpTest, MismatchedDimensions) { "num_input_elements != num_output_elements"); } } +#endif TEST_P(ReshapeOpTest, TooManyDimensions) { if (GetParam() == kAsReshapeOption) { +#ifdef GTEST_HAS_DEATH_TEST EXPECT_DEATH(ReshapeOpModel({1, 1, 2, 1, 1, 1, 1, 1, 1}, {9}, {1, 1, 1, 1, 1, 1, 1, 1, 2}, GetParam()), "Found too many dimensions"); +#endif } else { ReshapeOpModel m({1, 1, 2, 1, 1, 1, 1, 1, 1}, {9}, {1, 1, 1, 1, 1, 1, 1, 1, 2}, GetParam()); @@ -150,6 +154,7 @@ TEST_P(ReshapeOpTest, TooManyDimensions) { } } +#ifdef GTEST_HAS_DEATH_TEST TEST_P(ReshapeOpTest, TooManySpecialDimensions) { if (GetParam() != kAsTensor) { EXPECT_DEATH( @@ -160,6 +165,7 @@ TEST_P(ReshapeOpTest, TooManySpecialDimensions) { EXPECT_DEATH(m.Invoke(), "stretch_dim != -1"); } } +#endif // Create the model with a 2x2 shape. Processing still works because the new // shape ends up being hardcoded as a flat vector. @@ -202,12 +208,16 @@ TEST_P(ReshapeOpTest, ScalarOutput) { // and output are scalars. TEST_P(ReshapeOpTest, LegacyScalarOutput) { if (GetParam() == kAsConstantTensor) { +#ifdef GTEST_HAS_DEATH_TEST EXPECT_DEATH(ReshapeOpModel({1}, {1}, {0}, GetParam()), "num_input_elements != num_output_elements"); +#endif } else if (GetParam() == kAsTensor) { +#ifdef GTEST_HAS_DEATH_TEST ReshapeOpModel m({1}, {1}, {0}, GetParam()); m.SetInput({3}); EXPECT_DEATH(m.Invoke(), "num_input_elements != num_output_elements"); +#endif } else { ReshapeOpModel m({1}, {1}, {0}, GetParam()); m.SetInput({3}); diff --git a/tensorflow/lite/kernels/space_to_batch_nd_test.cc b/tensorflow/lite/kernels/space_to_batch_nd_test.cc index 4d55ba56b7..c5d6e9a530 100644 --- a/tensorflow/lite/kernels/space_to_batch_nd_test.cc +++ b/tensorflow/lite/kernels/space_to_batch_nd_test.cc @@ -106,12 +106,14 @@ class SpaceToBatchNDOpDynamicModel : public SpaceToBatchNDOpModel { } }; +#ifdef GTEST_HAS_DEATH_TEST TEST(SpaceToBatchNDOpTest, InvalidShapeTest) { EXPECT_DEATH( SpaceToBatchNDOpConstModel({TensorType_FLOAT32, {1, 3, 3, 1}}, {2, 2}, {0, 0, 0, 0}, {TensorType_FLOAT32}), "Cannot allocate tensors"); } +#endif TEST(SpaceToBatchNDOpTest, SimpleConstTest) { SpaceToBatchNDOpConstModel m({TensorType_FLOAT32, {1, 4, 4, 1}}, {2, 2}, @@ -220,6 +222,7 @@ class QuantizedSpaceToBatchNDOpTest : public ::testing::Test { } }; +#ifdef GTEST_HAS_DEATH_TEST TEST_F(QuantizedSpaceToBatchNDOpTest, ZeroNotInQuantizationRange) { // The test_util and actual quantization code currently ensure that the range // must include zero, but if that ever changes, this test will catch it. @@ -228,6 +231,7 @@ TEST_F(QuantizedSpaceToBatchNDOpTest, ZeroNotInQuantizationRange) { {0, 0, 1, 1, 1, 1, 0, 0}, {TensorType_UINT8, {}, 1.0, 2.0}), ".*Check failed: f_min <= 0.*"); } +#endif TEST_F(QuantizedSpaceToBatchNDOpTest, SimplePaddingConstTest) { SpaceToBatchNDOpConstModel m({TensorType_UINT8, {1, 5, 2, 1}, -1.0, 1.0}, diff --git a/tensorflow/lite/kernels/space_to_depth_test.cc b/tensorflow/lite/kernels/space_to_depth_test.cc index 5744669b6d..3fa8d86348 100644 --- a/tensorflow/lite/kernels/space_to_depth_test.cc +++ b/tensorflow/lite/kernels/space_to_depth_test.cc @@ -50,10 +50,12 @@ class SpaceToDepthOpModel : public SingleOpModel { int output_; }; +#ifdef GTEST_HAS_DEATH_TEST TEST(SpaceToDepthOpModel, BadBlockSize) { EXPECT_DEATH(SpaceToDepthOpModel({TensorType_FLOAT32, {1, 2, 2, 1}}, 3), "Cannot allocate tensors"); } +#endif TEST(SpaceToDepthOpModel, Float32) { SpaceToDepthOpModel m({TensorType_FLOAT32, {1, 2, 2, 2}}, 2); diff --git a/tensorflow/lite/kernels/strided_slice_test.cc b/tensorflow/lite/kernels/strided_slice_test.cc index 122e01b99e..34875bf049 100644 --- a/tensorflow/lite/kernels/strided_slice_test.cc +++ b/tensorflow/lite/kernels/strided_slice_test.cc @@ -72,6 +72,7 @@ class StridedSliceOpModel : public SingleOpModel { int output_; }; +#ifdef GTEST_HAS_DEATH_TEST TEST(StridedSliceOpTest, UnsupportedInputSize) { EXPECT_DEATH( StridedSliceOpModel<>({2, 2, 2, 2, 2}, {5}, {5}, {5}, 0, 0, 0, 0, 0), @@ -84,6 +85,7 @@ TEST(StridedSliceOpTest, UnssupportedArgs) { EXPECT_DEATH(StridedSliceOpModel<>({3, 2}, {2}, {2}, {2}, 0, 0, 0, 1, 0), "new_axis_mask is not implemented yet."); } +#endif TEST(StridedSliceOpTest, In1D) { StridedSliceOpModel<> m({4}, {1}, {1}, {1}, 0, 0, 0, 0, 0); diff --git a/tensorflow/lite/kernels/transpose_test.cc b/tensorflow/lite/kernels/transpose_test.cc index 3ebaf3ca27..93df2c81db 100644 --- a/tensorflow/lite/kernels/transpose_test.cc +++ b/tensorflow/lite/kernels/transpose_test.cc @@ -184,6 +184,7 @@ class TransposeOpDynamicModel : public TransposeOpModel { } }; +#ifdef GTEST_HAS_DEATH_TEST TEST(TransposeTest, TestUnequalPermSize) { EXPECT_DEATH(TransposeOpConstModel({1, 3, 3, 1}, {2}, {2, 2}), "2 != 4"); } @@ -194,6 +195,7 @@ TEST(TransposeTest, TestPermOutOfBounds) { EXPECT_DEATH(TransposeOpConstModel({1, 3, 3, 1}, {4}, {0, 1, 2, 4}), "Transpose op permutations array is out of bounds."); } +#endif TEST(TransposeTest, Test1DInputConstTensor) { TransposeOpConstModel m({3}, {1}, {0}); @@ -252,10 +254,12 @@ TEST(TransposeTest, Test3DInputDynamicTensor) { 2, 6, 10, 14, 18, 22, 3, 7, 11, 15, 19, 23})); } +#ifdef GTEST_HAS_DEATH_TEST TEST(TransposeTest, Test5DInputTensor) { EXPECT_DEATH(TransposeOpConstModel({1, 2, 3, 4, 5}, {5}, {0, 1, 2, 3, 4}), "Transpose op only supports 1D-4D input arrays."); } +#endif TEST(TransposeTest, SimpleTestNoReorderConstTensor) { TransposeOpConstModel m({1, 2, 3, 1}, {4}, {0, 1, 2, 3}); -- GitLab From aa36aa0a912a4addfb387e5fc0f759db225d03cc Mon Sep 17 00:00:00 2001 From: Davide Libenzi Date: Thu, 3 Jan 2019 12:26:05 -0800 Subject: [PATCH 0137/2345] Allow XRT release operations to support vectors of keys. PiperOrigin-RevId: 227730040 --- .../compiler/xrt/kernels/xrt_compile_ops.cc | 15 ++- .../compiler/xrt/kernels/xrt_state_ops.h | 16 +-- .../compiler/xrt/ops/xrt_compile_ops.cc | 6 +- tensorflow/compiler/xrt/ops/xrt_state_ops.cc | 5 +- tensorflow/compiler/xrt/tests/raw_api_test.cc | 98 ++++++++++++++++++- 5 files changed, 117 insertions(+), 23 deletions(-) diff --git a/tensorflow/compiler/xrt/kernels/xrt_compile_ops.cc b/tensorflow/compiler/xrt/kernels/xrt_compile_ops.cc index 2ccdf0f02d..2ee1a6cd1a 100644 --- a/tensorflow/compiler/xrt/kernels/xrt_compile_ops.cc +++ b/tensorflow/compiler/xrt/kernels/xrt_compile_ops.cc @@ -215,11 +215,6 @@ XRTReleaseCompilationRefOp::~XRTReleaseCompilationRefOp() = default; void XRTReleaseCompilationRefOp::Compute(OpKernelContext* ctx) { VLOG(1) << "XRTReleaseCompilationRefOp::Compute"; - const Tensor& key_tensor = ctx->input(0); - OP_REQUIRES(ctx, TensorShapeUtils::IsScalar(key_tensor.shape()), - errors::Internal("computation key should be a string scalar")); - int64 uid = key_tensor.scalar()(); - ResourceMgr* rm; OP_REQUIRES_OK(ctx, XRTGenericDeviceAccessor::GetResourceManager(ctx, &rm)); @@ -230,9 +225,13 @@ void XRTReleaseCompilationRefOp::Compute(OpKernelContext* ctx) { kXRTCompilationCacheResourceName, &cache)); core::ScopedUnref cache_unref(cache); - OP_REQUIRES_OK(ctx, cache->Release(uid)); - - VLOG(2) << "Released computation handle " << uid; + const Tensor& keys_tensor = ctx->input(0); + auto flat_keys = keys_tensor.flat(); + for (int64 i = 0; i < flat_keys.size(); ++i) { + int64 key = flat_keys(i); + OP_REQUIRES_OK(ctx, cache->Release(key)); + VLOG(2) << "Released computation handle " << key; + } } } // namespace diff --git a/tensorflow/compiler/xrt/kernels/xrt_state_ops.h b/tensorflow/compiler/xrt/kernels/xrt_state_ops.h index 2e2f3ff116..c8def16bbc 100644 --- a/tensorflow/compiler/xrt/kernels/xrt_state_ops.h +++ b/tensorflow/compiler/xrt/kernels/xrt_state_ops.h @@ -453,17 +453,17 @@ class XRTReleaseAllocationOp : public OpKernel { void Compute(OpKernelContext* ctx) override { VLOG(1) << "XRTReleaseAllocationOp::Compute"; - const Tensor& allocation_handle = ctx->input(0); - OP_REQUIRES(ctx, TensorShapeUtils::IsScalar(allocation_handle.shape()), - errors::Internal("handle input should be an int64 scalar")); - int64 key = allocation_handle.scalar()(); - ResourceMgr* rm; OP_REQUIRES_OK(ctx, DeviceAccessor::GetResourceManager(ctx, &rm)); - OP_REQUIRES_OK(ctx, XRTTupleAllocation::DeleteFromResourceManager(rm, key)); - - VLOG(2) << "Released allocation handle " << key; + const Tensor& allocation_handle = ctx->input(0); + auto flat_keys = allocation_handle.flat(); + for (int64 i = 0; i < flat_keys.size(); ++i) { + int64 key = flat_keys(i); + OP_REQUIRES_OK(ctx, + XRTTupleAllocation::DeleteFromResourceManager(rm, key)); + VLOG(2) << "Released allocation handle " << key; + } } }; diff --git a/tensorflow/compiler/xrt/ops/xrt_compile_ops.cc b/tensorflow/compiler/xrt/ops/xrt_compile_ops.cc index 7b3b50c695..9dd964e546 100644 --- a/tensorflow/compiler/xrt/ops/xrt_compile_ops.cc +++ b/tensorflow/compiler/xrt/ops/xrt_compile_ops.cc @@ -44,10 +44,10 @@ REGISTER_OP("XRTReleaseCompilationHandle") .SetShapeFn(tensorflow::shape_inference::NoOutputs) .Doc( R"( -Discards a computation from the compilation cache. The handle cannot be -subsequently used. +Discards one or more computation handles from the compilation cache. +The handle(s) cannot be subsequently used. -'handle' is an id returned from a XRTCompile Op. +'handle' is an ID (or vector of IDs) returned from a XRTCompile Op. )"); } // namespace tensorflow diff --git a/tensorflow/compiler/xrt/ops/xrt_state_ops.cc b/tensorflow/compiler/xrt/ops/xrt_state_ops.cc index fe6bee0dac..db58f0797d 100644 --- a/tensorflow/compiler/xrt/ops/xrt_state_ops.cc +++ b/tensorflow/compiler/xrt/ops/xrt_state_ops.cc @@ -127,10 +127,11 @@ REGISTER_OP("XRTReleaseAllocationHandle") .SetShapeFn(tensorflow::shape_inference::NoOutputs) .Doc( R"( -Discards an allocation from device memory. The handle cannot be subsequently +Discards one or more device memory handles. The handle(s) cannot be subsequently used. -'handle' is the id returned from the Op that produced the on-device allocation. +'handle' is the ID (or a vector of IDs) returned from the Op that produced the +on-device allocation. )"); REGISTER_OP("XRTReleaseAllAllocations") diff --git a/tensorflow/compiler/xrt/tests/raw_api_test.cc b/tensorflow/compiler/xrt/tests/raw_api_test.cc index 5f8121703e..c8479cb778 100644 --- a/tensorflow/compiler/xrt/tests/raw_api_test.cc +++ b/tensorflow/compiler/xrt/tests/raw_api_test.cc @@ -258,8 +258,102 @@ TEST(RawApiTest, AllocAndRewrite) { EXPECT_TRUE(new_response.ParseFromString(outputs[0].scalar()())); EXPECT_TRUE(CompareLiteralProtos(new_literal, new_response)); - auto release = - ops::XRTReleaseAllocationHandle(root, Input(allocation_handle)); + Tensor release_tensor(DT_INT64, TensorShape({1})); + release_tensor.flat()(0) = allocation_handle; + + auto release = ops::XRTReleaseAllocationHandle(root, release_tensor); + TF_EXPECT_OK(session.Run(tensorflow::ClientSession::FeedType(), {}, {release}, + &outputs)); +} + +TEST(RawApiTest, AllocReleaseMany) { + xrt::XLAAllocation alloc1; + *alloc1.mutable_value() = + xla::LiteralUtil::CreateR2({{4, 5}, {6, 7}}).ToProto(); + xrt::XLAAllocation alloc2; + *alloc2.mutable_value() = + xla::LiteralUtil::CreateR2({{6, 7}, {4, 5}}).ToProto(); + + Scope root = Scope::NewRootScope().WithDevice(DeviceFromFlag()); + auto value1 = + ops::Const(root.WithDevice("/device:CPU:0"), alloc1.SerializeAsString()); + auto value2 = + ops::Const(root.WithDevice("/device:CPU:0"), alloc2.SerializeAsString()); + auto handle1 = ops::XRTAllocate(root, value1); + auto handle2 = ops::XRTAllocate(root, value2); + TF_ASSERT_OK(root.status()); + + tensorflow::ClientSession session(root); + std::vector outputs; + TF_EXPECT_OK(session.Run({handle1, handle2}, &outputs)); + EXPECT_EQ(outputs.size(), 2); + + int64 allocation_handle1 = outputs[0].scalar()(); + int64 allocation_handle2 = outputs[1].scalar()(); + + Tensor release_tensor(DT_INT64, TensorShape({2})); + release_tensor.flat()(0) = allocation_handle1; + release_tensor.flat()(1) = allocation_handle2; + + auto release = ops::XRTReleaseAllocationHandle(root, release_tensor); + outputs.clear(); + TF_EXPECT_OK(session.Run(tensorflow::ClientSession::FeedType(), {}, {release}, + &outputs)); +} + +TEST(RawApiTest, CompileAndReleaseMany) { + xrt::XLAComputation c1; + auto config1 = c1.mutable_config(); + auto shapes1 = config1->mutable_program_shape(); + *shapes1->add_parameters() = + xla::ShapeUtil::MakeShape(xla::F32, {2}).ToProto(); + *shapes1->add_parameters() = + xla::ShapeUtil::MakeShape(xla::F32, {2}).ToProto(); + *shapes1->mutable_result() = + xla::ShapeUtil::MakeShape(xla::F32, {2}).ToProto(); + StoreComputationSnapshot(AddAndScale(), c1.mutable_hlo_snapshot()); + + xrt::XLAComputation c2; + auto config2 = c2.mutable_config(); + auto shapes2 = config2->mutable_program_shape(); + *shapes2->add_parameters() = + xla::ShapeUtil::MakeShape(xla::F32, {2}).ToProto(); + *shapes2->add_parameters() = + xla::ShapeUtil::MakeShape(xla::F32, {2}).ToProto(); + *shapes2->mutable_result() = + xla::ShapeUtil::MakeTupleShape({xla::ShapeUtil::MakeShape(xla::F32, {2})}) + .ToProto(); + StoreComputationSnapshot(AddAndTuple(), c2.mutable_hlo_snapshot()); + + xrt::XRTExecutionConfig e; + e.set_release_input_handles(true); + e.set_release_compilation_handle(false); + + Scope root = Scope::NewRootScope().WithDevice(DeviceFromFlag()); + auto e_config = + ops::Const(root.WithDevice("/device:CPU:0"), e.SerializeAsString()); + auto computation1 = + ops::Const(root.WithDevice("/device:CPU:0"), c1.SerializeAsString()); + auto c_handle1 = ops::XRTCompile(root, computation1); + auto computation2 = + ops::Const(root.WithDevice("/device:CPU:0"), c2.SerializeAsString()); + auto c_handle2 = ops::XRTCompile(root, computation2); + TF_ASSERT_OK(root.status()); + + ClientSession session(root); + std::vector outputs; + TF_EXPECT_OK(session.Run({c_handle1.handle, c_handle2.handle}, &outputs)); + EXPECT_EQ(outputs.size(), 2); + + int64 compilation_handle1 = outputs[0].scalar()(); + int64 compilation_handle2 = outputs[1].scalar()(); + + Tensor release_tensor(DT_INT64, TensorShape({2})); + release_tensor.flat()(0) = compilation_handle1; + release_tensor.flat()(1) = compilation_handle2; + + auto release = ops::XRTReleaseCompilationHandle(root, release_tensor); + outputs.clear(); TF_EXPECT_OK(session.Run(tensorflow::ClientSession::FeedType(), {}, {release}, &outputs)); } -- GitLab From cb4f2d41f22b6df49c80555a911d7dc2f08b7b90 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 12:27:49 -0800 Subject: [PATCH 0138/2345] Fix autograph detection of tensors that are wrapped in python structures. A dict with tensor values did not register as containing a Tensor, causing the python branch to be taken instead of the tf branch. PiperOrigin-RevId: 227730234 --- tensorflow/python/autograph/operators/BUILD | 1 + .../autograph/operators/control_flow.py | 4 ++- .../autograph/operators/control_flow_test.py | 30 +++++++++++++------ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tensorflow/python/autograph/operators/BUILD b/tensorflow/python/autograph/operators/BUILD index aedb901845..21a66c86b7 100644 --- a/tensorflow/python/autograph/operators/BUILD +++ b/tensorflow/python/autograph/operators/BUILD @@ -38,6 +38,7 @@ py_library( "//tensorflow/python:list_ops", "//tensorflow/python:tensor_array_ops", "//tensorflow/python:tensor_util", + "//tensorflow/python:util", "//tensorflow/python:variables", "//tensorflow/python/autograph/utils", "//tensorflow/python/data/ops:dataset_ops", diff --git a/tensorflow/python/autograph/operators/control_flow.py b/tensorflow/python/autograph/operators/control_flow.py index afa3787d42..035ea1bd92 100644 --- a/tensorflow/python/autograph/operators/control_flow.py +++ b/tensorflow/python/autograph/operators/control_flow.py @@ -23,6 +23,7 @@ from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import tensor_util from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import gen_math_ops +from tensorflow.python.util import nest def for_stmt(iter_, extra_test, body, init_state): @@ -160,7 +161,8 @@ 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 init_state + extra_deps): + if any(tensor_util.is_tensor(v) + for v in nest.flatten(init_state + extra_deps)): return _tf_while_stmt(test, body, init_state, opts) else: return _py_while_stmt(test, body, init_state, opts) diff --git a/tensorflow/python/autograph/operators/control_flow_test.py b/tensorflow/python/autograph/operators/control_flow_test.py index 0a7d4b6402..f9e006f7ad 100644 --- a/tensorflow/python/autograph/operators/control_flow_test.py +++ b/tensorflow/python/autograph/operators/control_flow_test.py @@ -36,7 +36,7 @@ class ForLoopTest(test.TestCase): extra_test=lambda s: True, body=lambda i, s: (s + i,), init_state=(0,)) - with self.cached_session() as sess: + with self.cached_session(): self.assertEqual((10,), self.evaluate(s)) def test_python(self): @@ -55,7 +55,7 @@ class ForLoopTest(test.TestCase): extra_test=lambda s: True, body=lambda i, s: (s + i,), init_state=(0,)) - with self.cached_session() as sess: + with self.cached_session(): self.assertEqual((10,), self.evaluate(s)) @@ -65,18 +65,30 @@ class WhileLoopTest(test.TestCase): def test_tensor(self): n = constant_op.constant(5) results = control_flow.while_stmt( - test=lambda i, s: i < n, - body=lambda i, s: (i + 1, s + i,), + test=lambda i, sum: i < n, + body=lambda i, sum: (i + 1, sum + i,), init_state=(0, 0), extra_deps=(n,)) - with self.cached_session() as sess: + with self.cached_session(): self.assertEqual((5, 10), self.evaluate(results)) + @test_util.run_deprecated_v1 + def test_tensor_dict_state(self): + n = 5 + init_state = {'i': constant_op.constant(0), 'sum': constant_op.constant(0)} + results = control_flow.while_stmt( + test=lambda s: s['i'] < n, + body=lambda s: ({'i': s['i'] + 1, 'sum': s['sum'] + s['i']},), + init_state=(init_state,), + extra_deps=()) + with self.cached_session(): + self.assertEqual(({'i': 5, 'sum': 10},), self.evaluate(results)) + def test_python(self): n = 5 results = control_flow.while_stmt( - test=lambda i, s: i < n, - body=lambda i, s: (i + 1, s + i), + test=lambda i, sum: i < n, + body=lambda i, sum: (i + 1, sum + i), init_state=(0, 0), extra_deps=(n,)) self.assertEqual((5, 10), results) @@ -93,7 +105,7 @@ class IfStmtTest(test.TestCase): @test_util.run_deprecated_v1 def test_tensor(self): - with self.cached_session() as sess: + with self.cached_session(): t = self.single_return_if_stmt(constant_op.constant(True)) self.assertEqual(1, self.evaluate(t)) t = self.single_return_if_stmt(constant_op.constant(False)) @@ -105,7 +117,7 @@ class IfStmtTest(test.TestCase): @test_util.run_deprecated_v1 def test_tensor_multiple_returns(self): - with self.cached_session() as sess: + with self.cached_session(): t = self.multi_return_if_stmt(constant_op.constant(True)) self.assertAllEqual([1, 2], self.evaluate(t)) t = self.multi_return_if_stmt(constant_op.constant(False)) -- GitLab From 7e2d53c1c371f38c7f0ef13c1c06336b22a195c0 Mon Sep 17 00:00:00 2001 From: Shivani Agrawal Date: Thu, 3 Jan 2019 12:46:47 -0800 Subject: [PATCH 0139/2345] [tf.data] Adds the expected check for better debugging. PiperOrigin-RevId: 227733165 --- tensorflow/core/kernels/data/experimental/scan_dataset_op.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/core/kernels/data/experimental/scan_dataset_op.cc b/tensorflow/core/kernels/data/experimental/scan_dataset_op.cc index 76ab33fe98..87263b5606 100644 --- a/tensorflow/core/kernels/data/experimental/scan_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/scan_dataset_op.cc @@ -186,6 +186,8 @@ class ScanDatasetOp : public UnaryDatasetOpKernel { Status s = instantiated_captured_func_->Run(ctx, std::move(args), &state_and_output); + DCHECK(state_and_output.size() <= + dataset()->state_types_.size() + output_dtypes().size()); if (s.ok()) { state_.clear(); size_t i = 0; -- GitLab From f9d4786119cda668d9b05d4ae49f2a4fe8d5738f Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Thu, 3 Jan 2019 13:01:24 -0800 Subject: [PATCH 0140/2345] Make tests all have their own main so initialization works. PiperOrigin-RevId: 227735303 --- tensorflow/lite/toco/BUILD | 12 ++++++++---- tensorflow/lite/toco/import_tensorflow_test.cc | 7 +++++++ tensorflow/lite/toco/toco_convert_test.cc | 7 +++++++ tensorflow/lite/toco/toco_port_test.cc | 7 +++++++ tensorflow/lite/toco/tooling_util_test.cc | 9 ++++++++- 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/tensorflow/lite/toco/BUILD b/tensorflow/lite/toco/BUILD index 93d41fcae1..d77234c80a 100644 --- a/tensorflow/lite/toco/BUILD +++ b/tensorflow/lite/toco/BUILD @@ -348,7 +348,8 @@ tf_cc_test( "//tensorflow/core:lib", "//tensorflow/core:ops", "//tensorflow/core:protos_all_cc", - "@com_google_googletest//:gtest_main", + "//tensorflow/lite/testing:util", + "@com_google_googletest//:gtest", ], ) @@ -387,7 +388,8 @@ tf_cc_test( ":model", ":tooling_util", "//tensorflow/core:lib", - "@com_google_googletest//:gtest_main", + "//tensorflow/lite/testing:util", + "@com_google_googletest//:gtest", ], ) @@ -451,12 +453,13 @@ tf_cc_test( ":toco_port", ":toco_tooling", ":types_proto_cc", - "@com_google_googletest//:gtest_main", + "@com_google_googletest//:gtest", "@com_google_absl//absl/strings", "//tensorflow/core:lib", # We cannot embed the core:ops dependency directly into :toco_tooling as # it can conflict with downstream deps when toco is used as a library. "//tensorflow/core:ops", + "//tensorflow/lite/testing:util", ], ) @@ -468,6 +471,7 @@ tf_cc_test( ], deps = [ ":toco_port", - "@com_google_googletest//:gtest_main", + "//tensorflow/lite/testing:util", + "@com_google_googletest//:gtest", ], ) diff --git a/tensorflow/lite/toco/import_tensorflow_test.cc b/tensorflow/lite/toco/import_tensorflow_test.cc index 260704fd2a..70382f546b 100644 --- a/tensorflow/lite/toco/import_tensorflow_test.cc +++ b/tensorflow/lite/toco/import_tensorflow_test.cc @@ -23,6 +23,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor.pb.h" #include "tensorflow/core/framework/tensor_shape.pb.h" #include "tensorflow/core/lib/core/status.h" +#include "tensorflow/lite/testing/util.h" namespace toco { @@ -564,3 +565,9 @@ TEST(ImportTest, UnsupportedOpWithMultipleOutputs) { } // namespace } // namespace toco + +int main(int argc, char** argv) { + ::tflite::LogToStderr(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tensorflow/lite/toco/toco_convert_test.cc b/tensorflow/lite/toco/toco_convert_test.cc index c3c440db94..3730c53ae1 100644 --- a/tensorflow/lite/toco/toco_convert_test.cc +++ b/tensorflow/lite/toco/toco_convert_test.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/lite/toco/toco_convert.h" #include #include +#include "tensorflow/lite/testing/util.h" namespace toco { namespace { @@ -171,3 +172,9 @@ TEST(TocoTest, TransientStringTensors) { } // namespace } // namespace toco + +int main(int argc, char** argv) { + ::tflite::LogToStderr(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tensorflow/lite/toco/toco_port_test.cc b/tensorflow/lite/toco/toco_port_test.cc index f5fbb4caeb..d80d423ed7 100644 --- a/tensorflow/lite/toco/toco_port_test.cc +++ b/tensorflow/lite/toco/toco_port_test.cc @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "tensorflow/lite/toco/toco_port.h" +#include "tensorflow/lite/testing/util.h" #include "tensorflow/lite/toco/toco_types.h" #include @@ -56,3 +57,9 @@ TEST(TocoPortTest, JoinPath) { } // namespace } // namespace port } // namespace toco + +int main(int argc, char** argv) { + ::tflite::LogToStderr(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tensorflow/lite/toco/tooling_util_test.cc b/tensorflow/lite/toco/tooling_util_test.cc index 6f1c9c563a..e44b94b712 100644 --- a/tensorflow/lite/toco/tooling_util_test.cc +++ b/tensorflow/lite/toco/tooling_util_test.cc @@ -16,9 +16,10 @@ limitations under the License. #include #include +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/lite/testing/util.h" #include "tensorflow/lite/toco/model.h" #include "tensorflow/lite/toco/tooling_util.h" -#include "tensorflow/core/lib/core/status.h" namespace toco { @@ -203,3 +204,9 @@ TEST(FusedActivationTest, DefaultsToUnfused) { } } // namespace toco + +int main(int argc, char** argv) { + ::tflite::LogToStderr(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} -- GitLab From 689db4ac81b13279677ceefd84700de3d0ec9925 Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Thu, 3 Jan 2019 13:29:58 -0800 Subject: [PATCH 0141/2345] Replace SimpleGraphView with GraphTopologyView. + added separate enter/advance predicates for DFS traversal PiperOrigin-RevId: 227740260 --- tensorflow/core/grappler/optimizers/BUILD | 7 + .../optimizers/arithmetic_optimizer.cc | 47 ++--- .../grappler/optimizers/loop_optimizer.cc | 40 +++-- .../grappler/optimizers/memory_optimizer.cc | 67 ++++--- tensorflow/core/grappler/utils.cc | 169 ------------------ tensorflow/core/grappler/utils.h | 73 -------- tensorflow/core/grappler/utils/traversal.cc | 19 +- tensorflow/core/grappler/utils/traversal.h | 39 +++- .../core/grappler/utils/traversal_test.cc | 46 ++++- 9 files changed, 184 insertions(+), 323 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 512c3b07b4..e2b9de7cc5 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -254,12 +254,16 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", + "//tensorflow/core/grappler:graph_topology_view", "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler:utils", "//tensorflow/core/grappler/costs:graph_properties", "//tensorflow/core/grappler/utils:symbolic_shapes", "//tensorflow/core/grappler/utils:topological_sort", + "//tensorflow/core/grappler/utils:traversal", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", ], ) @@ -411,6 +415,7 @@ cc_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", + "//tensorflow/core/grappler:graph_topology_view", "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:op_types", @@ -613,11 +618,13 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", + "//tensorflow/core/grappler:graph_topology_view", "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler/costs:graph_properties", "//tensorflow/core/grappler/utils:frame", + "//tensorflow/core/grappler/utils:traversal", "@com_google_absl//absl/container:flat_hash_set", ], ) diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index 94a87c3ff2..e85e0473b3 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -22,6 +22,8 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/attr_value_util.h" #include "tensorflow/core/framework/node_def.pb.h" @@ -31,6 +33,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor_shape.pb.h" #include "tensorflow/core/framework/types.h" #include "tensorflow/core/grappler/costs/graph_properties.h" +#include "tensorflow/core/grappler/graph_topology_view.h" #include "tensorflow/core/grappler/grappler_item.h" #include "tensorflow/core/grappler/op_types.h" #include "tensorflow/core/grappler/optimizers/constant_folding.h" @@ -38,6 +41,7 @@ limitations under the License. #include "tensorflow/core/grappler/utils.h" #include "tensorflow/core/grappler/utils/symbolic_shapes.h" #include "tensorflow/core/grappler/utils/topological_sort.h" +#include "tensorflow/core/grappler/utils/traversal.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/stringpiece.h" #include "tensorflow/core/lib/hash/hash.h" @@ -3334,36 +3338,37 @@ bool ArithmeticOptimizer::CanDedup(const NodeDef& node) const { } void ArithmeticOptimizer::DedupComputations() { - bool stop = true; - SimpleGraphView graph_view; - if (!graph_view.Initialize(*optimized_graph_).ok()) { - LOG(WARNING) << "Failed to build SimpleGraphView."; + GraphTopologyView graph_view; + if (!graph_view.InitializeFromGraph(*optimized_graph_).ok()) { + LOG(WARNING) << "Failed to initialize GraphTopologyView."; return; } - std::set duplicates; + + const absl::flat_hash_set ops_to_traverse = { + "Identity", "IdentityN", "Reshape", "ExpandDims", + "Enter", "Switch", "Merge"}; + // Populate feed_inplace_op; - std::unordered_set feeds_inplace_op; - for (int i = 0; i < optimized_graph_->node_size(); ++i) { - const NodeDef& root = optimized_graph_->node(i); - if (feeds_inplace_op.find(&root) != feeds_inplace_op.end()) { - continue; - } + absl::flat_hash_set feeds_inplace_op; + + for (const NodeDef& root : optimized_graph_->node()) { + if (feeds_inplace_op.find(&root) != feeds_inplace_op.end()) continue; if (ModifiesInputsInPlace(root)) { - const std::unordered_set op_types_to_traverse = { - root.op(), "Identity", "IdentityN", "Reshape", - "ExpandDims", "Enter", "Switch", "Merge"}; + const auto is_continue_traversal = [&](const NodeDef* node) -> bool { + return node->op() == root.op() || ops_to_traverse.count(node->op()) > 0; + }; - graph_view.DepthFirstSearchWithCallback( - op_types_to_traverse, i, - [&](const NodeDef& node) { - feeds_inplace_op.insert(&node); - return false; - }, - SimpleGraphView::kFollowInputs /* search through inputs */); + DfsTraversal(graph_view, {&root}, TraversalDirection::kFollowInputs, + DfsPredicates::Advance(is_continue_traversal), + DfsCallbacks::PreOrder([&](const NodeDef* node) { + feeds_inplace_op.insert(node); + })); } } + bool stop = true; + std::set duplicates; do { stop = true; UniqueNodes nodes; diff --git a/tensorflow/core/grappler/optimizers/loop_optimizer.cc b/tensorflow/core/grappler/optimizers/loop_optimizer.cc index 3606473840..cf5e4db29f 100644 --- a/tensorflow/core/grappler/optimizers/loop_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/loop_optimizer.cc @@ -30,12 +30,14 @@ limitations under the License. #include "tensorflow/core/framework/op.h" #include "tensorflow/core/framework/tensor.pb.h" #include "tensorflow/core/framework/types.h" +#include "tensorflow/core/grappler/graph_topology_view.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/optimizers/evaluation_utils.h" #include "tensorflow/core/grappler/utils/frame.h" +#include "tensorflow/core/grappler/utils/traversal.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/stringpiece.h" #include "tensorflow/core/lib/gtl/inlined_vector.h" @@ -451,16 +453,29 @@ Status LoopInvariantNodeMotionOptimizer::Optimize() { } std::vector GetStackPushNodesToConvert( - const SimpleGraphView& graph_view, + const GraphTopologyView& graph_view, const std::unordered_set& nodes_to_preserve, int stack_node_idx) { VLOG(1) << "Stack node: " << graph_view.graph()->node(stack_node_idx).name(); + const std::unordered_set op_types_to_traverse( {"Stack", "StackV2", "Enter", "RefEnter", "Switch", "RefSwitch", "Identity", "RefIdentity"}); + const auto is_op_to_traverse = [&](const NodeDef* node) -> bool { + return op_types_to_traverse.find(node->op()) != op_types_to_traverse.end(); + }; + std::vector nodes_to_convert; - std::set fanout; - graph_view.DepthFirstSearch(op_types_to_traverse, stack_node_idx, &fanout); - for (int fanout_idx : fanout) { + std::vector fanouts; + + DfsTraversal(graph_view, {graph_view.GetNode(stack_node_idx)}, + TraversalDirection::kFollowOutputs, + DfsPredicates::Advance(is_op_to_traverse), + DfsCallbacks::PreOrder([&](const NodeDef* node) { + const absl::optional idx = graph_view.GetNodeIndex(*node); + fanouts.push_back(idx.value()); + })); + + for (int fanout_idx : fanouts) { const NodeDef& fanout_node = graph_view.graph()->node(fanout_idx); VLOG(1) << "Fanout " << fanout_idx << " : " << fanout_node.name(); if (IsStackPushOp(fanout_node)) { @@ -468,13 +483,12 @@ std::vector GetStackPushNodesToConvert( // happen when the graph we have contains only the forward pass for a loop // (as when the forward and backward passes are split across different // functions). - if (graph_view.has_node(fanout_node.input(0))) { - const NodeDef* stack_node = - &graph_view.node(graph_view.index(fanout_node.input(0))); + if (graph_view.HasNode(fanout_node.input(0))) { + const NodeDef* stack_node = graph_view.GetNode(fanout_node.input(0)); while (stack_node->op() != "Stack" && stack_node->op() != "StackV2" && stack_node->input_size() > 0 && - graph_view.has_node(stack_node->input(0))) { - stack_node = &graph_view.node(graph_view.index(stack_node->input(0))); + graph_view.HasNode(stack_node->input(0))) { + stack_node = graph_view.GetNode(stack_node->input(0)); } if (nodes_to_preserve.find(stack_node->name()) == nodes_to_preserve.end()) { @@ -488,7 +502,7 @@ std::vector GetStackPushNodesToConvert( op_types_to_traverse.end()) { continue; } else if (!IsStackPopOp(fanout_node) || - (!graph_view.outputs(fanout_idx).empty() || + (!graph_view.GetFanout(fanout_idx).empty() || nodes_to_preserve.find(fanout_node.name()) != nodes_to_preserve.end())) { // The node is either a stack pop with consumers or something unexpected @@ -497,14 +511,16 @@ std::vector GetStackPushNodesToConvert( break; } } + return nodes_to_convert; } Status RemoveStackOps(const std::unordered_set& nodes_to_preserve, GraphDef* optimized_graph) { NodeMap node_map(optimized_graph); - SimpleGraphView graph_view; - TF_RETURN_IF_ERROR(graph_view.Initialize(*optimized_graph)); + GraphTopologyView graph_view; + TF_RETURN_IF_ERROR(graph_view.InitializeFromGraph(*optimized_graph)); + for (int node_idx = 0; node_idx < optimized_graph->node_size(); ++node_idx) { if (IsStackOp(optimized_graph->node(node_idx))) { for (int push_node_idx : GetStackPushNodesToConvert( diff --git a/tensorflow/core/grappler/optimizers/memory_optimizer.cc b/tensorflow/core/grappler/optimizers/memory_optimizer.cc index 6e33645537..b50d50f842 100644 --- a/tensorflow/core/grappler/optimizers/memory_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/memory_optimizer.cc @@ -30,6 +30,7 @@ limitations under the License. #include "tensorflow/core/grappler/costs/graph_memory.h" #include "tensorflow/core/grappler/costs/graph_properties.h" #include "tensorflow/core/grappler/costs/utils.h" +#include "tensorflow/core/grappler/graph_topology_view.h" #include "tensorflow/core/grappler/grappler_item.h" #include "tensorflow/core/grappler/mutable_graph_view.h" #include "tensorflow/core/grappler/op_types.h" @@ -188,13 +189,14 @@ std::vector GetOpGroupsToRecompute( } } // Recompute only nodes which eventually feed into a target node. - connected_subgraph(node_map, - true, // Collect inputs - false, // Collect outputs - [&unpruned_recompute_nodes](const NodeDef& node) { - return unpruned_recompute_nodes.count(&node) != 0; - }, - ¤t_recomputation.recomputed_source_nodes); + connected_subgraph( + node_map, + true, // Collect inputs + false, // Collect outputs + [&unpruned_recompute_nodes](const NodeDef& node) { + return unpruned_recompute_nodes.count(&node) != 0; + }, + ¤t_recomputation.recomputed_source_nodes); if (current_recomputation.target_nodes.empty()) { continue; } @@ -1268,46 +1270,55 @@ Status RelaxAllocatorConstraints(GraphDef* optimized_graph) { return Status::OK(); } - std::unordered_set optimized_nodes; - SimpleGraphView graph_view; - TF_RETURN_IF_ERROR(graph_view.Initialize(*optimized_graph)); + GraphTopologyView graph_view; + TF_RETURN_IF_ERROR(graph_view.InitializeFromGraph(*optimized_graph)); + std::unordered_set optimized_nodes; + for (int i : assign_nodes) { - if (optimized_nodes.find(i) == optimized_nodes.end()) { - const NodeDef& assign_node = optimized_graph->node(i); - optimized_nodes.insert(i); - std::vector assign_nodes_in_fanout; - assign_nodes_in_fanout.push_back(i); - std::set transitive_fanout; - graph_view.DepthFirstSearch(std::unordered_set{}, i, - &transitive_fanout); + const NodeDef& assign_node = optimized_graph->node(i); + + if (optimized_nodes.find(&assign_node) == optimized_nodes.end()) { + std::vector assign_nodes_in_fanout; + optimized_nodes.insert(&assign_node); + assign_nodes_in_fanout.push_back(&assign_node); + + std::vector transitive_fanout; + DfsTraversal(graph_view, {graph_view.GetNode(i)}, + TraversalDirection::kFollowOutputs, + DfsCallbacks::PreOrder([&](const NodeDef* node) { + transitive_fanout.push_back(node); + })); + bool relax_constraint = true; // If all nodes in the transitive fanout are on the same device as the // assign node, there is no need to allocate the output in pinned memory. - for (int fanout : transitive_fanout) { - const NodeDef& fanout_node = optimized_graph->node(fanout); + for (const NodeDef* fanout_node : transitive_fanout) { + // const NodeDef& fanout_node = optimized_graph->node(fanout); if (relax_constraint && - (IsSend(fanout_node) || - CrossesTaskOrCpuGpuBoundary(fanout_node, assign_node))) { + (IsSend(*fanout_node) || + CrossesTaskOrCpuGpuBoundary(*fanout_node, assign_node))) { relax_constraint = false; break; } - if (optimized_nodes.find(fanout) == optimized_nodes.end() && - IsAssign(fanout_node)) { - assign_nodes_in_fanout.push_back(fanout); + if (optimized_nodes.find(fanout_node) == optimized_nodes.end() && + IsAssign(*fanout_node)) { + assign_nodes_in_fanout.push_back(fanout_node); } } if (relax_constraint) { - for (int assign_idx : assign_nodes_in_fanout) { + for (const NodeDef* assign_node_in_fanout : assign_nodes_in_fanout) { // If all devices match in fanout of node(i) then, by transitivity, // they must also match in the fanout of other assign nodes // in the fanout of node(i), so we can process them here, // and save computing their transitive fanout later. - optimized_nodes.insert(assign_idx); + optimized_nodes.insert(assign_node_in_fanout); // Set an attribute telling AssignOp to ignore allocator constraints. + const absl::optional assign_node_idx = + graph_view.GetNodeIndex(*assign_node_in_fanout); NodeDef* assign_node_to_relax = - optimized_graph->mutable_node(assign_idx); + optimized_graph->mutable_node(assign_node_idx.value()); (*assign_node_to_relax ->mutable_attr())["_grappler_relax_allocator_constraints"] .set_b(true); diff --git a/tensorflow/core/grappler/utils.cc b/tensorflow/core/grappler/utils.cc index d8fb88d95f..cc9a38b2d2 100644 --- a/tensorflow/core/grappler/utils.cc +++ b/tensorflow/core/grappler/utils.cc @@ -402,175 +402,6 @@ void EraseNodesFromGraph(const std::set& nodes_to_delete, EraseNodesFromGraphImpl(nodes_idx_to_delete, graph); } -Status SimpleGraphView::Initialize( - const GraphDef& graph, - const std::vector>* - extra_dependencies, - bool dedup_inputs, bool dedup_outputs) { - graph_ = &graph; - const int num_nodes = graph.node_size(); - inputs_.clear(); - inputs_.resize(num_nodes); - outputs_.clear(); - outputs_.resize(num_nodes); - name_to_index_.clear(); - name_to_index_.reserve(num_nodes); - index_to_name_.clear(); - index_to_name_.reserve(num_nodes); - - // Build map from name to index and vice versa. - for (int node_idx = 0; node_idx < num_nodes; ++node_idx) { - const NodeDef& node = graph.node(node_idx); - name_to_index_.emplace(node.name(), node_idx); - index_to_name_.push_back(node.name()); - } - - if (extra_dependencies) { - for (const auto& dep : *extra_dependencies) { - auto itr_src = name_to_index_.find(dep.first->name()); - if (itr_src == name_to_index_.end()) { - return errors::InvalidArgument("Non-existent src ", dep.first->name()); - } - auto itr_tgt = name_to_index_.find(dep.second->name()); - if (itr_tgt == name_to_index_.end()) { - return errors::InvalidArgument("Non-existent tgt ", dep.second->name()); - } - const int src_idx = itr_src->second; - const int tgt_idx = itr_tgt->second; - inputs_[tgt_idx].push_back(src_idx); - outputs_[src_idx].push_back(tgt_idx); - } - } - - // Build forward and reverse adjacency lists. - for (int node_idx = 0; node_idx < num_nodes; ++node_idx) { - const NodeDef& node = graph.node(node_idx); - inputs_[node_idx].reserve(node.input_size()); - for (const string& input : node.input()) { - auto it = name_to_index_.find(NodeName(input)); - if (it == name_to_index_.end()) { - return errors::InvalidArgument("Non-existent input ", input, - " for node ", node.name()); - } - const int input_idx = it->second; - inputs_[node_idx].push_back(input_idx); - outputs_[input_idx].push_back(node_idx); - } - if (dedup_inputs) { - // Dedup the input list while it's still hot in cache. - STLSortAndRemoveDuplicates(&inputs_[node_idx]); - } - } - - // Dedup outputs. - if (dedup_outputs) { - for (int node_idx = 0; node_idx < num_nodes; ++node_idx) { - STLSortAndRemoveDuplicates(&outputs_[node_idx]); - } - } - return Status::OK(); -} - -void SimpleGraphView::DepthFirstSearch( - const std::unordered_set& op_types_to_traverse, int root_node, - std::set* nodes_found, - SimpleGraphView::SearchDirection direction) const { - nodes_found->clear(); - const string& op_type = graph_->node(root_node).op(); - if (!op_types_to_traverse.empty() && - op_types_to_traverse.find(op_type) == op_types_to_traverse.end()) { - return; - } - std::vector stack; - stack.reserve(32); - stack.push_back(root_node); - - auto push_neighbors = [&stack, - &nodes_found](absl::Span neighbors) { - for (auto output_idx : neighbors) { - if (nodes_found->find(output_idx) == nodes_found->end()) { - stack.push_back(output_idx); - } - } - }; - - while (!stack.empty()) { - const int node_idx = stack.back(); - stack.pop_back(); - nodes_found->insert(node_idx); - const string& op_type = graph_->node(node_idx).op(); - if (op_types_to_traverse.empty() || - op_types_to_traverse.find(op_type) != op_types_to_traverse.end()) { - if (direction == kFollowOutputs) { - push_neighbors(this->outputs(node_idx)); - } else { - push_neighbors(this->inputs(node_idx)); - } - } - } -} - -void SimpleGraphView::DepthFirstSearchWithCallback( - const std::unordered_set& op_types_to_traverse, int node_idx, - SimpleGraphView::DFSCallback callback, - SimpleGraphView::SearchDirection direction) const { - std::set nodes_found; - nodes_found.clear(); - const string& op_type = graph_->node(node_idx).op(); - if (!op_types_to_traverse.empty() && - op_types_to_traverse.find(op_type) == op_types_to_traverse.end()) { - return; - } - std::vector stack; - stack.reserve(32); - stack.push_back(node_idx); - auto push_neighbors = [&stack, - &nodes_found](absl::Span neighbors) { - for (auto output_idx : neighbors) { - if (nodes_found.find(output_idx) == nodes_found.end()) { - stack.push_back(output_idx); - } - } - }; - while (!stack.empty()) { - const int node_idx = stack.back(); - stack.pop_back(); - if (callback(graph_->node(node_idx))) { - return; - } - nodes_found.insert(node_idx); - const string& op_type = graph_->node(node_idx).op(); - if (op_types_to_traverse.empty() || - op_types_to_traverse.find(op_type) != op_types_to_traverse.end()) { - if (direction == kFollowOutputs) { - push_neighbors(this->outputs(node_idx)); - } else { - push_neighbors(this->inputs(node_idx)); - } - } - } -} - -string SimpleGraphView::PrintToString() const { - string str; - for (int i = 0; i < num_nodes(); ++i) { - strings::StrAppend(&str, "Node ", i, "'", node_name(i), "'\n", "Inputs: ["); - for (int input : inputs(i)) { - strings::StrAppend(&str, input, " '", node_name(input), "', "); - } - strings::StrAppend(&str, "]\n", "Outputs: ["); - for (int j = 0; j < outputs(i).size(); ++j) { - const int output = outputs(i)[j]; - if (j > 0) { - strings::StrAppend(&str, ", "); - } - strings::StrAppend(&str, output, " '", node_name(output), "'"); - } - strings::StrAppend(&str, "]\n"); - } - return str; -} - #define HANDLE_CASE(DTYPE) \ case DTYPE: \ if (!SafeSetScalarTensorValue::Type>( \ diff --git a/tensorflow/core/grappler/utils.h b/tensorflow/core/grappler/utils.h index a9d6abd6db..1e820977ae 100644 --- a/tensorflow/core/grappler/utils.h +++ b/tensorflow/core/grappler/utils.h @@ -302,79 +302,6 @@ void EraseNodesFromGraph(std::vector&& nodes_to_delete, GraphDef* graph); void EraseNodesFromGraph(const std::set& nodes_to_delete, GraphDef* graph); -class SimpleGraphView { - public: - // Build a graph view for the specified graphdef. - Status Initialize(const GraphDef& graph) { - return Initialize(graph, nullptr, true, true); - } - // Build a graph view for the specified graphdef augmented with the additional - // edges specified in 'extra_dependencies' if any. Note that - // extra_dependencies can be null. - Status Initialize( - const GraphDef& graph, - const std::vector>* - extra_dependencies) { - return Initialize(graph, extra_dependencies, true, true); - } - Status Initialize( - const GraphDef& graph, - const std::vector>* - extra_dependencies, - bool dedup_inputs, bool dedup_outputs); - - const GraphDef* graph() const { return graph_; } - inline int num_nodes() const { return index_to_name_.size(); } - inline bool has_node(const string& node_name) const { - return name_to_index_.find(node_name) != name_to_index_.end(); - } - inline const int index(const string& node_name) const { - const auto& it = name_to_index_.find(node_name); - DCHECK(it != name_to_index_.end()); - return it == name_to_index_.end() ? -1 : it->second; - } - inline const NodeDef& node(int node_idx) const { - return graph_->node(node_idx); - } - inline const string& node_name(int node_idx) const { - return index_to_name_[node_idx]; - } - inline const gtl::InlinedVector& inputs(int node_idx) const { - return inputs_[node_idx]; - } - inline const gtl::InlinedVector& outputs(int node_idx) const { - return outputs_[node_idx]; - } - - enum SearchDirection { kFollowOutputs = 1, kFollowInputs = 2 }; - - // Traverse the graph starting at `node_idx`, collecting indices of nodes - // visited in nodes_found. If a node has an op in `op_types_to_traverse`, the - // walk continues to its children. It is assumed that *graph_ was not modified - // after the call to Initialize(). - // If `op_types_to_traverse` is empty the DFS will traverse any node type. - void DepthFirstSearch(const std::unordered_set& op_types_to_traverse, - int node_idx, std::set* nodes_found, - SearchDirection direction = kFollowOutputs) const; - - typedef std::function DFSCallback; - - // Like DepthFirstSearch, but invoke `callback` as each node is discovered. If - // `callback` returns true, the search is terminated early. - void DepthFirstSearchWithCallback( - const std::unordered_set& op_types_to_traverse, int node_idx, - DFSCallback callback, SearchDirection direction = kFollowOutputs) const; - - string PrintToString() const; - - private: - const GraphDef* graph_; // Not owned. - std::vector index_to_name_; - gtl::FlatMap name_to_index_; - std::vector> inputs_; - std::vector> outputs_; -}; - } // end namespace grappler } // end namespace tensorflow diff --git a/tensorflow/core/grappler/utils/traversal.cc b/tensorflow/core/grappler/utils/traversal.cc index 3231094fd4..c602e8c0e4 100644 --- a/tensorflow/core/grappler/utils/traversal.cc +++ b/tensorflow/core/grappler/utils/traversal.cc @@ -44,8 +44,8 @@ enum class NodeState { kNotVisited, kVisiting, kDone }; void DfsTraversal(const GraphTopologyView& graph_view, const absl::Span from, - TraversalDirection direction, - const std::function& should_visit, + const TraversalDirection direction, + const DfsPredicates& predicates, const DfsCallbacks& callbacks) { std::vector stack; stack.reserve(from.size()); @@ -66,8 +66,8 @@ void DfsTraversal(const GraphTopologyView& graph_view, NodeState& state = node_state[w.node]; if (state == NodeState::kDone) continue; - // Skip nodes that do not match predicate. - if (should_visit && !should_visit(graph_view.GetNode(w.node))) { + // Skip nodes that we should not enter. + if (predicates.enter && !predicates.enter(graph_view.GetNode(w.node))) { state = NodeState::kDone; continue; } @@ -98,6 +98,11 @@ void DfsTraversal(const GraphTopologyView& graph_view, // Enqueue the node again with the children_visited flag set to true. stack.emplace_back(w.node, true, w.src); + // Check if we can continue traversal from the current node. + if (predicates.advance && !predicates.advance(graph_view.GetNode(w.node))) { + continue; + } + // Now enqueue the fanin/fanout nodes. if (direction == TraversalDirection::kFollowInputs) { for (const int fanin : graph_view.GetFanin(w.node)) { @@ -111,14 +116,10 @@ void DfsTraversal(const GraphTopologyView& graph_view, } } -// Traverse the graph in DFS order in the given direction, starting from the -// list of nodes specified in the `from` argument. Call corresponding -// callbacks for each visited node. void DfsTraversal(const GraphTopologyView& graph_view, const absl::Span from, TraversalDirection direction, const DfsCallbacks& callbacks) { - const auto visit_all = [](const NodeDef*) { return true; }; - DfsTraversal(graph_view, from, direction, visit_all, callbacks); + DfsTraversal(graph_view, from, direction, {}, callbacks); } } // namespace grappler diff --git a/tensorflow/core/grappler/utils/traversal.h b/tensorflow/core/grappler/utils/traversal.h index ec6009096d..5c9dada493 100644 --- a/tensorflow/core/grappler/utils/traversal.h +++ b/tensorflow/core/grappler/utils/traversal.h @@ -33,6 +33,7 @@ enum class TraversalDirection { kFollowInputs, kFollowOutputs }; // corresponding back edges. Moreover, the pre and post order will assume that // these back edges will be cut. struct DfsCallbacks { + DfsCallbacks() = default; DfsCallbacks(std::function pre, std::function post, std::function back_edge) @@ -53,16 +54,40 @@ struct DfsCallbacks { std::function on_back_edge; }; +// Encapsulate DFS predicates for traversing the graph. +// +// The `enter` predicate decides if traversal should enter the node, and the +// `advance` predicate decides if the traversal should follow inputs/outputs +// from the node. +// +// If predicates are empty (default initialized), it's assumed that we can enter +// into any node and advance from any node respectively. +struct DfsPredicates { + DfsPredicates() = default; + DfsPredicates(std::function enter, + std::function advance) + : enter(std::move(enter)), advance(std::move(advance)) {} + + static DfsPredicates Enter(std::function enter) { + return DfsPredicates(std::move(enter), nullptr); + } + + static DfsPredicates Advance(std::function advance) { + return DfsPredicates(nullptr, std::move(advance)); + } + + std::function enter; + std::function advance; +}; + // Traverse the graph in DFS order in the given direction, starting from the -// list of nodes specified in the `from` argument. Use `should_visit` to decide -// if the node should be visited. This predicate also applied to the `from` -// nodes. Call corresponding callbacks for each visited node. If `should_visit` -// is empty (default initialized) it's assumed that traversal can visit all the -// nodes. +// list of nodes specified in the `from` argument. Use `predicates` to decide if +// traversal should enter/advance to/from the graph node. These predicates also +// applied to the `from` nodes. Call corresponding callbacks for each visited +// node. void DfsTraversal(const GraphTopologyView& graph_view, absl::Span from, - TraversalDirection direction, - const std::function& should_visit, + TraversalDirection direction, const DfsPredicates& predicates, const DfsCallbacks& callbacks); // Traverse the graph in DFS order in the given direction, starting from the diff --git a/tensorflow/core/grappler/utils/traversal_test.cc b/tensorflow/core/grappler/utils/traversal_test.cc index 5a68fa201e..7b36d328e9 100644 --- a/tensorflow/core/grappler/utils/traversal_test.cc +++ b/tensorflow/core/grappler/utils/traversal_test.cc @@ -159,7 +159,7 @@ TEST(TraversalTest, OutputDfsWithLoop) { EXPECT_EQ(back_edges, expected_edges); } -TEST(TraversalTest, DfsWithPredicate) { +TEST(TraversalTest, DfsWithEnterPredicate) { const string op = "OpIsNotImportantInThisTest"; GraphDef graph = ::tensorflow::test::function::GDef( // @@ -171,8 +171,8 @@ TEST(TraversalTest, DfsWithPredicate) { NDef("6", op, {"3", "5"}, {})}, // /*funcs=*/{}); - // Do not go through nodes '2' and '3'. - const auto predicate = [](const NodeDef* node) { + // Do not enter the nodes '2' and '3'. + const auto enter = [](const NodeDef* node) { return node->name() != "2" && node->name() != "3"; }; @@ -185,7 +185,8 @@ TEST(TraversalTest, DfsWithPredicate) { GraphTopologyView graph_view; TF_CHECK_OK(graph_view.InitializeFromGraph(graph)); DfsTraversal(graph_view, start_nodes, TraversalDirection::kFollowOutputs, - predicate, MkCallbacks(&pre_order, &post_order, &back_edges)); + DfsPredicates::Enter(enter), + MkCallbacks(&pre_order, &post_order, &back_edges)); const std::vector expected_pre = {"1", "4", "5", "6"}; const std::vector expected_post = {"6", "5", "4", "1"}; @@ -195,6 +196,43 @@ TEST(TraversalTest, DfsWithPredicate) { EXPECT_TRUE(back_edges.empty()); } +TEST(TraversalTest, DfsWithAdvancePredicate) { + const string op = "OpIsNotImportantInThisTest"; + + GraphDef graph = ::tensorflow::test::function::GDef( // + {NDef("1", op, {}, {}), // 2 -> 3 + NDef("2", op, {"1"}, {}), // 1 -> / \ -> 6 + NDef("3", op, {"2"}, {}), // \ / + NDef("4", op, {"1"}, {}), // 4 -> 5 + NDef("5", op, {"4"}, {}), // + NDef("6", op, {"3", "5"}, {})}, // + {} /* empty function library*/); + + // Do not advance from the nodes '2' and '3'. + const auto advance = [](const NodeDef* node) { + return node->name() != "2" && node->name() != "3"; + }; + + std::vector start_nodes = {&graph.node(0)}; + + std::vector pre_order; + std::vector post_order; + std::vector back_edges; + + GraphTopologyView graph_view; + TF_CHECK_OK(graph_view.InitializeFromGraph(graph)); + DfsTraversal(graph_view, start_nodes, TraversalDirection::kFollowOutputs, + DfsPredicates::Advance(advance), + MkCallbacks(&pre_order, &post_order, &back_edges)); + + const std::vector expected_pre = {"1", "4", "5", "6", "2"}; + const std::vector expected_post = {"6", "5", "4", "2", "1"}; + + EXPECT_EQ(pre_order, expected_pre); + EXPECT_EQ(post_order, expected_post); + EXPECT_TRUE(back_edges.empty()); +} + } // namespace } // namespace grappler } // namespace tensorflow -- GitLab From 75f12a50203ca31370c5edc02d650ee4a47fdf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Thu, 3 Jan 2019 22:36:43 +0100 Subject: [PATCH 0142/2345] Fix several DeprecationWarning: invlid escape sequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mickaël Schoentgen --- .../contrib/kernel_methods/python/losses.py | 2 +- .../python/learn/learn_io/generator_io_test.py | 4 ++-- .../opt/python/training/adam_gs_optimizer.py | 2 +- tensorflow/contrib/optimizer_v2/adam.py | 2 +- tensorflow/python/client/session_test.py | 2 +- .../python/feature_column/feature_column_test.py | 12 ++++++------ .../feature_column/feature_column_v2_test.py | 16 ++++++++-------- .../python/kernel_tests/confusion_matrix_test.py | 4 ++-- tensorflow/python/training/adam.py | 2 +- tensorflow/tools/ci_build/copy_binary.py | 2 +- .../scripts_allreduce/k8s_generate_yaml_lib.py | 2 +- .../windows/msvc_wrapper_for_nvcc.py | 2 +- .../windows/msvc_wrapper_for_nvcc.py | 2 +- .../gcc-nvcc/windows/msvc_wrapper_for_nvcc.py | 2 +- 14 files changed, 28 insertions(+), 28 deletions(-) diff --git a/tensorflow/contrib/kernel_methods/python/losses.py b/tensorflow/contrib/kernel_methods/python/losses.py index 4ef0a66a52..294a7d69a7 100644 --- a/tensorflow/contrib/kernel_methods/python/losses.py +++ b/tensorflow/contrib/kernel_methods/python/losses.py @@ -34,7 +34,7 @@ def sparse_multiclass_hinge_loss( scope=None, loss_collection=ops.GraphKeys.LOSSES, reduction=losses.Reduction.SUM_BY_NONZERO_WEIGHTS): - """Adds Ops for computing the multiclass hinge loss. + r"""Adds Ops for computing the multiclass hinge loss. The implementation is based on the following paper: On the Algorithmic Implementation of Multiclass Kernel-based Vector Machines diff --git a/tensorflow/contrib/learn/python/learn/learn_io/generator_io_test.py b/tensorflow/contrib/learn/python/learn/learn_io/generator_io_test.py index 5e90d1fa20..318046733b 100644 --- a/tensorflow/contrib/learn/python/learn/learn_io/generator_io_test.py +++ b/tensorflow/contrib/learn/python/learn/learn_io/generator_io_test.py @@ -174,7 +174,7 @@ class GeneratorIoTest(test.TestCase): return np.arange(32, 36) with self.cached_session(): - with self.assertRaisesRegexp(TypeError, 'x\(\) must be generator'): + with self.assertRaisesRegexp(TypeError, r'x\(\) must be generator'): failing_input_fn = generator_io.generator_input_fn( generator, batch_size=2, shuffle=False, num_epochs=1) failing_input_fn() @@ -185,7 +185,7 @@ class GeneratorIoTest(test.TestCase): yield np.arange(32, 36) with self.cached_session(): - with self.assertRaisesRegexp(TypeError, 'x\(\) must yield dict'): + with self.assertRaisesRegexp(TypeError, r'x\(\) must yield dict'): failing_input_fn = generator_io.generator_input_fn( generator, batch_size=2, shuffle=False, num_epochs=1) failing_input_fn() diff --git a/tensorflow/contrib/opt/python/training/adam_gs_optimizer.py b/tensorflow/contrib/opt/python/training/adam_gs_optimizer.py index 3fb649ea82..855fbf58bf 100644 --- a/tensorflow/contrib/opt/python/training/adam_gs_optimizer.py +++ b/tensorflow/contrib/opt/python/training/adam_gs_optimizer.py @@ -41,7 +41,7 @@ class AdamGSOptimizer(optimizer.Optimizer): def __init__(self, global_step=0, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8, use_locking=False, name="Adam"): - """Construct a new Adam optimizer. + r"""Construct a new Adam optimizer. Branched from tf.train.AdamOptimizer. The only difference is to pass global step for computing beta1 and beta2 accumulators, instead of having diff --git a/tensorflow/contrib/optimizer_v2/adam.py b/tensorflow/contrib/optimizer_v2/adam.py index 248ffb1f7e..1b7800f324 100644 --- a/tensorflow/contrib/optimizer_v2/adam.py +++ b/tensorflow/contrib/optimizer_v2/adam.py @@ -36,7 +36,7 @@ class AdamOptimizer(optimizer_v2.OptimizerV2): def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8, use_locking=False, name="Adam"): - """Construct a new Adam optimizer. + r"""Construct a new Adam optimizer. Initialization: diff --git a/tensorflow/python/client/session_test.py b/tensorflow/python/client/session_test.py index c4a118a414..da6218663d 100644 --- a/tensorflow/python/client/session_test.py +++ b/tensorflow/python/client/session_test.py @@ -2036,7 +2036,7 @@ class SessionTest(test_util.TensorFlowTestCase): with self.cached_session() as sess: a = array_ops.placeholder(dtype=dtypes.string) with self.assertRaisesRegexp( - TypeError, 'Type of feed value 1 with type <(\w+) \'int\'> is not'): + TypeError, r'Type of feed value 1 with type <(\w+) \'int\'> is not'): sess.run(a, feed_dict={a: 1}) diff --git a/tensorflow/python/feature_column/feature_column_test.py b/tensorflow/python/feature_column/feature_column_test.py index daa0a3b3a4..0ded2bf8c9 100644 --- a/tensorflow/python/feature_column/feature_column_test.py +++ b/tensorflow/python/feature_column/feature_column_test.py @@ -1832,7 +1832,7 @@ class LinearModelTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): fc.linear_model(features, [price1, price2]) def test_subset_of_static_batch_size_mismatch(self): @@ -1847,7 +1847,7 @@ class LinearModelTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string fc.linear_model(features, [price1, price2, price3]) def test_runtime_batch_size_mismatch(self): @@ -2467,7 +2467,7 @@ class _LinearModelTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string get_keras_linear_model_predictions(features, [price1, price2]) def test_subset_of_static_batch_size_mismatch(self): @@ -2482,7 +2482,7 @@ class _LinearModelTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string get_keras_linear_model_predictions(features, [price1, price2, price3]) def test_runtime_batch_size_mismatch(self): @@ -2974,7 +2974,7 @@ class FunctionalInputLayerTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string fc.input_layer(features, [price1, price2]) def test_subset_of_static_batch_size_mismatch(self): @@ -2989,7 +2989,7 @@ class FunctionalInputLayerTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string fc.input_layer(features, [price1, price2, price3]) def test_runtime_batch_size_mismatch(self): diff --git a/tensorflow/python/feature_column/feature_column_v2_test.py b/tensorflow/python/feature_column/feature_column_v2_test.py index a247425369..2a864a0573 100644 --- a/tensorflow/python/feature_column/feature_column_v2_test.py +++ b/tensorflow/python/feature_column/feature_column_v2_test.py @@ -2052,7 +2052,7 @@ class LinearModelTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string model = fc.LinearModel([price1, price2]) model(features) @@ -2068,7 +2068,7 @@ class LinearModelTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string model = fc.LinearModel([price1, price2, price3]) model(features) @@ -2818,7 +2818,7 @@ class OldLinearModelTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string fc_old.linear_model(features, [price1, price2]) def test_subset_of_static_batch_size_mismatch(self): @@ -2833,7 +2833,7 @@ class OldLinearModelTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string fc_old.linear_model(features, [price1, price2, price3]) def test_runtime_batch_size_mismatch(self): @@ -3435,7 +3435,7 @@ class DenseFeaturesTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string fc.DenseFeatures([price1, price2])(features) def test_subset_of_static_batch_size_mismatch(self): @@ -3450,7 +3450,7 @@ class DenseFeaturesTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string fc.DenseFeatures([price1, price2, price3])(features) def test_runtime_batch_size_mismatch(self): @@ -4141,7 +4141,7 @@ class FunctionalInputLayerTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string fc_old.input_layer(features, [price1, price2]) def test_subset_of_static_batch_size_mismatch(self): @@ -4156,7 +4156,7 @@ class FunctionalInputLayerTest(test.TestCase): } with self.assertRaisesRegexp( ValueError, - 'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string + r'Batch size \(first dimension\) of each feature must be same.'): # pylint: disable=anomalous-backslash-in-string fc_old.input_layer(features, [price1, price2, price3]) def test_runtime_batch_size_mismatch(self): diff --git a/tensorflow/python/kernel_tests/confusion_matrix_test.py b/tensorflow/python/kernel_tests/confusion_matrix_test.py index ae13c8e32e..6670f0326c 100644 --- a/tensorflow/python/kernel_tests/confusion_matrix_test.py +++ b/tensorflow/python/kernel_tests/confusion_matrix_test.py @@ -472,7 +472,7 @@ class RemoveSqueezableDimensionsTest(test.TestCase): } with self.assertRaisesRegexp( errors_impl.InvalidArgumentError, - "Can not squeeze dim\[2\]"): + r"Can not squeeze dim\[2\]"): dynamic_labels.eval(feed_dict=feed_dict) self.assertAllEqual( prediction_values, dynamic_predictions.eval(feed_dict=feed_dict)) @@ -500,7 +500,7 @@ class RemoveSqueezableDimensionsTest(test.TestCase): label_values, dynamic_labels.eval(feed_dict=feed_dict)) with self.assertRaisesRegexp( errors_impl.InvalidArgumentError, - "Can not squeeze dim\[2\]"): + r"Can not squeeze dim\[2\]"): dynamic_predictions.eval(feed_dict=feed_dict) diff --git a/tensorflow/python/training/adam.py b/tensorflow/python/training/adam.py index 0c701f4712..c204a5c1ee 100644 --- a/tensorflow/python/training/adam.py +++ b/tensorflow/python/training/adam.py @@ -39,7 +39,7 @@ class AdamOptimizer(optimizer.Optimizer): def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8, use_locking=False, name="Adam"): - """Construct a new Adam optimizer. + r"""Construct a new Adam optimizer. Initialization: diff --git a/tensorflow/tools/ci_build/copy_binary.py b/tensorflow/tools/ci_build/copy_binary.py index 148526492d..40a7443745 100755 --- a/tensorflow/tools/ci_build/copy_binary.py +++ b/tensorflow/tools/ci_build/copy_binary.py @@ -33,7 +33,7 @@ import tempfile import zipfile TF_NIGHTLY_REGEX = (r"(.+)tf_nightly(|_gpu)-(\d\.[\d]{1,2}" - "\.\d.dev[\d]{0,8})-(.+)\.whl") + r"\.\d.dev[\d]{0,8})-(.+)\.whl") BINARY_STRING_TEMPLATE = "%s-%s-%s.whl" diff --git a/tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml_lib.py b/tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml_lib.py index c570d1a9f8..038a712d53 100644 --- a/tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml_lib.py +++ b/tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml_lib.py @@ -195,7 +195,7 @@ def generate_RSA(bits=2048, exponent=65537): def get_change_ssh_port(use_hostnet, port): if use_hostnet == 1: - return "sed -i '/Port 22/c\Port {}' /etc/ssh/sshd_config".format(port) + return r"sed -i '/Port 22/c\Port {}' /etc/ssh/sshd_config".format(port) return '' diff --git a/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda10.0/windows/msvc_wrapper_for_nvcc.py b/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda10.0/windows/msvc_wrapper_for_nvcc.py index 00483951af..2ea20b8b53 100755 --- a/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda10.0/windows/msvc_wrapper_for_nvcc.py +++ b/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda10.0/windows/msvc_wrapper_for_nvcc.py @@ -104,7 +104,7 @@ def InvokeNvcc(argv, log=False): """ src_files = [f for f in argv if - re.search('\.cpp$|\.cc$|\.c$|\.cxx$|\.C$', f)] + re.search(r'\.cpp$|\.cc$|\.c$|\.cxx$|\.C$', f)] if len(src_files) == 0: raise Error('No source files found for cuda compilation.') diff --git a/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda9.0/windows/msvc_wrapper_for_nvcc.py b/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda9.0/windows/msvc_wrapper_for_nvcc.py index 859b3196d5..2d0898e9cb 100755 --- a/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda9.0/windows/msvc_wrapper_for_nvcc.py +++ b/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda9.0/windows/msvc_wrapper_for_nvcc.py @@ -104,7 +104,7 @@ def InvokeNvcc(argv, log=False): """ src_files = [f for f in argv if - re.search('\.cpp$|\.cc$|\.c$|\.cxx$|\.C$', f)] + re.search(r'\.cpp$|\.cc$|\.c$|\.cxx$|\.C$', f)] if len(src_files) == 0: raise Error('No source files found for cuda compilation.') diff --git a/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc/windows/msvc_wrapper_for_nvcc.py b/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc/windows/msvc_wrapper_for_nvcc.py index 859b3196d5..2d0898e9cb 100755 --- a/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc/windows/msvc_wrapper_for_nvcc.py +++ b/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc/windows/msvc_wrapper_for_nvcc.py @@ -104,7 +104,7 @@ def InvokeNvcc(argv, log=False): """ src_files = [f for f in argv if - re.search('\.cpp$|\.cc$|\.c$|\.cxx$|\.C$', f)] + re.search(r'\.cpp$|\.cc$|\.c$|\.cxx$|\.C$', f)] if len(src_files) == 0: raise Error('No source files found for cuda compilation.') -- GitLab From 4ee52f8b0751004e76164464938cafed9f1f3623 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Thu, 3 Jan 2019 13:33:29 -0800 Subject: [PATCH 0143/2345] [TF:XLA] Bump open source llvm revision to r350327 PiperOrigin-RevId: 227740939 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 11ce55feda..7792824dbf 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -498,11 +498,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 = "65a1aeb29e5940f9f480a41e904659d944e738458afd139caa7bde14bd6aab8a", - strip_prefix = "llvm-331ffd31b3dd49b3f02a27556938b836b679f564", + sha256 = "3a1c8a817d2d9a85b85e1ad776a90faa51f475bb924fbf70383892bb1faceac9", + strip_prefix = "llvm-6da47bee848304d215e3ca08b2a4a4dc9c954310", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/331ffd31b3dd49b3f02a27556938b836b679f564.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/331ffd31b3dd49b3f02a27556938b836b679f564.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/6da47bee848304d215e3ca08b2a4a4dc9c954310.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/6da47bee848304d215e3ca08b2a4a4dc9c954310.tar.gz", ], ) -- GitLab From 6f4410f580ff48078f33c343b72d742095535fae Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Thu, 3 Jan 2019 14:05:20 -0800 Subject: [PATCH 0144/2345] Disable on mac a flaky test. PiperOrigin-RevId: 227746737 --- tensorflow/lite/kernels/internal/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/lite/kernels/internal/BUILD b/tensorflow/lite/kernels/internal/BUILD index 5eb8a353b7..92b4705908 100644 --- a/tensorflow/lite/kernels/internal/BUILD +++ b/tensorflow/lite/kernels/internal/BUILD @@ -654,6 +654,8 @@ cc_test( "logsoftmax_quantized_test.cc", ], tags = [ + # TODO(b/122242739): Reenable after fixing the flakiness? + "nomac", "tflite_not_portable", ], deps = [ -- GitLab From 4dc0ae2baf454ba3d3ee531d889079b0ff05bc95 Mon Sep 17 00:00:00 2001 From: Blake Hechtman Date: Thu, 3 Jan 2019 14:13:01 -0800 Subject: [PATCH 0145/2345] Fix typo. PiperOrigin-RevId: 227748088 --- tensorflow/compiler/xla/service/layout_assignment.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/layout_assignment.cc b/tensorflow/compiler/xla/service/layout_assignment.cc index ebf7a88fae..09246db68e 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.cc +++ b/tensorflow/compiler/xla/service/layout_assignment.cc @@ -1239,7 +1239,7 @@ Status LayoutAssignment::PropagateUseConstraintToDefs( namespace { // A transpose or a reshape that only changes trivial dimensions have meaningful // layouts that are valuable to propagate in a depthfirst manner to avoid -// unassinged layouts in the graph. +// unassigned layouts in the graph. bool InstructionShouldPropagateDepthFirst(const HloInstruction& hlo) { switch (hlo.opcode()) { case HloOpcode::kReshape: -- GitLab From 7c9323bedc48c98be3c07b72ec1d6f4dccdefb35 Mon Sep 17 00:00:00 2001 From: Penporn Koanantakool Date: Thu, 3 Jan 2019 14:22:17 -0800 Subject: [PATCH 0146/2345] Turn on MKL-DNN contraction kernels by default. To disable them, build with --define=tensorflow_mkldnn_contraction_kernel=0. Make TestFoldFusedBatchNormsWithConcat use ExpectClose which is more suitable for floating-point comparison than ExpectNear. PiperOrigin-RevId: 227749705 --- .bazelrc | 3 --- tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.bazelrc b/.bazelrc index ceba7bfdba..cd7e13ddfc 100644 --- a/.bazelrc +++ b/.bazelrc @@ -93,9 +93,6 @@ build --define=PREFIX=/usr build --define=LIBDIR=$(PREFIX)/lib build --define=INCLUDEDIR=$(PREFIX)/include -# Disable MKL-DNN contraction kernels by default. -build --define=tensorflow_mkldnn_contraction_kernel=0 - # Default options should come above this line # Options from ./configure diff --git a/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc b/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc index 435f46c107..6c7174926d 100644 --- a/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc +++ b/tensorflow/tools/graph_transforms/fold_old_batch_norms_test.cc @@ -291,7 +291,7 @@ class FoldOldBatchNormsTest : public ::testing::Test { std::vector fused_outputs; TF_ASSERT_OK(fused_session->Run({}, {"output"}, {}, &fused_outputs)); - test::ExpectTensorNear(original_outputs[0], fused_outputs[0], 1e-5); + test::ExpectClose(original_outputs[0], fused_outputs[0]); for (const NodeDef& node : fused_graph_def.node()) { EXPECT_NE("FusedBatchNorm", node.op()); -- GitLab From 9d78259894c0b533ac6454c9eee27f8eb8bef638 Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Thu, 3 Jan 2019 14:29:35 -0800 Subject: [PATCH 0147/2345] Add "element_shape" attr to TensorListConcat and use it in _GraphTensorArrayV2. When using while_v2, this change prevents errors like this: All except the first dimension must be fully defined when concating an empty tensor list. element_shape: Previously, TensorListConcat op could only determine the element size from the TensorList object at runtime. In the case of a while loop that executes zero times, it wouldn't be able to determine the size since no element was ever seen by the runtime. However, we may have determined the element size during graph construction, so we use the attribute to tell the runtime what the element size is. Note that the original TensorArray implementation already does this. PiperOrigin-RevId: 227750868 --- tensorflow/core/kernels/list_kernels.h | 44 +++++++++++++------ tensorflow/core/ops/list_ops.cc | 14 ++++-- tensorflow/python/ops/list_ops.py | 6 ++- .../ops/parallel_for/control_flow_ops_test.py | 5 --- tensorflow/python/ops/tensor_array_ops.py | 12 +++-- 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/tensorflow/core/kernels/list_kernels.h b/tensorflow/core/kernels/list_kernels.h index 686679474c..99b4f42084 100644 --- a/tensorflow/core/kernels/list_kernels.h +++ b/tensorflow/core/kernels/list_kernels.h @@ -158,6 +158,17 @@ class TensorListConcat : public OpKernel { std::vector::ConstMatrix>>; explicit TensorListConcat(OpKernelConstruction* c) : OpKernel(c) { OP_REQUIRES_OK(c, c->GetAttr("element_dtype", &element_dtype_)); + // TODO(skyewm): the HasAttr check can be removed once the + // element_shape_except_first_dim attr has been checked in for 2 weeks + // (around 1/14/2019). + if (c->HasAttr("element_shape")) { + PartialTensorShape element_shape; + OP_REQUIRES_OK(c, c->GetAttr("element_shape", &element_shape)); + if (!element_shape.unknown_rank()) { + element_shape_except_first_dim_ = PartialTensorShape( + gtl::ArraySlice(element_shape.dim_sizes()).subspan(1)); + } + } } ~TensorListConcat() {} @@ -178,29 +189,33 @@ class TensorListConcat : public OpKernel { " but list elements ", DataTypeString(tensor_list->element_dtype))); // If the TensorList is empty, its element_shape must be fully defined // except for the first dimension. - PartialTensorShape shape_except_first_dim; - if (!tensor_list->element_shape.unknown_rank()) { - OP_REQUIRES(c, tensor_list->element_shape.dims() >= 1, - errors::InvalidArgument( - "Concat requires elements to be at least vectors, ", - "found scalars instead.")); - shape_except_first_dim = PartialTensorShape( - gtl::ArraySlice(tensor_list->element_shape.dim_sizes()) - .subspan(1)); + if (!element_shape_except_first_dim_.IsFullyDefined()) { + if (!tensor_list->element_shape.unknown_rank()) { + OP_REQUIRES(c, tensor_list->element_shape.dims() >= 1, + errors::InvalidArgument( + "Concat requires elements to be at least vectors, ", + "found scalars instead.")); + PartialTensorShape shape_except_first_dim( + gtl::ArraySlice(tensor_list->element_shape.dim_sizes()) + .subspan(1)); + PartialTensorShape tmp = element_shape_except_first_dim_; + OP_REQUIRES_OK(c, tmp.MergeWith(shape_except_first_dim, + &element_shape_except_first_dim_)); + } } OP_REQUIRES(c, !tensor_list->tensors.empty() || - shape_except_first_dim.IsFullyDefined(), + element_shape_except_first_dim_.IsFullyDefined(), errors::InvalidArgument( "All except the first dimension must be fully defined ", "when concating an empty tensor list. element_shape: ", tensor_list->element_shape.DebugString())); // 1. Compute the shape of the output tensor. - // If `shape_except_first_dim` is fully-defined we just prepend the leading - // dim to it. Otherwise we use the shape of the first element tensor and - // check to make sure shapes of all tensors are compatible. + // If `element_shape_except_first_dim_` is fully-defined we just prepend the + // leading dim to it. Otherwise we use the shape of the first element tensor + // and check to make sure shapes of all tensors are compatible. TensorShape output_shape; - if (!shape_except_first_dim.AsTensorShape(&output_shape)) { + if (!element_shape_except_first_dim_.AsTensorShape(&output_shape)) { const Tensor& element_tensor = tensor_list->tensors[0]; OP_REQUIRES( c, TensorShapeUtils::IsVectorOrHigher(element_tensor.shape()), @@ -268,6 +283,7 @@ class TensorListConcat : public OpKernel { private: DataType element_dtype_; + PartialTensorShape element_shape_except_first_dim_; }; template diff --git a/tensorflow/core/ops/list_ops.cc b/tensorflow/core/ops/list_ops.cc index 5722170071..cbc9c7a2f4 100644 --- a/tensorflow/core/ops/list_ops.cc +++ b/tensorflow/core/ops/list_ops.cc @@ -212,10 +212,16 @@ REGISTER_OP("TensorListConcat") .Output("tensor: element_dtype") .Output("lengths: int64") .Attr("element_dtype: type") + .Attr("element_shape: shape = { unknown_rank: true }") .SetShapeFn([](shape_inference::InferenceContext* c) { DataType element_dtype; TF_RETURN_IF_ERROR(c->GetAttr("element_dtype", &element_dtype)); - shape_inference::ShapeHandle element_shape = c->UnknownShape(); + PartialTensorShape raw_element_shape; + TF_RETURN_IF_ERROR(c->GetAttr("element_shape", &raw_element_shape)); + shape_inference::ShapeHandle element_shape; + TF_RETURN_IF_ERROR(c->MakeShapeFromPartialTensorShape(raw_element_shape, + &element_shape)); + auto* handle_data = c->input_handle_shapes_and_types(0); if (handle_data != nullptr && handle_data->size() != 1) { return errors::InvalidArgument( @@ -231,10 +237,10 @@ REGISTER_OP("TensorListConcat") DataTypeString(list_shape_type.dtype), " but expected type ", DataTypeString(element_dtype)); } - shape_inference::ShapeHandle ignored; + shape_inference::ShapeHandle merged; TF_RETURN_IF_ERROR( - c->Merge(element_shape, list_shape_type.shape, &ignored)); - element_shape = list_shape_type.shape; + c->Merge(element_shape, list_shape_type.shape, &merged)); + element_shape = merged; } if (c->RankKnown(element_shape)) { shape_inference::ShapeHandle result; diff --git a/tensorflow/python/ops/list_ops.py b/tensorflow/python/ops/list_ops.py index 3eac97655a..7bb9ca89e0 100644 --- a/tensorflow/python/ops/list_ops.py +++ b/tensorflow/python/ops/list_ops.py @@ -71,11 +71,13 @@ def tensor_list_from_tensor(tensor, element_shape, name=None): name=name) -def tensor_list_concat(input_handle, element_dtype, name=None): +def tensor_list_concat(input_handle, element_dtype, element_shape=None, + name=None): # Ignore the lengths output of TensorListConcat. It is only used during # gradient computation. return gen_list_ops.tensor_list_concat( - input_handle=input_handle, element_dtype=element_dtype, name=name)[0] + input_handle=input_handle, element_dtype=element_dtype, + element_shape=element_shape, name=name)[0] def tensor_list_split(tensor, element_shape, lengths, name=None): diff --git a/tensorflow/python/ops/parallel_for/control_flow_ops_test.py b/tensorflow/python/ops/parallel_for/control_flow_ops_test.py index 8acb0d839c..933bddd8cc 100644 --- a/tensorflow/python/ops/parallel_for/control_flow_ops_test.py +++ b/tensorflow/python/ops/parallel_for/control_flow_ops_test.py @@ -27,7 +27,6 @@ from tensorflow.core.example import example_pb2 from tensorflow.core.example import feature_pb2 from tensorflow.python.client import session from tensorflow.python.eager import backprop -from tensorflow.python.eager import context from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops @@ -105,10 +104,6 @@ class PForTest(test.TestCase): flags.FLAGS.op_conversion_fallback_to_while_loop = False def test_parallel_iterations(self): - # TODO(b/121334512): Remove this check once this passes in Eager mode. - if context.executing_eagerly(): - return - for parallel_iterations in [2, 3, 8, 10]: x = random_ops.random_uniform([8, 3]) diff --git a/tensorflow/python/ops/tensor_array_ops.py b/tensorflow/python/ops/tensor_array_ops.py index 9b8bd2c8d3..90a8b0af46 100644 --- a/tensorflow/python/ops/tensor_array_ops.py +++ b/tensorflow/python/ops/tensor_array_ops.py @@ -588,10 +588,16 @@ class _GraphTensorArrayV2(object): def concat(self, name=None): """See TensorArray.""" - value = list_ops.tensor_list_concat( - input_handle=self._flow, element_dtype=self._dtype, name=name) if self._element_shape and self._element_shape[0].dims is not None: - value.set_shape([None] + self._element_shape[0].dims[1:]) + element_shape = [None] + self._element_shape[0].dims[1:] + else: + element_shape = None + + value = list_ops.tensor_list_concat( + input_handle=self._flow, + element_dtype=self._dtype, + element_shape=element_shape, + name=name) return value @tf_should_use.should_use_result -- GitLab From 000dc6e0face09fd2a2886b611e6d1d3d7776e5d Mon Sep 17 00:00:00 2001 From: Jian Li Date: Thu, 3 Jan 2019 14:36:59 -0800 Subject: [PATCH 0148/2345] Update svdf test case. PiperOrigin-RevId: 227752127 --- tensorflow/lite/kernels/svdf_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/svdf_test.cc b/tensorflow/lite/kernels/svdf_test.cc index fc2bb49223..c420260bf5 100644 --- a/tensorflow/lite/kernels/svdf_test.cc +++ b/tensorflow/lite/kernels/svdf_test.cc @@ -209,7 +209,7 @@ class HybridSVDFOpModel : public BaseSVDFOpModel { tensor_type_ = tensor_type; } - void SetWeights(int weights_idx, std::vector f) { + void SetWeights(int weights_idx, const std::vector& f) { if (tensor_type_ == TensorType_UINT8) { SymmetricQuantizeAndPopulate(weights_idx, f); } else { -- GitLab From 7d57d32e440bc4f1654fb217fd701531a82c3587 Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Thu, 3 Jan 2019 14:45:53 -0800 Subject: [PATCH 0149/2345] Fix dso_loader.cc's includes. PiperOrigin-RevId: 227753675 --- tensorflow/contrib/mpi_collectives/BUILD | 2 +- tensorflow/stream_executor/platform/default/dso_loader.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/mpi_collectives/BUILD b/tensorflow/contrib/mpi_collectives/BUILD index ecac06354d..d943ae6880 100644 --- a/tensorflow/contrib/mpi_collectives/BUILD +++ b/tensorflow/contrib/mpi_collectives/BUILD @@ -52,7 +52,7 @@ tf_custom_op_library( deps = [ ":mpi_defines", ":mpi_message_proto_cc", - "//tensorflow/stream_executor:stream_executor_headers_lib", + "//tensorflow/core:stream_executor_headers_lib", "//third_party/mpi", ], ) diff --git a/tensorflow/stream_executor/platform/default/dso_loader.cc b/tensorflow/stream_executor/platform/default/dso_loader.cc index 0f0bce3253..668eeee3f3 100644 --- a/tensorflow/stream_executor/platform/default/dso_loader.cc +++ b/tensorflow/stream_executor/platform/default/dso_loader.cc @@ -28,7 +28,7 @@ limitations under the License. #include "tensorflow/stream_executor/lib/path.h" #include "tensorflow/stream_executor/lib/str_util.h" #include "tensorflow/stream_executor/lib/stringprintf.h" -#include "tensorflow/stream_executor/platform/dso_loader.h" +#include "tensorflow/stream_executor/platform/default/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/platform/port.h" -- GitLab From 1051c377051b2ee24a495318737358d9ccf7280f Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 2 Jan 2019 14:34:42 -0800 Subject: [PATCH 0150/2345] Copy ben's changes --- .../contrib/tensorrt/convert/convert_nodes.cc | 25 ++- .../contrib/tensorrt/test/conv2d_test.py | 172 ++++++++++++++++++ 2 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 tensorflow/contrib/tensorrt/test/conv2d_test.py diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 2a402fe699..84d7f1aee5 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -62,14 +62,14 @@ limitations under the License. #define TFTRT_RETURN_ERROR_IF_FALSE(status, node) \ do { \ - if (status == false) { \ + if ((status) == false) { \ TFTRT_INTERNAL_ERROR_AT_NODE(node); \ } \ } while (0) #define TFTRT_RETURN_ERROR_IF_NULLPTR(ptr, node) \ do { \ - if (ptr == nullptr) { \ + if ((ptr) == nullptr) { \ TFTRT_INTERNAL_ERROR_AT_NODE(node); \ } \ } while (0) @@ -1577,12 +1577,14 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); TFAttrs attrs(node_def); + int c_index = 1; int h_index = 2; int w_index = 3; auto data_format = attrs.get("data_format"); if (data_format == "NHWC") { TF_RETURN_IF_ERROR(params->converter->TransposeTensor( const_cast(tensor), {0, 3, 1, 2}, &tensor)); + c_index = 3; h_index = 1; w_index = 2; // TODO(jie): transpose it @@ -1618,14 +1620,30 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { << tf_stride[3]; const nvinfer1::DimsHW stride(tf_stride[h_index], tf_stride[w_index]); + auto tf_dilations = attrs.get>("dilations"); + if ((int)tf_dilations.size() != 4) { + return tensorflow::errors::InvalidArgument( + "Convolution dilations field must specify 4 dimensions " + + node_def.name()); + } + if (tf_dilations[0] != 1 || tf_dilations[c_index] != 1) { + return tensorflow::errors::Unimplemented( + "Dilation rate must be 1 for batch and channel dimensions, at ", + node_def.name()); + } + nvinfer1::DimsHW dilation(tf_dilations[h_index], tf_dilations[w_index]); + std::vector> padding; // TODO(jie): padding. if (attrs.get("padding") == "SAME") { // This is NCHW tensor with no batch dimension. // 1 -> h // 2 -> w + nvinfer1::DimsHW effective_kernel_size = kernel_size; + effective_kernel_size.h() += (kernel_size.h() - 1) * (dilation.h() - 1); + effective_kernel_size.w() += (kernel_size.w() - 1) * (dilation.w() - 1); padding = CreateSamePadding( - stride, kernel_size, + stride, effective_kernel_size, {static_cast(tensor_dim.d[1]), static_cast(tensor_dim.d[2])}); } else { padding = {{0, 0}, {0, 0}}; @@ -1659,6 +1677,7 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { layer->setPadding({padding[0].first, padding[1].first}); layer->setName(node_def.name().c_str()); layer->setNbGroups(num_groups); + layer->setDilation(dilation); const nvinfer1::ITensor* output_tensor = layer->getOutput(0); VLOG(2) << "TENSOR out: " << DebugString(output_tensor->getDimensions()); VLOG(2) << "data_format: " << data_format; diff --git a/tensorflow/contrib/tensorrt/test/conv2d_test.py b/tensorflow/contrib/tensorrt/test/conv2d_test.py new file mode 100644 index 0000000000..bcf20fc504 --- /dev/null +++ b/tensorflow/contrib/tensorrt/test/conv2d_test.py @@ -0,0 +1,172 @@ +# 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. +# ============================================================================== +"""Model script to test TF-TensorRT integration.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.tensorrt.test import tf_trt_integration_test_base as trt_test +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_array_ops +from tensorflow.python.ops import gen_nn_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import nn_ops +from tensorflow.python.platform import test + + +def conv2d_layer(inputs, filters, kernel_size, strides=(1, 1), padding='valid', + data_format='channels_last', dilation_rate=(1, 1), name=None): + dtype = inputs.dtype + c_axis = -1 if data_format == 'channels_last' else 1 + nchan = inputs.shape[c_axis] + weights_shape = (kernel_size[0], kernel_size[1], nchan, filters) + weights = constant_op.constant(np.random.randn(*weights_shape), dtype=dtype) + padding = padding.upper() + if data_format == 'channels_last': + strides = [1] + list(strides) + [1] + dilations = [1] + list(dilation_rate) + [1] + data_format = 'NHWC' + else: + strides = [1, 1] + list(strides) + dilations = [1, 1] + list(dilation_rate) + data_format = 'NCHW' + return gen_nn_ops.conv2d(inputs, weights, strides=strides, padding=padding, + dilations=dilations, data_format=data_format) + +def div_round_up(n, d): + return (n - 1) // d + 1 + +class Conv2DNCHWTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Testing conversion of Conv2D (data_format=NCHW) in TF-TRT conversion.""" + np.random.seed(1234) + dtype = dtypes.float32 + input_name = "input" + n, c, h, w = 13, 3, 7, 11 + num_filters = 5 + input_dims = [n, c, h, w] + output_name = "output" + g = ops.Graph() + with g.as_default(): + inp = array_ops.placeholder( + dtype=dtype, shape=[None] + input_dims[1:], name=input_name) + with g.device("/GPU:0"): + results = [] + for kernel_size in [(3, 3), (3, 2)]: + for dilation_rate in [(1, 1), (2, 3)]: + result = conv2d_layer(inp, num_filters, kernel_size, + dilation_rate=dilation_rate, padding='same', + data_format='channels_first') + results.append(result) + output = sum(results) + output = array_ops.identity(output, name=output_name) + return trt_test.TfTrtIntegrationTestParams( + gdef=g.as_graph_def(), + input_names=[input_name], + input_dims=[input_dims], + output_names=[output_name], + expected_output_dims=[(n, num_filters, h, w)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["my_trt_op_0"] + + +class Conv2DStridedNCHWTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Testing conversion of strided Conv2D (data_format=NCHW) in TF-TRT + conversion.""" + np.random.seed(1234) + dtype = dtypes.float32 + input_name = "input" + n, c, h, w = 13, 3, 7, 11 + num_filters = 5 + input_dims = [n, c, h, w] + output_name = "output" + g = ops.Graph() + with g.as_default(): + inp = array_ops.placeholder( + dtype=dtype, shape=[None] + input_dims[1:], name=input_name) + with g.device("/GPU:0"): + output = inp + output = conv2d_layer(output, num_filters, (3, 2), strides=(2, 2), + padding='same', data_format='channels_first') + h = div_round_up(h, 2) + w = div_round_up(w, 2) + output = conv2d_layer(output, num_filters, (3, 3), strides=(2, 2), + dilation_rate=(2, 3), padding='same', + data_format='channels_first') + h = div_round_up(h, 2) + w = div_round_up(w, 2) + output = array_ops.identity(output, name=output_name) + return trt_test.TfTrtIntegrationTestParams( + gdef=g.as_graph_def(), + input_names=[input_name], + input_dims=[input_dims], + output_names=[output_name], + expected_output_dims=[(n, num_filters, h, w)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["my_trt_op_0"] + + +class Conv2DNHWCTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Testing conversion of Conv2D (data_format=NHWC) in TF-TRT conversion.""" + np.random.seed(1234) + dtype = dtypes.float32 + input_name = "input" + n, h, w, c = 13, 7, 11, 3 + num_filters = 5 + input_dims = [n, h, w, c] + output_name = "output" + g = ops.Graph() + with g.as_default(): + inp = array_ops.placeholder( + dtype=dtype, shape=[None] + input_dims[1:], name=input_name) + with g.device("/GPU:0"): + results = [] + for kernel_size in [(3, 3), (3, 2)]: + for dilation_rate in [(1, 1), (2, 3)]: + result = conv2d_layer(inp, num_filters, kernel_size, + dilation_rate=dilation_rate, padding='same', + data_format='channels_last') + results.append(result) + output = sum(results) + output = array_ops.identity(output, name=output_name) + return trt_test.TfTrtIntegrationTestParams( + gdef=g.as_graph_def(), + input_names=[input_name], + input_dims=[input_dims], + output_names=[output_name], + expected_output_dims=[(n, h, w, num_filters)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["my_trt_op_0"] + + +if __name__ == "__main__": + test.main() -- GitLab From db95d28671c790de369a84c891f9bc971663e0e4 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 2 Jan 2019 15:12:40 -0800 Subject: [PATCH 0151/2345] Tidy up Conv2D converter --- tensorflow/contrib/tensorrt/BUILD | 1 + .../contrib/tensorrt/convert/convert_nodes.cc | 94 ++++++++----------- .../contrib/tensorrt/test/conv2d_test.py | 6 +- 3 files changed, 43 insertions(+), 58 deletions(-) diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 089c9ad97c..c17dea02f1 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -491,6 +491,7 @@ cuda_py_tests( "test/binary_tensor_weight_broadcast_test.py", "test/concatenation_test.py", "test/const_broadcast_test.py", + "test/conv2d_test.py", "test/identity_output_test.py", "test/manual_test.py", "test/memory_alignment_test.py", diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 84d7f1aee5..614cc47130 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -62,14 +62,14 @@ limitations under the License. #define TFTRT_RETURN_ERROR_IF_FALSE(status, node) \ do { \ - if ((status) == false) { \ + if ((status) == false) { \ TFTRT_INTERNAL_ERROR_AT_NODE(node); \ } \ } while (0) #define TFTRT_RETURN_ERROR_IF_NULLPTR(ptr, node) \ do { \ - if ((ptr) == nullptr) { \ + if ((ptr) == nullptr) { \ TFTRT_INTERNAL_ERROR_AT_NODE(node); \ } \ } while (0) @@ -1567,41 +1567,56 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { node_def.name()); } TRT_ShapedWeights weights_rsck = inputs.at(1).weights(); - VLOG(2) << "weight shape: " << weights_rsck.DebugString(); if (weights_rsck.shape_.nbDims != 4) { return tensorflow::errors::Internal( "Conv2D expects kernel of dimension 4, at: " + node_def.name()); } + TFAttrs attrs(node_def); + auto data_format = attrs.get("data_format"); + 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"); + if (tf_dilations.size() != 4) { + return tensorflow::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( + "Dilation rate must be 1 for batch and channel dimensions, at ", + node_def.name()); + } + nvinfer1::DimsHW dilation(tf_dilations[h_index], tf_dilations[w_index]); + const auto tf_stride = attrs.get>("strides"); + if (tf_stride[0] != 1 || tf_stride[c_index] != 1) { + return tensorflow::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(); const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); - TFAttrs attrs(node_def); - int c_index = 1; - int h_index = 2; - int w_index = 3; - auto data_format = attrs.get("data_format"); - if (data_format == "NHWC") { + // Transpose to NCHW (NCHW is required for IConvLayer). + const bool need_transpose = (data_format == "NHWC"); + if (need_transpose) { TF_RETURN_IF_ERROR(params->converter->TransposeTensor( const_cast(tensor), {0, 3, 1, 2}, &tensor)); - c_index = 3; - h_index = 1; - w_index = 2; - // TODO(jie): transpose it } - - // tensor after transpose (NCHW) + // Dimensions of transposed tensor. const auto tensor_dim = tensor->getDimensions(); + // This is a depthwise convolution when num_groups is 0. Otherwise, num_groups + // will be 1. int num_groups = group; - if (num_groups == 0) num_groups = tensor_dim.d[0]; // depthwise convolution - VLOG(2) << "groups count: " << num_groups; + if (num_groups == 0) num_groups = tensor_dim.d[0]; if (params->converter->precision_mode() == FP16MODE) { weights_rsck = ConvertFP32ToFP16(params->weight_store, inputs.at(1).weights()); } - TRT_ShapedWeights weights = params->weight_store->GetTempWeights(weights_rsck); ReorderRSCKToKCRS(weights_rsck, &weights, num_groups); @@ -1610,35 +1625,10 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { nvinfer1::DimsHW kernel_size; kernel_size.h() = weights.shape_.d[2]; kernel_size.w() = weights.shape_.d[3]; - VLOG(2) << "RSCK: " << weights.DebugString(); - VLOG(2) << "kernel size: " << kernel_size.h() << ", " << kernel_size.w(); - - // TODO(jie): stride. (NHWC/NCHW) - const auto tf_stride = attrs.get>("strides"); - VLOG(2) << "h_INDEX" << h_index << ", w_index " << w_index; - VLOG(2) << "stride: " << tf_stride[0] << tf_stride[1] << tf_stride[2] - << tf_stride[3]; - const nvinfer1::DimsHW stride(tf_stride[h_index], tf_stride[w_index]); - - auto tf_dilations = attrs.get>("dilations"); - if ((int)tf_dilations.size() != 4) { - return tensorflow::errors::InvalidArgument( - "Convolution dilations field must specify 4 dimensions " + - node_def.name()); - } - if (tf_dilations[0] != 1 || tf_dilations[c_index] != 1) { - return tensorflow::errors::Unimplemented( - "Dilation rate must be 1 for batch and channel dimensions, at ", - node_def.name()); - } - nvinfer1::DimsHW dilation(tf_dilations[h_index], tf_dilations[w_index]); + // Add padding. std::vector> padding; - // TODO(jie): padding. if (attrs.get("padding") == "SAME") { - // This is NCHW tensor with no batch dimension. - // 1 -> h - // 2 -> w nvinfer1::DimsHW effective_kernel_size = kernel_size; effective_kernel_size.h() += (kernel_size.h() - 1) * (dilation.h() - 1); effective_kernel_size.w() += (kernel_size.w() - 1) * (dilation.w() - 1); @@ -1648,13 +1638,9 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { } else { padding = {{0, 0}, {0, 0}}; } - if (padding[0].first != padding[0].second || padding[1].first != padding[1].second) { - // TODO(jie): handle asymmetric padding - VLOG(2) << "Padding!!!: " << padding[0].first << padding[0].second - << padding[1].first << padding[1].second; - VLOG(2) << "TENSOR before: " << DebugString(tensor->getDimensions()); + // Handle asymmetric padding. auto pad_layer = params->converter->network()->addPadding( *const_cast(tensor), nvinfer1::DimsHW(padding[0].first, padding[1].first), @@ -1664,25 +1650,23 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { const_cast(tensor), pad_layer->getOutput(0)); padding = {{0, 0}, {0, 0}}; tensor = pad_layer->getOutput(0); - VLOG(2) << "TENSOR after: " << DebugString(tensor->getDimensions()); } + // Add convolution. nvinfer1::IConvolutionLayer* layer = params->converter->network()->addConvolution( *const_cast(tensor), noutput, kernel_size, weights.GetTrtWeights(), biases.GetTrtWeights()); TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); - layer->setStride(stride); layer->setPadding({padding[0].first, padding[1].first}); layer->setName(node_def.name().c_str()); layer->setNbGroups(num_groups); layer->setDilation(dilation); const nvinfer1::ITensor* output_tensor = layer->getOutput(0); - VLOG(2) << "TENSOR out: " << DebugString(output_tensor->getDimensions()); - VLOG(2) << "data_format: " << data_format; - if (data_format == "NHWC") { - // TODO(jie): transpose it back! + + // Restore transpose. + if (need_transpose) { TF_RETURN_IF_ERROR(params->converter->TransposeTensor( const_cast(output_tensor), {0, 2, 3, 1}, &output_tensor)); diff --git a/tensorflow/contrib/tensorrt/test/conv2d_test.py b/tensorflow/contrib/tensorrt/test/conv2d_test.py index bcf20fc504..c5228dea33 100644 --- a/tensorflow/contrib/tensorrt/test/conv2d_test.py +++ b/tensorflow/contrib/tensorrt/test/conv2d_test.py @@ -88,7 +88,7 @@ class Conv2DNCHWTest(trt_test.TfTrtIntegrationTestBase): def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" - return ["my_trt_op_0"] + return ["TRTEngineOp_0"] class Conv2DStridedNCHWTest(trt_test.TfTrtIntegrationTestBase): @@ -128,7 +128,7 @@ class Conv2DStridedNCHWTest(trt_test.TfTrtIntegrationTestBase): def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" - return ["my_trt_op_0"] + return ["TRTEngineOp_0"] class Conv2DNHWCTest(trt_test.TfTrtIntegrationTestBase): @@ -165,7 +165,7 @@ class Conv2DNHWCTest(trt_test.TfTrtIntegrationTestBase): def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" - return ["my_trt_op_0"] + return ["TRTEngineOp_0"] if __name__ == "__main__": -- GitLab From a42746772faea0ab58d421dc68220ebc98c053bc Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 2 Jan 2019 16:13:58 -0800 Subject: [PATCH 0152/2345] Add unit test for Conv2D validator --- .../contrib/tensorrt/convert/convert_nodes.cc | 23 ++-- .../tensorrt/convert/convert_nodes_test.cc | 102 ++++++++++++++++++ 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 614cc47130..34ba2aeaa4 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -1556,6 +1556,11 @@ enum class ConvolutionType { DEFAULT, DEPTHWISE_CONV }; tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; + if (inputs.size() != 2) { + return tensorflow::errors::InvalidArgument("Two inputs are expected for ", + node_def.op(), ", at ", + node_def.name()); + } if (inputs.at(0).is_weights()) { return tensorflow::errors::Unimplemented( node_def.op(), " is only implemented for tensors, not weights, at ", @@ -1568,8 +1573,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::Internal( - "Conv2D expects kernel of dimension 4, at: " + node_def.name()); + return tensorflow::errors::InvalidArgument( + "Conv2D expects kernel of dimension 4, at " + node_def.name()); } TFAttrs attrs(node_def); auto data_format = attrs.get("data_format"); @@ -1587,8 +1592,13 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { "Dilation rate must be 1 for batch and channel dimensions, at ", node_def.name()); } - nvinfer1::DimsHW dilation(tf_dilations[h_index], tf_dilations[w_index]); + const nvinfer1::DimsHW dilation(tf_dilations[h_index], tf_dilations[w_index]); const auto tf_stride = attrs.get>("strides"); + if (tf_stride.size() != 4) { + return tensorflow::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( "Stride must be 1 for batch and channel dimensions, at ", @@ -1608,10 +1618,9 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { // Dimensions of transposed tensor. const auto tensor_dim = tensor->getDimensions(); - // This is a depthwise convolution when num_groups is 0. Otherwise, num_groups - // will be 1. - int num_groups = group; - if (num_groups == 0) num_groups = tensor_dim.d[0]; + // For depthwise convolution, group will be 0 so set num_groups to size of + // input's channel dim. For a non-depthwise conv, num_groups will be 1. + const int num_groups = (group == 0) ? tensor_dim.d[0] : group; if (params->converter->precision_mode() == FP16MODE) { weights_rsck = diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc index a2ddfbffa5..cb73d2e07b 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc @@ -2378,6 +2378,8 @@ TEST_F(OpConverterTest, ConvertStridedSlice) { }; { + // Input is weights, should fail. + Reset(); NodeDef node_def = get_strided_slice_nodedef(); AddTestWeights("input", {1, 2, 3}, {1, 2, 3, 4, 5, 6}); AddTestWeights("begin", {4}, {0, 0, 0, 0}); @@ -2619,6 +2621,106 @@ TEST_F(OpConverterTest, ConvertStridedSlice) { } } +TEST_F(OpConverterTest, ConvertConv2D) { + { + // Input list is empty, should fail. + NodeDef node_def = MakeNodeDef("my_conv2d", "Conv2D", {}); + RunValidationAndConversion( + node_def, error::INVALID_ARGUMENT, + "Two inputs are expected for Conv2D, at my_conv2d"); + } + + // Get nodedef for Conv2D layer. + auto get_conv2d_nodedef = []( + std::vector strides = {1, 1, 1, 1}, string padding = "SAME", + string data_format = "NCHW", + std::vector dilations = {1, 1, 1, 1}) -> NodeDef { + Scope s = Scope::NewRootScope(); + auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); + auto filter = ops::Placeholder(s.WithOpName("weights"), DT_FLOAT); + ops::Conv2D::Attrs attrs = + ops::Conv2D::Attrs().DataFormat(data_format).Dilations(dilations); + auto conv2d = ops::Conv2D(s.WithOpName("my_conv2d"), input, filter, strides, + padding, attrs); + return conv2d.operation.node()->def(); + }; + + { + // Input is weights, should fail. + Reset(); + NodeDef node_def = get_conv2d_nodedef(); + AddTestWeights("input", {1, 2, 3}, {1, 2, 3, 4, 5, 6}); + AddTestWeights("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); + RunValidationAndConversion( + node_def, error::UNIMPLEMENTED, + "Conv2D is only implemented for tensors, not weights, at my_conv2d"); + } + { + // Filter is tensor, should fail. + Reset(); + NodeDef node_def = get_conv2d_nodedef(); + AddTestTensor("input", {1, 2, 3}); + AddTestTensor("weights", {3, 3, 1, 1}); + RunValidationAndConversion( + node_def, error::UNIMPLEMENTED, + "Kernel for Conv2D must be constant weights, at my_conv2d"); + } + { + // Filter is not 4D, should fail. + Reset(); + NodeDef node_def = get_conv2d_nodedef(); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", {3, 3, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); + RunValidationAndConversion( + node_def, error::INVALID_ARGUMENT, + "Conv2D expects kernel of dimension 4, at my_conv2d"); + } + { + // Dilations is not 4D, should fail. + Reset(); + NodeDef node_def = + get_conv2d_nodedef({1, 1, 1, 1}, "SAME", "NCHW", {1, 1, 1}); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); + RunValidationAndConversion( + node_def, error::INVALID_ARGUMENT, + "Convolution dilations field must specify 4 dimensions, at my_conv2d"); + } + { + // Dilation value is not 1 for channel, should fail. + Reset(); + NodeDef node_def = + get_conv2d_nodedef({1, 1, 1, 1}, "SAME", "NCHW", {1, 2, 1, 1}); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); + RunValidationAndConversion(node_def, error::UNIMPLEMENTED, + "Dilation rate must be 1 for batch and channel " + "dimensions, at my_conv2d"); + } + { + // Strides is not 4D, should fail. + Reset(); + NodeDef node_def = + get_conv2d_nodedef({1, 1, 1}, "SAME", "NCHW", {1, 1, 1, 1}); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); + RunValidationAndConversion( + node_def, error::INVALID_ARGUMENT, + "Convolution strides field must specify 4 dimensions, at my_conv2d"); + } + { + // Stride value is not 1 for channel, should fail. + Reset(); + NodeDef node_def = + get_conv2d_nodedef({1, 2, 1, 1}, "SAME", "NCHW", {1, 1, 1, 1}); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); + RunValidationAndConversion( + node_def, error::UNIMPLEMENTED, + "Stride must be 1 for batch and channel dimensions, at my_conv2d"); + } +} + } // namespace convert } // namespace tensorrt } // namespace tensorflow -- GitLab From 560e4dd67fa8f9b0b99b940e35d039cf6c40194a Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 2 Jan 2019 16:20:44 -0800 Subject: [PATCH 0153/2345] Remove unused imports --- tensorflow/contrib/tensorrt/test/conv2d_test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tensorflow/contrib/tensorrt/test/conv2d_test.py b/tensorflow/contrib/tensorrt/test/conv2d_test.py index c5228dea33..f7d9ca6826 100644 --- a/tensorflow/contrib/tensorrt/test/conv2d_test.py +++ b/tensorflow/contrib/tensorrt/test/conv2d_test.py @@ -25,10 +25,7 @@ from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.ops import array_ops -from tensorflow.python.ops import gen_array_ops from tensorflow.python.ops import gen_nn_ops -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import nn_ops from tensorflow.python.platform import test -- GitLab From cf471f0c15b0c9c78c44a238fe60fe1e31289503 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Thu, 3 Jan 2019 14:56:44 -0800 Subject: [PATCH 0154/2345] Add positive test cases for Conv2D. Reuse build_graph in conv2d_test. --- .../contrib/tensorrt/convert/convert_nodes.cc | 5 +- .../tensorrt/convert/convert_nodes_test.cc | 134 ++++++++++++++++++ .../contrib/tensorrt/test/conv2d_test.py | 106 ++++++-------- 3 files changed, 183 insertions(+), 62 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 34ba2aeaa4..179d631a3e 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -62,14 +62,14 @@ limitations under the License. #define TFTRT_RETURN_ERROR_IF_FALSE(status, node) \ do { \ - if ((status) == false) { \ + if (status == false) { \ TFTRT_INTERNAL_ERROR_AT_NODE(node); \ } \ } while (0) #define TFTRT_RETURN_ERROR_IF_NULLPTR(ptr, node) \ do { \ - if ((ptr) == nullptr) { \ + if (ptr == nullptr) { \ TFTRT_INTERNAL_ERROR_AT_NODE(node); \ } \ } while (0) @@ -1593,6 +1593,7 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group) { node_def.name()); } const nvinfer1::DimsHW dilation(tf_dilations[h_index], tf_dilations[w_index]); + const auto tf_stride = attrs.get>("strides"); if (tf_stride.size() != 4) { return tensorflow::errors::InvalidArgument( diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc index cb73d2e07b..1e49026a34 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc @@ -2697,6 +2697,17 @@ TEST_F(OpConverterTest, ConvertConv2D) { "Dilation rate must be 1 for batch and channel " "dimensions, at my_conv2d"); } + { + // Dilation value is not 1 for channel (NHWC), should fail. + Reset(); + NodeDef node_def = + get_conv2d_nodedef({1, 1, 1, 1}, "SAME", "NHWC", {1, 1, 1, 2}); + AddTestTensor("input", {2, 3, 1}); + AddTestWeights("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); + RunValidationAndConversion(node_def, error::UNIMPLEMENTED, + "Dilation rate must be 1 for batch and channel " + "dimensions, at my_conv2d"); + } { // Strides is not 4D, should fail. Reset(); @@ -2719,6 +2730,129 @@ TEST_F(OpConverterTest, ConvertConv2D) { node_def, error::UNIMPLEMENTED, "Stride must be 1 for batch and channel dimensions, at my_conv2d"); } + + struct TestParams { + TestParams(const std::vector& input_dims, + const std::vector& input, + const std::vector& filter_dims, + const std::vector& filter, + const std::vector& strides, const string& padding, + const string& data_format, const std::vector& dilations, + const std::vector& expected_output_dims, + const std::vector& expected_output) + : input_dims(input_dims), + input(input), + filter_dims(filter_dims), + filter(filter), + strides(strides), + padding(padding), + data_format(data_format), + dilations(dilations), + expected_output_dims(expected_output_dims), + expected_output(expected_output) {} + + std::vector input_dims; + std::vector input; + std::vector filter_dims; + std::vector filter; + std::vector strides; + string padding; + string data_format; + std::vector dilations; + std::vector expected_output_dims; + std::vector expected_output; + }; + + // Ok. + const int kConv2DOKCases = 6; + TestParams ok_params[kConv2DOKCases] = { + // Basic + TestParams{/*input_dims=*/{1, 2, 3}, + /*input=*/{0, 1, 2, 3, 3, 4}, + /*filter_dims=*/{1, 2, 1, 1}, + /*filter=*/{-1, 1}, + /*strides=*/{1, 1, 1, 1}, + /*padding=*/"VALID", + /*data_format=*/"NCHW", + /*dilations=*/{1, 1, 1, 1}, + /*expected_output_dims=*/{1, 2, 2}, + /*expected_output=*/{1, 1, 0, 1}}, + // SAME padding (Asymmetric) + TestParams{/*input_dims=*/{1, 2, 3}, + /*input=*/{0, 1, 2, 3, 3, 4}, + /*filter_dims=*/{1, 2, 1, 1}, + /*filter=*/{-1, 1}, + /*strides=*/{1, 1, 1, 1}, + /*padding=*/"SAME", + /*data_format=*/"NCHW", + /*dilations=*/{1, 1, 1, 1}, + /*expected_output_dims=*/{1, 2, 3}, + /*expected_output=*/{1, 1, -2, 0, 1, -4}}, + // SAME padding (Symmetric) + TestParams{/*input_dims=*/{1, 2, 3}, + /*input=*/{0, 1, 2, 3, 3, 4}, + /*filter_dims=*/{1, 3, 1, 1}, + /*filter=*/{-1, 0, 1}, + /*strides=*/{1, 1, 1, 1}, + /*padding=*/"SAME", + /*data_format=*/"NCHW", + /*dilations=*/{1, 1, 1, 1}, + /*expected_output_dims=*/{1, 2, 3}, + /*expected_output=*/{1, 2, -1, 3, 1, -3}}, + // NHWC + TestParams{/*input_dims=*/{2, 3, 1}, + /*input=*/{0, 1, 2, 3, 3, 4}, + /*filter_dims=*/{1, 2, 1, 1}, + /*filter=*/{-1, 1}, + /*strides=*/{1, 1, 1, 1}, + /*padding=*/"VALID", + /*data_format=*/"NHWC", + /*dilations=*/{1, 1, 1, 1}, + /*expected_output_dims=*/{2, 2, 1}, + /*expected_output=*/{1, 1, 0, 1}}, + // Dilated + TestParams{/*input_dims=*/{1, 2, 3}, + /*input=*/{0, 1, 2, 3, 3, 4}, + /*filter_dims=*/{1, 2, 1, 1}, + /*filter=*/{-1, 1}, + /*strides=*/{1, 1, 1, 1}, + /*padding=*/"VALID", + /*data_format=*/"NCHW", + /*dilations=*/{1, 1, 1, 2}, + /*expected_output_dims=*/{1, 2, 1}, + /*expected_output=*/{2, 1}}, + // Strided + TestParams{/*input_dims=*/{1, 2, 4}, + /*input=*/{0, 1, 2, 2, 3, 4, 4, 7}, + /*filter_dims=*/{1, 2, 1, 1}, + /*filter=*/{-1, 1}, + /*strides=*/{1, 1, 1, 2}, + /*padding=*/"VALID", + /*data_format=*/"NCHW", + /*dilations=*/{1, 1, 1, 1}, + /*expected_output_dims=*/{1, 2, 2}, + /*expected_output=*/{1, 0, 1, 3}}, + }; + + for (int i = 0; i < kConv2DOKCases; i++) { + Reset(); + NodeDef node_def = + get_conv2d_nodedef(ok_params[i].strides, ok_params[i].padding, + ok_params[i].data_format, ok_params[i].dilations); + AddTestTensor("input", ok_params[i].input_dims); + AddTestWeights("weights", ok_params[i].filter_dims, + ok_params[i].filter); + RunValidationAndConversion(node_def); + TRT_TensorOrWeights output; + TF_EXPECT_OK(GetTensorOrWeights("my_conv2d", &output)); + EXPECT_TRUE(output.is_tensor()); + ExpectTrtDimsEqualsArray(ok_params[i].expected_output_dims, + output.tensor()->getDimensions()); + std::vector output_data(ok_params[i].expected_output.size()); + BuildAndRun({{"input", ok_params[i].input}}, "my_conv2d", + &output_data); + EXPECT_THAT(output_data, ElementsAreArray(ok_params[i].expected_output)); + } } } // namespace convert diff --git a/tensorflow/contrib/tensorrt/test/conv2d_test.py b/tensorflow/contrib/tensorrt/test/conv2d_test.py index f7d9ca6826..68b6534823 100644 --- a/tensorflow/contrib/tensorrt/test/conv2d_test.py +++ b/tensorflow/contrib/tensorrt/test/conv2d_test.py @@ -51,37 +51,60 @@ def conv2d_layer(inputs, filters, kernel_size, strides=(1, 1), padding='valid', def div_round_up(n, d): return (n - 1) // d + 1 +def build_graph(input_dims, dtype, num_filters, data_format, kernel_sizes, + dilation_rates, padding='same'): + g = ops.Graph() + with g.as_default(): + inp = array_ops.placeholder( + dtype=dtype, shape=[None] + input_dims[1:], name="input") + with g.device("/GPU:0"): + results = [] + for kernel_size in kernel_sizes: + for dilation_rate in dilation_rates: + result = conv2d_layer(inp, num_filters, kernel_size, (1, 1), + padding, data_format, dilation_rate) + results.append(result) + output = sum(results) + output = array_ops.identity(output, name="output") + return g + + class Conv2DNCHWTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): """Testing conversion of Conv2D (data_format=NCHW) in TF-TRT conversion.""" np.random.seed(1234) - dtype = dtypes.float32 - input_name = "input" - n, c, h, w = 13, 3, 7, 11 - num_filters = 5 - input_dims = [n, c, h, w] - output_name = "output" - g = ops.Graph() - with g.as_default(): - inp = array_ops.placeholder( - dtype=dtype, shape=[None] + input_dims[1:], name=input_name) - with g.device("/GPU:0"): - results = [] - for kernel_size in [(3, 3), (3, 2)]: - for dilation_rate in [(1, 1), (2, 3)]: - result = conv2d_layer(inp, num_filters, kernel_size, - dilation_rate=dilation_rate, padding='same', - data_format='channels_first') - results.append(result) - output = sum(results) - output = array_ops.identity(output, name=output_name) + input_dims = [13, 3, 7, 11] + g = build_graph(input_dims=input_dims, dtype=dtypes.float32, num_filters=5, + data_format="channels_first", kernel_sizes=[(3, 3), (3, 2)], + dilation_rates=[(1, 1), (2, 3)]) return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), - input_names=[input_name], + input_names=["input"], input_dims=[input_dims], - output_names=[output_name], - expected_output_dims=[(n, num_filters, h, w)]) + output_names=["output"], + expected_output_dims=[(13, 5, 7, 11)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["TRTEngineOp_0"] + + +class Conv2DNHWCTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Testing conversion of Conv2D (data_format=NCHW) in TF-TRT conversion.""" + np.random.seed(1234) + input_dims = [13, 7, 11, 3] + g = build_graph(input_dims=input_dims, dtype=dtypes.float32, num_filters=5, + data_format="channels_last", kernel_sizes=[(3, 3), (3, 2)], + dilation_rates=[(1, 1), (2, 3)]) + return trt_test.TfTrtIntegrationTestParams( + gdef=g.as_graph_def(), + input_names=["input"], + input_dims=[input_dims], + output_names=["output"], + expected_output_dims=[(13, 7, 11, 5)]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -128,42 +151,5 @@ class Conv2DStridedNCHWTest(trt_test.TfTrtIntegrationTestBase): return ["TRTEngineOp_0"] -class Conv2DNHWCTest(trt_test.TfTrtIntegrationTestBase): - - def GetParams(self): - """Testing conversion of Conv2D (data_format=NHWC) in TF-TRT conversion.""" - np.random.seed(1234) - dtype = dtypes.float32 - input_name = "input" - n, h, w, c = 13, 7, 11, 3 - num_filters = 5 - input_dims = [n, h, w, c] - output_name = "output" - g = ops.Graph() - with g.as_default(): - inp = array_ops.placeholder( - dtype=dtype, shape=[None] + input_dims[1:], name=input_name) - with g.device("/GPU:0"): - results = [] - for kernel_size in [(3, 3), (3, 2)]: - for dilation_rate in [(1, 1), (2, 3)]: - result = conv2d_layer(inp, num_filters, kernel_size, - dilation_rate=dilation_rate, padding='same', - data_format='channels_last') - results.append(result) - output = sum(results) - output = array_ops.identity(output, name=output_name) - return trt_test.TfTrtIntegrationTestParams( - gdef=g.as_graph_def(), - input_names=[input_name], - input_dims=[input_dims], - output_names=[output_name], - expected_output_dims=[(n, h, w, num_filters)]) - - def ExpectedEnginesToBuild(self, run_params): - """Return the expected engines to build.""" - return ["TRTEngineOp_0"] - - if __name__ == "__main__": test.main() -- GitLab From 2ae28cb35f8060a1e7168b216adb13ce665cb349 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Thu, 3 Jan 2019 14:57:47 -0800 Subject: [PATCH 0155/2345] Add v2 log loss. PiperOrigin-RevId: 227755606 --- tensorflow/python/keras/losses.py | 46 +++++++++++++ tensorflow/python/keras/losses_test.py | 91 ++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index d05dfb03f1..e6db795bb8 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -500,6 +500,46 @@ class CategoricalHinge(Loss): return categorical_hinge(y_true, y_pred) +class LogLoss(Loss): + """Computes the log loss between `y_true` and `y_pred`. + + logloss = -y log(p) - (1-y) log(1-p) + + Usage: + + ```python + l = tf.losses.LogLoss() + loss = l([0., 1., 1.], [1., 0., 1.]) + print('Loss: ', loss.numpy()) # Loss: 10.745 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile('sgd', loss=tf.losses.LogLoss()) + ``` + + Args: + epsilon: A small increment to add to avoid taking a log of zero. + reduction: Type of `tf.losses.Reduction` to apply to loss. Default value is + `SUM_OVER_BATCH_SIZE`. + name: Optional name for the op. + """ + + def __init__(self, + epsilon=1e-7, + reduction=losses_impl.ReductionV2.SUM_OVER_BATCH_SIZE, + name=None): + super(LogLoss, self).__init__(reduction=reduction, name=name) + self.epsilon = epsilon + + def call(self, y_true, y_pred): + y_pred = ops.convert_to_tensor(y_pred) + y_true = math_ops.cast(y_true, y_pred.dtype) + return logloss(y_true, y_pred, epsilon=self.epsilon) + + @keras_export('keras.metrics.mean_squared_error', 'keras.metrics.mse', 'keras.metrics.MSE', @@ -562,6 +602,12 @@ def categorical_hinge(y_true, y_pred): return math_ops.maximum(0., neg - pos + 1.) +def logloss(y_true, y_pred, epsilon=1e-7): + losses = math_ops.multiply(y_true, math_ops.log(y_pred + epsilon)) + losses += math_ops.multiply((1 - y_true), math_ops.log(1 - y_pred + epsilon)) + return K.mean(-losses, axis=-1) + + @keras_export('keras.losses.logcosh') def logcosh(y_true, y_pred): """Logarithm of the hyperbolic cosine of the prediction error. diff --git a/tensorflow/python/keras/losses_test.py b/tensorflow/python/keras/losses_test.py index 19ed7c8ed9..76fadc4a24 100644 --- a/tensorflow/python/keras/losses_test.py +++ b/tensorflow/python/keras/losses_test.py @@ -1003,5 +1003,96 @@ class CategoricalHingeTest(test.TestCase): self.assertAlmostEqual(self.evaluate(loss), 0., 3) +@test_util.run_all_in_graph_and_eager_modes +class LogLossTest(test.TestCase): + + def setup(self): + # TODO(psv): Change to setUp() after b/122319309 is fixed. + y_pred = np.asarray([.9, .2, .2, .8, .4, .6]).reshape((2, 3)) + y_true = np.asarray([1., 0., 1., 1., 0., 0.]).reshape((2, 3)) + epsilon = 1e-7 # to avoid log 0 + + self.batch_size = 6 + self.expected_losses = np.multiply(y_true, np.log(y_pred + epsilon)) + self.expected_losses += np.multiply(1 - y_true, + np.log(1 - y_pred + epsilon)) + self.expected_losses = -self.expected_losses + + self.y_pred = constant_op.constant(y_pred) + self.y_true = constant_op.constant(y_true) + + def test_config(self): + log_loss_obj = keras.losses.LogLoss( + reduction=losses_impl.ReductionV2.SUM, name='log') + self.assertEqual(log_loss_obj.name, 'log') + self.assertEqual(log_loss_obj.reduction, losses_impl.ReductionV2.SUM) + + def test_all_correct(self): + self.setup() + log_loss_obj = keras.losses.LogLoss() + loss = log_loss_obj(self.y_true, self.y_true) + self.assertAlmostEqual(self.evaluate(loss), 0.0, 3) + + def test_unweighted(self): + self.setup() + log_loss_obj = keras.losses.LogLoss() + loss = log_loss_obj(self.y_true, self.y_pred) + actual_loss = np.sum(self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), actual_loss, 3) + + def test_scalar_weighted(self): + self.setup() + log_loss_obj = keras.losses.LogLoss() + sample_weight = 2.3 + loss = log_loss_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + actual_loss = sample_weight * np.sum(self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), actual_loss, 3) + + # Verify we get the same output when the same input is given + loss_2 = log_loss_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + self.assertAlmostEqual(self.evaluate(loss), self.evaluate(loss_2), 3) + + def test_sample_weighted(self): + self.setup() + log_loss_obj = keras.losses.LogLoss() + sample_weight = constant_op.constant((1.2, 3.4), shape=(2, 1)) + + loss = log_loss_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + actual_loss = np.multiply( + self.expected_losses, + np.asarray([1.2, 1.2, 1.2, 3.4, 3.4, 3.4]).reshape((2, 3))) + actual_loss = np.sum(actual_loss) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), actual_loss, 3) + + def test_timestep_weighted(self): + log_loss_obj = keras.losses.LogLoss() + + y_pred = np.asarray([.9, .2, .2, .8, .4, .6]).reshape((2, 3, 1)) + y_true = np.asarray([1., 0., 1., 1., 0., 0.]).reshape((2, 3, 1)) + epsilon = 1e-7 # to avoid log 0 + batch_size = 6 + + expected_losses = np.multiply(y_true, np.log(y_pred + epsilon)) + expected_losses += np.multiply(1 - y_true, np.log(1 - y_pred + epsilon)) + + y_pred = constant_op.constant(y_pred) + y_true = constant_op.constant(y_true) + sample_weight = np.array([3, 6, 5, 0, 4, 2]).reshape((2, 3, 1)) + loss = log_loss_obj( + y_true, + y_pred, + sample_weight=constant_op.constant(sample_weight, shape=(2, 3))) + actual_loss = np.multiply(-expected_losses, sample_weight) + actual_loss = np.sum(actual_loss) / batch_size + self.assertAlmostEqual(self.evaluate(loss), actual_loss, 3) + + def test_zero_weighted(self): + self.setup() + log_loss_obj = keras.losses.LogLoss() + sample_weight = 0 + loss = log_loss_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + self.assertAlmostEqual(self.evaluate(loss), 0., 3) + + if __name__ == '__main__': test.main() -- GitLab From 76df46cc71916d7027334b26cc2b390e4c432251 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 15:05:44 -0800 Subject: [PATCH 0156/2345] Automated rollback of commit 4d9173668fd3ba410532efd901467312b7418752 PiperOrigin-RevId: 227757156 --- tensorflow/cc/BUILD | 1 - tensorflow/cc/saved_model/BUILD | 2 - tensorflow/compiler/tf2xla/BUILD | 1 - tensorflow/core/BUILD | 3 +- tensorflow/core/kernels/BUILD | 83 +++++++++++++++++++++++++++++++- tensorflow/tensorflow.bzl | 2 - 6 files changed, 82 insertions(+), 10 deletions(-) diff --git a/tensorflow/cc/BUILD b/tensorflow/cc/BUILD index cf6d6050fa..a09becc49b 100644 --- a/tensorflow/cc/BUILD +++ b/tensorflow/cc/BUILD @@ -150,7 +150,6 @@ cc_library_with_android_deps( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:ops", "//tensorflow/core:protos_all_cc", ], ) diff --git a/tensorflow/cc/saved_model/BUILD b/tensorflow/cc/saved_model/BUILD index 836678d364..52345a376c 100644 --- a/tensorflow/cc/saved_model/BUILD +++ b/tensorflow/cc/saved_model/BUILD @@ -81,7 +81,6 @@ cc_library( ] + if_not_mobile([ "//tensorflow/core:core_cpu", "//tensorflow/core:lib", - "//tensorflow/core:ops", "//tensorflow/core:protos_all_cc", "//tensorflow/core:tensorflow", ]) + if_android([ @@ -101,7 +100,6 @@ cc_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:ops", "//tensorflow/core:protos_all_cc", "//tensorflow/core/util/tensor_bundle:naming", # mobile not supported yet diff --git a/tensorflow/compiler/tf2xla/BUILD b/tensorflow/compiler/tf2xla/BUILD index dfb6b57161..d8123e956f 100644 --- a/tensorflow/compiler/tf2xla/BUILD +++ b/tensorflow/compiler/tf2xla/BUILD @@ -281,7 +281,6 @@ tf_cc_test( "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", "//tensorflow/core:test_main", - "//tensorflow/core:testlib", ], ) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 6b32e1fead..7c9decbd09 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -1526,7 +1526,6 @@ cc_library( ":framework_internal", ":lib", ":lib_internal", - ":ops", ":protos_all_cc", ":shape_inference_testutil", ":tensor_testutil", @@ -3078,7 +3077,6 @@ tf_cuda_library( ":lib", ":lib_internal", ":metrics", - ":ops", ":proto_text", ":protos_all_cc", "//tensorflow/core/debug:debug_graph_utils", @@ -3861,6 +3859,7 @@ tf_cc_test( "ops/cudnn_rnn_ops_test.cc", ], deps = [ + ":cudnn_rnn_ops", "//tensorflow/core", "//tensorflow/core:framework", "//tensorflow/core:lib", diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index fd1df63c95..c72d04d3af 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -157,6 +157,7 @@ tf_kernel_library( name = "collective_ops", prefix = "collective_ops", deps = [ + "//tensorflow/core:collective_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", @@ -221,6 +222,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:nn_ops_op_lib", "//third_party/eigen3", ], alwayslink = 1, @@ -311,6 +313,7 @@ tf_kernel_library( "//tensorflow/core/nccl:nccl_lib", "//tensorflow/core:framework", "//tensorflow/core:gpu_headers_lib", + "//tensorflow/core:nccl_ops_op_lib", ]), ) @@ -456,6 +459,7 @@ cc_library( name = "batch_kernels", srcs = ["batch_kernels.cc"], deps = [ + "//tensorflow/core:batch_ops_op_lib", "//tensorflow/core:framework_headers_lib", "//tensorflow/core:protos_all_cc", "//tensorflow/core/kernels:concat_lib_hdrs", @@ -682,6 +686,7 @@ ARRAY_DEPS = [ ":ops_util", ":transpose_functor", "//tensorflow/core:array_grad", + "//tensorflow/core:array_ops_op_lib", "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", @@ -710,6 +715,7 @@ tf_kernel_library( deps = [ "//tensorflow/core:framework_headers_lib", "//tensorflow/core:lib", + "//tensorflow/core:set_ops_op_lib", "//third_party/eigen3", ], ) @@ -1073,6 +1079,7 @@ tf_kernel_library( srcs = ["ragged_gather_op.cc"], deps = [ "//tensorflow/core:framework", + "//tensorflow/core:ragged_array_ops_op_lib", ], ) @@ -1083,6 +1090,7 @@ tf_cc_test( deps = [ ":ragged_gather_op", "//tensorflow/core:framework", + "//tensorflow/core:ragged_array_ops_op_lib", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", @@ -1095,6 +1103,7 @@ tf_kernel_library( srcs = ["ragged_range_op.cc"], deps = [ "//tensorflow/core:framework", + "//tensorflow/core:ragged_math_ops_op_lib", ], ) @@ -1104,6 +1113,7 @@ tf_cc_test( deps = [ ":ragged_range_op", "//tensorflow/core:framework", + "//tensorflow/core:ragged_math_ops_op_lib", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", @@ -1116,6 +1126,7 @@ tf_kernel_library( srcs = ["ragged_tensor_to_sparse_kernel.cc"], deps = [ "//tensorflow/core:framework", + "//tensorflow/core:ragged_conversion_ops_op_lib", ], ) @@ -1127,6 +1138,7 @@ tf_cc_test( ":ragged_tensor_to_sparse_kernel", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:ragged_conversion_ops_op_lib", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", @@ -1140,6 +1152,7 @@ tf_kernel_library( visibility = ["//visibility:public"], deps = [ ":gpu_util_hdrs", + "//tensorflow/core:cudnn_rnn_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -1216,6 +1229,7 @@ tf_cuda_cc_test( "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:math_ops_op_lib", "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", "//tensorflow/core:test_main", @@ -1740,6 +1754,7 @@ tf_kernel_library( prefix = "candidate_sampler_ops", deps = [ ":range_sampler", + "//tensorflow/core:candidate_sampling_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", ], @@ -1773,6 +1788,7 @@ tf_kernel_library( name = "control_flow_ops", prefix = "control_flow_ops", deps = [ + "//tensorflow/core:control_flow_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", ], @@ -1784,6 +1800,7 @@ tf_kernel_library( deps = [ ":bounds_check", ":ops_util", + "//tensorflow/core:ctc_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core/util/ctc:ctc_beam_search_lib", @@ -1864,6 +1881,7 @@ DATA_FLOW_DEPS = [ ":typed_queue", "//third_party/eigen3", "//tensorflow/core:core_cpu", + "//tensorflow/core:data_flow_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -1928,6 +1946,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:scoped_allocator_ops_op_lib", ], ) @@ -1946,6 +1965,7 @@ tf_cuda_cc_test( "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:math_ops_op_lib", "//tensorflow/core:proto_text", "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", @@ -1993,6 +2013,7 @@ tf_kernel_library( DYNAMIC_DEPS = [ ":bounds_check", "//tensorflow/core:core_cpu", + "//tensorflow/core:data_flow_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -2025,6 +2046,7 @@ LOOKUP_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:lookup_ops_op_lib", ] tf_kernel_library( @@ -2053,6 +2075,7 @@ tf_kernel_library( deps = [ ":lookup_table_init_op", ":lookup_table_op", + "//tensorflow/core:checkpoint_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//third_party/eigen3", @@ -2063,6 +2086,7 @@ tf_kernel_library( name = "load_and_remap_matrix_op", srcs = ["load_and_remap_matrix_op.cc"], deps = [ + "//tensorflow/core:checkpoint_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -2209,6 +2233,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:resource_variable_ops_op_lib", "@com_google_absl//absl/strings", ], ) @@ -2225,6 +2250,7 @@ tf_kernel_library( ":concat_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:list_ops_op_lib", "//third_party/eigen3", ], ) @@ -2235,6 +2261,7 @@ tf_kernel_library( deps = [ "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:user_ops_op_lib", ], ) @@ -2257,6 +2284,7 @@ tf_kernel_library( "//tensorflow/core:core_cpu", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", + "//tensorflow/core:functional_ops_op_lib", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//third_party/eigen3", @@ -2269,6 +2297,7 @@ tf_kernel_library( deps = [ "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", + "//tensorflow/core:functional_ops_op_lib", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", "//tensorflow/core/grappler:grappler_item", @@ -2311,6 +2340,7 @@ IMAGE_DEPS = [ "//third_party/eigen3", "//tensorflow/core:framework", "//tensorflow/core:gif_internal", + "//tensorflow/core:image_ops_op_lib", "//tensorflow/core:jpeg_internal", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", @@ -2650,6 +2680,7 @@ cc_library( IO_DEPS = [ ":ops_util", "//tensorflow/core:framework", + "//tensorflow/core:io_ops_op_lib", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", @@ -2693,6 +2724,7 @@ SAVE_RESTORE_DEPS = [ ":bounds_check_lib", ":save_restore_tensor", "//tensorflow/core:framework", + "//tensorflow/core:io_ops_op_lib", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", @@ -2811,6 +2843,7 @@ LINALG_DEPS = [ "//third_party/eigen3", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:linalg_ops_op_lib", ] + if_cuda([ ":cuda_solvers", ":transpose_functor", @@ -2954,6 +2987,7 @@ LOGGING_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:logging_ops_op_lib", "//tensorflow/core:protos_all_cc", ] @@ -3025,6 +3059,7 @@ tf_kernel_library( ":bounds_check", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:manip_ops_op_lib", "//third_party/eigen3", ], ) @@ -3056,6 +3091,7 @@ MATH_DEPS = [ "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:math_grad", + "//tensorflow/core:math_ops_op_lib", "//third_party/eigen3", ] @@ -3166,7 +3202,7 @@ tf_kernel_library( tf_kernel_library( name = "cwise_op", prefix = "cwise_op", - deps = MATH_DEPS, + deps = MATH_DEPS + ["//tensorflow/core:bitwise_ops_op_lib"], ) tf_kernel_library( @@ -3185,6 +3221,7 @@ tf_kernel_library( name = "fft_ops", prefix = "fft_ops", deps = MATH_DEPS + [ + "//tensorflow/core:spectral_ops_op_lib", ] + if_cuda([ "//tensorflow/core/platform/default/build_config:cufft_plugin", ]), @@ -3383,7 +3420,10 @@ tf_cuda_cc_test( ":quantized_ops", "//tensorflow/cc:cc_ops", "//tensorflow/cc:client_session", + "//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", @@ -3641,6 +3681,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:nn_ops_op_lib", ] + select({ ":xsmm_convolutions": [ "@libxsmm_archive//:xsmm_avx", @@ -3662,6 +3703,7 @@ tf_kernel_library( "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:nn_ops_op_lib", ] + if_cuda([ "@cub_archive//:cub", "@local_config_cuda//cuda:cudnn_header", @@ -3681,6 +3723,7 @@ tf_kernel_library( "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:nn_ops_op_lib", ] + if_cuda([ "@local_config_cuda//cuda:cudnn_header", ]), @@ -3728,8 +3771,9 @@ NN_DEPS = [ "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:nn_grad", + "//tensorflow/core:nn_ops_op_lib", "//third_party/eigen3", -] +] + if_mkl(["//tensorflow/core:mkl_nn_ops_op_lib"]) tf_kernel_library( name = "batch_norm_op", @@ -3849,6 +3893,7 @@ tf_kernel_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:nn_grad", + "//tensorflow/core:nn_ops_op_lib", ] + if_cuda(["@cub_archive//:cub"]), ) @@ -3956,6 +4001,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:nn_ops_op_lib", "//tensorflow/core:stream_executor", "//third_party/eigen3", ], @@ -3999,6 +4045,7 @@ tf_kernel_library( "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:nn_ops_op_lib", "//third_party/eigen3", ], ) @@ -4080,6 +4127,7 @@ cc_library( PARSING_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:parsing_ops_op_lib", "//tensorflow/core:proto_text", "//tensorflow/core:protos_all_cc", ] @@ -4148,6 +4196,7 @@ RANDOM_OPS_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:random_ops_op_lib", ] tf_kernel_library( @@ -4186,6 +4235,7 @@ tf_kernel_library( ":random_op", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:stateless_random_ops_op_lib", ], ) @@ -4200,6 +4250,8 @@ cc_library( REQUIRED_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:no_op_op_lib", + "//tensorflow/core:sendrecv_ops_op_lib", ] tf_kernel_library( @@ -4260,6 +4312,7 @@ cc_library( SPARSE_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:sparse_ops_op_lib", ] tf_kernel_library( @@ -4504,6 +4557,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:sdca_ops_op_lib", "//third_party/eigen3", "@farmhash_archive//:farmhash", ], @@ -4543,6 +4597,7 @@ STATE_DEPS = [ "//third_party/eigen3", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:state_ops_op_lib", ] + if_sycl(["//tensorflow/core:sycl_runtime"]) tf_kernel_library( @@ -4680,6 +4735,7 @@ STRING_DEPS = [ "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:string_ops_op_lib", ] tf_kernel_library( @@ -4830,6 +4886,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:string_ops_op_lib", "//third_party/eigen3", "//third_party/icu/data:conversion_data", "@icu//:common", @@ -4851,6 +4908,7 @@ tf_kernel_library( ":variable_ops", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:training_ops_op_lib", "//third_party/eigen3", ], ) @@ -4909,6 +4967,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:random_ops_op_lib", ], ) @@ -4936,6 +4995,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:random_ops_op_lib", ], ) @@ -5844,9 +5904,12 @@ tf_kernel_library( ":ops_util", ":pooling_ops", ":quantization_utils", + "//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/eigen3", "@gemmlowp", ], @@ -6416,6 +6479,7 @@ tf_kernel_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", + "//tensorflow/core:remote_fused_graph_ops_op_lib", ], ) @@ -6570,6 +6634,8 @@ 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(), ) @@ -6619,6 +6685,8 @@ 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(), ) @@ -6637,6 +6705,8 @@ 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(), ) @@ -6650,6 +6720,8 @@ 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", "//third_party/eigen3", ] + mkl_deps(), ) @@ -6664,6 +6736,8 @@ 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", "//third_party/eigen3", ] + mkl_deps(), ) @@ -6804,6 +6878,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", + "//tensorflow/core:summary_ops_op_lib", "//tensorflow/core/lib/db:sqlite", ] + if_not_v2([ "//tensorflow/contrib/tensorboard/db:schema", @@ -6818,6 +6893,7 @@ tf_kernel_library( "decode_proto_op.cc", ], deps = [ + "//tensorflow/core:decode_proto_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core/util/proto:decode", @@ -6831,6 +6907,7 @@ tf_kernel_library( name = "encode_proto_op", srcs = ["encode_proto_op.cc"], deps = [ + "//tensorflow/core:encode_proto_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core/util/proto:descriptors", @@ -6848,6 +6925,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:rpc_ops_op_lib", "//tensorflow/core/util/rpc:call_container", "//tensorflow/core/util/rpc:rpc_factory", "//tensorflow/core/util/rpc:rpc_factory_registry", @@ -6860,6 +6938,7 @@ tf_kernel_library( srcs = ["unicode_script_op.cc"], deps = [ "//tensorflow/core:framework", + "//tensorflow/core:string_ops_op_lib", "@icu//:common", ], ) diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index b9069768e8..1024b686eb 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -590,7 +590,6 @@ def tf_gen_op_wrappers_cc( clean_dep("//tensorflow/core:core_cpu"), clean_dep("//tensorflow/core:framework"), clean_dep("//tensorflow/core:lib"), - clean_dep("//tensorflow/core:ops"), clean_dep("//tensorflow/core:protos_all_cc"), ]) + if_android([ clean_dep("//tensorflow/core:android_tensorflow_lib"), @@ -607,7 +606,6 @@ def tf_gen_op_wrappers_cc( clean_dep("//tensorflow/core:core_cpu"), clean_dep("//tensorflow/core:framework"), clean_dep("//tensorflow/core:lib"), - clean_dep("//tensorflow/core:ops"), clean_dep("//tensorflow/core:protos_all_cc"), ]) + if_android([ clean_dep("//tensorflow/core:android_tensorflow_lib"), -- GitLab From 8f1e280350d1a0a6b4134327ecd029195438e767 Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Thu, 3 Jan 2019 15:08:10 -0800 Subject: [PATCH 0157/2345] Add a clang-specific warning -Wmismatched-tags. PiperOrigin-RevId: 227757550 --- tensorflow/stream_executor/cuda/cuda_dnn.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index 7c05701895..01ff248150 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -51,6 +51,12 @@ limitations under the License. #include "absl/strings/string_view.h" // clang-format on +#pragma clang diagnostic push + +// Make sure that Eigen::half forward declaration in dnn.h matches the +// declaration in Eigen. +#pragma clang diagnostic warning "-Wmismatched-tags" + namespace stream_executor { namespace cuda { @@ -4531,5 +4537,7 @@ void initialize_cudnn() { } // namespace stream_executor +#pragma clang diagnostic pop + REGISTER_MODULE_INITIALIZER(register_cudnn, { stream_executor::initialize_cudnn(); }); -- GitLab From 2c65e8b01301d3d001952b3fc92d4a06c1f08bf5 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Thu, 3 Jan 2019 15:23:43 -0800 Subject: [PATCH 0158/2345] Throw an error when running loop inside scope PiperOrigin-RevId: 227760023 --- .../contrib/distribute/python/keras_test.py | 19 +++++++++++++++++-- .../engine/distributed_training_utils.py | 11 +++++++++++ .../keras/engine/training_distributed.py | 6 ++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/distribute/python/keras_test.py b/tensorflow/contrib/distribute/python/keras_test.py index ba5a5e6400..4cfcc0375f 100644 --- a/tensorflow/contrib/distribute/python/keras_test.py +++ b/tensorflow/contrib/distribute/python/keras_test.py @@ -1161,8 +1161,8 @@ class TestDistributionStrategyWithNormalizationLayer( np.testing.assert_allclose(out.std(), 1.0, atol=1e-1) -class TestDistributionStrategyVariableValidation(test.TestCase, - parameterized.TestCase): +class TestDistributionStrategyValidation(test.TestCase, + parameterized.TestCase): @combinations.generate(all_strategy_combinations_minus_default()) def test_layer_outside_scope(self, distribution): @@ -1192,6 +1192,21 @@ class TestDistributionStrategyVariableValidation(test.TestCase, metrics = ['mae', keras.metrics.CategoricalAccuracy()] model.compile(optimizer, loss, metrics=metrics) + @combinations.generate(all_strategy_combinations_minus_default()) + def test_loop_in_scope(self, distribution): + with self.cached_session(): + with self.assertRaisesRegexp( + RuntimeError, 'should not be run inside the distribution strategy'): + with distribution.scope(): + x = keras.layers.Input(shape=(3,), name='input') + y = keras.layers.Dense(4, name='dense')(x) + model = keras.Model(x, y) + optimizer = gradient_descent.GradientDescentOptimizer(0.001) + loss = 'mse' + model.compile(optimizer, loss) + input_array = np.zeros((3, 3), dtype=np.float32) + model.predict(input_array) + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/engine/distributed_training_utils.py b/tensorflow/python/keras/engine/distributed_training_utils.py index 231c59ba93..9e3bdd26cb 100644 --- a/tensorflow/python/keras/engine/distributed_training_utils.py +++ b/tensorflow/python/keras/engine/distributed_training_utils.py @@ -38,10 +38,21 @@ from tensorflow.python.ops import array_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import variables from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.training import distribution_strategy_context from tensorflow.python.training.mode_keys import ModeKeys from tensorflow.python.util import nest +def validate_not_in_strategy_scope(): + """Validate fit/eval/predict are not running in DS scope.""" + if distribution_strategy_context.has_distribution_strategy(): + if distribution_strategy_context.in_cross_replica_context(): + raise RuntimeError( + 'Fit/Eval/Predict should not be run inside the distribution strategy ' + 'scope. Only model creation and compilation should be in ' + 'distribution strategy scope.') + + def set_weights(distribution_strategy, dist_model, weights): """Sets the weights of the replicated models. diff --git a/tensorflow/python/keras/engine/training_distributed.py b/tensorflow/python/keras/engine/training_distributed.py index 0bd79f2b47..9f63f7bada 100644 --- a/tensorflow/python/keras/engine/training_distributed.py +++ b/tensorflow/python/keras/engine/training_distributed.py @@ -53,6 +53,8 @@ def fit_distributed(model, steps_per_epoch=None, validation_steps=None): """Fit loop for Distribution Strategies.""" + # TODO(b/122314600): Remove the scope validate. + distributed_training_utils.validate_not_in_strategy_scope() distributed_training_utils.validate_callbacks(callbacks, model.optimizer) distributed_training_utils.validate_inputs( x, y, model._distribution_strategy) @@ -136,6 +138,8 @@ def evaluate_distributed(model, steps=None, callbacks=None): """Evaluate loop for Distribution Strategies.""" + # TODO(b/122314600): Remove the scope validate. + distributed_training_utils.validate_not_in_strategy_scope() distributed_training_utils.validate_inputs(x, y, model._distribution_strategy) first_x_value = nest.flatten(x)[0] if isinstance(first_x_value, np.ndarray): @@ -171,6 +175,8 @@ def predict_distributed(model, steps=None, callbacks=None): """Predict loop for Distribution Strategies.""" + # TODO(b/122314600): Remove the scope validate. + distributed_training_utils.validate_not_in_strategy_scope() distributed_training_utils.validate_inputs( x, None, model._distribution_strategy) first_x_value = nest.flatten(x)[0] -- GitLab From 6932e795621a76a7e520bee529d915265b40e9bd Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 15:26:41 -0800 Subject: [PATCH 0159/2345] In tf.name_scope, throw an exception when the `default_name` arg is passed in but not a string and warn the user that they likely meant to pass it in as the `values` kwarg. PiperOrigin-RevId: 227760445 --- tensorflow/python/framework/ops.py | 8 ++++++++ tensorflow/python/framework/ops_test.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index 5dc8e418a2..079fad4210 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -6029,7 +6029,15 @@ class name_scope(object): # pylint: disable=invalid-name name: The name argument that is passed to the op function. default_name: The default name to use if the `name` argument is `None`. values: The list of `Tensor` arguments that are passed to the op function. + + Raises: + TypeError: if `default_name` is passed in but not a string. """ + if not (default_name is None or isinstance(default_name, six.string_types)): + raise TypeError( + "`default_name` type (%s) is not a string type. You likely meant to " + "pass this into the `values` kwarg." + % type(default_name)) self._name = default_name if name is None else name self._default_name = default_name self._values = values diff --git a/tensorflow/python/framework/ops_test.py b/tensorflow/python/framework/ops_test.py index 2d7ee1a99e..58d311fe4e 100644 --- a/tensorflow/python/framework/ops_test.py +++ b/tensorflow/python/framework/ops_test.py @@ -2052,6 +2052,9 @@ class OpScopeTest(test_util.TensorFlowTestCase): with ops.name_scope(None, default_scope_name, [a, b]) as scope: self.assertEqual("%s/" % default_scope_name, scope) self.assertEqual(g0, ops.get_default_graph()) + with self.assertRaises(TypeError): + with ops.name_scope(scope_name, [a, b]): + pass def _testGraphElements(self, graph_elements): scope_name = "my_scope" -- GitLab From 88c658a1bc737385e2edcbb7068406321151d875 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 16:18:43 -0800 Subject: [PATCH 0160/2345] Update ops-related pbtxt files. PiperOrigin-RevId: 227768824 --- .../core/ops/compat/ops_history.v1.pbtxt | 28 +++++++++++++++++++ tensorflow/core/ops/ops.pbtxt | 9 ++++++ 2 files changed, 37 insertions(+) diff --git a/tensorflow/core/ops/compat/ops_history.v1.pbtxt b/tensorflow/core/ops/compat/ops_history.v1.pbtxt index d27f7a5eac..28d085b2d2 100644 --- a/tensorflow/core/ops/compat/ops_history.v1.pbtxt +++ b/tensorflow/core/ops/compat/ops_history.v1.pbtxt @@ -76625,6 +76625,34 @@ op { type: "type" } } +op { + name: "TensorListConcat" + input_arg { + name: "input_handle" + type: DT_VARIANT + } + output_arg { + name: "tensor" + type_attr: "element_dtype" + } + output_arg { + name: "lengths" + type: DT_INT64 + } + attr { + name: "element_dtype" + type: "type" + } + attr { + name: "element_shape" + type: "shape" + default_value { + shape { + unknown_rank: true + } + } + } +} op { name: "TensorListConcatLists" input_arg { diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt index 412b4160d9..b4767d35d2 100644 --- a/tensorflow/core/ops/ops.pbtxt +++ b/tensorflow/core/ops/ops.pbtxt @@ -36756,6 +36756,15 @@ op { name: "element_dtype" type: "type" } + attr { + name: "element_shape" + type: "shape" + default_value { + shape { + unknown_rank: true + } + } + } } op { name: "TensorListConcatLists" -- GitLab From be57fb3a05ccfe01089fc2926e885c35ad5bda00 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 16:34:45 -0800 Subject: [PATCH 0161/2345] Small refactoring in preparation for a larger change. PiperOrigin-RevId: 227771218 --- tensorflow/lite/delegates/flex/kernel.cc | 116 +++++++++++------- tensorflow/lite/delegates/flex/kernel_test.cc | 5 +- 2 files changed, 70 insertions(+), 51 deletions(-) diff --git a/tensorflow/lite/delegates/flex/kernel.cc b/tensorflow/lite/delegates/flex/kernel.cc index d6e12ef650..250fc31043 100644 --- a/tensorflow/lite/delegates/flex/kernel.cc +++ b/tensorflow/lite/delegates/flex/kernel.cc @@ -52,11 +52,11 @@ namespace flex { namespace kernel { // Controls the lifetime of tensor handles in a vector. -class VectorOfHandles { +class OpOutputs { public: - explicit VectorOfHandles(int num_elements) : vector_(num_elements, nullptr) {} + explicit OpOutputs(int num_elements) : vector_(num_elements, nullptr) {} - ~VectorOfHandles() { + ~OpOutputs() { for (auto* handle : vector_) { if (handle) handle->Unref(); } @@ -72,6 +72,21 @@ class VectorOfHandles { tensorflow::gtl::InlinedVector vector_; }; +// A single node within the larger 'op'. Note that this kernel executes many +// TensorFlow ops within a single TF Lite op. +struct OpNode { + // The name of the TensorFlow op to execute. + string name; + // Index of this node into TF Lite's operator list. + int index; + // The corresponding NodeDef, containing the attributes for the op. + tensorflow::NodeDef nodedef; + // List of inputs, as TF Lite tensor indices. + std::vector inputs; + // List of outputs, as TF Lite tensor indices. + std::vector outputs; +}; + // Executes the TensorFlow op given by 'op_name', with the attributes specified // in 'nodedef'. Inputs and outputs are given as indices into the 'buffer_map'. tensorflow::Status ExecuteFlexOp(tensorflow::EagerContext* eager_context, @@ -114,7 +129,7 @@ tensorflow::Status ExecuteFlexOp(tensorflow::EagerContext* eager_context, } int num_retvals = outputs.size(); - VectorOfHandles retvals(num_retvals); + OpOutputs retvals(num_retvals); TF_RETURN_WITH_CONTEXT_IF_ERROR( EagerExecute(&op, retvals.GetVector(), &num_retvals), " (while executing '", op_name, "' via Eager)"); @@ -133,22 +148,38 @@ tensorflow::Status ExecuteFlexOp(tensorflow::EagerContext* eager_context, return tensorflow::Status::OK(); } -// A single node within the larger 'op'. Note that this kernel executes many -// TensorFlow ops within a single TF Lite op. -struct OpNode { - // The name of the TensorFlow op to execute. - string name; - // Index of this node into TF Lite's operator list. - int index; - // The corresponding NodeDef, containing the attributes for the op. - tensorflow::NodeDef nodedef; - // List of inputs, as TF Lite tensor indices. - std::vector inputs; - // List of outputs, as TF Lite tensor indices. - std::vector outputs; -}; +tensorflow::Status InitializeNodeDef(const void* custom_initial_data, + int custom_initial_data_size, + OpNode* node_data) { + if (!custom_initial_data) { + return tensorflow::errors::Internal( + "Cannot convert empty data into a valid NodeDef"); + } + // The flexbuffer contains a vector where the first elements is the + // op name and the second is a serialized NodeDef. + const flexbuffers::Vector& v = + flexbuffers::GetRoot( + reinterpret_cast(custom_initial_data), + custom_initial_data_size) + .AsVector(); + + node_data->name = v[0].AsString().str(); + if (!node_data->nodedef.ParseFromString(v[1].AsString().str())) { + node_data->nodedef.Clear(); + return tensorflow::errors::Internal( + "Failed to parse data into a valid NodeDef"); + } + + // Fill NodeDef with defaults if it's a valid op. + const tensorflow::OpRegistrationData* op_reg_data; + TF_RETURN_IF_ERROR(tensorflow::OpRegistry::Global()->LookUp( + node_data->nodedef.op(), &op_reg_data)); + AddDefaultsToNodeDef(op_reg_data->op_def, &node_data->nodedef); -// The Larger 'op', which contains all the nodes in a supported subgraph. + return tensorflow::Status::OK(); +} + +// The larger 'op', which contains all the nodes in a supported subgraph. struct OpData { tensorflow::EagerContext* eager_context; BufferMap* buffer_map; @@ -182,6 +213,7 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { } CHECK(params->nodes_to_replace); + tensorflow::Status status; for (auto node_index : TfLiteIntArrayView(params->nodes_to_replace)) { TfLiteNode* node; TfLiteRegistration* reg; @@ -192,29 +224,10 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { node_data.index = node_index; node_data.name = ""; - if (node->custom_initial_data) { - // The flexbuffer contains a vector where the first elements is the - // op name and the second is a serialized NodeDef. - const flexbuffers::Vector& v = - flexbuffers::GetRoot( - reinterpret_cast(node->custom_initial_data), - node->custom_initial_data_size) - .AsVector(); - - node_data.name = v[0].AsString().str(); - if (!node_data.nodedef.ParseFromString(v[1].AsString().str())) { - // We will just leave the nodedef empty and error out in Eval(). - node_data.nodedef.Clear(); - } - } - // Fill NodeDef with defaults if it's a valid op. - const tensorflow::OpRegistrationData* op_reg_data; - auto tf_status = tensorflow::OpRegistry::Global()->LookUp( - node_data.nodedef.op(), &op_reg_data); - if (tf_status.ok()) { - AddDefaultsToNodeDef(op_reg_data->op_def, &node_data.nodedef); - } + status = InitializeNodeDef(node->custom_initial_data, + node->custom_initial_data_size, &node_data); + if (!status.ok()) break; for (auto input_index : TfLiteIntArrayView(node->inputs)) { node_data.inputs.push_back(input_index); @@ -224,6 +237,13 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { } } + if (ConvertStatus(context, status) != kTfLiteOk) { + // We can't return an error from this function but ConvertStatus will + // report them and we will stop processing in Prepare() if anything went + // wrong. + return op_data; + } + return op_data; } @@ -263,6 +283,12 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { } for (const auto& node_data : op_data->nodes) { + if (node_data.nodedef.op().empty()) { + context->ReportError(context, "Invalid NodeDef in Flex op '%s'", + node_data.name.c_str()); + return kTfLiteError; + } + for (int tensor_index : node_data.inputs) { ++tensor_ref_count[tensor_index]; } @@ -303,12 +329,8 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { for (const auto& node_data : op_data->nodes) { SCOPED_TAGGED_OPERATOR_PROFILE( reinterpret_cast(context->profiler), - node_data.name.c_str(), node_data.index); - if (node_data.nodedef.op().empty()) { - context->ReportError(context, "Invalid NodeDef in Flex op '%s'", - node_data.name.c_str()); - return kTfLiteError; - } + node_data->name.c_str(), node_data->index); + auto status = ExecuteFlexOp(eager_context, buffer_map, node_data.name, node_data.nodedef, node_data.inputs, node_data.outputs); diff --git a/tensorflow/lite/delegates/flex/kernel_test.cc b/tensorflow/lite/delegates/flex/kernel_test.cc index cc5c8b32a0..647c199d4f 100644 --- a/tensorflow/lite/delegates/flex/kernel_test.cc +++ b/tensorflow/lite/delegates/flex/kernel_test.cc @@ -166,10 +166,7 @@ TEST_F(KernelTest, WrongSetOfNodes) { return GenericPrepare(context, delegate, {0, 1}); }); - SetShape(0, {2, 2, 1}); - SetValues(0, {1.1f, 2.2f, 3.3f, 4.4f}); - - ASSERT_FALSE(Invoke()); + ASSERT_NE(interpreter_->AllocateTensors(), kTfLiteOk); ASSERT_THAT(error_reporter().error_messages(), ContainsRegex("Invalid NodeDef in Flex op")); } -- GitLab From 03258acbf396a1241dee813e86848dc768394afd Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Thu, 3 Jan 2019 17:15:41 -0800 Subject: [PATCH 0162/2345] [tf.data] Replacing active / total parallel calls metric with thread utilization metric. PiperOrigin-RevId: 227776910 --- .../experimental/map_and_batch_dataset_op.cc | 22 ++++++++----------- .../data/parallel_interleave_dataset_op.cc | 19 +++++++--------- .../kernels/data/parallel_map_iterator.cc | 19 +++++++--------- .../kernel_tests/stats_dataset_test_base.py | 4 +--- 4 files changed, 26 insertions(+), 38 deletions(-) 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 ef75c84456..a6348e4647 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 @@ -259,7 +259,7 @@ class MapAndBatchDatasetOp : public UnaryDatasetOpKernel { params.dataset->batch_size_)) { std::vector components = str_util::Split(params.prefix, "::", str_util::SkipEmpty()); - prefix_end_ = components.back(); + key_prefix_ = components.back(); } ~Iterator() override { @@ -397,8 +397,9 @@ class MapAndBatchDatasetOp : public UnaryDatasetOpKernel { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::active_parallel_calls"), - static_cast(num_calls_)); + strings::StrCat(key_prefix_, "::thread_utilization"), + static_cast(num_calls_) / + static_cast(num_parallel_calls_->value)); } cond_var_->notify_all(); } @@ -637,18 +638,13 @@ class MapAndBatchDatasetOp : public UnaryDatasetOpKernel { num_calls_++; } } - const std::shared_ptr& stats_aggregator = - ctx->stats_aggregator(); + const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { mutex_lock l(*mu_); - // TODO(shivaniagrawal): add `parallel_calls_utilization` in the - // monitoring code or as histogram at fixed time intervals. stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::active_parallel_calls"), - static_cast(num_calls_)); - stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::num_parallel_calls"), - static_cast(num_parallel_calls_->value)); + strings::StrCat(key_prefix_, "::thread_utilization"), + static_cast(num_calls_) / + static_cast(num_parallel_calls_->value)); } for (const auto& call : new_calls) { CallFunction(ctx, call.first, call.second); @@ -803,7 +799,7 @@ 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 prefix_end_; + string key_prefix_; std::unique_ptr instantiated_captured_func_; }; diff --git a/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc b/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc index 74791669ce..315f96d4b5 100644 --- a/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc +++ b/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc @@ -209,7 +209,7 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { false /* low_latency_hint */)) { std::vector components = str_util::Split(params.prefix, "::", str_util::SkipEmpty()); - prefix_end_ = components.back(); + key_prefix_ = components.back(); } ~ParallelInterleaveIterator() override { @@ -430,8 +430,9 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::active_parallel_calls"), - static_cast(num_calls_)); + strings::StrCat(key_prefix_, "::thread_utilization"), + static_cast(num_calls_) / + static_cast(num_parallel_calls_->value)); } cond_var_->notify_all(); } @@ -515,14 +516,10 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { } const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { - // TODO(shivaniagrawal): add `parallel_calls_utilization` in the - // monitoring code or as histogram at fixed time intervals. stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::active_parallel_calls"), - static_cast(num_calls_)); - stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::num_parallel_calls"), - static_cast(num_parallel_calls_->value)); + strings::StrCat(key_prefix_, "::thread_utilization"), + static_cast(num_calls_) / + static_cast(num_parallel_calls_->value)); } cond_var_->notify_all(); } @@ -693,7 +690,7 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { // Identifies whether background activity should be cancelled. bool cancelled_ GUARDED_BY(*mu_) = false; - string prefix_end_; + 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 b62e7059ba..9a4ce981d0 100644 --- a/tensorflow/core/kernels/data/parallel_map_iterator.cc +++ b/tensorflow/core/kernels/data/parallel_map_iterator.cc @@ -60,7 +60,7 @@ class ParallelMapIterator : public DatasetBaseIterator { preserve_cardinality_(params.preserve_cardinality) { std::vector components = str_util::Split(base_params.prefix, "::", str_util::SkipEmpty()); - prefix_end_ = components.back(); + key_prefix_ = components.back(); } ~ParallelMapIterator() override { @@ -207,8 +207,9 @@ class ParallelMapIterator : public DatasetBaseIterator { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::active_parallel_calls"), - static_cast(num_calls_)); + strings::StrCat(key_prefix_, "::thread_utilization"), + static_cast(num_calls_) / + static_cast(num_parallel_calls_->value)); } RecordBufferEnqueue(ctx.get(), result->return_values); result->notification.Notify(); @@ -300,14 +301,10 @@ class ParallelMapIterator : public DatasetBaseIterator { } const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { - // TODO(shivaniagrawal): add `parallel_calls_utilization` in the - // monitoring code or as histogram at fixed time intervals. - stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::active_parallel_calls"), - static_cast(num_calls_)); stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::num_parallel_calls"), - static_cast(num_parallel_calls_->value)); + strings::StrCat(key_prefix_, "::thread_utilization"), + static_cast(num_calls_) / + static_cast(num_parallel_calls_->value)); } cond_var_->notify_all(); } @@ -403,7 +400,7 @@ class ParallelMapIterator : public DatasetBaseIterator { GUARDED_BY(*mu_); std::unique_ptr runner_thread_ GUARDED_BY(*mu_); bool cancelled_ GUARDED_BY(*mu_) = false; - string prefix_end_; + string key_prefix_; }; } // namespace 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 b80aab994e..f5a15f4c84 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 @@ -104,9 +104,7 @@ class StatsDatasetTestBase(test_base.DatasetTestBase): self._assertSummaryHasCountMoreOrEqualGeneralisedTag( summary_str, "::execution_time", float(i + 1)) self._assertSummaryContains(summary_str, - dataset_name + "::num_parallel_calls") - self._assertSummaryContains(summary_str, - dataset_name + "::active_parallel_calls") + dataset_name + "::thread_utilization") with self.assertRaises(errors.OutOfRangeError): self.evaluate(next_element()) if function_processing_time: -- GitLab From 8cfb8d2c11c93e36d7db2635518427260fcc4d41 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 17:16:06 -0800 Subject: [PATCH 0163/2345] Turn OpNode into a proper class, in preparation for a larger change. PiperOrigin-RevId: 227776959 --- tensorflow/lite/delegates/flex/kernel.cc | 118 +++++++++++++---------- 1 file changed, 68 insertions(+), 50 deletions(-) diff --git a/tensorflow/lite/delegates/flex/kernel.cc b/tensorflow/lite/delegates/flex/kernel.cc index 250fc31043..7d18c39098 100644 --- a/tensorflow/lite/delegates/flex/kernel.cc +++ b/tensorflow/lite/delegates/flex/kernel.cc @@ -74,17 +74,66 @@ class OpOutputs { // A single node within the larger 'op'. Note that this kernel executes many // TensorFlow ops within a single TF Lite op. -struct OpNode { +class OpNode { + public: + OpNode() {} + ~OpNode() {} + + const string& name() const { return name_; } + void set_name(const string& name) { name_ = name; } + + int index() const { return index_; } + void set_index(int index) { index_ = index; } + + const tensorflow::NodeDef& nodedef() const { return nodedef_; } + + const std::vector& inputs() const { return inputs_; } + std::vector* mutable_inputs() { return &inputs_; } + + const std::vector& outputs() const { return outputs_; } + std::vector* mutable_outputs() { return &outputs_; } + + tensorflow::Status InitializeNodeDef(const void* custom_initial_data, + int custom_initial_data_size) { + if (!custom_initial_data) { + return tensorflow::errors::Internal( + "Cannot convert empty data into a valid NodeDef"); + } + // The flexbuffer contains a vector where the first elements is the + // op name and the second is a serialized NodeDef. + const flexbuffers::Vector& v = + flexbuffers::GetRoot( + reinterpret_cast(custom_initial_data), + custom_initial_data_size) + .AsVector(); + + name_ = v[0].AsString().str(); + if (!nodedef_.ParseFromString(v[1].AsString().str())) { + nodedef_.Clear(); + return tensorflow::errors::Internal( + "Failed to parse data into a valid NodeDef"); + } + + // Fill NodeDef with defaults if it's a valid op. + const tensorflow::OpRegistrationData* op_reg_data; + TF_RETURN_IF_ERROR( + tensorflow::OpRegistry::Global()->LookUp(nodedef_.op(), &op_reg_data)); + AddDefaultsToNodeDef(op_reg_data->op_def, &nodedef_); + + return tensorflow::Status::OK(); + } + + private: // The name of the TensorFlow op to execute. - string name; + string name_; // Index of this node into TF Lite's operator list. - int index; + int index_; // The corresponding NodeDef, containing the attributes for the op. - tensorflow::NodeDef nodedef; + tensorflow::NodeDef nodedef_; // List of inputs, as TF Lite tensor indices. - std::vector inputs; + std::vector inputs_; // List of outputs, as TF Lite tensor indices. - std::vector outputs; + std::vector outputs_; }; // Executes the TensorFlow op given by 'op_name', with the attributes specified @@ -148,37 +197,6 @@ tensorflow::Status ExecuteFlexOp(tensorflow::EagerContext* eager_context, return tensorflow::Status::OK(); } -tensorflow::Status InitializeNodeDef(const void* custom_initial_data, - int custom_initial_data_size, - OpNode* node_data) { - if (!custom_initial_data) { - return tensorflow::errors::Internal( - "Cannot convert empty data into a valid NodeDef"); - } - // The flexbuffer contains a vector where the first elements is the - // op name and the second is a serialized NodeDef. - const flexbuffers::Vector& v = - flexbuffers::GetRoot( - reinterpret_cast(custom_initial_data), - custom_initial_data_size) - .AsVector(); - - node_data->name = v[0].AsString().str(); - if (!node_data->nodedef.ParseFromString(v[1].AsString().str())) { - node_data->nodedef.Clear(); - return tensorflow::errors::Internal( - "Failed to parse data into a valid NodeDef"); - } - - // Fill NodeDef with defaults if it's a valid op. - const tensorflow::OpRegistrationData* op_reg_data; - TF_RETURN_IF_ERROR(tensorflow::OpRegistry::Global()->LookUp( - node_data->nodedef.op(), &op_reg_data)); - AddDefaultsToNodeDef(op_reg_data->op_def, &node_data->nodedef); - - return tensorflow::Status::OK(); -} - // The larger 'op', which contains all the nodes in a supported subgraph. struct OpData { tensorflow::EagerContext* eager_context; @@ -222,18 +240,18 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { op_data->nodes.push_back(OpNode()); OpNode& node_data = op_data->nodes.back(); - node_data.index = node_index; - node_data.name = ""; + node_data.set_index(node_index); + node_data.set_name(""); - status = InitializeNodeDef(node->custom_initial_data, - node->custom_initial_data_size, &node_data); + status = node_data.InitializeNodeDef(node->custom_initial_data, + node->custom_initial_data_size); if (!status.ok()) break; for (auto input_index : TfLiteIntArrayView(node->inputs)) { - node_data.inputs.push_back(input_index); + node_data.mutable_inputs()->push_back(input_index); } for (auto output_index : TfLiteIntArrayView(node->outputs)) { - node_data.outputs.push_back(output_index); + node_data.mutable_outputs()->push_back(output_index); } } @@ -283,13 +301,13 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { } for (const auto& node_data : op_data->nodes) { - if (node_data.nodedef.op().empty()) { + if (node_data.nodedef().op().empty()) { context->ReportError(context, "Invalid NodeDef in Flex op '%s'", - node_data.name.c_str()); + node_data.name().c_str()); return kTfLiteError; } - for (int tensor_index : node_data.inputs) { + for (int tensor_index : node_data.inputs()) { ++tensor_ref_count[tensor_index]; } } @@ -329,11 +347,11 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { for (const auto& node_data : op_data->nodes) { SCOPED_TAGGED_OPERATOR_PROFILE( reinterpret_cast(context->profiler), - node_data->name.c_str(), node_data->index); + node_data->name().c_str(), node_data->index()); - auto status = - ExecuteFlexOp(eager_context, buffer_map, node_data.name, - node_data.nodedef, node_data.inputs, node_data.outputs); + auto status = ExecuteFlexOp(eager_context, buffer_map, node_data.name(), + node_data.nodedef(), node_data.inputs(), + node_data.outputs()); TF_LITE_ENSURE_OK(context, ConvertStatus(context, status)); } -- GitLab From 1bad2986b24e520b6b1558d5970b4b25abff0ab3 Mon Sep 17 00:00:00 2001 From: Tong Shen Date: Thu, 3 Jan 2019 17:22:28 -0800 Subject: [PATCH 0164/2345] Support outside compilation in TPUPartitionedCall. PiperOrigin-RevId: 227777744 --- tensorflow/compiler/tf2xla/BUILD | 1 + .../compiler/tf2xla/side_effect_util.cc | 39 +++++++++++++++++++ tensorflow/compiler/tf2xla/side_effect_util.h | 4 ++ 3 files changed, 44 insertions(+) diff --git a/tensorflow/compiler/tf2xla/BUILD b/tensorflow/compiler/tf2xla/BUILD index d8123e956f..0366ec45fb 100644 --- a/tensorflow/compiler/tf2xla/BUILD +++ b/tensorflow/compiler/tf2xla/BUILD @@ -669,6 +669,7 @@ cc_library( name = "side_effect_util", srcs = ["side_effect_util.cc"], hdrs = ["side_effect_util.h"], + visibility = [":friends"], deps = [ "//tensorflow/core:core_cpu", "@com_google_absl//absl/strings", diff --git a/tensorflow/compiler/tf2xla/side_effect_util.cc b/tensorflow/compiler/tf2xla/side_effect_util.cc index b62f8e9115..a9144cfc4f 100644 --- a/tensorflow/compiler/tf2xla/side_effect_util.cc +++ b/tensorflow/compiler/tf2xla/side_effect_util.cc @@ -26,6 +26,45 @@ const char kXlaTokenArgNodeName[] = "_xla_token_arg_node"; const char kXlaHasHostTransferAttrName[] = "_xla_has_host_transfer"; +Status SetDeviceOrdinalAttributeForNode(Node* node, int device_ordinal) { + if (!HasNodeAttr(node->def(), kXlaHasHostTransferAttrName)) { + return errors::InvalidArgument("Node ", node->DebugString(), + " does not have attribute ", + kXlaHasHostTransferAttrName); + } + + if (node->type_string() == "_XlaRecvAtHost" || + node->type_string() == "_XlaSendFromHost") { + node->ClearAttr("device_ordinal"); + node->AddAttr("device_ordinal", device_ordinal); + } else if (node->type_string() == "If") { + AttrValue device_ordinal_value; + device_ordinal_value.set_i(device_ordinal); + for (const string& attr_name : + std::vector{"then_branch", "else_branch"}) { + NameAttrList branch_func; + TF_RETURN_IF_ERROR(GetNodeAttr(node->attrs(), attr_name, &branch_func)); + (*branch_func.mutable_attr())["device_ordinal"] = device_ordinal_value; + node->ClearAttr(attr_name); + node->AddAttr(attr_name, branch_func); + } + } else if (node->type_string() == "While") { + AttrValue device_ordinal_value; + device_ordinal_value.set_i(device_ordinal); + for (const string& attr_name : std::vector{"cond", "body"}) { + NameAttrList branch_func; + TF_RETURN_IF_ERROR(GetNodeAttr(node->attrs(), attr_name, &branch_func)); + (*branch_func.mutable_attr())["device_ordinal"] = device_ordinal_value; + node->ClearAttr(attr_name); + node->AddAttr(attr_name, branch_func); + } + } else { + return errors::Internal("Unknown node type to set 'device_ordinal': ", + node->DebugString()); + } + return Status::OK(); +} + std::set CalculateTokenInputsForOutputToken(const Graph& g) { std::set results; Node* first_side_effecting_node_on_path = nullptr; diff --git a/tensorflow/compiler/tf2xla/side_effect_util.h b/tensorflow/compiler/tf2xla/side_effect_util.h index 7081b362c3..75e1f253fb 100644 --- a/tensorflow/compiler/tf2xla/side_effect_util.h +++ b/tensorflow/compiler/tf2xla/side_effect_util.h @@ -38,6 +38,10 @@ extern const char kXlaTokenArgNodeName[]; // This node have XlaRecvAtHost/XlaSendFromHost in its associated functions. extern const char kXlaHasHostTransferAttrName[]; +// Sets device ordinal attribute for nodes with attribute +// `kXlaHasHostTransferAttrName`. +Status SetDeviceOrdinalAttributeForNode(Node* node, int device_ordinal); + // Calculates side-effect dependencies for the graph's token output. // Returns a set of node names representing these dependencies. std::set CalculateTokenInputsForOutputToken(const Graph& g); -- GitLab From 7f9d4f0b93f256440662fa12e5508b552537803a Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Thu, 3 Jan 2019 17:31:51 -0800 Subject: [PATCH 0165/2345] Enable Tensorflow custom contraction kernel support in XLA single threaded matmul. PiperOrigin-RevId: 227778830 --- tensorflow/compiler/xla/service/cpu/BUILD | 1 + .../xla/service/cpu/runtime_single_threaded_matmul.cc | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index f49b5110be..a4ca772c7c 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -631,6 +631,7 @@ cc_library( deps = [ ":runtime_matvec", "//tensorflow/core:framework_lite", + "//tensorflow/core/kernels:eigen_contraction_kernel", "//third_party/eigen3", ], ) diff --git a/tensorflow/compiler/xla/service/cpu/runtime_single_threaded_matmul.cc b/tensorflow/compiler/xla/service/cpu/runtime_single_threaded_matmul.cc index 1ed743afc3..1f7204e67a 100644 --- a/tensorflow/compiler/xla/service/cpu/runtime_single_threaded_matmul.cc +++ b/tensorflow/compiler/xla/service/cpu/runtime_single_threaded_matmul.cc @@ -20,6 +20,10 @@ limitations under the License. #include "tensorflow/core/platform/dynamic_annotations.h" #include "tensorflow/core/platform/types.h" +#if defined(TENSORFLOW_USE_CUSTOM_CONTRACTION_KERNEL) +#include "tensorflow/core/kernels/eigen_contraction_kernel.h" +#endif + using tensorflow::int32; using tensorflow::int64; -- GitLab From 846dba8e81fde8a47196c9b04d31f847e5cc336f Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Thu, 3 Jan 2019 18:05:00 -0800 Subject: [PATCH 0166/2345] Automated rollback of commit 64596a68235709e7f3246d0b120a4146521a998b PiperOrigin-RevId: 227782472 --- tensorflow/lite/experimental/swift/BUILD | 100 ----- tensorflow/lite/experimental/swift/LICENSE | 202 ---------- tensorflow/lite/experimental/swift/README.md | 54 --- .../swift/Sources/Interpreter.swift | 265 -------------- .../swift/Sources/InterpreterError.swift | 99 ----- .../swift/Sources/InterpreterOptions.swift | 29 -- .../experimental/swift/Sources/Model.swift | 40 -- .../Sources/QuantizationParameters.swift | 38 -- .../experimental/swift/Sources/Tensor.swift | 138 ------- .../Configs/TensorFlowLite.tulsigen | 57 --- .../project.tulsiconf | 14 - .../project.pbxproj | 345 ------------------ .../TensorFlowLiteApp/AppDelegate.swift | 24 -- .../Array+TensorFlowLite.swift | 22 -- .../AppIcon.appiconset/Contents.json | 98 ----- .../Assets.xcassets/Contents.json | 6 - .../Base.lproj/LaunchScreen.storyboard | 44 --- .../Base.lproj/Main.storyboard | 95 ----- .../Data+TensorFlowLite.swift | 13 - .../TensorFlowLiteApp/Info.plist | 46 --- .../TensorFlowLiteApp/ViewController.swift | 299 --------------- .../swift/Tests/InterpreterOptionsTests.swift | 54 --- .../swift/Tests/InterpreterTests.swift | 315 ---------------- .../experimental/swift/Tests/ModelTests.swift | 59 --- .../Tests/QuantizationParametersTests.swift | 43 --- .../swift/Tests/TensorTests.swift | 83 ----- .../tools/pip_package/pip_smoke_test.py | 36 +- 27 files changed, 15 insertions(+), 2603 deletions(-) delete mode 100644 tensorflow/lite/experimental/swift/BUILD delete mode 100644 tensorflow/lite/experimental/swift/LICENSE delete mode 100644 tensorflow/lite/experimental/swift/README.md delete mode 100644 tensorflow/lite/experimental/swift/Sources/Interpreter.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/InterpreterError.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/Model.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/Tensor.swift delete mode 100644 tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen delete mode 100644 tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/ModelTests.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/TensorTests.swift diff --git a/tensorflow/lite/experimental/swift/BUILD b/tensorflow/lite/experimental/swift/BUILD deleted file mode 100644 index 3bd288a1e1..0000000000 --- a/tensorflow/lite/experimental/swift/BUILD +++ /dev/null @@ -1,100 +0,0 @@ -# TensorFlow Lite for Swift. - -package(default_visibility = ["//visibility:private"]) - -licenses(["notice"]) # Apache 2.0 - -exports_files(["LICENSE"]) - -load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_unit_test") -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -MINIMUM_OS_VERSION = "9.0" - -SWIFT_COPTS = [ - "-wmo", -] - -swift_library( - name = "TensorFlowLite", - srcs = glob(["Sources/*.swift"]), - copts = SWIFT_COPTS, - module_name = "TensorFlowLite", - tags = ["manual"], - deps = [ - "//tensorflow/lite/experimental/c:c_api", - ], -) - -ios_unit_test( - name = "TensorFlowLiteTests", - size = "small", - minimum_os_version = MINIMUM_OS_VERSION, - tags = [ - "manual", - # DISABLED: Following sanitizer tests are not supported by iOS test targets. - "noasan", - "nomsan", - "notsan", - ], - deps = [":TensorFlowLiteTestsLib"], -) - -swift_library( - name = "TensorFlowLiteTestsLib", - testonly = 1, - srcs = glob(["Tests/*.swift"]), - copts = SWIFT_COPTS, - tags = ["manual"], - deps = [ - ":TensorFlowLite", - ":TestResources", - ], -) - -objc_library( - name = "TestResources", - resources = [ - "//tensorflow/lite:testdata/add.bin", - "//tensorflow/lite:testdata/add_quantized.bin", - "//tensorflow/lite:testdata/multi_add.bin", - ], - tags = ["manual"], -) - -ios_application( - name = "TensorFlowLiteApp", - app_icons = glob(["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/**"]), - bundle_id = "com.tensorflow.lite.swift.TensorFlowLite", - families = [ - "ipad", - "iphone", - ], - infoplists = ["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist"], - launch_storyboard = "TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard", - minimum_os_version = MINIMUM_OS_VERSION, - sdk_frameworks = [ - "CoreGraphics", - ], - tags = ["manual"], - deps = [":TensorFlowLiteAppLib"], -) - -swift_library( - name = "TensorFlowLiteAppLib", - srcs = glob(["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/*.swift"]), - tags = ["manual"], - deps = [ - ":TensorFlowLite", - ":TensorFlowLiteAppResources", - ], -) - -objc_library( - name = "TensorFlowLiteAppResources", - storyboards = glob([ - "TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/*.storyboard", - ]), - tags = ["manual"], - deps = [":TestResources"], -) diff --git a/tensorflow/lite/experimental/swift/LICENSE b/tensorflow/lite/experimental/swift/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/tensorflow/lite/experimental/swift/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/tensorflow/lite/experimental/swift/README.md b/tensorflow/lite/experimental/swift/README.md deleted file mode 100644 index deb16c1e91..0000000000 --- a/tensorflow/lite/experimental/swift/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# TensorFlow Lite for Swift - -[TensorFlow Lite](https://www.tensorflow.org/lite/) is TensorFlow's lightweight -solution for Swift developers. It enables low-latency inference of on-device -machine learning models with a small binary size and fast performance supporting -hardware acceleration. - -## Getting Started - -### Bazel - -In your `BUILD` file, add the `TensorFlowLite` dependency: - -```python -swift_library( - # ... - deps = [ - "//tensorflow/lite/swift:TensorFlowLite", - ], -) -``` - -In your Swift files, import the module: - -```swift -import TensorFlowLite -``` - -### Tulsi - -Open the `TensorFlowLite.tulsiproj` using the [TulsiApp](https://github.com/bazelbuild/tulsi) or by -running the [`generate_xcodeproj.sh`](https://github.com/bazelbuild/tulsi/blob/master/src/tools/generate_xcodeproj.sh) -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 -``` diff --git a/tensorflow/lite/experimental/swift/Sources/Interpreter.swift b/tensorflow/lite/experimental/swift/Sources/Interpreter.swift deleted file mode 100644 index a14b5966b1..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/Interpreter.swift +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation -import TensorFlowLiteCAPI - -/// A TensorFlow Lite interpreter that performs inference from a given model. -public final class Interpreter { - - /// The `TFL_Interpreter` C pointer type represented as an `UnsafePointer`. - private typealias CInterpreter = OpaquePointer - - /// Total number of input tensors associated with the model. - public var inputTensorCount: Int { - return Int(TFL_InterpreterGetInputTensorCount(cInterpreter)) - } - - /// Total number of output tensors associated with the model. - public var outputTensorCount: Int { - return Int(TFL_InterpreterGetOutputTensorCount(cInterpreter)) - } - - /// The underlying `TFL_Interpreter` C pointer. - private var cInterpreter: CInterpreter? - - /// Creates a new model interpreter instance. - /// - /// - Parameters: - /// - modelPath: Local file path to a TensorFlow Lite model. - /// - options: Custom configurations for the interpreter. The default is `nil` indicating that - /// interpreter will determine the configuration options. - /// - Throws: An error if the model could not be loaded or the interpreter could not be created. - public init(modelPath: String, options: InterpreterOptions? = nil) throws { - guard let model = Model(filePath: modelPath) else { throw InterpreterError.failedToLoadModel } - - let cInterpreterOptions: OpaquePointer? = try options.map { options in - guard let cOptions = TFL_NewInterpreterOptions() else { - throw InterpreterError.failedToCreateInterpreter - } - if let threadCount = options.threadCount, threadCount > 0 { - TFL_InterpreterOptionsSetNumThreads(cOptions, Int32(threadCount)) - } - if options.isErrorLoggingEnabled { - TFL_InterpreterOptionsSetErrorReporter( - cOptions, - { (_, format, arguments) in - guard let cFormat = format, - let message = String(cFormat: cFormat, arguments: arguments) - else { - return - } - print(String(describing: InterpreterError.tensorFlowLiteError(message))) - }, - nil - ) - } - return cOptions - } - defer { TFL_DeleteInterpreterOptions(cInterpreterOptions) } - - guard let cInterpreter = TFL_NewInterpreter(model.cModel, cInterpreterOptions) else { - throw InterpreterError.failedToCreateInterpreter - } - self.cInterpreter = cInterpreter - } - - deinit { - TFL_DeleteInterpreter(cInterpreter) - } - - /// Invokes the interpreter to perform inference from the loaded graph. - /// - /// - Throws: An error if the model was not ready because tensors were not allocated. - public func invoke() throws { - guard TFL_InterpreterInvoke(cInterpreter) == kTfLiteOk else { - // TODO(b/117510052): Determine which error to throw. - throw InterpreterError.allocateTensorsRequired - } - } - - /// Returns the input tensor at the given index. - /// - /// - Parameters: - /// - index: The index for the input tensor. - /// - Throws: An error if the index is invalid or the tensors have not been allocated. - /// - Returns: The input tensor at the given index. - public func input(at index: Int) throws -> Tensor { - let maxIndex = inputTensorCount - 1 - guard case 0...maxIndex = index else { - throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) - } - guard let cTensor = TFL_InterpreterGetInputTensor(cInterpreter, Int32(index)), - let bytes = TFL_TensorData(cTensor), - let nameCString = TFL_TensorName(cTensor) - else { - throw InterpreterError.allocateTensorsRequired - } - guard let dataType = TensorDataType(type: TFL_TensorType(cTensor)) else { - throw InterpreterError.invalidTensorDataType - } - - let name = String(cString: nameCString) - let rank = TFL_TensorNumDims(cTensor) - let dimensions = (0.. Tensor { - let maxIndex = outputTensorCount - 1 - guard case 0...maxIndex = index else { - throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) - } - guard let cTensor = TFL_InterpreterGetOutputTensor(cInterpreter, Int32(index)), - let bytes = TFL_TensorData(cTensor), - let nameCString = TFL_TensorName(cTensor) - else { - // TODO(b/117510052): Determine which error to throw. - throw InterpreterError.invokeInterpreterRequired - } - guard let dataType = TensorDataType(type: TFL_TensorType(cTensor)) else { - throw InterpreterError.invalidTensorDataType - } - - let name = String(cString: nameCString) - let rank = TFL_TensorNumDims(cTensor) - let dimensions = (0.. Tensor { - let maxIndex = inputTensorCount - 1 - guard case 0...maxIndex = index else { - throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) - } - guard let cTensor = TFL_InterpreterGetInputTensor(cInterpreter, Int32(index)) else { - throw InterpreterError.allocateTensorsRequired - } - - let byteCount = TFL_TensorByteSize(cTensor) - guard data.count == byteCount else { - throw InterpreterError.invalidTensorDataCount(provided: data.count, required: byteCount) - } - - let status = data.withUnsafeBytes { TFL_TensorCopyFromBuffer(cTensor, $0, data.count) } - guard status == kTfLiteOk else { throw InterpreterError.failedToCopyDataToInputTensor } - return try input(at: index) - } - - /// Allocates memory for all input tensors based on their `TensorShape`s. - /// - /// - Note: This is a relatively expensive operation and should only be called after creating the - /// interpreter and/or resizing any input tensors. - /// - Throws: An error if memory could not be allocated for the input tensors. - public func allocateTensors() throws { - guard TFL_InterpreterAllocateTensors(cInterpreter) == kTfLiteOk else { - throw InterpreterError.failedToAllocateTensors - } - } -} - -// MARK: - Extensions - -extension String { - /// Returns a new `String` initialized by using the given format C array as a template into which - /// the remaining argument values are substituted according to the user’s default locale. - /// - /// - Note: Returns `nil` if a new `String` could not be constructed from the given values. - /// - Parameters: - /// - cFormat: The format C array as a template for substituting values. - /// - arguments: A C pointer to a `va_list` of arguments to substitute into `cFormat`. - init?(cFormat: UnsafePointer, arguments: CVaListPointer) { - var buffer: UnsafeMutablePointer? - guard vasprintf(&buffer, cFormat, arguments) != 0, let cString = buffer else { return nil } - self.init(validatingUTF8: cString) - } -} diff --git a/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift b/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift deleted file mode 100644 index 5de58b997a..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation - -/// TensorFlow Lite interpreter errors. -public enum InterpreterError: Error { - case invalidTensorIndex(index: Int, maxIndex: Int) - case invalidTensorDataCount(provided: Int, required: Int) - case invalidTensorDataType - case failedToLoadModel - case failedToCreateInterpreter - case failedToResizeInputTensor(index: Int) - case failedToCopyDataToInputTensor - case failedToAllocateTensors - case allocateTensorsRequired - case invokeInterpreterRequired - case tensorFlowLiteError(String) -} - -// MARK: - Extensions - -extension InterpreterError: LocalizedError { - /// Localized description of the interpreter error. - public var errorDescription: String? { - switch self { - case .invalidTensorIndex(let index, let maxIndex): - return "Invalid tensor index \(index), max index is \(maxIndex)." - case .invalidTensorDataCount(let providedCount, let requiredCount): - return "Provided data count \(providedCount) must match the required count \(requiredCount)." - case .invalidTensorDataType: - return "Tensor data type is unsupported or could not be determined because of a model error." - case .failedToLoadModel: - return "Failed to load the given model." - case .failedToCreateInterpreter: - return "Failed to create the interpreter." - case .failedToResizeInputTensor(let index): - return "Failed to resize input tesnor at index \(index)." - case .failedToCopyDataToInputTensor: - return "Failed to copy data to input tensor." - case .failedToAllocateTensors: - return "Failed to allocate memory for input tensors." - case .allocateTensorsRequired: - return "Must call allocateTensors()." - case .invokeInterpreterRequired: - return "Must call invoke()." - case .tensorFlowLiteError(let message): - return "TensorFlow Lite Error: \(message)" - } - } -} - -extension InterpreterError: CustomStringConvertible { - /// Textual representation of the TensorFlow Lite interpreter error. - public var description: String { - return errorDescription ?? "Unknown error." - } -} - -#if swift(>=4.2) -extension InterpreterError: Equatable {} -#else -extension InterpreterError: Equatable { - public static func == (lhs: InterpreterError, rhs: InterpreterError) -> Bool { - switch (lhs, rhs) { - case (.invalidTensorDataType, .invalidTensorDataType), - (.failedToLoadModel, .failedToLoadModel), - (.failedToCreateInterpreter, .failedToCreateInterpreter), - (.failedToAllocateTensors, .failedToAllocateTensors), - (.allocateTensorsRequired, .allocateTensorsRequired), - (.invokeInterpreterRequired, .invokeInterpreterRequired): - return true - case (.invalidTensorIndex(let lhsIndex, let lhsMaxIndex), - .invalidTensorIndex(let rhsIndex, let rhsMaxIndex)): - return lhsIndex == rhsIndex && lhsMaxIndex == rhsMaxIndex - case (.invalidTensorDataCount(let lhsProvidedCount, let lhsRequiredCount), - .invalidTensorDataCount(let rhsProvidedCount, let rhsRequiredCount)): - return lhsProvidedCount == rhsProvidedCount && lhsRequiredCount == rhsRequiredCount - case (.failedToResizeInputTensor(let lhsIndex), .failedToResizeInputTensor(let rhsIndex)): - return lhsIndex == rhsIndex - case (.tensorFlowLiteError(let lhsMessage), .tensorFlowLiteError(let rhsMessage)): - return lhsMessage == rhsMessage - default: - return false - } - } -} -#endif // swift(>=4.2) diff --git a/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift b/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift deleted file mode 100644 index 2365fd7ade..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation - -/// Custom configuration options for a TensorFlow Lite interpreter. -public struct InterpreterOptions: Equatable { - - /// Maximum number of CPU threads that the interpreter should run on. Default is `nil` which - /// indicates that the `Interpreter` will decide the number of threads to use. - public var threadCount: Int? = nil - - /// Whether error logging to the console is enabled. The default is `false`. - public var isErrorLoggingEnabled = false - - /// Creates a new instance of interpreter options. - public init() {} -} diff --git a/tensorflow/lite/experimental/swift/Sources/Model.swift b/tensorflow/lite/experimental/swift/Sources/Model.swift deleted file mode 100644 index e8c49ff1ae..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/Model.swift +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation -import TensorFlowLiteCAPI - -/// A TensorFlow Lite model used by the 'Interpreter` to perform inference. -final class Model { - - /// The `TFL_Model` C pointer type represented as an `UnsafePointer`. - typealias CModel = OpaquePointer - - /// The underlying `TFL_Model` C pointer. - let cModel: CModel? - - /// Creates a new model instance. - /// - /// - Precondition: Initialization can fail if the given `filePath` is invalid. - /// - Parameters: - /// - filePath: Local file path to a TensorFlow Lite model. - init?(filePath: String) { - guard !filePath.isEmpty, let cModel = TFL_NewModelFromFile(filePath) else { return nil } - self.cModel = cModel - } - - deinit { - TFL_DeleteModel(cModel) - } -} diff --git a/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift b/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift deleted file mode 100644 index f367875644..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation - -/// Parameters that determine the mapping of quantized values to real values. Quantized values can -/// be mapped to float values using the following conversion: -/// `realValue = scale * (quantizedValue - zeroPoint)`. -public struct QuantizationParameters { - - /// Difference between real values corresponding to consecutive quantized values differing by 1. - /// For example, the range of quantized values for `UInt8` data type is [0, 255]. - public let scale: Float - - /// Quantized value that corresponds to the real 0 value. - public let zeroPoint: Int - - /// Creates a new quantization parameters instance. - /// - /// - Parameters: - /// - scale: Scale value for asymmetric quantization. - /// - zeroPoint: Zero point for asymmetric quantization. - init(scale: Float, zeroPoint: Int) { - self.scale = scale - self.zeroPoint = zeroPoint - } -} diff --git a/tensorflow/lite/experimental/swift/Sources/Tensor.swift b/tensorflow/lite/experimental/swift/Sources/Tensor.swift deleted file mode 100644 index b738d87549..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/Tensor.swift +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation -import TensorFlowLiteCAPI - -/// An input or output tensor in a TensorFlow Lite graph. -public struct Tensor { - - /// Name of the tensor. - public let name: String - - /// Data type of the tensor. - public let dataType: TensorDataType - - /// Shape of the tensor. - public let shape: TensorShape - - /// Data in the input or output tensor. - public let data: Data - - /// Quantization parameters for the tensor if using a quantized model. - public let quantizationParameters: QuantizationParameters? - - /// Creates a new input or output tensor instance. - /// - /// - Parameters: - /// - name: Name of the tensor. - /// - dataType: Data type of the tensor. - /// - data: Data in the input tensor. - /// - quantizationParameters Quantization parameters for the tensor if using a quantized model. - /// The default is `nil`. - init( - name: String, - dataType: TensorDataType, - shape: TensorShape, - data: Data, - quantizationParameters: QuantizationParameters? = nil - ) { - self.name = name - self.dataType = dataType - self.shape = shape - self.data = data - self.quantizationParameters = quantizationParameters - } -} - -/// Supported TensorFlow Lite tensor data types. -public enum TensorDataType: Equatable { - /// 32-bit single precision floating point tensor data type. - case float32 - /// 8-bit unsigned integer tensor data type. - case uInt8 - /// 16-bit signed integer tensor data type. - case int16 - /// 32-bit signed integer tensor data type. - case int32 - /// 64-bit signed integer tensor data type. - case int64 - /// Boolean tensor data type. - case bool - - /// Creates a new tensor data type from the given `TFL_Type` or `nil` if the data type is - /// unsupported or could not be determined because there was an error. - /// - /// - Parameter type: A data type supported by a tensor. - init?(type: TFL_Type) { - switch type { - case kTfLiteFloat32: - self = .float32 - case kTfLiteUInt8: - self = .uInt8 - case kTfLiteInt16: - self = .int16 - case kTfLiteInt32: - self = .int32 - case kTfLiteInt64: - self = .int64 - case kTfLiteBool: - self = .bool - case kTfLiteNoType: - fallthrough - default: - return nil - } - } -} - -/// The shape of a TensorFlow Lite tensor. -public struct TensorShape { - - /// The number of dimensions of the tensor. - public let rank: Int - - /// Array of dimensions for the tensor. - public let dimensions: [Int] - - /// Array of `Int32` dimensions for the tensor. - var int32Dimensions: [Int32] { return dimensions.map(Int32.init) } - - /// Creates a new tensor shape instance with the given array of dimensions. - /// - /// - Parameters: - /// - dimensions: Dimensions for the tensor. - public init(_ dimensions: [Int]) { - self.rank = dimensions.count - self.dimensions = dimensions - } - - /// Creates a new tensor shape instance with the given elements representing the dimensions. - /// - /// - Parameters: - /// - elements: Dimensions for the tensor. - public init(_ elements: Int...) { - self.init(elements) - } -} - -extension TensorShape: ExpressibleByArrayLiteral { - /// Creates a new tensor shape instance with the given array literal representing the dimensions. - /// - /// - Parameters: - /// - arrayLiteral: Dimensions for the tensor. - public init(arrayLiteral: Int...) { - self.init(arrayLiteral) - } -} diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen deleted file mode 100644 index 4010fab49e..0000000000 --- a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen +++ /dev/null @@ -1,57 +0,0 @@ -{ - "sourceFilters" : [ - "third_party/tensorflow/lite/experimental/c", - "third_party/tensorflow/lite/experimental/swift", - "third_party/tensorflow/lite/experimental/swift/Sources", - "third_party/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp", - "third_party/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj", - "third_party/tensorflow/lite/experimental/swift/Tests", - ], - "buildTargets" : [ - "//third_party/tensorflow/lite/experimental/swift:TensorFlowLite", - "//third_party/tensorflow/lite/experimental/swift:TensorFlowLiteApp", - "//third_party/tensorflow/lite/experimental/swift:TensorFlowLiteTests", - ], - "projectName" : "TensorFlowLite", - "optionSet" : { - "LaunchActionPreActionScript" : { - "p" : "$(inherited)" - }, - "BazelBuildStartupOptionsRelease" : { - "p" : "$(inherited)" - }, - "BazelBuildOptionsRelease" : { - "p" : "$(inherited)" - }, - "BazelBuildOptionsDebug" : { - "p" : "$(inherited)" - }, - "EnvironmentVariables" : { - "p" : "$(inherited)" - }, - "BuildActionPreActionScript" : { - "p" : "$(inherited)" - }, - "CommandlineArguments" : { - "p" : "$(inherited)" - }, - "TestActionPreActionScript" : { - "p" : "$(inherited)" - }, - "BazelBuildStartupOptionsDebug" : { - "p" : "$(inherited)" - }, - "BuildActionPostActionScript" : { - "p" : "$(inherited)" - }, - "TestActionPostActionScript" : { - "p" : "$(inherited)" - }, - "LaunchActionPostActionScript" : { - "p" : "$(inherited)" - } - }, - "additionalFilePaths" : [ - "third_party/tensorflow/lite/experimental/swift/BUILD" - ] -} diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf deleted file mode 100644 index 14cff94453..0000000000 --- a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configDefaults" : { - "optionSet" : { - "ProjectPrioritizesSwift" : { - "p" : "YES" - } - } - }, - "projectName" : "TensorFlowLite", - "packages" : [ - "third_party/tensorflow/lite/experimental/swift" - ], - "workspaceRoot" : "../../../../../.." -} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj deleted file mode 100644 index fbbf9a1de2..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj +++ /dev/null @@ -1,345 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - -/* Begin PBXBuildFile section */ - 4A7304B421500B8400C90B21 /* Data+TensorFlowLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */; }; - 4AA72B732146ED64006C3AEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA72B722146ED64006C3AEF /* AppDelegate.swift */; }; - 4AA72B752146ED64006C3AEF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA72B742146ED64006C3AEF /* ViewController.swift */; }; - 4AA72B782146ED64006C3AEF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B762146ED64006C3AEF /* Main.storyboard */; }; - 4AA72B7A2146ED66006C3AEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B792146ED66006C3AEF /* Assets.xcassets */; }; - 4AA72B7D2146ED66006C3AEF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */; }; - 4ADDE0CE2176600E00FF07A2 /* Array+TensorFlowLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+TensorFlowLite.swift"; sourceTree = ""; }; - 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TensorFlowLiteApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 4AA72B722146ED64006C3AEF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 4AA72B742146ED64006C3AEF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - 4AA72B772146ED64006C3AEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 4AA72B792146ED66006C3AEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 4AA72B7C2146ED66006C3AEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 4AA72B7E2146ED66006C3AEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+TensorFlowLite.swift"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 4AA72B6C2146ED64006C3AEF /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 4AA72B662146ED64006C3AEF = { - isa = PBXGroup; - children = ( - 4AA72B712146ED64006C3AEF /* TensorFlowLiteApp */, - 4AA72B702146ED64006C3AEF /* Products */, - ); - sourceTree = ""; - }; - 4AA72B702146ED64006C3AEF /* Products */ = { - isa = PBXGroup; - children = ( - 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */, - ); - name = Products; - sourceTree = ""; - }; - 4AA72B712146ED64006C3AEF /* TensorFlowLiteApp */ = { - isa = PBXGroup; - children = ( - 4AA72B722146ED64006C3AEF /* AppDelegate.swift */, - 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */, - 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */, - 4AA72B742146ED64006C3AEF /* ViewController.swift */, - 4AA72B762146ED64006C3AEF /* Main.storyboard */, - 4AA72B792146ED66006C3AEF /* Assets.xcassets */, - 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */, - 4AA72B7E2146ED66006C3AEF /* Info.plist */, - ); - path = TensorFlowLiteApp; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 4AA72B6E2146ED64006C3AEF /* TensorFlowLiteApp */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4AA72B812146ED66006C3AEF /* Build configuration list for PBXNativeTarget "TensorFlowLiteApp" */; - buildPhases = ( - 4AA72B6B2146ED64006C3AEF /* Sources */, - 4AA72B6C2146ED64006C3AEF /* Frameworks */, - 4AA72B6D2146ED64006C3AEF /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = TensorFlowLiteApp; - productName = TensorFlowLiteApp; - productReference = 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 4AA72B672146ED64006C3AEF /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0940; - LastUpgradeCheck = 0940; - ORGANIZATIONNAME = Google; - TargetAttributes = { - 4AA72B6E2146ED64006C3AEF = { - CreatedOnToolsVersion = 9.4.1; - }; - }; - }; - buildConfigurationList = 4AA72B6A2146ED64006C3AEF /* Build configuration list for PBXProject "TensorFlowLiteApp" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 4AA72B662146ED64006C3AEF; - productRefGroup = 4AA72B702146ED64006C3AEF /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 4AA72B6E2146ED64006C3AEF /* TensorFlowLiteApp */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 4AA72B6D2146ED64006C3AEF /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4AA72B7D2146ED66006C3AEF /* LaunchScreen.storyboard in Resources */, - 4AA72B7A2146ED66006C3AEF /* Assets.xcassets in Resources */, - 4AA72B782146ED64006C3AEF /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 4AA72B6B2146ED64006C3AEF /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4AA72B732146ED64006C3AEF /* AppDelegate.swift in Sources */, - 4ADDE0CE2176600E00FF07A2 /* Array+TensorFlowLite.swift in Sources */, - 4A7304B421500B8400C90B21 /* Data+TensorFlowLite.swift in Sources */, - 4AA72B752146ED64006C3AEF /* ViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 4AA72B762146ED64006C3AEF /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 4AA72B772146ED64006C3AEF /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 4AA72B7C2146ED66006C3AEF /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 4AA72B7F2146ED66006C3AEF /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 4AA72B802146ED66006C3AEF /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 4AA72B822146ED66006C3AEF /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = TensorFlowLiteApp/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.tensorflow.lite.swift.TensorFlowLite; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 4AA72B832146ED66006C3AEF /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = TensorFlowLiteApp/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.tensorflow.lite.swift.TensorFlowLite; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 4AA72B6A2146ED64006C3AEF /* Build configuration list for PBXProject "TensorFlowLiteApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4AA72B7F2146ED66006C3AEF /* Debug */, - 4AA72B802146ED66006C3AEF /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4AA72B812146ED66006C3AEF /* Build configuration list for PBXNativeTarget "TensorFlowLiteApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4AA72B822146ED66006C3AEF /* Debug */, - 4AA72B832146ED66006C3AEF /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 4AA72B672146ED64006C3AEF /* Project object */; -} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift deleted file mode 100644 index ffa90a06ad..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift +++ /dev/null @@ -1,24 +0,0 @@ -import UIKit - -@UIApplicationMain - -final class AppDelegate: UIResponder, UIApplicationDelegate { - - /// The main window of the app. - var window: UIWindow? - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil - ) -> Bool { - return true - } -} - -// MARK: - Extensions - -#if !swift(>=4.2) -extension UIApplication { - typealias LaunchOptionsKey = UIApplicationLaunchOptionsKey -} -#endif // !swift(>=4.2) diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift deleted file mode 100644 index 56df1ce659..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -extension Array { - /// Creates a new array from the bytes of the given unsafe data. - /// - /// - Warning: The array's `Element` type must be trivial in that it can be copied bit for bit - /// with no indirection or reference-counting operations; otherwise, copying the raw bytes in - /// the `unsafeData`'s buffer to a new array returns an unsafe copy. - /// - Note: Returns `nil` if `unsafeData.count` is not a multiple of - /// `MemoryLayout.stride`. - /// - Parameter unsafeData: The data containing the bytes to turn into an array. - init?(unsafeData: Data) { - guard unsafeData.count % MemoryLayout.stride == 0 else { return nil } - let elements = unsafeData.withUnsafeBytes { - UnsafeBufferPointer( - start: $0, - count: unsafeData.count / MemoryLayout.stride - ) - } - self.init(elements) - } -} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d8db8d65fd..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" - }, - { - "idiom" : "ios-marketing", - "size" : "1024x1024", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json deleted file mode 100644 index da4a164c91..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index a07a1321be..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard deleted file mode 100644 index d0e91d63c4..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift deleted file mode 100644 index bc8a70c848..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension Data { - /// Creates a new buffer by copying the buffer pointer of the given array. - /// - /// - Warning: The given array's element type `T` must be trivial in that it can be copied bit - /// for bit with no indirection or reference-counting operations; otherwise, reinterpreting - /// data from the resulting buffer has undefined behavior. - /// - Parameter array: An array with elements of type `T`. - init(copyingBufferOf array: [T]) { - self = array.withUnsafeBufferPointer(Data.init) - } -} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist deleted file mode 100644 index 3ca3875f04..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist +++ /dev/null @@ -1,46 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 0.0.1 - LSRequiresIPhoneOS - - NSCameraUsageDescription - NSCameraUsageDescription - NSPhotoLibraryUsageDescription - Select a photo to detect objects in. - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - - diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift deleted file mode 100644 index 73c74fd19c..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift +++ /dev/null @@ -1,299 +0,0 @@ -import TensorFlowLite -import UIKit - -class ViewController: UIViewController { - - // MARK: - Properties - - /// TensorFlowLite interpreter object for performing inference from a given model. - private var interpreter: Interpreter? - - /// Serial dispatch queue for managing `Interpreter` calls. - private let interpreterQueue = DispatchQueue( - label: Constant.dispatchQueueLabel, - qos: .userInitiated - ) - - /// The currently selected model. - private var currentModel: Model { - guard let currentModel = Model(rawValue: modelControl.selectedSegmentIndex) else { - preconditionFailure("Invalid model for selected segment index.") - } - return currentModel - } - - /// A description of the current model. - private var modelDescription: String { - guard let interpreter = interpreter else { return "" } - let inputCount = interpreter.inputTensorCount - let outputCount = interpreter.outputTensorCount - let inputTensors = (0.. String = { - guard let results = [Float32](unsafeData: outputTensor.data) else { return "No results." } - return resultsText + results.description - } - self.updateResultsText(results()) - } catch let error { - self.updateResultsText( - "Failed to invoke the interpreter with error: \(error.localizedDescription)" - ) - return - } - } - } - - private func invokeAddQuantized() { - interpreterQueue.async { - guard let interpreter = self.interpreter else { - self.updateResultsText(Constant.nilInterpreterErrorMessage) - return - } - do { - try interpreter.resizeInput(at: 0, to: [2]) - try interpreter.allocateTensors() - let input: [UInt8] = [1, 3] - let resultsText = self.modelDescription + "\n\n" + - "Performing 2 add operations on quantized input \(input.description) equals: " - self.updateResultsText(resultsText) - let data = Data(input) - try interpreter.copy(data, toInputAt: 0) - try interpreter.invoke() - let outputTensor = try interpreter.output(at: 0) - let results: () -> String = { - guard let quantizationParameters = outputTensor.quantizationParameters else { - return "No results." - } - let quantizedResults = [UInt8](outputTensor.data) - let dequantizedResults = quantizedResults.map { - quantizationParameters.scale * Float(Int($0) - quantizationParameters.zeroPoint) - } - return resultsText + quantizedResults.description + - ", dequantized results: " + dequantizedResults.description - } - self.updateResultsText(results()) - } catch let error { - self.updateResultsText( - "Failed to invoke the interpreter with error: \(error.localizedDescription)" - ) - return - } - } - } - - private func invokeMultiAdd() { - interpreterQueue.async { - guard let interpreter = self.interpreter else { - self.updateResultsText(Constant.nilInterpreterErrorMessage) - return - } - do { - let shape = TensorShape(2) - try (0.. [Float32] in - let input = [Float32(index + 1), Float32(index + 2)] - let data = Data(copyingBufferOf: input) - try interpreter.copy(data, toInputAt: index) - return input - } - let resultsText = self.modelDescription + "\n\n" + - "Performing 3 add operations on inputs \(inputs.description) equals: " - self.updateResultsText(resultsText) - try interpreter.invoke() - let results = try (0.. [Float32] in - let tensor = try interpreter.output(at: index) - return [Float32](unsafeData: tensor.data) ?? [] - } - self.updateResultsText(resultsText + results.description) - } catch let error { - self.updateResultsText( - "Failed to invoke the interpreter with error: \(error.localizedDescription)" - ) - return - } - } - } - - private func updateResultsText(_ text: String? = nil) { - safeDispatchOnMain { self.resultsTextView.text = text } - } -} - -// MARK: - Constants - -private enum Constant { - static let dispatchQueueLabel = "TensorFlowLiteInterpreterQueue" - static let nilInterpreterErrorMessage = - "Failed to invoke the interpreter because the interpreter was nil." -} - -/// Models that can be loaded by the TensorFlow Lite `Interpreter`. -private enum Model: Int, CustomStringConvertible { - /// A float model that performs two add operations on one input tensor and returns the result in - /// one output tensor. - case add = 0 - /// A quantized model that performs two add operations on one input tensor and returns the result - /// in one output tensor. - case addQuantized = 1 - /// A float model that performs three add operations on four input tensors and returns the results - /// in 2 output tensors. - case multiAdd = 2 - - var fileInfo: (name: String, extension: String) { - switch self { - case .add: - return Add.fileInfo - case .addQuantized: - return AddQuantized.fileInfo - case .multiAdd: - return MultiAdd.fileInfo - } - } - - // MARK: - CustomStringConvertible - - var description: String { - switch self { - case .add: - return Add.name - case .addQuantized: - return AddQuantized.name - case .multiAdd: - return MultiAdd.name - } - } -} - -/// Values for the `Add` model. -private enum Add { - static let name = "Add" - static let fileInfo = (name: "add", extension: "bin") -} - -/// Values for the `AddQuantized` model. -private enum AddQuantized { - static let name = "AddQuantized" - static let fileInfo = (name: "add_quantized", extension: "bin") -} - -/// Values for the `MultiAdd` model. -private enum MultiAdd { - static let name = "MultiAdd" - static let fileInfo = (name: "multi_add", extension: "bin") -} - -// MARK: - Fileprivate - -/// Safely dispatches the given block on the main queue. If the current thread is `main`, the block -/// is executed synchronously; otherwise, the block is executed asynchronously on the main thread. -fileprivate func safeDispatchOnMain(_ block: @escaping () -> Void) { - if Thread.isMainThread { block(); return } - DispatchQueue.main.async { block() } -} diff --git a/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift b/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift deleted file mode 100644 index 54b4f59b28..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class InterpreterOptionsTests: XCTestCase { - - func testInterpreterOptions_InitWithDefaultValues() { - let options = InterpreterOptions() - XCTAssertNil(options.threadCount) - XCTAssertFalse(options.isErrorLoggingEnabled) - } - - func testInterpreterOptions_InitWithCustomValues() { - var options = InterpreterOptions() - options.threadCount = 2 - XCTAssertEqual(options.threadCount, 2) - options.isErrorLoggingEnabled = true - XCTAssertTrue(options.isErrorLoggingEnabled) - } - - func testInterpreterOptions_Equatable() { - var options1 = InterpreterOptions() - var options2 = InterpreterOptions() - XCTAssertEqual(options1, options2) - - options1.threadCount = 2 - options2.threadCount = 2 - XCTAssertEqual(options1, options2) - - options2.threadCount = 3 - XCTAssertNotEqual(options1, options2) - options2.threadCount = 2 - - options1.isErrorLoggingEnabled = true - options2.isErrorLoggingEnabled = true - XCTAssertEqual(options1, options2) - - options2.isErrorLoggingEnabled = false - XCTAssertNotEqual(options1, options2) - } -} diff --git a/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift b/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift deleted file mode 100644 index e98da5f951..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class InterpreterTests: XCTestCase { - - var interpreter: Interpreter! - - override func setUp() { - super.setUp() - - interpreter = try! Interpreter(modelPath: AddModel.path) - } - - override func tearDown() { - interpreter = nil - - super.tearDown() - } - - func testInterpreter_InitWithModelPath() { - XCTAssertNoThrow(try Interpreter(modelPath: AddModel.path)) - } - - func testInterpreter_Init_ThrowsFailedToLoadModel() { - XCTAssertThrowsError(try Interpreter(modelPath: "/invalid/path")) { error in - self.assertEqualErrors(actual: error, expected: .failedToLoadModel) - } - } - - func testInterpreter_InitWithModelPathAndOptions() { - var options = InterpreterOptions() - options.threadCount = 2 - XCTAssertNoThrow(try Interpreter(modelPath: AddModel.path, options: options)) - } - - func testInterpreter_InputTensorCount() { - XCTAssertEqual(interpreter.inputTensorCount, AddModel.inputTensorCount) - } - - func testInterpreter_OutputTensorCount() { - XCTAssertEqual(interpreter.outputTensorCount, AddModel.outputTensorCount) - } - - func testInterpreter_Invoke() throws { - try interpreter.allocateTensors() - XCTAssertNoThrow(try interpreter.invoke()) - } - - func testInterpreter_Invoke_ThrowsAllocateTensorsRequired_ModelNotReady() { - XCTAssertThrowsError(try interpreter.invoke()) { error in - self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired) - } - } - - func testInterpreter_InputTensorAtIndex() throws { - try setUpAddModelInputTensor() - let inputTensor = try interpreter.input(at: AddModel.validIndex) - XCTAssertEqual(inputTensor, AddModel.inputTensor) - } - - func testInterpreter_InputTensorAtIndex_QuantizedModel() throws { - interpreter = try Interpreter(modelPath: AddQuantizedModel.path) - try setUpAddQuantizedModelInputTensor() - let inputTensor = try interpreter.input(at: AddQuantizedModel.inputOutputIndex) - XCTAssertEqual(inputTensor, AddQuantizedModel.inputTensor) - } - - func testInterpreter_InputTensorAtIndex_ThrowsInvalidIndex() throws { - try interpreter.allocateTensors() - XCTAssertThrowsError(try interpreter.input(at: AddModel.invalidIndex)) { error in - let maxIndex = AddModel.inputTensorCount - 1 - self.assertEqualErrors( - actual: error, - expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) - ) - } - } - - func testInterpreter_InputTensorAtIndex_ThrowsAllocateTensorsRequired() { - XCTAssertThrowsError(try interpreter.input(at: AddModel.validIndex)) { error in - self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired) - } - } - - func testInterpreter_OutputTensorAtIndex() throws { - try setUpAddModelInputTensor() - try interpreter.invoke() - let outputTensor = try interpreter.output(at: AddModel.validIndex) - XCTAssertEqual(outputTensor, AddModel.outputTensor) - let expectedResults = [Float32](unsafeData: outputTensor.data) - XCTAssertEqual(expectedResults, AddModel.results) - } - - func testInterpreter_OutputTensorAtIndex_QuantizedModel() throws { - interpreter = try Interpreter(modelPath: AddQuantizedModel.path) - try setUpAddQuantizedModelInputTensor() - try interpreter.invoke() - let outputTensor = try interpreter.output(at: AddQuantizedModel.inputOutputIndex) - XCTAssertEqual(outputTensor, AddQuantizedModel.outputTensor) - let expectedResults = [UInt8](outputTensor.data) - XCTAssertEqual(expectedResults, AddQuantizedModel.results) - } - - func testInterpreter_OutputTensorAtIndex_ThrowsInvalidIndex() throws { - try interpreter.allocateTensors() - try interpreter.invoke() - XCTAssertThrowsError(try interpreter.output(at: AddModel.invalidIndex)) { error in - let maxIndex = AddModel.outputTensorCount - 1 - self.assertEqualErrors( - actual: error, - expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) - ) - } - } - - func testInterpreter_OutputTensorAtIndex_ThrowsInvokeInterpreterRequired() { - XCTAssertThrowsError(try interpreter.output(at: AddModel.validIndex)) { error in - self.assertEqualErrors(actual: error, expected: .invokeInterpreterRequired) - } - } - - func testInterpreter_ResizeInputTensorAtIndexToShape() { - XCTAssertNoThrow(try interpreter.resizeInput(at: AddModel.validIndex, to: [2, 2, 3])) - XCTAssertNoThrow(try interpreter.allocateTensors()) - } - - func testInterpreter_ResizeInputTensorAtIndexToShape_ThrowsInvalidIndex() { - XCTAssertThrowsError(try interpreter.resizeInput( - at: AddModel.invalidIndex, - to: [2, 2, 3] - )) { error in - let maxIndex = AddModel.inputTensorCount - 1 - self.assertEqualErrors( - actual: error, - expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) - ) - } - } - - func testInterpreter_CopyDataToInputTensorAtIndex() throws { - try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) - try interpreter.allocateTensors() - let inputTensor = try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex) - XCTAssertEqual(inputTensor.data, AddModel.inputData) - } - - func testInterpreter_CopyDataToInputTensorAtIndex_ThrowsInvalidIndex() { - XCTAssertThrowsError(try interpreter.copy( - AddModel.inputData, - toInputAt: AddModel.invalidIndex - )) { error in - let maxIndex = AddModel.inputTensorCount - 1 - self.assertEqualErrors( - actual: error, - expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) - ) - } - } - - func testInterpreter_CopyDataToInputTensorAtIndex_ThrowsInvalidDataCount() throws { - try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) - try interpreter.allocateTensors() - let invalidData = Data(count: AddModel.dataCount - 1) - XCTAssertThrowsError(try interpreter.copy( - invalidData, - toInputAt: AddModel.validIndex - )) { error in - self.assertEqualErrors( - actual: error, - expected: .invalidTensorDataCount(provided: invalidData.count, required: AddModel.dataCount) - ) - } - } - - func testInterpreter_AllocateTensors() { - XCTAssertNoThrow(try interpreter.allocateTensors()) - } - - // MARK: - Private - - private func setUpAddModelInputTensor() throws { - precondition(interpreter != nil) - try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) - try interpreter.allocateTensors() - try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex) - } - - private func setUpAddQuantizedModelInputTensor() throws { - precondition(interpreter != nil) - try interpreter.resizeInput(at: AddQuantizedModel.inputOutputIndex, to: AddQuantizedModel.shape) - try interpreter.allocateTensors() - try interpreter.copy(AddQuantizedModel.inputData, toInputAt: AddQuantizedModel.inputOutputIndex) - } - - private func assertEqualErrors(actual: Error, expected: InterpreterError) { - guard let actual = actual as? InterpreterError else { - XCTFail("Actual error should be of type InterpreterError.") - return - } - XCTAssertEqual(actual, expected) - } -} - -// MARK: - Constants - -/// Values for the `add.bin` model. -private enum AddModel { - static let info = (name: "add", extension: "bin") - static let inputTensorCount = 1 - static let outputTensorCount = 1 - static let invalidIndex = 1 - static let validIndex = 0 - static let shape: TensorShape = [2] - static let dataCount = inputData.count - static let inputData = Data(copyingBufferOf: [Float32(1.0), Float32(3.0)]) - static let outputData = Data(copyingBufferOf: [Float32(3.0), Float32(9.0)]) - static let results = [Float32(3.0), Float32(9.0)] - - static let inputTensor = Tensor( - name: "input", - dataType: .float32, - shape: shape, - data: inputData - ) - static let outputTensor = Tensor( - name: "output", - dataType: .float32, - shape: shape, - data: outputData - ) - - static var path: String = { - let bundle = Bundle(for: InterpreterTests.self) - guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" } - return path - }() -} - -/// Values for the `add_quantized.bin` model. -private enum AddQuantizedModel { - static let info = (name: "add_quantized", extension: "bin") - static let inputOutputIndex = 0 - static let shape: TensorShape = [2] - static let inputData = Data([1, 3]) - static let outputData = Data([3, 9]) - static let quantizationParameters = QuantizationParameters(scale: 0.003922, zeroPoint: 0) - static let results: [UInt8] = [3, 9] - - static let inputTensor = Tensor( - name: "input", - dataType: .uInt8, - shape: shape, - data: inputData, - quantizationParameters: quantizationParameters - ) - static let outputTensor = Tensor( - name: "output", - dataType: .uInt8, - shape: shape, - data: outputData, - quantizationParameters: quantizationParameters - ) - - static var path: String = { - let bundle = Bundle(for: InterpreterTests.self) - guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" } - return path - }() -} - -// MARK: - Extensions - -extension Array { - /// Creates a new array from the bytes of the given unsafe data. - /// - /// - Note: Returns `nil` if `unsafeData.count` is not a multiple of - /// `MemoryLayout.stride`. - /// - Parameter unsafeData: The data containing the bytes to turn into an array. - init?(unsafeData: Data) { - guard unsafeData.count % MemoryLayout.stride == 0 else { return nil } - let elements = unsafeData.withUnsafeBytes { - UnsafeBufferPointer( - start: $0, - count: unsafeData.count / MemoryLayout.stride - ) - } - self.init(elements) - } -} - -extension Data { - /// Creates a new buffer by copying the buffer pointer of the given array. - /// - /// - Warning: The given array's element type `T` must be trivial in that it can be copied bit - /// for bit with no indirection or reference-counting operations; otherwise, reinterpreting - /// data from the resulting buffer has undefined behavior. - /// - Parameter array: An array with elements of type `T`. - init(copyingBufferOf array: [T]) { - self = array.withUnsafeBufferPointer(Data.init) - } -} diff --git a/tensorflow/lite/experimental/swift/Tests/ModelTests.swift b/tensorflow/lite/experimental/swift/Tests/ModelTests.swift deleted file mode 100644 index 025db18906..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/ModelTests.swift +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class ModelTests: XCTestCase { - - var modelPath: String! - - override func setUp() { - super.setUp() - - let bundle = Bundle(for: type(of: self)) - guard let modelPath = bundle.path( - forResource: Constant.modelInfo.name, - ofType: Constant.modelInfo.extension) - else { - XCTFail("Failed to get the model file path.") - return - } - self.modelPath = modelPath - } - - override func tearDown() { - modelPath = nil - - super.tearDown() - } - - func testModel_InitWithFilePath() { - XCTAssertNotNil(Model(filePath: modelPath)) - } - - func testModel_InitWithEmptyFilePath_FailsInitialization() { - XCTAssertNil(Model(filePath: "")) - } - - func testModel_InitWithInvalidFilePath_FailsInitialization() { - XCTAssertNil(Model(filePath: "invalid/path")) - } -} - -// MARK: - Constants - -private enum Constant { - static let modelInfo = (name: "add", extension: "bin") -} diff --git a/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift b/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift deleted file mode 100644 index 65648c2698..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class QuantizationParametersTests: XCTestCase { - - func testQuantizationParameters_InitWithCustomValues() { - let parameters = QuantizationParameters(scale: 0.5, zeroPoint: 1) - XCTAssertEqual(parameters.scale, 0.5) - XCTAssertEqual(parameters.zeroPoint, 1) - } - - func testQuantizationParameters_Equatable() { - let parameters1 = QuantizationParameters(scale: 0.5, zeroPoint: 1) - let parameters2 = QuantizationParameters(scale: 0.5, zeroPoint: 1) - XCTAssertEqual(parameters1, parameters2) - - let parameters3 = QuantizationParameters(scale: 0.4, zeroPoint: 1) - XCTAssertNotEqual(parameters1, parameters3) - XCTAssertNotEqual(parameters2, parameters3) - } -} - -// MARK: - Extensions - -extension QuantizationParameters: Equatable { - public static func == (lhs: QuantizationParameters, rhs: QuantizationParameters) -> Bool { - return lhs.scale == rhs.scale && lhs.zeroPoint == rhs.zeroPoint - } -} diff --git a/tensorflow/lite/experimental/swift/Tests/TensorTests.swift b/tensorflow/lite/experimental/swift/Tests/TensorTests.swift deleted file mode 100644 index 4540043a16..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/TensorTests.swift +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class TensorTests: XCTestCase { - - // MARK: - Tensor - - func testTensor_Init() { - let name = "InputTensor" - let dataType: TensorDataType = .uInt8 - let shape = TensorShape(Constant.dimensions) - guard let data = name.data(using: .utf8) else { XCTFail("Data should not be nil."); return } - let quantizationParameters = QuantizationParameters(scale: 0.5, zeroPoint: 1) - let inputTensor = Tensor( - name: name, - dataType: dataType, - shape: shape, - data: data, - quantizationParameters: quantizationParameters - ) - XCTAssertEqual(inputTensor.name, name) - XCTAssertEqual(inputTensor.dataType, dataType) - XCTAssertEqual(inputTensor.shape, shape) - XCTAssertEqual(inputTensor.data, data) - XCTAssertEqual(inputTensor.quantizationParameters, quantizationParameters) - } - - // MARK: - TensorShape - - func testTensorShape_InitWithArray() { - let shape = TensorShape(Constant.dimensions) - XCTAssertEqual(shape.rank, Constant.dimensions.count) - XCTAssertEqual(shape.dimensions, Constant.dimensions) - } - - func testTensorShape_InitWithElements() { - let shape = TensorShape(2, 2, 3) - XCTAssertEqual(shape.rank, Constant.dimensions.count) - XCTAssertEqual(shape.dimensions, Constant.dimensions) - } - - func testTensorShape_InitWithArrayLiteral() { - let shape: TensorShape = [2, 2, 3] - XCTAssertEqual(shape.rank, Constant.dimensions.count) - XCTAssertEqual(shape.dimensions, Constant.dimensions) - } -} - -// MARK: - Constants - -private enum Constant { - /// Array of 2 arrays of 2 arrays of 3 numbers: [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]]. - static let dimensions = [2, 2, 3] -} - -// MARK: - Extensions - -extension TensorShape: Equatable { - public static func == (lhs: TensorShape, rhs: TensorShape) -> Bool { - return lhs.rank == rhs.rank && lhs.dimensions == rhs.dimensions - } -} - -extension Tensor: Equatable { - public static func == (lhs: Tensor, rhs: Tensor) -> Bool { - return lhs.name == rhs.name && lhs.dataType == rhs.dataType && lhs.shape == rhs.shape && - lhs.data == rhs.data && lhs.quantizationParameters == rhs.quantizationParameters - } -} diff --git a/tensorflow/tools/pip_package/pip_smoke_test.py b/tensorflow/tools/pip_package/pip_smoke_test.py index 952c71c615..51d010c9e1 100644 --- a/tensorflow/tools/pip_package/pip_smoke_test.py +++ b/tensorflow/tools/pip_package/pip_smoke_test.py @@ -30,19 +30,14 @@ os.chdir(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))) PIP_PACKAGE_QUERY_EXPRESSION = ( "deps(//tensorflow/tools/pip_package:build_pip_package)") -# List of file paths containing BUILD files that should not be included for the -# pip smoke test. -BUILD_BLACKLIST = [ - "tensorflow/lite/examples/android", - "tensorflow/lite/experimental/swift", -] def GetBuild(dir_base): """Get the list of BUILD file all targets recursively startind at dir_base.""" items = [] for root, _, files in os.walk(dir_base): for name in files: - if (name == "BUILD" and root not in BUILD_BLACKLIST): + if (name == "BUILD" and + root.find("tensorflow/lite/examples/android") == -1): items.append("//" + root + ":all") return items @@ -72,9 +67,9 @@ def BuildPyTestDependencies(): PYTHON_TARGETS, PY_TEST_QUERY_EXPRESSION = BuildPyTestDependencies() +# Hard-coded blacklist of files if not included in pip package # TODO(amitpatankar): Clean up blacklist. -# List of dependencies that should not included in the pip package. -DEPENDENCY_BLACKLIST = [ +BLACKLIST = [ "//tensorflow/python:extra_py_tests_deps", "//tensorflow/cc/saved_model:saved_model_half_plus_two", "//tensorflow:no_tensorflow_py_deps", @@ -87,7 +82,9 @@ DEPENDENCY_BLACKLIST = [ "//tensorflow/core/kernels/cloud:bigquery_reader_ops", "//tensorflow/python/feature_column:vocabulary_testdata", "//tensorflow/python:framework/test_file_system.so", - # lite + # contrib + "//tensorflow/contrib/session_bundle:session_bundle_half_plus_two", + "//tensorflow/contrib/keras:testing_utils", "//tensorflow/lite/experimental/examples/lstm:tflite_lstm", "//tensorflow/lite/experimental/examples/lstm:tflite_lstm.py", "//tensorflow/lite/experimental/examples/lstm:unidirectional_sequence_lstm_test", # pylint:disable=line-too-long @@ -96,9 +93,6 @@ DEPENDENCY_BLACKLIST = [ "//tensorflow/lite/python:interpreter_test", "//tensorflow/lite/python:interpreter.py", "//tensorflow/lite/python:interpreter_test.py", - # contrib - "//tensorflow/contrib/session_bundle:session_bundle_half_plus_two", - "//tensorflow/contrib/keras:testing_utils", "//tensorflow/contrib/ffmpeg:test_data", "//tensorflow/contrib/fused_conv:fused_conv2d_bias_activation_op_test_base", "//tensorflow/contrib/hadoop:test_data", @@ -155,8 +149,8 @@ def main(): # File extensions and endings to ignore ignore_extensions = ["_test", "_test.py", "_test_gpu", "_test_gpu.py"] - ignored_files_count = 0 - blacklisted_dependencies_count = len(DEPENDENCY_BLACKLIST) + ignored_files = 0 + blacklisted_files = len(BLACKLIST) # Compare dependencies for dependency in tf_py_test_dependencies_list: if dependency and dependency.startswith("//tensorflow"): @@ -164,16 +158,16 @@ def main(): # Ignore extensions if any(dependency.endswith(ext) for ext in ignore_extensions): ignore = True - ignored_files_count += 1 + ignored_files += 1 - # Check if the dependency is in the pip package, the dependency blacklist, - # or should be ignored because of its file extension. + # Check if the dependency is in the pip package, the blacklist, or + # should be ignored because of its file extension if not (ignore or dependency in pip_package_dependencies_list or - dependency in DEPENDENCY_BLACKLIST): + dependency in BLACKLIST): missing_dependencies.append(dependency) - print("Ignored files count: %d" % ignored_files_count) - print("Blacklisted dependencies count: %d" % blacklisted_dependencies_count) + print("Ignored files: %d" % ignored_files) + print("Blacklisted files: %d" % blacklisted_files) if missing_dependencies: print("Missing the following dependencies from pip_packages:") for missing_dependency in missing_dependencies: -- GitLab From 2e55da567448374f9f7e2498c1e338d07baf812f Mon Sep 17 00:00:00 2001 From: Andy Ly Date: Thu, 3 Jan 2019 18:15:14 -0800 Subject: [PATCH 0167/2345] [Grappler] MutableGraphView dedup control dependencies on init and on mutations. Update max regular output port on mutations. PiperOrigin-RevId: 227783394 --- tensorflow/core/grappler/graph_view.h | 5 +- .../core/grappler/mutable_graph_view.cc | 275 ++++++++---- tensorflow/core/grappler/mutable_graph_view.h | 72 ++-- .../core/grappler/mutable_graph_view_test.cc | 395 ++++++++++++++---- .../optimizers/data/graph_test_utils.cc | 4 +- .../optimizers/data/latency_all_edges_test.cc | 4 +- 6 files changed, 552 insertions(+), 203 deletions(-) diff --git a/tensorflow/core/grappler/graph_view.h b/tensorflow/core/grappler/graph_view.h index 16156d0f20..a17d17524a 100644 --- a/tensorflow/core/grappler/graph_view.h +++ b/tensorflow/core/grappler/graph_view.h @@ -172,9 +172,8 @@ class GraphViewInternal { if (fanin.index() < -1) { return false; } - string fanin_string = TensorIdToString(fanin); - for (int i = 0; i < node.input_size(); ++i) { - if (node.input(i) == fanin_string) { + for (const string& input : node.input()) { + if (ParseTensorName(input) == fanin) { return true; } } diff --git a/tensorflow/core/grappler/mutable_graph_view.cc b/tensorflow/core/grappler/mutable_graph_view.cc index ca4d5255c0..5df8bd8113 100644 --- a/tensorflow/core/grappler/mutable_graph_view.cc +++ b/tensorflow/core/grappler/mutable_graph_view.cc @@ -39,8 +39,103 @@ bool IsTensorIdPortValid(const TensorId& tensor_id) { return tensor_id.index() >= Graph::kControlSlot; } +// Determines if node is an Identity where it's first regular input is a Switch +// node. +bool IsIdentityConsumingSwitch(const MutableGraphView& graph, + const NodeDef& node) { + if ((IsIdentity(node) || IsIdentityNSingleInput(node)) && + node.input_size() > 0) { + TensorId tensor_id = ParseTensorName(node.input(0)); + if (tensor_id.index() == Graph::kControlSlot) return false; + NodeDef* input_node = graph.GetNode(tensor_id.node()); + return IsSwitch(*input_node); + } + return false; +} + +// Determines if node input can be deduped by regular inputs when used as a +// control dependency. Specifically, if a node is an Identity that leads to a +// Switch node, when used as a control dependency, that control dependency +// should not be deduped even though the same node is used as a regular input. +bool CanDedupControlWithRegularInput(const MutableGraphView& graph, + const NodeDef& control_node) { + return !IsIdentityConsumingSwitch(graph, control_node); +} + +// Determines if node input can be deduped by regular inputs when used as a +// control dependency. Specifically, if a node is an Identity that leads to a +// Switch node, when used as a control dependency, that control dependency +// should not be deduped even though the same node is used as a regular input. +bool CanDedupControlWithRegularInput(const MutableGraphView& graph, + absl::string_view control_node_name) { + NodeDef* control_node = graph.GetNode(control_node_name); + return CanDedupControlWithRegularInput(graph, *control_node); +} + } // namespace +void MutableGraphView::AddAndDedupFanouts(NodeDef* node) { + absl::flat_hash_set fanins; + absl::flat_hash_set controlling_fanins; + int pos = 0; + const int last_idx = node->input_size() - 1; + int last_pos = last_idx; + while (pos <= last_pos) { + TensorId tensor_id = ParseTensorName(node->input(pos)); + absl::string_view input_node_name = tensor_id.node(); + bool is_control_input = IsControlInput(tensor_id); + bool can_dedup_control_with_regular_input = + CanDedupControlWithRegularInput(*this, input_node_name); + bool can_dedup_control = + is_control_input && (can_dedup_control_with_regular_input || + (!can_dedup_control_with_regular_input && + controlling_fanins.contains(input_node_name))); + if (!gtl::InsertIfNotPresent(&fanins, input_node_name) && + can_dedup_control) { + node->mutable_input()->SwapElements(pos, last_pos--); + } else { + OutputPort output(nodes()[input_node_name], tensor_id.index()); + + if (is_control_input) { + fanouts()[output].emplace(node, Graph::kControlSlot); + } else { + max_regular_output_port()[output.node] = + std::max(max_regular_output_port()[output.node], output.port_id); + fanouts()[output].emplace(node, pos); + } + ++pos; + } + if (is_control_input) { + controlling_fanins.insert(input_node_name); + } + } + + if (last_pos < last_idx) { + node->mutable_input()->DeleteSubrange(last_pos + 1, last_idx - last_pos); + } +} + +void MutableGraphView::UpdateMaxRegularOutputPortForRemovedFanin( + const OutputPort& fanin, + const absl::flat_hash_set& fanin_fanouts) { + int max_port = max_regular_output_port()[fanin.node]; + if (!fanin_fanouts.empty() || max_port != fanin.port_id) { + return; + } + bool updated_max_port = false; + for (int i = fanin.port_id - 1; i >= 0; --i) { + OutputPort fanin_port(fanin.node, i); + if (!fanouts()[fanin_port].empty()) { + max_regular_output_port()[fanin.node] = i; + updated_max_port = true; + break; + } + } + if (!updated_max_port) { + max_regular_output_port().erase(fanin.node); + } +} + const absl::flat_hash_set& MutableGraphView::GetFanout(const GraphView::OutputPort& port) const { return GetFanout(MutableGraphView::OutputPort(const_cast(port.node), @@ -65,16 +160,16 @@ NodeDef* MutableGraphView::AddNode(NodeDef&& node) { AddUniqueNodeOrDie(node_in_graph); - AddFanouts(node_in_graph); + AddAndDedupFanouts(node_in_graph); return node_in_graph; } -void MutableGraphView::UpdateFanouts(absl::string_view from_node, +bool MutableGraphView::UpdateFanouts(absl::string_view from_node, absl::string_view to_node) { NodeDef* from_node_ptr = GetNode(from_node); NodeDef* to_node_ptr = GetNode(to_node); if (from_node_ptr && to_node_ptr) { - UpdateFanouts(from_node_ptr, to_node_ptr); + return UpdateFanoutsInternal(from_node_ptr, to_node_ptr); } else if (!from_node_ptr) { LOG(WARNING) << absl::Substitute( "Can't update fanouts from '$0' to '$1', from node was not found.", @@ -84,9 +179,11 @@ void MutableGraphView::UpdateFanouts(absl::string_view from_node, "Can't update fanouts from '$0' to '$1', to node was not found.", from_node, to_node); } + return false; } -void MutableGraphView::UpdateFanouts(NodeDef* from_node, NodeDef* to_node) { +bool MutableGraphView::UpdateFanoutsInternal(NodeDef* from_node, + NodeDef* to_node) { VLOG(2) << absl::Substitute("Update fanouts from '$0' to '$1'.", from_node->name(), to_node->name()); @@ -112,6 +209,7 @@ void MutableGraphView::UpdateFanouts(NodeDef* from_node, NodeDef* to_node) { // input to some other node. int keep_max_regular_output_port = -1; + bool modified = false; for (const Edge& edge : regular_edges) { const OutputPort output_port = edge.src; const InputPort input_port = edge.dst; @@ -120,7 +218,7 @@ void MutableGraphView::UpdateFanouts(NodeDef* from_node, NodeDef* to_node) { // AddAndUpdateFanoutsWithoutSelfLoops test for an example). if (input_port.node == to_node) { keep_max_regular_output_port = - std::max(keep_max_regular_output_port, input_port.port_id); + std::max(keep_max_regular_output_port, output_port.port_id); continue; } @@ -135,6 +233,11 @@ void MutableGraphView::UpdateFanouts(NodeDef* from_node, NodeDef* to_node) { remove_edge(output_port, input_port); // Add an edge between the `to_node` and new fanout node. add_edge(OutputPort(to_node, output_port.port_id), input_port); + // Dedup control dependency. + if (CanDedupControlWithRegularInput(*this, *to_node)) { + RemoveControllingFaninInternal(input_port.node, to_node); + } + modified = true; } // For the control fanouts we do not know the input index in a NodeDef, @@ -142,7 +245,6 @@ void MutableGraphView::UpdateFanouts(NodeDef* from_node, NodeDef* to_node) { auto control_fanouts = GetFanout(GraphView::OutputPort(from_node, Graph::kControlSlot)); - if (control_fanouts.empty()) return; const string from_control_input = absl::StrCat("^", from_node->name()); const string to_control_input = absl::StrCat("^", to_node->name()); @@ -151,20 +253,9 @@ void MutableGraphView::UpdateFanouts(NodeDef* from_node, NodeDef* to_node) { // Node can't be control dependency of itself. if (control_port.node == to_node) continue; - // Find and update input corresponding to control dependency. NodeDef* node = control_port.node; - for (int i = node->input_size() - 1; i >= 0; --i) { - const string& input = node->input(i); - if (!IsControlInput(input)) break; // we reached regular inputs - if (input == from_control_input) { - node->set_input(i, to_control_input); - } - } - - // Remove old edge between the `from_node` and the fanout node. - remove_edge(OutputPort(from_node, Graph::kControlSlot), control_port); - // Add an edge between the `to_node` and new fanout node. - add_edge(OutputPort(to_node, Graph::kControlSlot), control_port); + modified |= RemoveControllingFaninInternal(node, from_node); + modified |= AddFaninInternal(node, {to_node, Graph::kControlSlot}); } // Because we update all regular fanouts of `from_node`, we can just copy @@ -177,21 +268,33 @@ void MutableGraphView::UpdateFanouts(NodeDef* from_node, NodeDef* to_node) { } else { max_regular_output_port().erase(from_node); } + + return modified; } bool MutableGraphView::AddFaninInternal(NodeDef* node, const OutputPort& fanin) { int num_non_controlling_fanins = NumFanins(*node, /*include_controlling_nodes=*/false); + bool input_is_control = fanin.port_id == Graph::kControlSlot; + bool can_dedup_control_with_regular_input = + CanDedupControlWithRegularInput(*this, *fanin.node); + // Don't add duplicate control dependencies. + if (input_is_control) { + const int start = + can_dedup_control_with_regular_input ? 0 : num_non_controlling_fanins; + for (int i = start; i < node->input_size(); ++i) { + if (ParseTensorName(node->input(i)).node() == fanin.node->name()) { + return false; + } + } + } + InputPort input; input.node = node; - input.port_id = fanin.port_id == Graph::kControlSlot - ? Graph::kControlSlot - : num_non_controlling_fanins; + input.port_id = + input_is_control ? Graph::kControlSlot : num_non_controlling_fanins; - if (!gtl::InsertIfNotPresent(&fanouts()[fanin], input)) { - return false; - } node->add_input(TensorIdToString({fanin.node->name(), fanin.port_id})); if (fanin.port_id > Graph::kControlSlot) { int node_input_size = node->input_size() - 1; @@ -202,6 +305,17 @@ bool MutableGraphView::AddFaninInternal(NodeDef* node, num_non_controlling_fanins); } } + + fanouts()[fanin].insert(input); + if (max_regular_output_port()[fanin.node] < fanin.port_id) { + max_regular_output_port()[fanin.node] = fanin.port_id; + } + + // Dedup control dependencies. + if (!input_is_control && can_dedup_control_with_regular_input) { + RemoveControllingFaninInternal(node, fanin.node); + } + return true; } @@ -244,16 +358,18 @@ bool MutableGraphView::RemoveFanins(NodeDef* node, input.port_id = tensor_id.index() == Graph::kControlSlot ? Graph::kControlSlot : i; + auto& fanouts_set = fanouts()[fanin]; if (remove_fanin) { - fanouts()[fanin].erase(input); + fanouts_set.erase(input); } else { // Shift inputs to be retained. if (tensor_id.index() > Graph::kControlSlot) { - fanouts()[fanin].erase(input); - fanouts()[fanin].insert(InputPort(node, i)); + fanouts_set.erase(input); + fanouts_set.insert(InputPort(node, i)); } mutable_inputs->SwapElements(i, curr_pos++); } + UpdateMaxRegularOutputPortForRemovedFanin(fanin, fanouts_set); modified = true; } else { @@ -314,15 +430,12 @@ bool MutableGraphView::UpdateFanin(absl::string_view node_name, return false; } - bool is_from_fanin_control = from_fanin.index() == Graph::kControlSlot; - bool is_to_fanin_control = to_fanin.index() == Graph::kControlSlot; // When replacing a non control dependency fanin with a control dependency, or // vice versa, remove and add, so ports can be updated properly in fanout(s). - if (is_from_fanin_control || is_to_fanin_control) { + if (from_fanin.index() == Graph::kControlSlot || + to_fanin.index() == Graph::kControlSlot) { bool modified = RemoveFanins(node, {from_fanin}); - if (!HasFanin(*node, to_fanin)) { - modified |= AddFaninInternal(node, to_fanin); - } + modified |= AddFaninInternal(node, to_fanin); return modified; } @@ -336,79 +449,47 @@ bool MutableGraphView::UpdateFanin(absl::string_view node_name, string to_fanin_string = TensorIdToString(to_fanin); int num_inputs = node->input_size(); bool modified = false; + absl::flat_hash_set* from_fanin_port_fanouts = nullptr; + absl::flat_hash_set* to_fanin_port_fanouts = nullptr; for (int i = 0; i < num_inputs; ++i) { if (ParseTensorName(node->input(i)) == from_fanin) { - OutputPort from_fanin_port(from_fanin_node, from_fanin.index()); InputPort old_input; old_input.node = node; old_input.port_id = from_fanin.index() == Graph::kControlSlot ? Graph::kControlSlot : i; - fanouts()[from_fanin_port].erase(old_input); + if (from_fanin_port_fanouts == nullptr) { + OutputPort from_fanin_port(from_fanin_node, from_fanin.index()); + from_fanin_port_fanouts = &fanouts()[from_fanin_port]; + } + from_fanin_port_fanouts->erase(old_input); - OutputPort to_fanin_port(to_fanin_node, to_fanin.index()); InputPort new_input; new_input.node = node; new_input.port_id = to_fanin.index() == Graph::kControlSlot ? Graph::kControlSlot : i; - fanouts()[to_fanin_port].insert(new_input); + if (to_fanin_port_fanouts == nullptr) { + OutputPort to_fanin_port(to_fanin_node, to_fanin.index()); + to_fanin_port_fanouts = &fanouts()[to_fanin_port]; + } + to_fanin_port_fanouts->insert(new_input); node->set_input(i, to_fanin_string); modified = true; } } - return modified; -} - -bool MutableGraphView::DedupControllingFanins(NodeDef* node) { - absl::flat_hash_set fanins; - absl::flat_hash_set removed_fanins; - int pos = 0; - const int last_idx = node->input_size() - 1; - int last_pos = last_idx; - while (pos <= last_pos) { - const string& input = node->input(pos); - TensorId tensor_id = ParseTensorName(input); - if (!gtl::InsertIfNotPresent(&fanins, tensor_id.node()) && - IsControlInput(tensor_id)) { - node->mutable_input()->SwapElements(pos, last_pos--); - removed_fanins.insert(input); - } else { - ++pos; + // Dedup control dependencies and update max regular output ports. + if (modified) { + UpdateMaxRegularOutputPortForRemovedFanin( + {from_fanin_node, from_fanin.index()}, *from_fanin_port_fanouts); + if (max_regular_output_port()[to_fanin_node] < to_fanin.index()) { + max_regular_output_port()[to_fanin_node] = to_fanin.index(); } - } - - if (last_pos < last_idx) { - absl::flat_hash_set retained_fanins( - node->input().begin(), node->input().begin() + last_pos + 1); - for (const auto& removed : removed_fanins) { - if (!retained_fanins.contains(removed)) { - OutputPort fanin(nodes()[ParseTensorName(removed).node()], - Graph::kControlSlot); - fanouts()[fanin].erase({node, Graph::kControlSlot}); - } + if (CanDedupControlWithRegularInput(*this, *to_fanin_node)) { + RemoveControllingFaninInternal(node, to_fanin_node); } - node->mutable_input()->DeleteSubrange(last_pos + 1, last_idx - last_pos); - return true; } - return false; -} - -bool MutableGraphView::DedupControllingFanins(absl::string_view node_name) { - NodeDef* node = GetNode(node_name); - if (node == nullptr) { - return false; - } - return DedupControllingFanins(node); -} - -bool MutableGraphView::DedupControllingFanins() { - const int num_nodes = graph()->node_size(); - bool modified = false; - for (int i = 0; i < num_nodes; ++i) { - modified |= DedupControllingFanins(graph()->mutable_node(i)); - } return modified; } @@ -463,6 +544,24 @@ bool MutableGraphView::AddControllingFanin(absl::string_view node_name, } } +bool MutableGraphView::RemoveControllingFaninInternal(NodeDef* node, + NodeDef* fanin_node) { + for (int i = node->input_size() - 1; i >= 0; --i) { + TensorId tensor_id = ParseTensorName(node->input(i)); + if (tensor_id.index() > Graph::kControlSlot) { + break; + } + if (tensor_id.node() == fanin_node->name()) { + fanouts()[{fanin_node, Graph::kControlSlot}].erase( + {node, Graph::kControlSlot}); + node->mutable_input()->SwapElements(i, node->input_size() - 1); + node->mutable_input()->RemoveLast(); + return true; + } + } + return false; +} + void MutableGraphView::DeleteNodes(const std::set& nodes_to_delete) { for (const string& node_name_to_delete : nodes_to_delete) RemoveFaninsInternal(nodes().at(node_name_to_delete), @@ -488,7 +587,9 @@ void MutableGraphView::RemoveFaninsInternal(NodeDef* deleted_node, else input.port_id = i; - fanouts()[fanin].erase(input); + auto& fanouts_set = fanouts()[fanin]; + fanouts_set.erase(input); + UpdateMaxRegularOutputPortForRemovedFanin(fanin, fanouts_set); } } diff --git a/tensorflow/core/grappler/mutable_graph_view.h b/tensorflow/core/grappler/mutable_graph_view.h index f7c2a1118e..93f0958d5e 100644 --- a/tensorflow/core/grappler/mutable_graph_view.h +++ b/tensorflow/core/grappler/mutable_graph_view.h @@ -24,8 +24,10 @@ limitations under the License. #include "absl/types/span.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/node_def.pb.h" +#include "tensorflow/core/graph/graph.h" #include "tensorflow/core/graph/tensor_id.h" #include "tensorflow/core/grappler/graph_view.h" +#include "tensorflow/core/grappler/op_types.h" #include "tensorflow/core/platform/types.h" namespace tensorflow { @@ -41,7 +43,7 @@ class MutableGraphView : public internal::GraphViewInternal { public: explicit MutableGraphView(GraphDef* graph) : GraphViewInternal(graph) { for (NodeDef& node : *graph->mutable_node()) AddUniqueNodeOrDie(&node); - for (NodeDef& node : *graph->mutable_node()) AddFanouts(&node); + for (NodeDef& node : *graph->mutable_node()) AddAndDedupFanouts(&node); } // Lookup fanouts/fanins using immutable ports. @@ -63,16 +65,20 @@ class MutableGraphView : public internal::GraphViewInternal { // Updates all fanouts (input ports fetching output tensors) from `from_node` // to the `to_node`, including control dependencies. // - // Example: We have 2 nodes that use `bar` node output tensors as inputs: - // 1. foo1(bar:0, bar:1, other:0, ^bar) + // Example: We have 3 nodes that use `bar` node output tensors as inputs: + // 1. foo1(bar:0, bar:1, other:0) // 2. foo2(bar:1, other:1) + // 3. foo3(other:2, ^bar) // // After calling ForwardOutputs(bar, new_bar): - // 1. foo1(new_bar:0, new_bar:1, other:0, ^new_bar) + // 1. foo1(new_bar:0, new_bar:1, other:0) // 2. foo2(new_bar:1, other:1) - void UpdateFanouts(absl::string_view from_node, absl::string_view to_node); + // 3. foo3(other:2, ^new_bar) + // + // This will return true iff the nodes are modified. + bool UpdateFanouts(absl::string_view from_node, absl::string_view to_node); - // Add fanin to node `node_name`. If the node or fanin do not exist in the + // Adds fanin to node `node_name`. If the node or fanin do not exist in the // graph, nothing will be modified in the graph. If fanin is a control // dependency, existing control dependencies will be checked first before // adding. Otherwise fanin will be added after existing non control dependency @@ -82,7 +88,7 @@ class MutableGraphView : public internal::GraphViewInternal { // already exists, the node will not be modified. bool AddFanin(absl::string_view node_name, const TensorId& fanin); - // Remove fanin from node `node_name`. If the node or fanin do not exist in + // Removes fanin from node `node_name`. If the node or fanin do not exist in // the graph, nothing will be modified in the graph. If there are multiple // inputs that match the fanin, all of them will be removed. // @@ -90,32 +96,20 @@ class MutableGraphView : public internal::GraphViewInternal { // fanin, the node will not be modified. bool RemoveFanin(absl::string_view node_name, const TensorId& fanin); - // Remove all fanins from node `node_name`. Control dependencies will be + // Removes all fanins from node `node_name`. Control dependencies will be // retained if keep_controlling_fanins is true. // // This will return true iff the node is modified. bool RemoveAllFanins(absl::string_view node_name, bool keep_controlling_fanins); - // Replace all fanins `from_fanin` with `to_fanin` in node `node_name`. If + // Replaces all fanins `from_fanin` with `to_fanin` in node `node_name`. If // the fanins or node do not exist, nothing will be modified in the graph. // // This will return true iff the node is modified. bool UpdateFanin(absl::string_view node_name, const TensorId& from_fanin, const TensorId& to_fanin); - // Removes redundant control fanins from node `node_name`. - // - // This will return true iff the node is modified. - // TODO(lyandy): Measure performance of deduping on every AddFanin compared to - // deduping once at the end. - bool DedupControllingFanins(absl::string_view node_name); - - // Removes redundant control fanins from all nodes in the graph. - // - // This will return true iff the node is modified. - bool DedupControllingFanins(); - // Adds a control dependency to the target node named `node_name`. // // Case 1: If the fanin is not a Switch node, the control dependency is simply @@ -140,27 +134,42 @@ class MutableGraphView : public internal::GraphViewInternal { void DeleteNodes(const std::set& nodes_to_delete); private: + // Adds fanouts for fanins of node to graph, while deduping control + // dependencies from existing control dependencies and regular fanins. Note, + // node inputs will be mutated if control dependencies can be deduped. + void AddAndDedupFanouts(NodeDef* node); + + // Finds next output port smaller than fanin.port_id and update. The + // max_regular_output_port is only updated if fanin.port_id is the same as the + // current max_regular_output_port and if the fanouts set is empty. If there + // are no regular outputs, max_regular_output_port will be erased. + void UpdateMaxRegularOutputPortForRemovedFanin( + const OutputPort& fanin, + const absl::flat_hash_set& fanin_fanouts); + // Updates all fanouts (input ports fetching output tensors) from `from_node` // to the `to_node`, including control dependencies. // - // Example: We have 2 nodes that use `bar` node output tensors as inputs: - // 1. foo1(bar:0, bar:1, other:0, ^bar) + // Example: We have 3 nodes that use `bar` node output tensors as inputs: + // 1. foo1(bar:0, bar:1, other:0) // 2. foo2(bar:1, other:1) + // 3. foo3(other:2, ^bar) // // After calling ForwardOutputs(bar, new_bar): - // 1. foo1(new_bar:0, new_bar:1, other:0, ^new_bar) + // 1. foo1(new_bar:0, new_bar:1, other:0) // 2. foo2(new_bar:1, other:1) + // 3. foo3(other:2, ^new_bar) // // IMPORTANT: If `from_node` or `to_node` is not in the underlying graph, the // behavior is undefined. - void UpdateFanouts(NodeDef* from_node, NodeDef* to_node); + bool UpdateFanoutsInternal(NodeDef* from_node, NodeDef* to_node); // Removes fanins of the deleted node from internal state. Control // dependencies are retained iff keep_controlling_fanins is true. void RemoveFaninsInternal(NodeDef* deleted_node, bool keep_controlling_fanins); - // Add fanin to node. If fanin is a control dependency, existing control + // Adds fanin to node. If fanin is a control dependency, existing control // dependencies will be checked first before adding. Otherwise fanin will be // added after existing non control dependency inputs. // @@ -168,7 +177,7 @@ class MutableGraphView : public internal::GraphViewInternal { // already exists, the node will not be modified. bool AddFaninInternal(NodeDef* node, const OutputPort& fanin); - // Add fanin to node. If the node or fanin do not exist in the graph, nothing + // Adds fanin to node. If the node or fanin do not exist in the graph, nothing // will be modified in the graph. If fanin is a control dependency, existing // control dependencies will be checked first before adding. Otherwise fanin // will be added after existing non control dependency inputs. @@ -178,10 +187,15 @@ class MutableGraphView : public internal::GraphViewInternal { bool AddFaninInternal(NodeDef* node, const TensorId& fanin); // Removes any fanin in node that matches to a fanin in fanins. + // + // This will return true iff the node is modified. bool RemoveFanins(NodeDef* node, absl::Span fanins); - // Removes redundant control fanins from node. - bool DedupControllingFanins(NodeDef* node); + // Removes controlling fanin `fanin_node` from node if such controlling fanin + // exists. + // + // This will return true iff the node is modified. + bool RemoveControllingFaninInternal(NodeDef* node, NodeDef* fanin_node); }; } // end namespace grappler diff --git a/tensorflow/core/grappler/mutable_graph_view_test.cc b/tensorflow/core/grappler/mutable_graph_view_test.cc index cdc212f6f9..526ca85ec0 100644 --- a/tensorflow/core/grappler/mutable_graph_view_test.cc +++ b/tensorflow/core/grappler/mutable_graph_view_test.cc @@ -35,7 +35,8 @@ TEST(MutableGraphViewTest, AddAndUpdateFanouts) { {NDef("bar", "NotImportant", {}, {}), NDef("other", "NotImportant", {}, {}), NDef("foo_1", "NotImportant", {"bar", "other", "bar:1", "^bar"}), - NDef("foo_2", "NotImportant", {"other:1", "bar:2", "^bar"})}, + NDef("foo_2", "NotImportant", {"other:1", "bar:2", "^bar"}), + NDef("foo_3", "NotImportant", {"other:2", "^bar"})}, /*funcs=*/{}); MutableGraphView graph(&graph_def); @@ -43,7 +44,56 @@ TEST(MutableGraphViewTest, AddAndUpdateFanouts) { NodeDef* new_bar = graph.AddNode(NDef("new_bar", "NotImportant", {}, {})); NodeDef* bar = graph.GetNode("bar"); - graph.UpdateFanouts(bar->name(), new_bar->name()); + EXPECT_TRUE(graph.UpdateFanouts(bar->name(), new_bar->name())); + + // Fanout nodes must have their inputs updated. + NodeDef* foo_1 = graph.GetNode("foo_1"); + ASSERT_NE(foo_1, nullptr); + ASSERT_EQ(foo_1->input_size(), 3); + EXPECT_EQ(foo_1->input(0), "new_bar"); + EXPECT_EQ(foo_1->input(1), "other"); + EXPECT_EQ(foo_1->input(2), "new_bar:1"); + + NodeDef* foo_2 = graph.GetNode("foo_2"); + ASSERT_NE(foo_2, nullptr); + ASSERT_EQ(foo_2->input_size(), 2); + EXPECT_EQ(foo_2->input(0), "other:1"); + EXPECT_EQ(foo_2->input(1), "new_bar:2"); + + NodeDef* foo_3 = graph.GetNode("foo_3"); + ASSERT_NE(foo_3, nullptr); + ASSERT_EQ(foo_3->input_size(), 2); + EXPECT_EQ(foo_3->input(0), "other:2"); + EXPECT_EQ(foo_3->input(1), "^new_bar"); + + // And fanouts mapping must be also updated for both nodes. + bool include_control_fanouts = true; + auto old_node_fanouts = graph.GetFanouts(*bar, include_control_fanouts); + auto new_node_fanouts = graph.GetFanouts(*new_bar, include_control_fanouts); + + EXPECT_TRUE(old_node_fanouts.empty()); + + EXPECT_EQ(new_node_fanouts.size(), 4); + EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_1, 0)), 1); + EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_1, 2)), 1); + EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_2, 1)), 1); + EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_3, -1)), 1); +} + +TEST(MutableGraphViewTest, AddAndUpdateFanoutsKeepControls) { + GraphDef graph_def = test::function::GDef( + {NDef("bar_1", "Switch", {}, {}), NDef("bar_2", "Identity", {"bar_1:1"}), + NDef("other", "NotImportant", {}, {}), + NDef("foo_1", "NotImportant", {"bar_2", "other", "bar_2:1", "^bar_2"}), + NDef("foo_2", "NotImportant", {"other:1", "bar_2:2", "^bar_2"})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + NodeDef* new_bar = graph.AddNode(NDef("new_bar", "Identity", {"bar_1:2"})); + NodeDef* bar_2 = graph.GetNode("bar_2"); + + EXPECT_TRUE(graph.UpdateFanouts(bar_2->name(), new_bar->name())); // Fanout nodes must have their inputs updated. NodeDef* foo_1 = graph.GetNode("foo_1"); @@ -63,7 +113,7 @@ TEST(MutableGraphViewTest, AddAndUpdateFanouts) { // And fanouts mapping must be also updated for both nodes. bool include_control_fanouts = true; - auto old_node_fanouts = graph.GetFanouts(*bar, include_control_fanouts); + auto old_node_fanouts = graph.GetFanouts(*bar_2, include_control_fanouts); auto new_node_fanouts = graph.GetFanouts(*new_bar, include_control_fanouts); EXPECT_TRUE(old_node_fanouts.empty()); @@ -78,7 +128,8 @@ TEST(MutableGraphViewTest, AddAndUpdateFanoutsWithoutSelfLoops) { // Actual node.op() is not important in this test. GraphDef graph_def = test::function::GDef({NDef("bar", "NotImportant", {}, {}), - NDef("foo", "NotImportant", {"bar", "^bar"})}, + NDef("foo_1", "NotImportant", {"bar", "^bar"}), + NDef("foo_2", "NotImportant", {"^bar"})}, /*funcs=*/{}); MutableGraphView graph(&graph_def); @@ -87,14 +138,18 @@ TEST(MutableGraphViewTest, AddAndUpdateFanoutsWithoutSelfLoops) { NodeDef* new_bar = graph.AddNode(NDef("new_bar", "NewBar", {"bar"}, {})); NodeDef* bar = graph.GetNode("bar"); - graph.UpdateFanouts("bar", new_bar->name()); + EXPECT_TRUE(graph.UpdateFanouts("bar", new_bar->name())); // Foo node must read from `new_bar`. - NodeDef* foo = graph.GetNode("foo"); - ASSERT_NE(foo, nullptr); - ASSERT_EQ(foo->input_size(), 2); - EXPECT_EQ(foo->input(0), "new_bar"); - EXPECT_EQ(foo->input(1), "^new_bar"); + NodeDef* foo_1 = graph.GetNode("foo_1"); + ASSERT_NE(foo_1, nullptr); + ASSERT_EQ(foo_1->input_size(), 1); + EXPECT_EQ(foo_1->input(0), "new_bar"); + + NodeDef* foo_2 = graph.GetNode("foo_2"); + ASSERT_NE(foo_2, nullptr); + ASSERT_EQ(foo_2->input_size(), 1); + EXPECT_EQ(foo_2->input(0), "^new_bar"); // And the `new_bar` should read from the original `bar`. ASSERT_EQ(new_bar->input_size(), 1); @@ -109,8 +164,8 @@ TEST(MutableGraphViewTest, AddAndUpdateFanoutsWithoutSelfLoops) { EXPECT_EQ(bar_fanouts.count(MutableGraphView::InputPort(new_bar, 0)), 1); EXPECT_EQ(new_bar_fanouts.size(), 2); - EXPECT_EQ(new_bar_fanouts.count(MutableGraphView::InputPort(foo, 0)), 1); - EXPECT_EQ(new_bar_fanouts.count(MutableGraphView::InputPort(foo, -1)), 1); + EXPECT_EQ(new_bar_fanouts.count(MutableGraphView::InputPort(foo_1, 0)), 1); + EXPECT_EQ(new_bar_fanouts.count(MutableGraphView::InputPort(foo_2, -1)), 1); } GraphDef SimpleMutateFaninGraph() { @@ -176,7 +231,7 @@ TEST(MutableGraphViewTest, AddFanin) { expected_node = NDef("", "", {"b", "a:1", "a:1", "b:2"}); TestAddFanin("foo_3", {"b", 2}, /*modified=*/true, &expected_node); // Add input to node with 1 input multiple controls. - expected_node = NDef("", "", {"b", "a", "^c", "^a"}); + expected_node = NDef("", "", {"b", "a", "^c"}); TestAddFanin("foo_2", {"a", 0}, /*modified=*/true, &expected_node); // Add input to node with multiple inputs and controls. expected_node = NDef("", "", {"a", "b:2", "b:2", "a:1", "^d", "^c"}); @@ -201,8 +256,8 @@ TEST(MutableGraphViewTest, AddFanin) { TestAddFanin("foo_2", {"d", Graph::kControlSlot}, /*modified=*/true, &expected_node); // Add control to node with multiple input multiple controls. - expected_node = NDef("", "", {"a", "b:2", "b:2", "^c", "^d", "^a"}); - TestAddFanin("foo_4", {"a", Graph::kControlSlot}, /*modified=*/true, + expected_node = NDef("", "", {"a", "b:2", "b:2", "^c", "^d"}); + TestAddFanin("foo_4", {"a", Graph::kControlSlot}, /*modified=*/false, &expected_node); // Add control to node with 0 inputs 0 controls. expected_node = NDef("", "", {"^a"}); @@ -431,7 +486,7 @@ TEST(MutableGraphViewTest, UpdateFanin) { TestUpdateFanin("foo_4", {"d", Graph::kControlSlot}, {"d", 1}, /*modified=*/true, &expected_node); // Update fanin from control to control. - expected_node = NDef("", "", {"a", "b:2", "b:2", "^d", "^b"}); + expected_node = NDef("", "", {"a", "b:2", "b:2", "^d"}); TestUpdateFanin("foo_4", {"c", Graph::kControlSlot}, {"b", Graph::kControlSlot}, /*modified=*/true, &expected_node); @@ -463,85 +518,266 @@ TEST(MutableGraphViewTest, UpdateFanin) { /*modified=*/false, /*expected_node=*/nullptr); } -GraphDef SimpleDuplicateControllingFaninsGraph() { +TEST(MutableGraphViewTest, DedupControllingFaninsOnGraphInit) { + // Actual node.op() is not important in this test. + GraphDef graph_def = test::function::GDef( + { + NDef("a", "NotImportant", {}, {}), + NDef("b", "NotImportant", {}, {}), + NDef("c", "Switch", {}, {}), + NDef("d", "Identity", {"c:1"}), + NDef("foo_1", "IdentityN", {"a", "b:1", "^b"}), + NDef("foo_2", "IdentityN", {"a", "^b", "^b"}), + NDef("foo_3", "IdentityN", {"a", "b:1", "^b", "^b"}), + NDef("foo_4", "IdentityN", {"a:2", "b:1", "^b", "^b", "^a", "^a"}), + NDef("foo_5", "NotImportant", {"a:2", "b:1", "^b", "^b", "^a", "^a"}), + NDef("foo_6", "Identity", {"d", "^d"}), + NDef("foo_7", "NotImportant", + {"a:3", "b:2", "d", "^d", "^d", "^a", "^b", "^a", "^b"}), + }, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + EXPECT_EQ(graph.graph()->node_size(), 11); + NodeDef* a = graph.GetNode("a"); + ASSERT_NE(a, nullptr); + ASSERT_EQ(a->input_size(), 0); + NodeDef* b = graph.GetNode("b"); + ASSERT_NE(b, nullptr); + ASSERT_EQ(b->input_size(), 0); + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + ASSERT_EQ(c->input_size(), 0); + NodeDef* d = graph.GetNode("d"); + ASSERT_NE(d, nullptr); + ASSERT_EQ(d->input_size(), 1); + EXPECT_EQ(d->input(0), "c:1"); + NodeDef* foo_1 = graph.GetNode("foo_1"); + ASSERT_NE(foo_1, nullptr); + ASSERT_EQ(foo_1->input_size(), 2); + EXPECT_EQ(foo_1->input(0), "a"); + EXPECT_EQ(foo_1->input(1), "b:1"); + NodeDef* foo_2 = graph.GetNode("foo_2"); + ASSERT_NE(foo_2, nullptr); + ASSERT_EQ(foo_2->input_size(), 2); + EXPECT_EQ(foo_2->input(0), "a"); + EXPECT_EQ(foo_2->input(1), "^b"); + NodeDef* foo_3 = graph.GetNode("foo_3"); + ASSERT_NE(foo_3, nullptr); + ASSERT_EQ(foo_3->input_size(), 2); + EXPECT_EQ(foo_3->input(0), "a"); + EXPECT_EQ(foo_3->input(1), "b:1"); + NodeDef* foo_4 = graph.GetNode("foo_4"); + ASSERT_NE(foo_4, nullptr); + ASSERT_EQ(foo_4->input_size(), 2); + EXPECT_EQ(foo_4->input(0), "a:2"); + EXPECT_EQ(foo_4->input(1), "b:1"); + NodeDef* foo_5 = graph.GetNode("foo_5"); + ASSERT_NE(foo_5, nullptr); + ASSERT_EQ(foo_5->input_size(), 2); + EXPECT_EQ(foo_5->input(0), "a:2"); + EXPECT_EQ(foo_5->input(1), "b:1"); + NodeDef* foo_6 = graph.GetNode("foo_6"); + ASSERT_NE(foo_6, nullptr); + ASSERT_EQ(foo_6->input_size(), 2); + EXPECT_EQ(foo_6->input(0), "d"); + EXPECT_EQ(foo_6->input(1), "^d"); + NodeDef* foo_7 = graph.GetNode("foo_7"); + ASSERT_NE(foo_7, nullptr); + ASSERT_EQ(foo_7->input_size(), 4); + EXPECT_EQ(foo_7->input(0), "a:3"); + EXPECT_EQ(foo_7->input(1), "b:2"); + EXPECT_EQ(foo_7->input(2), "d"); + EXPECT_EQ(foo_7->input(3), "^d"); +} + +TEST(MutableGraphViewTest, DedupControllingFaninsOnAddFanin) { + // Actual node.op() is not important in this test. + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"^a"}), + NDef("c", "NotImportant", {"a:1"})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + EXPECT_TRUE(graph.AddFanin("b", {"a", 2})); + NodeDef* b = graph.GetNode("b"); + ASSERT_NE(b, nullptr); + ASSERT_EQ(b->input_size(), 1); + EXPECT_EQ(b->input(0), "a:2"); + + EXPECT_FALSE(graph.AddFanin("c", {"a", Graph::kControlSlot})); + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + ASSERT_EQ(c->input_size(), 1); + EXPECT_EQ(c->input(0), "a:1"); +} + +TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnAddFanin) { + GraphDef graph_def = test::function::GDef( + {NDef("a", "Switch", {}, {}), NDef("b", "Identity", {"a:1"}), + NDef("c", "", {}, {}), NDef("d", "", {}, {})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + EXPECT_TRUE(graph.AddFanin("c", {"b", 2})); + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + ASSERT_EQ(c->input_size(), 1); + EXPECT_EQ(c->input(0), "b:2"); + EXPECT_TRUE(graph.AddFanin("c", {"b", Graph::kControlSlot})); + ASSERT_EQ(c->input_size(), 2); + EXPECT_EQ(c->input(0), "b:2"); + EXPECT_EQ(c->input(1), "^b"); + EXPECT_FALSE(graph.AddFanin("c", {"b", Graph::kControlSlot})); + ASSERT_EQ(c->input_size(), 2); + EXPECT_EQ(c->input(0), "b:2"); + EXPECT_EQ(c->input(1), "^b"); + + EXPECT_TRUE(graph.AddFanin("d", {"b", Graph::kControlSlot})); + NodeDef* d = graph.GetNode("d"); + ASSERT_NE(d, nullptr); + ASSERT_EQ(d->input_size(), 1); + EXPECT_EQ(d->input(0), "^b"); + EXPECT_FALSE(graph.AddFanin("d", {"b", Graph::kControlSlot})); + ASSERT_EQ(d->input_size(), 1); + EXPECT_EQ(d->input(0), "^b"); + EXPECT_TRUE(graph.AddFanin("d", {"b", 3})); + ASSERT_EQ(d->input_size(), 2); + EXPECT_EQ(d->input(0), "b:3"); + EXPECT_EQ(d->input(1), "^b"); +} + +TEST(MutableGraphViewTest, DedupControllingFaninsOnUpdateFanin) { // Actual node.op() is not important in this test. GraphDef graph_def = test::function::GDef( {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {}, {}), - NDef("foo_1", "NotImportant", {"a", "b:1", "^b"}), - NDef("foo_2", "NotImportant", {"a", "^b", "^b"}), - NDef("foo_3", "NotImportant", {"a", "b:1", "^b", "^b"}), - NDef("foo_4", "NotImportant", {"a:2", "b:1", "^b", "^b", "^a", "^a"})}, + NDef("c", "NotImportant", {"a:1", "^b"})}, /*funcs=*/{}); - return graph_def; + + MutableGraphView graph(&graph_def); + + EXPECT_TRUE(graph.UpdateFanin("c", {"a", 1}, {"b", 2})); + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + ASSERT_EQ(c->input_size(), 1); + EXPECT_EQ(c->input(0), "b:2"); } -void CheckDedupControllingFaninsForNode(MutableGraphView* graph, - absl::string_view node_name, - const NodeDef* expected_node) { - // Deduping again should result in no change. - EXPECT_FALSE(graph->DedupControllingFanins(node_name)); - NodeDef* node = graph->GetNode(node_name); - ASSERT_NE(node, nullptr); - ASSERT_EQ(node->input_size(), expected_node->input_size()); - CompareNodeInputs(*graph, expected_node, node); - for (int i = 0; i < node->input_size(); ++i) { - TensorId tensor_id = ParseTensorName(node->input(i)); - if (tensor_id.index() > Graph::kControlSlot) { - CheckFanout(*graph, {tensor_id.node(), Graph::kControlSlot}, node_name); - } - } +TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnUpdateFanin) { + GraphDef graph_def = test::function::GDef( + {NDef("a", "Switch", {}, {}), NDef("b", "Identity", {"a:1"}), + NDef("c", "Identity", {"a:2"}), NDef("d", "NotImportant", {"c", "^b"}), + NDef("e", "NotImportant", {"b", "^c"})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + EXPECT_TRUE(graph.UpdateFanin("d", {"b", Graph::kControlSlot}, + {"c", Graph::kControlSlot})); + NodeDef* d = graph.GetNode("d"); + ASSERT_NE(d, nullptr); + ASSERT_EQ(d->input_size(), 2); + EXPECT_EQ(d->input(0), "c"); + EXPECT_EQ(d->input(1), "^c"); + + EXPECT_TRUE(graph.UpdateFanin("e", {"b", 0}, {"c", 3})); + NodeDef* e = graph.GetNode("e"); + ASSERT_NE(e, nullptr); + ASSERT_EQ(e->input_size(), 2); + EXPECT_EQ(e->input(0), "c:3"); + EXPECT_EQ(e->input(1), "^c"); + + EXPECT_TRUE(graph.UpdateFanin("e", {"c", 3}, {"c", Graph::kControlSlot})); + ASSERT_NE(e, nullptr); + ASSERT_EQ(e->input_size(), 1); + EXPECT_EQ(e->input(0), "^c"); +} + +TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnAddFanin) { + // Actual node.op() is not important in this test. + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"a:1"}), + NDef("c", "NotImportant", {"^b"})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + EXPECT_TRUE(graph.AddFanin("c", {"a", 3})); + NodeDef* a = graph.GetNode("a"); + ASSERT_NE(a, nullptr); + auto fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true); + EXPECT_EQ(fanouts.size(), 2); + NodeDef* b = graph.GetNode("b"); + ASSERT_NE(b, nullptr); + EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(b, 0)), 1); + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(c, 0)), 1); } -void TestDedupControllingFaninsForNode(MutableGraphView* graph, - absl::string_view node_name, - const NodeDef* expected_node) { - EXPECT_TRUE(graph->DedupControllingFanins(node_name)); - CheckDedupControllingFaninsForNode(graph, node_name, expected_node); +TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnRemoveFanin) { + // Actual node.op() is not important in this test. + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"a:1"}), + NDef("c", "NotImportant", {"a:2"})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + EXPECT_TRUE(graph.RemoveFanin("c", {"a", 2})); + NodeDef* a = graph.GetNode("a"); + ASSERT_NE(a, nullptr); + auto fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true); + EXPECT_EQ(fanouts.size(), 1); + NodeDef* b = graph.GetNode("b"); + ASSERT_NE(b, nullptr); + EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(b, 0)), 1); } -TEST(MutableGraphViewTest, DedupControllingFaninsForNode) { - GraphDef graph_def = SimpleDuplicateControllingFaninsGraph(); +TEST(MutableGraphViewTest, KeepMaxRegularOutputPortOnRemoveFanin) { + // Actual node.op() is not important in this test. + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"a:1"}), + NDef("c", "NotImportant", {"a:2"})}, + /*funcs=*/{}); MutableGraphView graph(&graph_def); - NodeDef expected_node; - // Remove redundant control dependency '^b'. - expected_node = NDef("", "", {"a", "b:1"}); - TestDedupControllingFaninsForNode(&graph, "foo_1", &expected_node); - // Remove extra control dependency '^b'. - expected_node = NDef("", "", {"a", "^b"}); - TestDedupControllingFaninsForNode(&graph, "foo_2", &expected_node); - // Remove redundant and extra control dependencies '^b'. - expected_node = NDef("", "", {"a", "b:1"}); - TestDedupControllingFaninsForNode(&graph, "foo_3", &expected_node); - // Remove multiple redundant control dependencies. - expected_node = NDef("", "", {"a:2", "b:1"}); - TestDedupControllingFaninsForNode(&graph, "foo_4", &expected_node); - // Missing node. - EXPECT_FALSE(graph.DedupControllingFanins("missing")); + EXPECT_TRUE(graph.RemoveFanin("b", {"a", 1})); + NodeDef* a = graph.GetNode("a"); + ASSERT_NE(a, nullptr); + auto fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true); + EXPECT_EQ(fanouts.size(), 1); + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(c, 0)), 1); } -TEST(MutableGraphViewTest, DedupControllingFaninsForGraph) { - GraphDef graph_def = SimpleDuplicateControllingFaninsGraph(); +TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnUpdateFanin) { + // Actual node.op() is not important in this test. + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"a:1"}), + NDef("c", "NotImportant", {"a:2"})}, + /*funcs=*/{}); MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.DedupControllingFanins()); - // Deduping again should result in no change. - EXPECT_FALSE(graph.DedupControllingFanins()); - NodeDef expected_node; - // Remove redundant control dependency '^b'. - expected_node = NDef("", "", {"a", "b:1"}); - CheckDedupControllingFaninsForNode(&graph, "foo_1", &expected_node); - // Remove extra control dependency '^b'. - expected_node = NDef("", "", {"a", "^b"}); - CheckDedupControllingFaninsForNode(&graph, "foo_2", &expected_node); - // Remove redundant and extra control dependencies '^b'. - expected_node = NDef("", "", {"a", "b:1"}); - CheckDedupControllingFaninsForNode(&graph, "foo_3", &expected_node); - // Remove multiple redundant control dependencies. - expected_node = NDef("", "", {"a:2", "b:1"}); - CheckDedupControllingFaninsForNode(&graph, "foo_4", &expected_node); + EXPECT_TRUE(graph.UpdateFanin("c", {"a", 2}, {"b", 3})); + NodeDef* a = graph.GetNode("a"); + ASSERT_NE(a, nullptr); + auto a_fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true); + EXPECT_EQ(a_fanouts.size(), 1); + NodeDef* b = graph.GetNode("b"); + ASSERT_NE(b, nullptr); + EXPECT_EQ(a_fanouts.count(MutableGraphView::InputPort(b, 0)), 1); + auto b_fanouts = graph.GetFanouts(*b, /*include_controlled_nodes=*/true); + EXPECT_EQ(b_fanouts.size(), 1); + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + EXPECT_EQ(b_fanouts.count(MutableGraphView::InputPort(c, 0)), 1); } TEST(MutableGraphViewTest, AddControllingFaninMissing) { @@ -655,7 +891,7 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithNoExistingIdentity) { TEST(MutableGraphViewTest, AddControllingFaninSwitchWithExistingAddedIdentity) { GraphDef graph_def = test::function::GDef( {NDef("a", "NotImportant", {}, {}), NDef("switch", "Switch", {}, {}), - NDef("ConstantFoldingCtrl/switch_0", "Identity", {}, {})}, + NDef("ConstantFoldingCtrl/switch_0", "Identity", {"switch"})}, /*funcs=*/{}); MutableGraphView graph(&graph_def); @@ -694,9 +930,8 @@ TEST(MutableGraphViewTest, DeleteNodes) { auto bar_fanouts = graph.GetFanouts(*bar, include_control_fanouts); auto other_fanouts = graph.GetFanouts(*other, include_control_fanouts); - EXPECT_EQ(bar_fanouts.size(), 2); + EXPECT_EQ(bar_fanouts.size(), 1); EXPECT_EQ(bar_fanouts.count(MutableGraphView::InputPort(foo_2, 1)), 1); - EXPECT_EQ(bar_fanouts.count(MutableGraphView::InputPort(foo_2, -1)), 1); EXPECT_EQ(other_fanouts.size(), 1); EXPECT_EQ(other_fanouts.count(MutableGraphView::InputPort(foo_2, 0)), 1); diff --git a/tensorflow/core/grappler/optimizers/data/graph_test_utils.cc b/tensorflow/core/grappler/optimizers/data/graph_test_utils.cc index 9d8b388a3a..202dfb5ac8 100644 --- a/tensorflow/core/grappler/optimizers/data/graph_test_utils.cc +++ b/tensorflow/core/grappler/optimizers/data/graph_test_utils.cc @@ -42,7 +42,7 @@ NodeDef MakeMapAndBatchNode(StringPiece name, StringPiece input_node_name, StringPiece function_name) { return test::function::NDef( name, "ExperimentalMapAndBatchDataset", - {string(input_node_name), "", string(batch_size_node_name), + {string(input_node_name), string(batch_size_node_name), string(num_parallel_calls_node_name), string(drop_remainder_node_name)}, {{"f", FunctionDefHelper::FunctionRef(string(function_name))}, {"Targuments", {}}, @@ -68,7 +68,7 @@ NodeDef MakeParallelInterleaveNode(StringPiece name, StringPiece function_name, bool sloppy) { return test::function::NDef( name, "ParallelInterleaveDatasetV2", - {string(input_node_name), "", string(cycle_length_node_name), + {string(input_node_name), string(cycle_length_node_name), string(block_length_node_name), string(num_parallel_calls_node_name)}, { {"f", FunctionDefHelper::FunctionRef(string(function_name))}, diff --git a/tensorflow/core/grappler/optimizers/data/latency_all_edges_test.cc b/tensorflow/core/grappler/optimizers/data/latency_all_edges_test.cc index d428d04a66..426c1dca5b 100644 --- a/tensorflow/core/grappler/optimizers/data/latency_all_edges_test.cc +++ b/tensorflow/core/grappler/optimizers/data/latency_all_edges_test.cc @@ -30,9 +30,9 @@ TEST(LatencyAllEdgesTest, AddLatenciesAfterTensorMapPrefetch) { using test::function::NDef; GrapplerItem item; NodeDef component_node = - NDef("component_nodes", "Const", {}, {{"value", 1}, {"dtype", DT_INT32}}); + NDef("component_node", "Const", {}, {{"value", 1}, {"dtype", DT_INT32}}); NodeDef from_tensor_node = - NDef("from_tensor_nodes", "TensorDataset", {"component_nodes"}, + NDef("from_tensor_node", "TensorDataset", {"component_node"}, {{"Toutput_types", {}}, {"output_shapes", {}}}); NodeDef captured_input_node = NDef("captured_input_node", "Const", {}, -- GitLab From db48d17b671623a24dcee7e90f8b18d87f51d009 Mon Sep 17 00:00:00 2001 From: Michael Kuperstein Date: Thu, 3 Jan 2019 18:17:13 -0800 Subject: [PATCH 0168/2345] [XLA] Make the verifier check bitcasts don't change the element type This is what BitcastConvert is for. PiperOrigin-RevId: 227783600 --- .../compiler/xla/service/hlo_verifier.cc | 8 ++++++ .../compiler/xla/service/hlo_verifier_test.cc | 18 +++++++++++++ .../while_loop_invariant_code_motion_test.cc | 27 ++++++++++--------- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/tensorflow/compiler/xla/service/hlo_verifier.cc b/tensorflow/compiler/xla/service/hlo_verifier.cc index 14e29533c2..9274d42280 100644 --- a/tensorflow/compiler/xla/service/hlo_verifier.cc +++ b/tensorflow/compiler/xla/service/hlo_verifier.cc @@ -387,6 +387,14 @@ 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()) { + return InternalError( + "Bitcast can not change the element type from %s to %s", + PrimitiveType_Name(bitcast->operand(0)->shape().element_type()), + PrimitiveType_Name(bitcast->shape().element_type())); + } return Status::OK(); } diff --git a/tensorflow/compiler/xla/service/hlo_verifier_test.cc b/tensorflow/compiler/xla/service/hlo_verifier_test.cc index 91f247a9bb..9b2d884f79 100644 --- a/tensorflow/compiler/xla/service/hlo_verifier_test.cc +++ b/tensorflow/compiler/xla/service/hlo_verifier_test.cc @@ -480,5 +480,23 @@ TEST_F(HloVerifierTestLayoutSensitive, ConcatWithLayoutChangeNotAllowed) { EXPECT_THAT(status.error_message(), HasSubstr("Instruction shouldn't change layouts")); } + +TEST_F(HloVerifierTest, BitcastCanNotChangeElementType) { + const char* const hlo_string = R"( + HloModule Module + + ENTRY BitcastCanNotChangeElementType { + constant.0 = f32[2] constant({0.0, 0.0}) + ROOT bitcast = s32[2] bitcast(constant.0) + } + )"; + TF_ASSERT_OK_AND_ASSIGN(auto module, ParseHloString(hlo_string)); + + auto status = verifier().Run(module.get()).status(); + ASSERT_FALSE(status.ok()); + EXPECT_THAT(status.error_message(), + HasSubstr("Bitcast can not change the element type")); +} + } // namespace } // namespace xla diff --git a/tensorflow/compiler/xla/service/while_loop_invariant_code_motion_test.cc b/tensorflow/compiler/xla/service/while_loop_invariant_code_motion_test.cc index 8e7c4bc882..3587c016b4 100644 --- a/tensorflow/compiler/xla/service/while_loop_invariant_code_motion_test.cc +++ b/tensorflow/compiler/xla/service/while_loop_invariant_code_motion_test.cc @@ -299,7 +299,7 @@ TEST_F(WhileLoopInvariantCodeMotionTest, DontHoistBitcastAlone) { // bitcast either. auto m = CreateNewVerifiedModule(); auto scalar_s32 = ShapeUtil::MakeShape(S32, {}); - auto scalar_f32 = ShapeUtil::MakeShape(F32, {}); + auto effective_scalar_s32 = ShapeUtil::MakeShape(S32, {1}); auto token_shape = ShapeUtil::MakeTokenShape(); Shape while_shape = ShapeUtil::MakeTupleShape({scalar_s32, scalar_s32, token_shape}); @@ -314,10 +314,12 @@ TEST_F(WhileLoopInvariantCodeMotionTest, DontHoistBitcastAlone) { HloInstruction::CreateGetTupleElement(scalar_s32, param, 1)); HloInstruction* in_token = builder.AddInstruction( HloInstruction::CreateGetTupleElement(token_shape, param, 2)); - HloInstruction* bitcast_inst = builder.AddInstruction( - HloInstruction::CreateUnary(scalar_f32, HloOpcode::kBitcast, gte_0)); - HloInstruction* out_token = builder.AddInstruction( - HloInstruction::CreateOutfeed(scalar_f32, bitcast_inst, in_token, "")); + HloInstruction* bitcast_inst = + builder.AddInstruction(HloInstruction::CreateUnary( + effective_scalar_s32, HloOpcode::kBitcast, gte_0)); + HloInstruction* out_token = + builder.AddInstruction(HloInstruction::CreateOutfeed( + effective_scalar_s32, bitcast_inst, in_token, "")); builder.AddInstruction( HloInstruction::CreateTuple({gte_0, gte_1, out_token})); @@ -352,9 +354,9 @@ TEST_F(WhileLoopInvariantCodeMotionTest, HoistBitcastIfNeeded) { // The bitcast's user can be hoisted, so hoist the bitcast too. auto m = CreateNewVerifiedModule(); auto scalar_s32 = ShapeUtil::MakeShape(S32, {}); - auto scalar_f32 = ShapeUtil::MakeShape(F32, {}); - Shape while_shape = - ShapeUtil::MakeTupleShape({scalar_s32, scalar_f32, scalar_f32}); + auto effective_scalar_s32 = ShapeUtil::MakeShape(S32, {1}); + Shape while_shape = ShapeUtil::MakeTupleShape( + {scalar_s32, effective_scalar_s32, effective_scalar_s32}); HloComputation* while_body = [&]() { HloComputation::Builder builder(TestName() + ".while_body"); @@ -363,12 +365,13 @@ TEST_F(WhileLoopInvariantCodeMotionTest, HoistBitcastIfNeeded) { HloInstruction* gte_0 = builder.AddInstruction( HloInstruction::CreateGetTupleElement(scalar_s32, param, 0)); HloInstruction* gte_1 = builder.AddInstruction( - HloInstruction::CreateGetTupleElement(scalar_f32, param, 1)); - HloInstruction* bitcast_inst = builder.AddInstruction( - HloInstruction::CreateUnary(scalar_f32, HloOpcode::kBitcast, gte_0)); + HloInstruction::CreateGetTupleElement(effective_scalar_s32, param, 1)); + HloInstruction* bitcast_inst = + builder.AddInstruction(HloInstruction::CreateUnary( + effective_scalar_s32, HloOpcode::kBitcast, gte_0)); HloInstruction* add_inst = builder.AddInstruction(HloInstruction::CreateBinary( - scalar_f32, HloOpcode::kAdd, bitcast_inst, gte_1)); + effective_scalar_s32, HloOpcode::kAdd, bitcast_inst, gte_1)); builder.AddInstruction( HloInstruction::CreateTuple({gte_0, gte_1, add_inst})); -- GitLab From 328ca94210ba39e13b51228200c8d2da27246f7f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 18:19:05 -0800 Subject: [PATCH 0169/2345] Refactor ExecuteFlexOp into smaller methods of OpNode. PiperOrigin-RevId: 227783768 --- tensorflow/lite/delegates/flex/kernel.cc | 145 +++++++++++++---------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/tensorflow/lite/delegates/flex/kernel.cc b/tensorflow/lite/delegates/flex/kernel.cc index 7d18c39098..3012241f4f 100644 --- a/tensorflow/lite/delegates/flex/kernel.cc +++ b/tensorflow/lite/delegates/flex/kernel.cc @@ -88,10 +88,18 @@ class OpNode { const tensorflow::NodeDef& nodedef() const { return nodedef_; } const std::vector& inputs() const { return inputs_; } - std::vector* mutable_inputs() { return &inputs_; } + void InitializeInputs(const TfLiteIntArray* inputs) { + for (int index : TfLiteIntArrayView(inputs)) { + inputs_.push_back(index); + } + } const std::vector& outputs() const { return outputs_; } - std::vector* mutable_outputs() { return &outputs_; } + void InitializeOutputs(const TfLiteIntArray* outputs) { + for (int index : TfLiteIntArrayView(outputs)) { + outputs_.push_back(index); + } + } tensorflow::Status InitializeNodeDef(const void* custom_initial_data, int custom_initial_data_size) { @@ -123,6 +131,66 @@ class OpNode { return tensorflow::Status::OK(); } + // Build thew new EagerOperation. In case of error, the returned 'op' is + // guaranteed to be 'nullptr'. + tensorflow::Status BuildEagerOp( + tensorflow::EagerContext* eager_context, + std::unique_ptr* op) { + op->reset(); + + const tensorflow::AttrTypeMap* attr_types; + bool is_function = false; + TF_RETURN_WITH_CONTEXT_IF_ERROR( + tensorflow::AttrTypeMapForOp(name_.c_str(), &attr_types, &is_function), + " (while processing attributes of '", name_, "')"); + if (is_function) { + return tensorflow::errors::NotFound( + "Operation '", name_, + "' is not registered. (while processing attributes of '", name_, + "')"); + } + + op->reset(new tensorflow::EagerOperation(eager_context, name_.c_str(), + /*is_function=*/false, + attr_types)); + for (const auto& attr : nodedef_.attr()) { + (*op)->MutableAttrs()->Set(attr.first, attr.second); + } + + return tensorflow::Status::OK(); + } + + tensorflow::Status BuildEagerInputs(BufferMap* buffer_map, + tensorflow::EagerOperation* op) { + for (int input_index : inputs_) { + if (!buffer_map->HasTensor(input_index)) { + return tensorflow::errors::Internal( + "Cannot read from invalid tensor index ", input_index); + } + auto* handle = new tensorflow::TensorHandle( + buffer_map->GetTensor(input_index), nullptr, nullptr, nullptr); + op->AddInput(handle); + handle->Unref(); + + if (buffer_map->IsForwardable(input_index)) { + // Take it out of the map, so Eager/TF can reuse the buffer for an + // output tensor of the op. + buffer_map->RemoveTensor(input_index); + } + } + return tensorflow::Status::OK(); + } + + tensorflow::Status PersistEagerOutputs(BufferMap* buffer_map, + OpOutputs* retvals) { + for (int i = 0; i < outputs_.size(); ++i) { + const tensorflow::Tensor* tensor = nullptr; + TF_RETURN_IF_ERROR(retvals->GetHandle(i)->Tensor(&tensor)); + buffer_map->SetFromTensorFlow(outputs_[i], *tensor); + } + return tensorflow::Status::OK(); + } + private: // The name of the TensorFlow op to execute. string name_; @@ -139,60 +207,23 @@ class OpNode { // Executes the TensorFlow op given by 'op_name', with the attributes specified // in 'nodedef'. Inputs and outputs are given as indices into the 'buffer_map'. tensorflow::Status ExecuteFlexOp(tensorflow::EagerContext* eager_context, - BufferMap* buffer_map, const string& op_name, - const tensorflow::NodeDef& nodedef, - const std::vector& inputs, - const std::vector& outputs) { - const tensorflow::AttrTypeMap* attr_types; - bool is_function = false; - TF_RETURN_WITH_CONTEXT_IF_ERROR( - tensorflow::AttrTypeMapForOp(op_name.c_str(), &attr_types, &is_function), - " (while processing attributes of '", op_name, "')"); - if (is_function) { - return tensorflow::errors::NotFound( - "Operation '", op_name, - "' is not registered. (while processing attributes of '", op_name, - "')"); - } - tensorflow::EagerOperation op(eager_context, op_name.c_str(), - /*is_function=*/false, attr_types); - for (const auto& attr : nodedef.attr()) { - op.MutableAttrs()->Set(attr.first, attr.second); - } + BufferMap* buffer_map, OpNode* node_data) { + std::unique_ptr op; + TF_RETURN_IF_ERROR(node_data->BuildEagerOp(eager_context, &op)); + TF_RETURN_IF_ERROR(node_data->BuildEagerInputs(buffer_map, op.get())); - for (int input_index : inputs) { - if (!buffer_map->HasTensor(input_index)) { - return tensorflow::errors::Internal( - "Cannot read from invalid tensor index ", input_index); - } - auto* handle = new tensorflow::TensorHandle( - buffer_map->GetTensor(input_index), nullptr, nullptr, nullptr); - op.AddInput(handle); - handle->Unref(); - - if (buffer_map->IsForwardable(input_index)) { - // Take it out of the map, so Eager/TF can reuse the buffer for an output - // tensor of the op. - buffer_map->RemoveTensor(input_index); - } - } - - int num_retvals = outputs.size(); + int num_retvals = node_data->outputs().size(); OpOutputs retvals(num_retvals); TF_RETURN_WITH_CONTEXT_IF_ERROR( - EagerExecute(&op, retvals.GetVector(), &num_retvals), - " (while executing '", op_name, "' via Eager)"); + EagerExecute(op.get(), retvals.GetVector(), &num_retvals), + " (while executing '", node_data->name(), "' via Eager)"); - if (num_retvals != outputs.size()) { + if (num_retvals != node_data->outputs().size()) { return tensorflow::errors::Internal( "Unexpected number of outputs from EagerExecute"); } - for (int i = 0; i < num_retvals; ++i) { - const tensorflow::Tensor* tensor = nullptr; - TF_RETURN_IF_ERROR(retvals.GetHandle(i)->Tensor(&tensor)); - buffer_map->SetFromTensorFlow(outputs[i], *tensor); - } + TF_RETURN_IF_ERROR(node_data->PersistEagerOutputs(buffer_map, &retvals)); return tensorflow::Status::OK(); } @@ -247,12 +278,8 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { node->custom_initial_data_size); if (!status.ok()) break; - for (auto input_index : TfLiteIntArrayView(node->inputs)) { - node_data.mutable_inputs()->push_back(input_index); - } - for (auto output_index : TfLiteIntArrayView(node->outputs)) { - node_data.mutable_outputs()->push_back(output_index); - } + node_data.InitializeInputs(node->inputs); + node_data.InitializeOutputs(node->outputs); } if (ConvertStatus(context, status) != kTfLiteOk) { @@ -325,7 +352,7 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { } TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { - const auto* op_data = reinterpret_cast(node->user_data); + auto* op_data = reinterpret_cast(node->user_data); BufferMap* buffer_map = op_data->buffer_map; tensorflow::EagerContext* eager_context = op_data->eager_context; @@ -344,14 +371,12 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { } // Execute the TensorFlow Ops sequentially. - for (const auto& node_data : op_data->nodes) { + for (OpNode& node_data : op_data->nodes) { SCOPED_TAGGED_OPERATOR_PROFILE( reinterpret_cast(context->profiler), - node_data->name().c_str(), node_data->index()); + node_data.name().c_str(), node_data.index()); - auto status = ExecuteFlexOp(eager_context, buffer_map, node_data.name(), - node_data.nodedef(), node_data.inputs(), - node_data.outputs()); + auto status = ExecuteFlexOp(eager_context, buffer_map, &node_data); TF_LITE_ENSURE_OK(context, ConvertStatus(context, status)); } -- GitLab From d0ce85e772fa17afcc7914c63bcab1b90df6664e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 18:26:08 -0800 Subject: [PATCH 0170/2345] Internal change. PiperOrigin-RevId: 227784390 --- tensorflow/compiler/xla/service/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index eed958dc6b..b84792cfc3 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -3576,6 +3576,7 @@ cc_library( tf_cc_test( name = "indexed_array_analysis_test", srcs = ["indexed_array_analysis_test.cc"], + extra_copts = ["-Wno-string-plus-int"], deps = [ ":hlo_matchers", ":indexed_array_analysis", -- GitLab From a2f7f39d982682fa8de050e001522581570c510f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 3 Jan 2019 19:50:14 -0800 Subject: [PATCH 0171/2345] Add support non-stacking(cross-links) but connected to other bidi-lstm ops case, tensorflow equivalent: tf.nn.static_bidirectional_rnn PiperOrigin-RevId: 227791656 --- .../kernels/bidirectional_sequence_lstm.cc | 54 +++-- .../bidirectional_sequence_lstm_test.cc | 202 +++++++++++++++++- 2 files changed, 233 insertions(+), 23 deletions(-) diff --git a/tensorflow/lite/kernels/bidirectional_sequence_lstm.cc b/tensorflow/lite/kernels/bidirectional_sequence_lstm.cc index 1ddfe7201e..31c6e3f44c 100644 --- a/tensorflow/lite/kernels/bidirectional_sequence_lstm.cc +++ b/tensorflow/lite/kernels/bidirectional_sequence_lstm.cc @@ -105,7 +105,10 @@ constexpr int kBwInputActivationStateTensor = 37; // Cell state tensors of size {n_batch, n_cell} constexpr int kBwInputCellStateTensor = 38; -// Auxiliary input and weights when stacking. +// Used as auxiliary input and weights when stacking for +// tf.contrib.rnn.stack_bidirectional_rnn case (with cross links); Used as input +// to the backward cell when stacking for tf.nn.static_bidirectional_rnn case +// (without cross links). constexpr int kAuxInputTensor = 39; // Optional // Forward weights. constexpr int kFwAuxInputToInputWeightsTensor = 40; // Optional @@ -459,8 +462,8 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { const TfLiteTensor* bw_aux_input_to_output_weights = GetOptionalInputTensor(context, node, kBwAuxInputToOutputWeightsTensor); - const bool aux_inputs_all_or_none = - ((aux_input != nullptr) && (fw_aux_input_to_cell_weights != nullptr) && + const bool aux_inputs_weights_all_or_none = + ((fw_aux_input_to_cell_weights != nullptr) && (fw_aux_input_to_forget_weights != nullptr) && (fw_aux_input_to_output_weights != nullptr) && (bw_aux_input_to_cell_weights != nullptr) && @@ -472,8 +475,9 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { (bw_aux_input_to_cell_weights == nullptr) && (bw_aux_input_to_forget_weights == nullptr) && (bw_aux_input_to_output_weights == nullptr)); - TF_LITE_ENSURE(context, aux_inputs_all_or_none); - const bool has_aux_input = (aux_input != nullptr); + TF_LITE_ENSURE(context, aux_inputs_weights_all_or_none); + + const bool has_aux_input = (fw_aux_input_to_forget_weights != nullptr); if (has_aux_input) { // Check that aux_input has the same dimensions (except last) as the input. @@ -870,6 +874,9 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { const TfLiteTensor* bw_aux_input_to_output_weights = GetOptionalInputTensor(context, node, kBwAuxInputToOutputWeightsTensor); + const bool has_previous_bw_output = (aux_input != nullptr); + const bool use_aux_input = (fw_aux_input_to_forget_weights != nullptr); + // Populate a TfLiteLSTMParams struct for the evaluation functions. TfLiteLSTMParams lstm_params = {params->activation, params->cell_clip, params->proj_clip, kTfLiteLSTMFullKernel}; @@ -879,6 +886,26 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { const auto actual_bw_output = params->merge_outputs ? fw_output : bw_output; const bool time_major = params->time_major; + + // We want to cover the following cases: + // + // If not stacking (not connected after other bidi lstms): + // both fw & bw will just use `input`; aux_input will be null. + // + // If stacking with cross_links, TensorFlow equivalent + // (tf.contrib.rnn.stack_bidirectional_rnn): + // both fw & bw will use `input`, but aux_input will be none null. + // Note, this time, whether connected after other bidi lstms both works. + // + // If stacking without cross_links, but connected after other bidi lstms, + // TensorFlow equivalent (tf.nn.static_bidirectional_rnn): + // fw will use `input`, bw will use aux_input, and the `real aux_input` + // will be null. + + const bool non_stacking_mode = !use_aux_input && has_previous_bw_output; + const TfLiteTensor* bw_input = non_stacking_mode ? aux_input : input; + const TfLiteTensor* real_aux_input = non_stacking_mode ? nullptr : aux_input; + switch (fw_input_to_output_weights->type) { case kTfLiteFloat32: { TfLiteStatus fw_pass_status = lstm_eval::EvalFloat( @@ -891,7 +918,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { /*input_layer_norm_coefficients=*/nullptr, /*forget_layer_norm_coefficients=*/nullptr, /*cell_layer_norm_coefficients=*/nullptr, - /*output_layer_norm_coefficients=*/nullptr, aux_input, + /*output_layer_norm_coefficients=*/nullptr, real_aux_input, fw_aux_input_to_input_weights, fw_aux_input_to_forget_weights, fw_aux_input_to_cell_weights, fw_aux_input_to_output_weights, fw_input_gate_bias, fw_forget_gate_bias, fw_cell_bias, @@ -902,7 +929,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_OK(context, fw_pass_status); TfLiteStatus bw_pass_status = lstm_eval::EvalFloat( - input, bw_input_to_input_weights, bw_input_to_forget_weights, + bw_input, bw_input_to_input_weights, bw_input_to_forget_weights, bw_input_to_cell_weights, bw_input_to_output_weights, bw_recurrent_to_input_weights, bw_recurrent_to_forget_weights, bw_recurrent_to_cell_weights, bw_recurrent_to_output_weights, @@ -911,7 +938,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { /*input_layer_norm_coefficients=*/nullptr, /*forget_layer_norm_coefficients=*/nullptr, /*cell_layer_norm_coefficients=*/nullptr, - /*output_layer_norm_coefficients=*/nullptr, aux_input, + /*output_layer_norm_coefficients=*/nullptr, real_aux_input, bw_aux_input_to_input_weights, bw_aux_input_to_forget_weights, bw_aux_input_to_cell_weights, bw_aux_input_to_output_weights, bw_input_gate_bias, bw_forget_gate_bias, bw_cell_bias, @@ -942,9 +969,8 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* recovered_cell_weights = GetTemporary(context, node, kRecoveredCellWeights); TfLiteTensor* aux_input_quantized = - (aux_input == nullptr) - ? nullptr - : GetTemporary(context, node, kAuxInputQuantized); + use_aux_input ? GetTemporary(context, node, kAuxInputQuantized) + : nullptr; TfLiteStatus fw_pass_status = lstm_eval::EvalHybrid( input, fw_input_to_input_weights, fw_input_to_forget_weights, @@ -956,7 +982,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { /*input_layer_norm_coefficients=*/nullptr, /*forget_layer_norm_coefficients=*/nullptr, /*cell_layer_norm_coefficients=*/nullptr, - /*output_layer_norm_coefficients=*/nullptr, aux_input, + /*output_layer_norm_coefficients=*/nullptr, real_aux_input, fw_aux_input_to_input_weights, fw_aux_input_to_forget_weights, fw_aux_input_to_cell_weights, fw_aux_input_to_output_weights, fw_input_gate_bias, fw_forget_gate_bias, fw_cell_bias, @@ -970,7 +996,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_OK(context, fw_pass_status); TfLiteStatus bw_pass_status = lstm_eval::EvalHybrid( - input, bw_input_to_input_weights, bw_input_to_forget_weights, + bw_input, bw_input_to_input_weights, bw_input_to_forget_weights, bw_input_to_cell_weights, bw_input_to_output_weights, bw_recurrent_to_input_weights, bw_recurrent_to_forget_weights, bw_recurrent_to_cell_weights, bw_recurrent_to_output_weights, @@ -979,7 +1005,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { /*input_layer_norm_coefficients=*/nullptr, /*forget_layer_norm_coefficients=*/nullptr, /*cell_layer_norm_coefficients=*/nullptr, - /*output_layer_norm_coefficients=*/nullptr, aux_input, + /*output_layer_norm_coefficients=*/nullptr, real_aux_input, bw_aux_input_to_input_weights, bw_aux_input_to_forget_weights, bw_aux_input_to_cell_weights, bw_aux_input_to_output_weights, bw_input_gate_bias, bw_forget_gate_bias, bw_cell_bias, diff --git a/tensorflow/lite/kernels/bidirectional_sequence_lstm_test.cc b/tensorflow/lite/kernels/bidirectional_sequence_lstm_test.cc index f5df6d15af..59ea47a2a2 100644 --- a/tensorflow/lite/kernels/bidirectional_sequence_lstm_test.cc +++ b/tensorflow/lite/kernels/bidirectional_sequence_lstm_test.cc @@ -38,7 +38,7 @@ class BidirectionalLSTMOpModel : public SingleOpModel { int sequence_length, bool use_cifg, bool use_peephole, bool use_projection_weights, bool use_projection_bias, bool merge_outputs, - float cell_clip, float proj_clip, + bool use_aux_input, float cell_clip, float proj_clip, bool quantize_weights, bool time_major, const std::vector>& input_shapes) : n_batch_(n_batch), @@ -185,7 +185,11 @@ class BidirectionalLSTMOpModel : public SingleOpModel { bw_output_ = AddOutput(TensorType_FLOAT32); } - aux_input_ = AddNullInput(); + if (use_aux_input) { + aux_input_ = AddInput(TensorType_FLOAT32); + } else { + aux_input_ = AddNullInput(); + } fw_aux_input_to_input_weights_ = AddNullInput(); fw_aux_input_to_forget_weights_ = AddNullInput(); fw_aux_input_to_cell_weights_ = AddNullInput(); @@ -302,6 +306,10 @@ class BidirectionalLSTMOpModel : public SingleOpModel { PopulateTensor(input_, offset, begin, end); } + void SetAuxInput(int offset, float* begin, float* end) { + PopulateTensor(aux_input_, offset, begin, end); + } + std::vector GetFwOutput() { return ExtractVector(fw_output_); } std::vector GetBwOutput() { return ExtractVector(bw_output_); } @@ -406,7 +414,8 @@ TEST_P(LSTMOpTest, BlackBoxTestNoCifgNoPeepholeNoProjectionNoClipping) { BidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, /*use_cifg=*/false, /*use_peephole=*/false, /*use_projection_weights=*/false, - /*use_projection_bias=*/false, /*merge_outputs=*/false, /*cell_clip=*/0.0, + /*use_projection_bias=*/false, /*merge_outputs=*/false, + /*use_aux_input=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, quantize_weights, /*time_major=*/true, { {sequence_length, n_batch, n_input}, // input tensor @@ -570,7 +579,8 @@ TEST_P(LSTMOpTest, BlackBoxTestMergedOutput) { BidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, /*use_cifg=*/false, /*use_peephole=*/false, /*use_projection_weights=*/false, - /*use_projection_bias=*/false, /*merge_outputs=*/true, /*cell_clip=*/0.0, + /*use_projection_bias=*/false, /*merge_outputs=*/true, + /*use_aux_input=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, quantize_weights, /*time_major=*/true, { {sequence_length, n_batch, n_input}, // input tensor @@ -733,7 +743,8 @@ TEST(LSTMOpTest, BlackBoxTestNoCifgNoPeepholeNoProjectionNoClippingReverse) { BidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, /*use_cifg=*/false, /*use_peephole=*/false, /*use_projection_weights=*/false, - /*use_projection_bias=*/false, /*merge_outputs=*/false, /*cell_clip=*/0.0, + /*use_projection_bias=*/false, /*merge_outputs=*/false, + /*use_aux_input=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, /*quantize_weights=*/false, /*time_major=*/true, { {sequence_length, n_batch, n_input}, // input tensor @@ -895,7 +906,8 @@ TEST(LSTMOpTest, BlackBoxTestWithCifgWithPeepholeNoProjectionNoClipping) { BidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, /*use_cifg=*/true, /*use_peephole=*/true, /*use_projection_weights=*/false, - /*use_projection_bias=*/false, /*merge_outputs=*/false, /*cell_clip=*/0.0, + /*use_projection_bias=*/false, /*merge_outputs=*/false, + /*use_aux_input=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, /*quantize_weights=*/false, /*time_major=*/true, { {sequence_length, n_batch, n_input}, // input tensor @@ -1047,7 +1059,8 @@ TEST(LSTMOpTest, BidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, /*use_cifg=*/true, /*use_peephole=*/true, /*use_projection_weights=*/false, - /*use_projection_bias=*/false, /*merge_outputs=*/false, /*cell_clip=*/0.0, + /*use_projection_bias=*/false, /*merge_outputs=*/false, + /*use_aux_input=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, /*quantize_weights=*/false, /*time_major=*/true, { {sequence_length, n_batch, n_input}, // input tensor @@ -1199,7 +1212,8 @@ TEST(LSTMOpTest, BlackBoxTestWithPeepholeWithProjectionNoClipping) { BidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, /*use_cifg=*/false, /*use_peephole=*/true, /*use_projection_weights=*/true, - /*use_projection_bias=*/false, /*merge_outputs=*/false, /*cell_clip=*/0.0, + /*use_projection_bias=*/false, /*merge_outputs=*/false, + /*use_aux_input=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, /*quantize_weights=*/false, /*time_major=*/true, { {sequence_length, n_batch, n_input}, // input tensor @@ -1903,7 +1917,8 @@ TEST(LSTMOpTest, BlackBoxTestWithPeepholeWithProjectionNoClippingBatchMajor) { BidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, /*use_cifg=*/false, /*use_peephole=*/true, /*use_projection_weights=*/true, - /*use_projection_bias=*/false, /*merge_outputs=*/false, /*cell_clip=*/0.0, + /*use_projection_bias=*/false, /*merge_outputs=*/false, + /*use_aux_input=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, /*quantize_weights=*/false, /*time_major=*/false, { {n_batch, sequence_length, n_input}, // input tensor @@ -2590,6 +2605,175 @@ TEST(LSTMOpTest, BlackBoxTestWithPeepholeWithProjectionNoClippingBatchMajor) { EXPECT_THAT(combined, ElementsAreArray(ArrayFloatNear(expected))); } +// Same as the no cifg no peephole no projection no clipping test, but have an +// aux input (without aux input weights), this is the case when stacking but no +// cross-links. +TEST_P(LSTMOpTest, BlackBoxTestWithAuxInput) { + const int n_batch = 1; + const int n_input = 2; + // n_cell and n_output have the same size when there is no projection. + const int n_cell = 4; + const int n_output = 4; + const int sequence_length = 3; + const bool quantize_weights = GetParam(); + + BidirectionalLSTMOpModel lstm( + n_batch, n_input, n_cell, n_output, sequence_length, /*use_cifg=*/false, + /*use_peephole=*/false, /*use_projection_weights=*/false, + /*use_projection_bias=*/false, /*merge_outputs=*/false, + /*use_aux_input=*/true, /*cell_clip=*/0.0, + /*proj_clip=*/0.0, quantize_weights, /*time_major=*/true, + { + {sequence_length, n_batch, n_input}, // input tensor + + // Forward cell + {n_cell, n_input}, // input_to_input_weight tensor + {n_cell, n_input}, // input_to_forget_weight tensor + {n_cell, n_input}, // input_to_cell_weight tensor + {n_cell, n_input}, // input_to_output_weight tensor + + {n_cell, n_output}, // recurrent_to_input_weight tensor + {n_cell, n_output}, // recurrent_to_forget_weight tensor + {n_cell, n_output}, // recurrent_to_cell_weight tensor + {n_cell, n_output}, // recurrent_to_output_weight tensor + + {0}, // cell_to_input_weight tensor + {0}, // cell_to_forget_weight tensor + {0}, // cell_to_output_weight tensor + + {n_cell}, // input_gate_bias tensor + {n_cell}, // forget_gate_bias tensor + {n_cell}, // cell_bias tensor + {n_cell}, // output_gate_bias tensor + + {0, 0}, // projection_weight tensor + {0}, // projection_bias tensor + + // Backward cell + {n_cell, n_input}, // input_to_input_weight tensor + {n_cell, n_input}, // input_to_forget_weight tensor + {n_cell, n_input}, // input_to_cell_weight tensor + {n_cell, n_input}, // input_to_output_weight tensor + + {n_cell, n_output}, // recurrent_to_input_weight tensor + {n_cell, n_output}, // recurrent_to_forget_weight tensor + {n_cell, n_output}, // recurrent_to_cell_weight tensor + {n_cell, n_output}, // recurrent_to_output_weight tensor + + {0}, // cell_to_input_weight tensor + {0}, // cell_to_forget_weight tensor + {0}, // cell_to_output_weight tensor + + {n_cell}, // input_gate_bias tensor + {n_cell}, // forget_gate_bias tensor + {n_cell}, // cell_bias tensor + {n_cell}, // output_gate_bias tensor + + {0, 0}, // projection_weight tensor + {0}, // projection_bias tensor + + {n_batch, n_output}, // activation_state tensor + {n_batch, n_cell}, // cell_state tensor + + {n_batch, n_output}, // activation_state tensor + {n_batch, n_cell}, // cell_state tensor + + // TODO(b/121134029): Update tests so tensor shapes after state tensor + // are used. They are currently ignored by test_util. + {sequence_length, n_batch, n_input}, // aux_input tensor + {n_cell, 0}, // aux_fw_input_to_input tensor + {n_cell, 0}, // aux_fw_input_to_forget tensor + {n_cell, 0}, // aux_fw_input_to_cell tensor + {n_cell, 0}, // aux_fw_input_to_output tensor + {n_cell, 0}, // aux_bw_input_to_input tensor + {n_cell, 0}, // aux_bw_input_to_forget tensor + {n_cell, 0}, // aux_bw_input_to_cell tensor + {n_cell, 0}, // aux_bw_input_to_output tensor + }); + + lstm.SetInputToInputWeights({-0.45018822, -0.02338299, -0.0870589, + -0.34550029, 0.04266912, -0.15680569, + -0.34856534, 0.43890524}); + + lstm.SetInputToCellWeights({-0.50013041, 0.1370284, 0.11810488, 0.2013163, + -0.20583314, 0.44344562, 0.22077113, + -0.29909778}); + + lstm.SetInputToForgetWeights({0.09701663, 0.20334584, -0.50592935, + -0.31343272, -0.40032279, 0.44781327, + 0.01387155, -0.35593212}); + + lstm.SetInputToOutputWeights({-0.25065863, -0.28290087, 0.04613829, + 0.40525138, 0.44272184, 0.03897077, -0.1556896, + 0.19487578}); + + lstm.SetInputGateBias({0., 0., 0., 0.}); + + lstm.SetCellBias({0., 0., 0., 0.}); + + lstm.SetForgetGateBias({1., 1., 1., 1.}); + + lstm.SetOutputGateBias({0., 0., 0., 0.}); + + lstm.SetRecurrentToInputWeights( + {-0.0063535, -0.2042388, 0.31454784, -0.35746509, 0.28902304, 0.08183324, + -0.16555229, 0.02286911, -0.13566875, 0.03034258, 0.48091322, + -0.12528998, 0.24077177, -0.51332325, -0.33502164, 0.10629296}); + + lstm.SetRecurrentToCellWeights( + {-0.3407414, 0.24443203, -0.2078532, 0.26320225, 0.05695659, -0.00123841, + -0.4744786, -0.35869038, -0.06418842, -0.13502428, -0.501764, 0.22830659, + -0.46367589, 0.26016325, -0.03894562, -0.16368064}); + + lstm.SetRecurrentToForgetWeights( + {-0.48684245, -0.06655136, 0.42224967, 0.2112639, 0.27654213, 0.20864892, + -0.07646349, 0.45877004, 0.00141793, -0.14609534, 0.36447752, 0.09196436, + 0.28053468, 0.01560611, -0.20127171, -0.01140004}); + + lstm.SetRecurrentToOutputWeights( + {0.43385774, -0.17194885, 0.2718237, 0.09215671, 0.24107647, -0.39835793, + 0.18212086, 0.01301402, 0.48572797, -0.50656658, 0.20047462, -0.20607421, + -0.51818722, -0.15390486, 0.0468148, 0.39922136}); + + // Input should have n_input * sequence_length many values. + static float lstm_input[] = {2., 3., 3., 4., 1., 1.}; + static float lstm_fw_golden_output[] = { + -0.02973187, 0.1229473, 0.20885126, -0.15358765, + -0.03716109, 0.12507336, 0.41193449, -0.20860538, + -0.15053082, 0.09120187, 0.24278517, -0.12222792}; + static float lstm_bw_golden_output[] = { + -0.0806187, 0.139077, 0.400476, -0.197842, -0.0332076, 0.123838, + 0.309777, -0.17621, -0.0490733, 0.0739237, 0.067706, -0.0208124}; + + float* batch0_start = lstm_input; + float* batch0_end = batch0_start + lstm.num_inputs() * lstm.sequence_length(); + + lstm.SetInput(0, batch0_start, batch0_end); + // Aux input and input are the same, so we should observe the same outputs + // as there's no aux input. + lstm.SetAuxInput(0, batch0_start, batch0_end); + + lstm.Invoke(); + + float* fw_golden_start = lstm_fw_golden_output; + float* fw_golden_end = + fw_golden_start + lstm.num_fw_outputs() * lstm.sequence_length(); + std::vector fw_expected; + fw_expected.insert(fw_expected.end(), fw_golden_start, fw_golden_end); + EXPECT_THAT(lstm.GetFwOutput(), + ElementsAreArray( + ArrayFloatNear(fw_expected, quantize_weights ? 1e-2 : 1e-5))); + + float* bw_golden_start = lstm_bw_golden_output; + float* bw_golden_end = + bw_golden_start + lstm.num_bw_outputs() * lstm.sequence_length(); + std::vector bw_expected; + bw_expected.insert(bw_expected.end(), bw_golden_start, bw_golden_end); + EXPECT_THAT(lstm.GetBwOutput(), + ElementsAreArray( + ArrayFloatNear(bw_expected, quantize_weights ? 1e-2 : 1e-5))); +} + } // namespace } // namespace tflite -- GitLab From cbb4b0bcd794d40a29196f0670396415c24b2881 Mon Sep 17 00:00:00 2001 From: manhyuk Date: Fri, 4 Jan 2019 13:27:45 +0900 Subject: [PATCH 0172/2345] fix typo --- tensorflow/contrib/cmake/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/cmake/README.md b/tensorflow/contrib/cmake/README.md index b2badc5785..b4f4b028f6 100644 --- a/tensorflow/contrib/cmake/README.md +++ b/tensorflow/contrib/cmake/README.md @@ -147,7 +147,7 @@ suitable interface for project configuration and dependency setting. * Go (required if you need ssl support, optional) * NASM/YASM (required by grpc for ssl support, optional) 2. Start CMake GUI -3. Click on `Browse Source` and direct to the the folder +3. Click on `Browse Source` and direct to the folder `/tensorflow/contrib/cmake` 4. Click on `Browse Build` and spectify a location that you want tensorflow to be build -- GitLab From e913092464cc5376343063d7905d491d29808c09 Mon Sep 17 00:00:00 2001 From: manhyuk Date: Fri, 4 Jan 2019 13:28:05 +0900 Subject: [PATCH 0173/2345] fix typo --- tensorflow/contrib/ignite/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/ignite/README.md b/tensorflow/contrib/ignite/README.md index 5a8c650fb9..7493ea6759 100644 --- a/tensorflow/contrib/ignite/README.md +++ b/tensorflow/contrib/ignite/README.md @@ -30,7 +30,7 @@ system based on Apache Ignite. ## Features -Ignite Dataset provides features that that you can use in a wide range of cases. The most important and interesting features are described below. +Ignite Dataset provides features that you can use in a wide range of cases. The most important and interesting features are described below. ### Distributed In-Memory Datasource [Apache Ignite](https://ignite.apache.org/) is a distributed in-memory database, caching, and processing platform that provides fast data access. It allows you to avoid limitations of hard drive and store and operate with as much data as you need in distributed cluster. You can utilize -- GitLab From f332be115267be60ed7e16fed661555c15f7d990 Mon Sep 17 00:00:00 2001 From: Pariksheet Pinjari Date: Fri, 4 Jan 2019 12:35:09 +0530 Subject: [PATCH 0174/2345] Update api_def_RegexReplace.pbtxt Documentation issue fixed --- tensorflow/core/api_def/base_api/api_def_RegexReplace.pbtxt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/api_def/base_api/api_def_RegexReplace.pbtxt b/tensorflow/core/api_def/base_api/api_def_RegexReplace.pbtxt index 70ad521926..2cc1a55676 100644 --- a/tensorflow/core/api_def/base_api/api_def_RegexReplace.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_RegexReplace.pbtxt @@ -10,7 +10,7 @@ op { } in_arg { name: "rewrite" - description: "The rewrite to be applied to the matched expresion." + description: "The rewrite to be applied to the matched expression." } out_arg { name: "output" -- GitLab From 72f0dadfffd8c2f635ec58ded5c38154644c6f3f Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Thu, 3 Jan 2019 23:49:44 -0800 Subject: [PATCH 0175/2345] Automated rollback of commit 7d57d32e440bc4f1654fb217fd701531a82c3587 PiperOrigin-RevId: 227809861 --- tensorflow/contrib/mpi_collectives/BUILD | 2 +- tensorflow/stream_executor/platform/default/dso_loader.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/mpi_collectives/BUILD b/tensorflow/contrib/mpi_collectives/BUILD index d943ae6880..ecac06354d 100644 --- a/tensorflow/contrib/mpi_collectives/BUILD +++ b/tensorflow/contrib/mpi_collectives/BUILD @@ -52,7 +52,7 @@ tf_custom_op_library( deps = [ ":mpi_defines", ":mpi_message_proto_cc", - "//tensorflow/core:stream_executor_headers_lib", + "//tensorflow/stream_executor:stream_executor_headers_lib", "//third_party/mpi", ], ) diff --git a/tensorflow/stream_executor/platform/default/dso_loader.cc b/tensorflow/stream_executor/platform/default/dso_loader.cc index 668eeee3f3..0f0bce3253 100644 --- a/tensorflow/stream_executor/platform/default/dso_loader.cc +++ b/tensorflow/stream_executor/platform/default/dso_loader.cc @@ -28,7 +28,7 @@ limitations under the License. #include "tensorflow/stream_executor/lib/path.h" #include "tensorflow/stream_executor/lib/str_util.h" #include "tensorflow/stream_executor/lib/stringprintf.h" -#include "tensorflow/stream_executor/platform/default/dso_loader.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/platform/port.h" -- GitLab From 6965d80c135a7c0008d2e17ce74529ab5798a5e4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 01:02:44 -0800 Subject: [PATCH 0176/2345] compat: Update forward compatibility horizon to 2019-01-04 PiperOrigin-RevId: 227817111 --- 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 c60c42ee65..66c4fa4893 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, 1, 3) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 4) @tf_export("compat.forward_compatible") -- GitLab From f76dc80f344440637b0b8ae84754c50bb4868b97 Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Fri, 4 Jan 2019 15:19:33 +0530 Subject: [PATCH 0177/2345] Add api_def for ExperimentalTakeWhileDataset --- ...api_def_ExperimentalTakeWhileDataset.pbtxt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tensorflow/core/api_def/base_api/api_def_ExperimentalTakeWhileDataset.pbtxt diff --git a/tensorflow/core/api_def/base_api/api_def_ExperimentalTakeWhileDataset.pbtxt b/tensorflow/core/api_def/base_api/api_def_ExperimentalTakeWhileDataset.pbtxt new file mode 100644 index 0000000000..699e0c2e39 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ExperimentalTakeWhileDataset.pbtxt @@ -0,0 +1,25 @@ +op { + graph_op_name: "ExperimentalTakeWhileDataset" + visibility: HIDDEN + in_arg { + name: "other_arguments" + description: < Date: Fri, 4 Jan 2019 15:25:21 +0530 Subject: [PATCH 0178/2345] Format files according to TF style guide and minor fixes --- .../experimental/take_while_dataset_op.cc | 32 ++++---- .../kernel_tests/take_while_test.py | 82 +++++++++---------- .../data/experimental/ops/take_while_ops.py | 40 ++++----- 3 files changed, 72 insertions(+), 82 deletions(-) diff --git a/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc index b18cc32051..cf9c5f0cb6 100644 --- a/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc @@ -40,7 +40,7 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { std::unique_ptr captured_func; OP_REQUIRES_OK(ctx, CapturedFunction::Create(func_, ctx, "other_arguments", &captured_func)); - + // TODO (squadrick): check short-circuit *output = new Dataset(ctx, input, func_, std::move(captured_func)); } @@ -49,7 +49,7 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { class Dataset : public DatasetBase { public: Dataset(OpKernelContext* ctx, const DatasetBase* input, - const NameAttrList& func, + const NameAttrList& func, std::unique_ptr captured_func) : DatasetBase(DatasetContext(ctx)), input_(input), @@ -74,7 +74,9 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { return input_->output_shapes(); } - string DebugString() const override { return "TakeWhileDatasetOp::Dataset"; } + string DebugString() const override { + return "TakeWhileDatasetOp::Dataset"; + } int64 Cardinality() const override { return kUnknownCardinality; } @@ -109,11 +111,11 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { b->BuildAttrValue(other_arguments_types, &other_arguments_types_attr); TF_RETURN_IF_ERROR(b->AddDataset( - this, {std::make_pair(0, input_node)}, - {std::make_pair(1, other_arguments)}, - {std::make_pair("predicate", f_attr), - std::make_pair("Targuments", other_arguments_types_attr)}, - output)); + this, {std::make_pair(0, input_node)}, + {std::make_pair(1, other_arguments)}, + {std::make_pair("predicate", f_attr), + std::make_pair("Targuments", other_arguments_types_attr)}, + output)); return Status::OK(); } @@ -135,7 +137,7 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { bool* end_of_sequence) override { { tf_shared_lock l(mu_); - if(!input_impl_) { + if (!input_impl_) { *end_of_sequence = true; return Status::OK(); } @@ -154,17 +156,15 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { ctx, *out_tensors, &bool_output); if (s.ok()) { - if(bool_output.size() != 1 || bool_output[0].dtype() != DT_BOOL || - bool_output[0].NumElements() != 1) { + if (bool_output.size() != 1 || bool_output[0].dtype() != DT_BOOL || + bool_output[0].NumElements() != 1) { return errors::InvalidArgument( "`predicate` must returns a scalar bool tensor."); } - if (!bool_output[0].scalar()()) { - *end_of_sequence = true; - return Status::OK(); - } + *end_of_sequence = !bool_output[0].scalar()(); + return Status::OK(); } - return s; // propagate error to caller + return s; // propagate error to caller } protected: diff --git a/tensorflow/python/data/experimental/kernel_tests/take_while_test.py b/tensorflow/python/data/experimental/kernel_tests/take_while_test.py index d561a3cd35..b1e7974471 100644 --- a/tensorflow/python/data/experimental/kernel_tests/take_while_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/take_while_test.py @@ -18,76 +18,70 @@ from __future__ import division from __future__ import print_function import numpy as np +from absl.testing import parameterized from tensorflow.python.data.kernel_tests import test_base from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.experimental.ops import take_while_ops from tensorflow.python.framework import errors -from tensorflow.python.framework import sparse_tensor from tensorflow.python.framework import test_util from tensorflow.python.framework import constant_op from tensorflow.python.ops import array_ops -from tensorflow.python.ops import functional_ops from tensorflow.python.ops import math_ops from tensorflow.python.platform import test @test_util.run_all_in_graph_and_eager_modes -class TakeWhileTest(test_base.DatasetTestBase): - - def testTakeWhileDataset(self): - def do_test(num_elements, window_size): - def predicate_func(elem): - return array_ops.shape(elem)[0] > (window_size - 1) - - flatten_func = lambda x: dataset_ops.Dataset.from_tensor_slices(x) - take_while = take_while_ops.take_while(predicate_func) - - dataset = dataset_ops.Dataset.range(num_elements).batch(window_size) - dataset = dataset.apply(take_while).flat_map(flatten_func) - - self.assertDatasetProduces(dataset, - np.arange(int(num_elements / window_size) * window_size)) - - do_test(14, 2) - do_test(15, 2) - do_test(100, 3) - - def testTakeWhileDatasetRange(self): - def get_dataset(num_elemets, upper_bound): - return dataset_ops.Dataset.range(num_elemets).apply( - take_while_ops.take_while(lambda x: x < upper_bound)) - - def do_test(num_elemets, upper_bound): - self.assertDatasetProduces(get_dataset(num_elemets, upper_bound), - np.arange(upper_bound)) - - def out_of_bounds(num_elemets, upper_bound): +class TakeWhileTest(test_base.DatasetTestBase, parameterized.TestCase): + @parameterized.parameters( + (14, 2), + (15, 2), + (100, 3)) + def testTakeWhileDataset(self, num_elements, window_size): + def _predicate_func(elem): + return array_ops.shape(elem)[0] > (window_size - 1) + + take_while = take_while_ops.take_while(_predicate_func) + + dataset = dataset_ops.Dataset.range(num_elements).batch(window_size) + dataset = dataset.apply(take_while).flat_map( + lambda x: dataset_ops.Dataset.from_tensor_slices(x)) + + expected_num_elements = int(num_elements / window_size) * window_size + self.assertDatasetProduces(dataset, np.arange(expected_num_elements)) + + @parameterized.parameters( + (10, 2, False), + (16, 7, False), + (100, 99, False), + (100, 101, True), + (0, 1, True)) + def testTakeWhileDatasetRange(self, num_elements, upper_bound, out_of_bounds): + dataset = dataset_ops.Dataset.range(num_elements).apply( + take_while_ops.take_while(lambda x: x < upper_bound)) + + if out_of_bounds: with self.assertRaises(errors.OutOfRangeError): - self.assertDatasetProduces(get_dataset(num_elemets, upper_bound), - np.arange(upper_bound)) + self.assertDatasetProduces(dataset, np.arange(upper_bound)) - do_test(10, 2) - do_test(16, 7) - do_test(100, 99) - out_of_bounds(100, 101) - out_of_bounds(0, 1) + else: + self.assertDatasetProduces(dataset, np.arange(upper_bound)) def testTakeWhileDatasetString(self): - def stringNotEquals(string): - return lambda x: math_ops.not_equal(x, constant_op.constant(string)) + def not_equal(string): + return lambda x: math_ops.not_equal(x, constant_op.constant(string)) string = ["this", "is", "the", "test", "for", "strings"] dataset = dataset_ops.Dataset.from_tensor_slices(string).apply( - take_while_ops.take_while(stringNotEquals("test"))) - + take_while_ops.take_while(not_equal("test"))) + next_element = self.getNext(dataset) self.assertEqual(b"this", self.evaluate(next_element())) self.assertEqual(b"is", self.evaluate(next_element())) self.assertEqual(b"the", self.evaluate(next_element())) with self.assertRaises(errors.OutOfRangeError): - self.assertEqual(b"test", self.evaluate(next_element())) + self.assertEqual(b"test", self.evaluate(next_element())) if __name__ == "__main__": diff --git a/tensorflow/python/data/experimental/ops/take_while_ops.py b/tensorflow/python/data/experimental/ops/take_while_ops.py index 224eb8aa77..c9518cc9ad 100644 --- a/tensorflow/python/data/experimental/ops/take_while_ops.py +++ b/tensorflow/python/data/experimental/ops/take_while_ops.py @@ -17,12 +17,9 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections - from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.util import structure as structure_lib from tensorflow.python.framework import dtypes -from tensorflow.python.framework import ops from tensorflow.python.ops import gen_experimental_dataset_ops from tensorflow.python.util.tf_export import tf_export @@ -32,28 +29,27 @@ class _TakeWhileDataset(dataset_ops.UnaryUnchangedStructureDataset): def __init__(self, input_dataset, predicate): """See `take_while()` for details.""" - + self._input_dataset = input_dataset wrapped_func = dataset_ops.StructuredFunctionWrapper( - predicate, - "tf.data.experimental.take_while()", - dataset=self._input_dataset) - + predicate, + "tf.data.experimental.take_while()", + dataset=self._input_dataset) + if not wrapped_func.output_structure.is_compatible_with( - structure_lib.TensorStructure(dtypes.bool, [])): - raise ValueError("`predicate` must return a scalar boolean tensor.") - + structure_lib.TensorStructure(dtypes.bool, [])): + raise ValueError("`predicate` must return a scalar boolean tensor.") + self._predicate = wrapped_func - - variant_tensor = gen_experimental_dataset_ops.experimental_take_while_dataset( - self._input_dataset._variant_tensor, - other_arguments=self._predicate.function.captured_inputs, - predicate=self._predicate.function, - **dataset_ops.flat_structure(self)) - super(_TakeWhileDataset, self).__init__(input_dataset, variant_tensor) + var_tensor = gen_experimental_dataset_ops.experimental_take_while_dataset( + self._input_dataset._variant_tensor, # pylint: disable=protected-access + other_arguments=self._predicate.function.captured_inputs, + predicate=self._predicate.function, + **dataset_ops.flat_structure(self)) + super(_TakeWhileDataset, self).__init__(input_dataset, var_tensor) def _functions(self): - return [self._predicate] + return [self._predicate] @tf_export("data.experimental.take_while") @@ -61,8 +57,8 @@ def take_while(predicate): """A transformation that stops dataset iteration based on a `predicate` condition Args: - predicate: A function that maps a nested structure of tensors - (having shapes and types defined by `self.output_shapes` and + predicate: A function that maps a nested structure of tensors + (having shapes and types defined by `self.output_shapes` and `self.output_types`) to a scalar `tf.bool` tensor. Returns: @@ -70,6 +66,6 @@ def take_while(predicate): `tf.data.Dataset.apply`. """ def _apply_fn(dataset): - return _TakeWhileDataset(dataset, predicate) + return _TakeWhileDataset(dataset, predicate) return _apply_fn -- GitLab From 28edbb813a3a94892d57ce29354e69c0a960a23e Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Fri, 4 Jan 2019 15:32:40 +0530 Subject: [PATCH 0179/2345] Use parameterized test case --- .../take_while_dataset_serialization_test.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/take_while_dataset_serialization_test.py b/tensorflow/python/data/experimental/kernel_tests/serialization/take_while_dataset_serialization_test.py index 9b5498cca5..a96e37f0b4 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/take_while_dataset_serialization_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/take_while_dataset_serialization_test.py @@ -17,6 +17,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from absl.testing import parameterized + from tensorflow.python.data.experimental.kernel_tests.serialization import dataset_serialization_test_base from tensorflow.python.data.experimental.ops import take_while_ops from tensorflow.python.data.ops import dataset_ops @@ -24,21 +26,21 @@ from tensorflow.python.platform import test class TakeWhileDatasetSerializationTest( - dataset_serialization_test_base.DatasetSerializationTestBase): + dataset_serialization_test_base.DatasetSerializationTestBase, + parameterized.TestCase): def _build_dataset(self, num_elements, upper_bound): return dataset_ops.Dataset.range(num_elements).apply( - take_while_ops.take_while(lambda x: x < upper_bound)) - - def testCore(self): - def run_test(num_elem1, num_elem2, upper_bound): - self.run_core_tests(lambda: self._build_dataset(num_elem1, upper_bound), - lambda: self._build_dataset(num_elem2, upper_bound), - upper_bound) - - run_test(23, 10, 7) - run_test(10, 50, 0) - run_test(25, 30, 25) + take_while_ops.take_while(lambda x: x < upper_bound)) + + @parameterized.parameters( + (23, 10, 7), + (10, 50, 0), + (25, 30, 25)) + def testCore(self, num_elem1, num_elem2, upper_bound): + self.run_core_tests(lambda: self._build_dataset(num_elem1, upper_bound), + lambda: self._build_dataset(num_elem2, upper_bound), + upper_bound) if __name__ == "__main__": -- GitLab From 8e4ca33c8b5861526bcdc43eb1af6d73e1670e92 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 04:10:23 -0800 Subject: [PATCH 0180/2345] Correctly handle S32 and U32 in the AR-CRS combiner Some of the transforms we are doing are only valid on floating point types so we have to condition them on the element type of the operations. PiperOrigin-RevId: 227836047 --- tensorflow/compiler/xla/service/BUILD | 1 + .../compiler/xla/service/ar_crs_combiner.cc | 24 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index b84792cfc3..755c477a12 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -3678,6 +3678,7 @@ cc_library( ":pattern_matcher", "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:literal_util", + "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:status_macros", "//tensorflow/compiler/xla:statusor", "//tensorflow/compiler/xla:types", diff --git a/tensorflow/compiler/xla/service/ar_crs_combiner.cc b/tensorflow/compiler/xla/service/ar_crs_combiner.cc index 47d2c7e357..4a227d3b5c 100644 --- a/tensorflow/compiler/xla/service/ar_crs_combiner.cc +++ b/tensorflow/compiler/xla/service/ar_crs_combiner.cc @@ -26,6 +26,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/service/hlo_opcode.h" #include "tensorflow/compiler/xla/service/pattern_matcher.h" +#include "tensorflow/compiler/xla/shape_util.h" #include "tensorflow/compiler/xla/status_macros.h" #include "tensorflow/compiler/xla/types.h" #include "tensorflow/compiler/xla/xla_data.pb.h" @@ -44,11 +45,24 @@ bool MatchesArCrsPattern(HloInstruction* instruction) { if (instruction->user_count() != 1) { return false; } - auto opcode = instruction->opcode(); - return opcode == HloOpcode::kBitcast || opcode == HloOpcode::kTranspose || - opcode == HloOpcode::kReshape || opcode == HloOpcode::kConvert || - opcode == HloOpcode::kAdd || opcode == HloOpcode::kSubtract || - opcode == HloOpcode::kMultiply; + switch (instruction->opcode()) { + case HloOpcode::kBitcast: + case HloOpcode::kTranspose: + case HloOpcode::kReshape: + return true; + case HloOpcode::kConvert: + // Can be moved across if both input and output is either float or + // integer (e.g. S32<->U32 or F32<->BF16) + return ShapeUtil::ElementIsFloating(instruction->shape()) == + ShapeUtil::ElementIsFloating(instruction->operand(0)->shape()); + case HloOpcode::kAdd: + case HloOpcode::kSubtract: + case HloOpcode::kMultiply: + // Only supported for floating point operands. + return ShapeUtil::ElementIsFloating(instruction->shape()); + default: + return false; + } }; auto computation_is_addition = [](HloComputation* c) { -- GitLab From 0bc20afeec2b06437a7196b0d191d61f181cd81a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 07:27:10 -0800 Subject: [PATCH 0181/2345] Update RBE CPU Dockerfile with instructions on how to build/push. PiperOrigin-RevId: 227853285 --- tensorflow/tools/ci_build/Dockerfile.rbe.cpu | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tensorflow/tools/ci_build/Dockerfile.rbe.cpu b/tensorflow/tools/ci_build/Dockerfile.rbe.cpu index 7e5860aeec..500fb6e0b3 100644 --- a/tensorflow/tools/ci_build/Dockerfile.rbe.cpu +++ b/tensorflow/tools/ci_build/Dockerfile.rbe.cpu @@ -1,3 +1,8 @@ +# To push a new version, run: +# $ docker build -f Dockerfile.rbe.cpu \ +# --tag "gcr.io/tensorflow-testing/nosla-ubuntu16.04" . +# $ docker push gcr.io/tensorflow-testing/nosla-ubuntu16.04 + FROM launcher.gcr.io/google/rbe-ubuntu16-04:r327695 LABEL maintainer="Yu Yi " -- GitLab From bd2f78fd205faa739c657d26156bd27e02223df0 Mon Sep 17 00:00:00 2001 From: Scott Zhu Date: Fri, 4 Jan 2019 08:31:47 -0800 Subject: [PATCH 0182/2345] Make contrib.seq2seq more compatible with tf 2.0. (1/6) 1. Add a new loss object which wrap around the existing loss function, which use can use it keras model. 2. Slightly update the code for readability and reduce duplication. 3. Update the test case to be eager compatible. PiperOrigin-RevId: 227860442 --- .../seq2seq/python/kernel_tests/loss_test.py | 290 ++++++++++++++---- tensorflow/contrib/seq2seq/python/ops/loss.py | 109 +++++-- 2 files changed, 315 insertions(+), 84 deletions(-) diff --git a/tensorflow/contrib/seq2seq/python/kernel_tests/loss_test.py b/tensorflow/contrib/seq2seq/python/kernel_tests/loss_test.py index 5aa32b532f..41b2a53ca5 100644 --- a/tensorflow/contrib/seq2seq/python/kernel_tests/loss_test.py +++ b/tensorflow/contrib/seq2seq/python/kernel_tests/loss_test.py @@ -14,80 +14,254 @@ # ============================================================================== """Tests for contrib.seq2seq.python.seq2seq.loss_ops.""" -# pylint: disable=unused-import,g-bad-import-order from __future__ import absolute_import from __future__ import division from __future__ import print_function -# pylint: enable=unused-import import numpy as np from tensorflow.contrib.seq2seq.python.ops import loss from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes +from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops -from tensorflow.python.ops import init_ops -from tensorflow.python.ops import variable_scope from tensorflow.python.platform import test +@test_util.run_all_in_graph_and_eager_modes class LossTest(test.TestCase): + def setUp(self): + self.batch_size = 2 + self.sequence_length = 3 + self.number_of_classes = 5 + logits = [ + constant_op.constant(i + 0.5, shape=[self.batch_size, + self.number_of_classes]) + for i in range(self.sequence_length) + ] + self.logits = array_ops.stack(logits, axis=1) + targets = [ + constant_op.constant(i, dtypes.int32, shape=[self.batch_size]) + for i in range(self.sequence_length) + ] + self.targets = array_ops.stack(targets, axis=1) + weights = [ + constant_op.constant(1.0, shape=[self.batch_size]) + for _ in range(self.sequence_length) + ] + self.weights = array_ops.stack(weights, axis=1) + # expected_loss = sparse_softmax_cross_entropy_with_logits(targets, logits) + # where targets = [0, 1, 2], and logits = [[0.5] * 5, [1.5] * 5, [2.5] * 5] + self.expected_loss = 1.60944 + def testSequenceLoss(self): - with self.session(use_gpu=True) as sess: - with variable_scope.variable_scope( - 'root', initializer=init_ops.constant_initializer(0.5)): - batch_size = 2 - sequence_length = 3 - number_of_classes = 5 - logits = [ - constant_op.constant( - i + 0.5, shape=[batch_size, number_of_classes]) - for i in range(sequence_length) - ] - logits = array_ops.stack(logits, axis=1) - targets = [ - constant_op.constant( - i, dtypes.int32, shape=[batch_size]) - for i in range(sequence_length) - ] - targets = array_ops.stack(targets, axis=1) - weights = [ - constant_op.constant( - 1.0, shape=[batch_size]) for i in range(sequence_length) - ] - weights = array_ops.stack(weights, axis=1) - - average_loss_per_example = loss.sequence_loss( - logits, targets, weights, - average_across_timesteps=True, - average_across_batch=True) - res = sess.run(average_loss_per_example) - self.assertAllClose(1.60944, res) - - average_loss_per_sequence = loss.sequence_loss( - logits, targets, weights, - average_across_timesteps=False, - average_across_batch=True) - res = sess.run(average_loss_per_sequence) - compare_per_sequence = np.ones((sequence_length)) * 1.60944 - self.assertAllClose(compare_per_sequence, res) - - average_loss_per_batch = loss.sequence_loss( - logits, targets, weights, - average_across_timesteps=True, - average_across_batch=False) - res = sess.run(average_loss_per_batch) - compare_per_batch = np.ones((batch_size)) * 1.60944 - self.assertAllClose(compare_per_batch, res) - - total_loss = loss.sequence_loss( - logits, targets, weights, - average_across_timesteps=False, - average_across_batch=False) - res = sess.run(total_loss) - compare_total = np.ones((batch_size, sequence_length)) * 1.60944 - self.assertAllClose(compare_total, res) + with self.test_session(use_gpu=True): + average_loss_per_example = loss.sequence_loss( + self.logits, self.targets, self.weights, + average_across_timesteps=True, + average_across_batch=True) + res = self.evaluate(average_loss_per_example) + self.assertAllClose(self.expected_loss, res) + + average_loss_per_sequence = loss.sequence_loss( + self.logits, self.targets, self.weights, + average_across_timesteps=False, + average_across_batch=True) + res = self.evaluate(average_loss_per_sequence) + compare_per_sequence = np.full((self.sequence_length), self.expected_loss) + self.assertAllClose(compare_per_sequence, res) + + average_loss_per_batch = loss.sequence_loss( + self.logits, self.targets, self.weights, + average_across_timesteps=True, + average_across_batch=False) + res = self.evaluate(average_loss_per_batch) + compare_per_batch = np.full((self.batch_size), self.expected_loss) + self.assertAllClose(compare_per_batch, res) + + total_loss = loss.sequence_loss( + self.logits, self.targets, self.weights, + average_across_timesteps=False, + average_across_batch=False) + res = self.evaluate(total_loss) + compare_total = np.full((self.batch_size, self.sequence_length), + self.expected_loss) + self.assertAllClose(compare_total, res) + + def testSequenceLossClass(self): + with self.test_session(use_gpu=True): + seq_loss = loss.SequenceLoss(average_across_timesteps=True, + average_across_batch=True, + sum_over_timesteps=False, + sum_over_batch=False) + average_loss_per_example = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(average_loss_per_example) + self.assertAllClose(self.expected_loss, res) + + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=True, + sum_over_timesteps=False, + sum_over_batch=False) + average_loss_per_sequence = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(average_loss_per_sequence) + compare_per_sequence = np.full((self.sequence_length), self.expected_loss) + self.assertAllClose(compare_per_sequence, res) + + seq_loss = loss.SequenceLoss(average_across_timesteps=True, + average_across_batch=False, + sum_over_timesteps=False, + sum_over_batch=False) + average_loss_per_batch = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(average_loss_per_batch) + compare_per_batch = np.full((self.batch_size), self.expected_loss) + self.assertAllClose(compare_per_batch, res) + + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=False, + sum_over_batch=False) + total_loss = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(total_loss) + compare_total = np.full((self.batch_size, self.sequence_length), + self.expected_loss) + self.assertAllClose(compare_total, res) + + def testSumReduction(self): + with self.test_session(use_gpu=True): + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=True, + sum_over_batch=True) + average_loss_per_example = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(average_loss_per_example) + self.assertAllClose(self.expected_loss, res) + + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=False, + sum_over_batch=True) + average_loss_per_sequence = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(average_loss_per_sequence) + compare_per_sequence = np.full((self.sequence_length), self.expected_loss) + self.assertAllClose(compare_per_sequence, res) + + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=True, + sum_over_batch=False) + average_loss_per_batch = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(average_loss_per_batch) + compare_per_batch = np.full((self.batch_size), self.expected_loss) + self.assertAllClose(compare_per_batch, res) + + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=False, + sum_over_batch=False) + total_loss = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(total_loss) + compare_total = np.full((self.batch_size, self.sequence_length), + self.expected_loss) + self.assertAllClose(compare_total, res) + + def testWeightedSumReduction(self): + weights = [ + constant_op.constant(1.0, shape=[self.batch_size]) + for _ in range(self.sequence_length) + ] + # Make the last element in the sequence to have zero weights. + weights[-1] = constant_op.constant(0.0, shape=[self.batch_size]) + self.weights = array_ops.stack(weights, axis=1) + with self.test_session(use_gpu=True): + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=True, + sum_over_batch=True) + average_loss_per_example = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(average_loss_per_example) + self.assertAllClose(self.expected_loss, res) + + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=False, + sum_over_batch=True) + average_loss_per_sequence = seq_loss( + self.targets, self.logits, self.weights) + res = self.evaluate(average_loss_per_sequence) + compare_per_sequence = np.full((self.sequence_length), self.expected_loss) + # The last element in every sequence are zeros, which will be filtered. + compare_per_sequence[-1] = 0. + self.assertAllClose(compare_per_sequence, res) + + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=True, + sum_over_batch=False) + average_loss_per_batch = seq_loss(self.targets, self.logits, self.weights) + res = self.evaluate(average_loss_per_batch) + compare_per_batch = np.full((self.batch_size), self.expected_loss) + self.assertAllClose(compare_per_batch, res) + + seq_loss = loss.SequenceLoss(average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=False, + sum_over_batch=False) + total_loss = seq_loss(self.targets, self.logits, self.weights) + res = self.evaluate(total_loss) + compare_total = np.full((self.batch_size, self.sequence_length), + self.expected_loss) + # The last element in every sequence are zeros, which will be filtered. + compare_total[:, -1] = 0 + self.assertAllClose(compare_total, res) + + def testZeroWeights(self): + weights = [ + constant_op.constant(0.0, shape=[self.batch_size]) + for _ in range(self.sequence_length) + ] + weights = array_ops.stack(weights, axis=1) + with self.test_session(use_gpu=True): + average_loss_per_example = loss.sequence_loss( + self.logits, self.targets, weights, + average_across_timesteps=True, + average_across_batch=True) + res = self.evaluate(average_loss_per_example) + self.assertAllClose(0.0, res) + + average_loss_per_sequence = loss.sequence_loss( + self.logits, self.targets, weights, + average_across_timesteps=False, + average_across_batch=True) + res = self.evaluate(average_loss_per_sequence) + compare_per_sequence = np.zeros((self.sequence_length)) + self.assertAllClose(compare_per_sequence, res) + + average_loss_per_batch = loss.sequence_loss( + self.logits, self.targets, weights, + average_across_timesteps=True, + average_across_batch=False) + res = self.evaluate(average_loss_per_batch) + compare_per_batch = np.zeros((self.batch_size)) + self.assertAllClose(compare_per_batch, res) + + total_loss = loss.sequence_loss( + self.logits, self.targets, weights, + average_across_timesteps=False, + average_across_batch=False) + res = self.evaluate(total_loss) + compare_total = np.zeros((self.batch_size, self.sequence_length)) + self.assertAllClose(compare_total, res) + if __name__ == '__main__': test.main() diff --git a/tensorflow/contrib/seq2seq/python/ops/loss.py b/tensorflow/contrib/seq2seq/python/ops/loss.py index 39a6d2f58b..0fbfd61870 100644 --- a/tensorflow/contrib/seq2seq/python/ops/loss.py +++ b/tensorflow/contrib/seq2seq/python/ops/loss.py @@ -20,11 +20,12 @@ from __future__ import division from __future__ import print_function from tensorflow.python.framework import ops +from tensorflow.python.keras.losses import Loss from tensorflow.python.ops import array_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import nn_ops -__all__ = ["sequence_loss"] +__all__ = ["sequence_loss", "SequenceLoss"] def sequence_loss(logits, @@ -32,16 +33,26 @@ def sequence_loss(logits, weights, average_across_timesteps=True, average_across_batch=True, + sum_over_timesteps=False, + sum_over_batch=False, softmax_loss_function=None, name=None): """Weighted cross-entropy loss for a sequence of logits. - Depending on the values of `average_across_timesteps` and - `average_across_batch`, the return Tensor will have rank 0, 1, or 2 as these - arguments reduce the cross-entropy at each target, which has shape - `[batch_size, sequence_length]`, over their respective dimensions. For - example, if `average_across_timesteps` is `True` and `average_across_batch` - is `False`, then the return Tensor will have shape `[batch_size]`. + Depending on the values of `average_across_timesteps` / `sum_over_timesteps` + and `average_across_batch` / `sum_over_batch`, the return Tensor will have + rank 0, 1, or 2 as these arguments reduce the cross-entropy at each target, + which has shape `[batch_size, sequence_length]`, over their respective + dimensions. For example, if `average_across_timesteps` is `True` and + `average_across_batch` is `False`, then the return Tensor will have shape + `[batch_size]`. + + Note that `average_across_timesteps` and `sum_over_timesteps` cannot be True + at same time. Same for `average_across_batch` and `sum_over_batch`. + + The recommended loss reduction in tf 2.0 has been changed to sum_over, instead + of weighted average. User are recommend to use `sum_over_timesteps` and + `sum_over_batch` for reduction. Args: logits: A Tensor of shape @@ -58,6 +69,12 @@ def sequence_loss(logits, dimension and divide the cost by the total label weight across timesteps. average_across_batch: If set, sum the cost across the batch dimension and divide the returned cost by the batch size. + sum_over_timesteps: If set, sum the cost across the sequence dimension and + divide the size of the sequence. Note that any element with 0 weights will + be excluded from size calculation. + sum_over_batch: if set, sum the cost across the batch dimension and divide + the total cost by the batch size. Not that any element with 0 weights will + be excluded from size calculation. softmax_loss_function: Function (labels, logits) -> loss-batch to be used instead of the standard softmax (the default if this is None). **Note that to avoid confusion, it is required for the function to accept @@ -78,11 +95,15 @@ def sequence_loss(logits, raise ValueError("Logits must be a " "[batch_size x sequence_length x logits] tensor") if len(targets.get_shape()) != 2: - raise ValueError("Targets must be a [batch_size x sequence_length] " - "tensor") + raise ValueError("Targets must be a [batch_size x sequence_length] tensor") if len(weights.get_shape()) != 2: - raise ValueError("Weights must be a [batch_size x sequence_length] " - "tensor") + raise ValueError("Weights must be a [batch_size x sequence_length] tensor") + if average_across_timesteps and sum_over_timesteps: + raise ValueError("average_across_timesteps and sum_over_timesteps cannot " + "be set to True at same time.") + if average_across_batch and sum_over_batch: + raise ValueError("average_across_batch and sum_over_batch cannot be set " + "to True at same time.") with ops.name_scope(name, "sequence_loss", [logits, targets, weights]): num_classes = array_ops.shape(logits)[2] logits_flat = array_ops.reshape(logits, [-1, num_classes]) @@ -96,20 +117,56 @@ def sequence_loss(logits, if average_across_timesteps and average_across_batch: crossent = math_ops.reduce_sum(crossent) total_size = math_ops.reduce_sum(weights) - total_size += 1e-12 # to avoid division by 0 for all-0 weights - crossent /= total_size + crossent = math_ops.div_no_nan(crossent, total_size) + elif sum_over_timesteps and sum_over_batch: + crossent = math_ops.reduce_sum(crossent) + total_count = math_ops.cast(math_ops.count_nonzero(weights), + crossent.dtype) + crossent = math_ops.div_no_nan(crossent, total_count) else: - batch_size = array_ops.shape(logits)[0] - sequence_length = array_ops.shape(logits)[1] - crossent = array_ops.reshape(crossent, [batch_size, sequence_length]) - if average_across_timesteps and not average_across_batch: - crossent = math_ops.reduce_sum(crossent, axis=[1]) - total_size = math_ops.reduce_sum(weights, axis=[1]) - total_size += 1e-12 # to avoid division by 0 for all-0 weights - crossent /= total_size - if not average_across_timesteps and average_across_batch: - crossent = math_ops.reduce_sum(crossent, axis=[0]) - total_size = math_ops.reduce_sum(weights, axis=[0]) - total_size += 1e-12 # to avoid division by 0 for all-0 weights - crossent /= total_size + crossent = array_ops.reshape(crossent, array_ops.shape(logits)[0:2]) + if average_across_timesteps or average_across_batch: + reduce_axis = [0] if average_across_batch else [1] + crossent = math_ops.reduce_sum(crossent, axis=reduce_axis) + total_size = math_ops.reduce_sum(weights, axis=reduce_axis) + crossent = math_ops.div_no_nan(crossent, total_size) + elif sum_over_timesteps or sum_over_batch: + reduce_axis = [0] if sum_over_batch else [1] + crossent = math_ops.reduce_sum(crossent, axis=reduce_axis) + total_count = math_ops.cast( + math_ops.count_nonzero(weights, axis=reduce_axis), + dtype=crossent.dtype) + crossent = math_ops.div_no_nan(crossent, total_count) return crossent + + +class SequenceLoss(Loss): + """Weighted cross-entropy loss for a sequence of logits.""" + + def __init__(self, + average_across_timesteps=False, + average_across_batch=False, + sum_over_timesteps=True, + sum_over_batch=True, + softmax_loss_function=None, + name=None): + super(SequenceLoss, self).__init__(name=name) + self.average_across_timesteps = average_across_timesteps + self.average_across_batch = average_across_batch + self.sum_over_timesteps = sum_over_timesteps + self.sum_over_batch = sum_over_batch + self.softmax_loss_function = softmax_loss_function + + def __call__(self, y_true, y_pred, sample_weight=None): + """Override the parent __call__ to have a customized reduce behavior.""" + return sequence_loss(y_pred, y_true, sample_weight, + average_across_timesteps=self.average_across_timesteps, + average_across_batch=self.average_across_batch, + sum_over_timesteps=self.sum_over_timesteps, + sum_over_batch=self.sum_over_batch, + softmax_loss_function=self.softmax_loss_function, + name=self.name) + + def call(self, y_true, y_pred): + # Skip this method since the __call__ contains real implementation. + pass -- GitLab From 7a830dab71dc530226936306b109fecead4366d6 Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Fri, 4 Jan 2019 22:42:38 +0530 Subject: [PATCH 0183/2345] Add ShortCircuit optimization for take_while and corresponding test --- .../core/kernels/data/experimental/BUILD | 1 + .../experimental/take_while_dataset_op.cc | 80 +++++++++++++------ .../kernel_tests/take_while_test.py | 16 ++++ 3 files changed, 73 insertions(+), 24 deletions(-) diff --git a/tensorflow/core/kernels/data/experimental/BUILD b/tensorflow/core/kernels/data/experimental/BUILD index 4ce14b0140..53d1f02a3a 100644 --- a/tensorflow/core/kernels/data/experimental/BUILD +++ b/tensorflow/core/kernels/data/experimental/BUILD @@ -303,6 +303,7 @@ tf_kernel_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core/kernels/data:captured_function", + "//tensorflow/core/kernels/data:dataset_utils" ], ) diff --git a/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc index cf9c5f0cb6..b670e4efaa 100644 --- a/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc @@ -20,6 +20,7 @@ limitations under the License. #include "tensorflow/core/framework/partial_tensor_shape.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/kernels/data/captured_function.h" +#include "tensorflow/core/kernels/data/dataset_utils.h" namespace tensorflow { namespace data { @@ -30,6 +31,10 @@ namespace { class TakeWhileDatasetOp : public UnaryDatasetOpKernel { public: + using LoopIteratorPredicate = + std::function, bool*)>; + explicit TakeWhileDatasetOp(OpKernelConstruction* ctx) : UnaryDatasetOpKernel(ctx) { OP_REQUIRES_OK(ctx, ctx->GetAttr("predicate", &func_)); @@ -41,8 +46,45 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { OP_REQUIRES_OK(ctx, CapturedFunction::Create(func_, ctx, "other_arguments", &captured_func)); - // TODO (squadrick): check short-circuit - *output = new Dataset(ctx, input, func_, std::move(captured_func)); + std::vector indices; + OP_REQUIRES_OK(ctx, ComputeShortCircuitIndices(ctx, func_, &indices)); + OP_REQUIRES( + ctx, indices.size() <= 1, + errors::InvalidArgument("`predicate` has more than one return value.")); + + LoopIteratorPredicate loop_pred; + if (indices.empty()) { + loop_pred = [](IteratorContext* ctx, + InstantiatedCapturedFunction* inst_captured_func, + const std::vector& args, bool* end_of_sequence) { + std::vector result; + TF_RETURN_IF_ERROR( + inst_captured_func->RunWithBorrowedArgs(ctx, args, &result)); + + if (result.size() != 1 || result[0].dtype() != DT_BOOL || + result[0].NumElements() != 1) { + return errors::InvalidArgument( + "`predicate` must returns a scalar bool tensor."); + } + *end_of_sequence = !result[0].scalar()(); + return Status::OK(); + }; + } else { + loop_pred = [indices](IteratorContext* ctx, + InstantiatedCapturedFunction* inst_captured_func, + const std::vector& args, + bool* end_of_sequence) { + const Tensor& predicate = args[indices[0]]; + if (predicate.dtype() != DT_BOOL || predicate.NumElements() != 1) { + return errors::InvalidArgument( + "`predicate` must returns a scalar bool tensor."); + } + *end_of_sequence = !predicate.scalar()(); + return Status::OK(); + }; + } + *output = new Dataset(ctx, input, func_, std::move(captured_func), + std::move(loop_pred)); } private: @@ -50,11 +92,13 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { public: Dataset(OpKernelContext* ctx, const DatasetBase* input, const NameAttrList& func, - std::unique_ptr captured_func) + std::unique_ptr captured_func, + LoopIteratorPredicate loop_pred) : DatasetBase(DatasetContext(ctx)), input_(input), func_(func), - captured_func_(std::move(captured_func)) { + captured_func_(std::move(captured_func)), + loop_pred_(std::move(loop_pred)) { input_->Ref(); } @@ -62,8 +106,8 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { std::unique_ptr MakeIteratorInternal( const string& prefix) const override { - return std::unique_ptr( - new Iterator({this, strings::StrCat(prefix, "::TakeWhile")})); + return std::unique_ptr(new Iterator( + {this, strings::StrCat(prefix, "::TakeWhile")}, loop_pred_)); } const DataTypeVector& output_dtypes() const override { @@ -122,8 +166,8 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { private: class Iterator : public DatasetIterator { public: - explicit Iterator(const Params& params) - : DatasetIterator(params) {} + explicit Iterator(const Params& params, LoopIteratorPredicate loop_pred) + : DatasetIterator(params), loop_pred_(loop_pred) {} Status Initialize(IteratorContext* ctx) override { TF_RETURN_IF_ERROR( @@ -149,22 +193,8 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { input_impl_.reset(); return Status::OK(); } - - std::vector bool_output; - - Status s = instantiated_captured_func_->RunWithBorrowedArgs( - ctx, *out_tensors, &bool_output); - - if (s.ok()) { - if (bool_output.size() != 1 || bool_output[0].dtype() != DT_BOOL || - bool_output[0].NumElements() != 1) { - return errors::InvalidArgument( - "`predicate` must returns a scalar bool tensor."); - } - *end_of_sequence = !bool_output[0].scalar()(); - return Status::OK(); - } - return s; // propagate error to caller + return loop_pred_(ctx, instantiated_captured_func_.get(), *out_tensors, + end_of_sequence); } protected: @@ -198,11 +228,13 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { mutex mu_; std::unique_ptr input_impl_ GUARDED_BY(mu_); std::unique_ptr instantiated_captured_func_; + const LoopIteratorPredicate loop_pred_; }; const DatasetBase* const input_; const NameAttrList func_; const std::unique_ptr captured_func_; + const LoopIteratorPredicate loop_pred_; }; NameAttrList func_; diff --git a/tensorflow/python/data/experimental/kernel_tests/take_while_test.py b/tensorflow/python/data/experimental/kernel_tests/take_while_test.py index b1e7974471..3faee0639f 100644 --- a/tensorflow/python/data/experimental/kernel_tests/take_while_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/take_while_test.py @@ -83,6 +83,22 @@ class TakeWhileTest(test_base.DatasetTestBase, parameterized.TestCase): with self.assertRaises(errors.OutOfRangeError): self.assertEqual(b"test", self.evaluate(next_element())) + def testTakewhileDatasetShortCircuit(self): + def _predicate_func(data_elem): + return data_elem + + boolean_array = [True, True, True, True, False] + dataset = dataset_ops.Dataset.from_tensor_slices(boolean_array).apply( + take_while_ops.take_while(_predicate_func)) + + next_element = self.getNext(dataset) + self.assertEqual(True, self.evaluate(next_element())) + self.assertEqual(True, self.evaluate(next_element())) + self.assertEqual(True, self.evaluate(next_element())) + self.assertEqual(True, self.evaluate(next_element())) + + with self.assertRaises(errors.OutOfRangeError): + self.evaluate(next_element()) if __name__ == "__main__": test.main() -- GitLab From 533d43705aa018d3bd07dc448ad63625776be63c Mon Sep 17 00:00:00 2001 From: "Joshua V. Dillon" Date: Fri, 4 Jan 2019 09:13:05 -0800 Subject: [PATCH 0184/2345] Remove tf.contrib.distributions seq2seq dependecy. PiperOrigin-RevId: 227865533 --- tensorflow/contrib/seq2seq/BUILD | 2 - .../python/kernel_tests/basic_decoder_test.py | 6 +- .../contrib/seq2seq/python/ops/helper.py | 86 ++++++++++++++++--- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/tensorflow/contrib/seq2seq/BUILD b/tensorflow/contrib/seq2seq/BUILD index 18b56cd219..89176180ae 100644 --- a/tensorflow/contrib/seq2seq/BUILD +++ b/tensorflow/contrib/seq2seq/BUILD @@ -33,7 +33,6 @@ tf_custom_op_py_library( srcs_version = "PY2AND3", deps = [ ":beam_search_ops", - "//tensorflow/contrib/distributions:distributions_py", "//tensorflow/contrib/layers:layers_py", "//tensorflow/contrib/rnn:rnn_py", "//tensorflow/contrib/util:util_py", @@ -59,7 +58,6 @@ tf_custom_op_py_library( "//tensorflow/python:tensor_util", "//tensorflow/python:util", "//tensorflow/python:variable_scope", - "//tensorflow/python/ops/distributions", "//third_party/py/numpy", "@six_archive//:six", ], diff --git a/tensorflow/contrib/seq2seq/python/kernel_tests/basic_decoder_test.py b/tensorflow/contrib/seq2seq/python/kernel_tests/basic_decoder_test.py index b7f9f3fb09..abcf71c61b 100644 --- a/tensorflow/contrib/seq2seq/python/kernel_tests/basic_decoder_test.py +++ b/tensorflow/contrib/seq2seq/python/kernel_tests/basic_decoder_test.py @@ -34,8 +34,6 @@ from tensorflow.python.ops import math_ops from tensorflow.python.ops import rnn_cell from tensorflow.python.ops import variables from tensorflow.python.ops import variable_scope -from tensorflow.python.ops.distributions import bernoulli -from tensorflow.python.ops.distributions import categorical from tensorflow.python.platform import test # pylint: enable=g-import-not-at-top @@ -517,7 +515,7 @@ class BasicDecoderTest(test.TestCase): vocabulary_size) # The sample function samples categorically from the logits. - sample_fn = lambda x: categorical.Categorical(logits=x).sample() + sample_fn = lambda x: helper_py.categorical_sample(logits=x) # The next inputs are a one-hot encoding of the sampled labels. next_inputs_fn = ( lambda x: array_ops.one_hot(x, vocabulary_size, dtype=dtypes.float32)) @@ -599,7 +597,7 @@ class BasicDecoderTest(test.TestCase): # The sample function samples independent bernoullis from the logits. sample_fn = ( - lambda x: bernoulli.Bernoulli(logits=x, dtype=dtypes.bool).sample()) + lambda x: helper_py.bernoulli_sample(logits=x, dtype=dtypes.bool)) # The next inputs are a one-hot encoding of the sampled labels. next_inputs_fn = math_ops.to_float end_fn = lambda sample_ids: sample_ids[:, end_token] diff --git a/tensorflow/contrib/seq2seq/python/ops/helper.py b/tensorflow/contrib/seq2seq/python/ops/helper.py index 3245cc5e72..033c2eb080 100644 --- a/tensorflow/contrib/seq2seq/python/ops/helper.py +++ b/tensorflow/contrib/seq2seq/python/ops/helper.py @@ -32,9 +32,8 @@ from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import embedding_ops from tensorflow.python.ops import gen_array_ops from tensorflow.python.ops import math_ops +from tensorflow.python.ops import random_ops from tensorflow.python.ops import tensor_array_ops -from tensorflow.python.ops.distributions import bernoulli -from tensorflow.python.ops.distributions import categorical from tensorflow.python.util import nest __all__ = [ @@ -51,6 +50,68 @@ __all__ = [ _transpose_batch_time = decoder._transpose_batch_time # pylint: disable=protected-access +# The following sample functions (_call_sampler, bernoulli_sample, +# categorical_sample) mimic TensorFlow Probability distribution semantics. + + +def _call_sampler(sample_n_fn, sample_shape, name=None): + """Reshapes vector of samples.""" + with ops.name_scope(name, "call_sampler", values=[sample_shape]): + sample_shape = ops.convert_to_tensor( + sample_shape, dtype=dtypes.int32, name="sample_shape") + # Ensure sample_shape is a vector (vs just a scalar). + pad = math_ops.cast(math_ops.equal(array_ops.rank(sample_shape), 0), + dtypes.int32) + sample_shape = array_ops.reshape( + sample_shape, + array_ops.pad(array_ops.shape(sample_shape), + paddings=[[pad, 0]], + constant_values=1)) + samples = sample_n_fn(math_ops.reduce_prod(sample_shape)) + batch_event_shape = array_ops.shape(samples)[1:] + final_shape = array_ops.concat([sample_shape, batch_event_shape], 0) + return array_ops.reshape(samples, final_shape) + + +def bernoulli_sample(probs=None, logits=None, dtype=dtypes.int32, + sample_shape=(), seed=None): + """Samples from Bernoulli distribution.""" + if probs is None: + probs = math_ops.sigmoid(logits, name="probs") + else: + probs = ops.convert_to_tensor(probs, name="probs") + batch_shape_tensor = array_ops.shape(probs) + def _sample_n(n): + """Sample vector of Bernoullis.""" + new_shape = array_ops.concat([[n], batch_shape_tensor], 0) + uniform = random_ops.random_uniform( + new_shape, seed=seed, dtype=probs.dtype) + return math_ops.cast(math_ops.less(uniform, probs), dtype) + return _call_sampler(_sample_n, sample_shape) + + +def categorical_sample(logits, dtype=dtypes.int32, + sample_shape=(), seed=None): + """Samples from categorical distribution.""" + logits = ops.convert_to_tensor(logits, name="logits") + event_size = array_ops.shape(logits)[-1] + batch_shape_tensor = array_ops.shape(logits)[:-1] + def _sample_n(n): + """Sample vector of categoricals.""" + if logits.shape.ndims == 2: + logits_2d = logits + else: + logits_2d = array_ops.reshape(logits, [-1, event_size]) + sample_dtype = dtypes.int64 if logits.dtype.size > 4 else dtypes.int32 + draws = random_ops.multinomial( + logits_2d, n, seed=seed, output_dtype=sample_dtype) + draws = array_ops.reshape( + array_ops.transpose(draws), + array_ops.concat([[n], batch_shape_tensor], 0)) + return math_ops.cast(draws, dtype) + return _call_sampler(_sample_n, sample_shape) + + def _unstack_ta(inp): return tensor_array_ops.TensorArray( dtype=inp.dtype, size=array_ops.shape(inp)[0], @@ -307,14 +368,14 @@ class ScheduledEmbeddingTrainingHelper(TrainingHelper): with ops.name_scope(name, "ScheduledEmbeddingTrainingHelperSample", [time, outputs, state]): # Return -1s where we did not sample, and sample_ids elsewhere - select_sampler = bernoulli.Bernoulli( - probs=self._sampling_probability, dtype=dtypes.bool) - select_sample = select_sampler.sample( - sample_shape=self.batch_size, seed=self._scheduling_seed) - sample_id_sampler = categorical.Categorical(logits=outputs) + select_sample = bernoulli_sample( + probs=self._sampling_probability, + dtype=dtypes.bool, + sample_shape=self.batch_size, + seed=self._scheduling_seed) return array_ops.where( select_sample, - sample_id_sampler.sample(seed=self._seed), + categorical_sample(logits=outputs, seed=self._seed), gen_array_ops.fill([self.batch_size], -1)) def next_inputs(self, time, outputs, state, sample_ids, name=None): @@ -425,8 +486,10 @@ class ScheduledOutputTrainingHelper(TrainingHelper): def sample(self, time, outputs, state, name=None): with ops.name_scope(name, "ScheduledOutputTrainingHelperSample", [time, outputs, state]): - sampler = bernoulli.Bernoulli(probs=self._sampling_probability) - return sampler.sample(sample_shape=self.batch_size, seed=self._seed) + return bernoulli_sample( + probs=self._sampling_probability, + sample_shape=self.batch_size, + seed=self._seed) def next_inputs(self, time, outputs, state, sample_ids, name=None): with ops.name_scope(name, "ScheduledOutputTrainingHelperNextInputs", @@ -610,8 +673,7 @@ class SampleEmbeddingHelper(GreedyEmbeddingHelper): else: logits = outputs / self._softmax_temperature - sample_id_sampler = categorical.Categorical(logits=logits) - sample_ids = sample_id_sampler.sample(seed=self._seed) + sample_ids = categorical_sample(logits=logits, seed=self._seed) return sample_ids -- GitLab From 586c507c879c816eb001bfb32a14deb7053640a6 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 09:58:03 -0800 Subject: [PATCH 0185/2345] [tfgan] Adds GANEstimator wrapper that allows backpropagation to input latent space. PiperOrigin-RevId: 227871387 --- tensorflow/contrib/gan/BUILD | 36 +++ .../contrib/gan/python/estimator/__init__.py | 5 +- .../estimator/python/latent_gan_estimator.py | 28 +++ .../python/latent_gan_estimator_impl.py | 205 ++++++++++++++++++ .../python/latent_gan_estimator_test.py | 119 ++++++++++ 5 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator.py create mode 100644 tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator_impl.py create mode 100644 tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator_test.py diff --git a/tensorflow/contrib/gan/BUILD b/tensorflow/contrib/gan/BUILD index 0626875b76..08df38ad27 100644 --- a/tensorflow/contrib/gan/BUILD +++ b/tensorflow/contrib/gan/BUILD @@ -107,6 +107,7 @@ py_library( deps = [ ":gan_estimator", ":head", + ":latent_gan_estimator", ":stargan_estimator", ":tpu_gan_estimator", "//tensorflow/python:util", @@ -643,6 +644,41 @@ py_test( ], ) +py_library( + name = "latent_gan_estimator", + srcs = [ + "python/estimator/python/latent_gan_estimator.py", + "python/estimator/python/latent_gan_estimator_impl.py", + ], + srcs_version = "PY2AND3", + deps = [ + ":train", + "//tensorflow/python:clip_ops", + "//tensorflow/python:gradients", + "//tensorflow/python:random_ops", + "//tensorflow/python:summary", + "//tensorflow/python:training_util", + "//tensorflow/python:variable_scope", + "//tensorflow/python/estimator:estimator_py", + ], +) + +py_test( + name = "latent_gan_estimator_test", + srcs = [ + "python/estimator/python/latent_gan_estimator_test.py", + ], + srcs_version = "PY2AND3", + deps = [ + ":latent_gan_estimator", + "//tensorflow/python:array_ops", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python/estimator:run_config", + "//tensorflow/python/ops/losses", + ], +) + py_library( name = "sliced_wasserstein", srcs = [ diff --git a/tensorflow/contrib/gan/python/estimator/__init__.py b/tensorflow/contrib/gan/python/estimator/__init__.py index 03a639165b..430266555b 100644 --- a/tensorflow/contrib/gan/python/estimator/__init__.py +++ b/tensorflow/contrib/gan/python/estimator/__init__.py @@ -26,11 +26,13 @@ from __future__ import print_function # pylint: disable=unused-import,wildcard-import from tensorflow.contrib.gan.python.estimator.python import gan_estimator from tensorflow.contrib.gan.python.estimator.python import head +from tensorflow.contrib.gan.python.estimator.python import latent_gan_estimator from tensorflow.contrib.gan.python.estimator.python import stargan_estimator from tensorflow.contrib.gan.python.estimator.python import tpu_gan_estimator from tensorflow.contrib.gan.python.estimator.python.gan_estimator import * from tensorflow.contrib.gan.python.estimator.python.head import * +from tensorflow.contrib.gan.python.estimator.python.latent_gan_estimator import * from tensorflow.contrib.gan.python.estimator.python.stargan_estimator import * from tensorflow.contrib.gan.python.estimator.python.tpu_gan_estimator import * # pylint: enable=unused-import,wildcard-import @@ -41,7 +43,8 @@ _allowed_symbols = ([ 'gan_estimator', 'stargan_estimator', 'tpu_gan_estimator', + 'latent_gan_estimator', 'head', ] + gan_estimator.__all__ + stargan_estimator.__all__ + head.__all__ + - tpu_gan_estimator.__all__) + tpu_gan_estimator.__all__ + latent_gan_estimator.__all__) remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator.py b/tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator.py new file mode 100644 index 0000000000..4e164e2416 --- /dev/null +++ b/tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator.py @@ -0,0 +1,28 @@ +# 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. +# ============================================================================== +"""`tf.Learn` components for `Train Input Estimator`.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.gan.python.estimator.python import latent_gan_estimator_impl +# pylint: disable=wildcard-import +from tensorflow.contrib.gan.python.estimator.python.latent_gan_estimator_impl import * +# pylint: enable=wildcard-import +from tensorflow.python.util.all_util import remove_undocumented + +__all__ = latent_gan_estimator_impl.__all__ +remove_undocumented(__name__, __all__) diff --git a/tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator_impl.py b/tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator_impl.py new file mode 100644 index 0000000000..f5afc77319 --- /dev/null +++ b/tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator_impl.py @@ -0,0 +1,205 @@ +# 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. +# ============================================================================== +"""Implements an estimator wrapper that allows training the input latent space. + +This file implements a latent gan estimator that wraps around a previously +trained GAN. The latent gan estimator trains a single variable z, representing +the hidden latent distribution that is the 'noise' input to the GAN. By training +z, the inpainting estimator can move around the latent z space towards +minimizing a specific loss function. + +The latent gan estimator has a few key differences from a normal estimator. + +First: the variables in the estimator should not be saved, as we are not +updating the original GAN and are only adding a new z variable that is meant +to be different for each run. In order to do distributed training using +train_and_evaluate, the Tensorflow RunConfig is expected to save checkpoints +by having either save_checkpoints_steps or save_checkpoints_secs saved. +To avoid this conflict, we purposely set the save_checkpoints_steps value in +the RunConfig to be one step more than the total number of steps that the +inpainter estimator will run. + +Second: we need to specify warm start settings, as we are reloading the +GAN model into a different graph (specifically, one with a new z variable). +The warm start settings defined below reload all GAN variables and ignore the +new z variable (and the optimizer). + +Usage: + + def _generator(net, mode): + ... + + def _discriminator(net, condition, mode): + ... + + def _loss(gan_model, features, labels, add_summaries): + ... + + def optimizer(): + ... + + params = {} + config = tf.estimator.RunConfig() + tmp_dir = path/to/output/storage + + estimator = latent_gan_estimator.get_latent_gan_estimator( + _generator, _discriminator, _loss, optimizer, params, config, tmp_dir) + + def input_fn(): + ... + + estimator.train(input_fn=input_fn) + +See latent_gan_estimator_test.py or tensorflow_models/gan/face_inpainting for +further examples. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import functools +from tensorflow.contrib.gan.python import train as tfgan_train +from tensorflow.python.estimator import estimator +from tensorflow.python.estimator import model_fn as model_fn_lib +from tensorflow.python.ops import clip_ops +from tensorflow.python.ops import gradients_impl +from tensorflow.python.ops import random_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.summary import summary +from tensorflow.python.training import training_util + + +INPUT_NAME = 'new_var_z_input' # The name for the new z space input variable. +OPTIMIZER_NAME = 'latent_gan_optimizer' # The name for the new optimizer vars. + +__all__ = [ + 'get_latent_gan_estimator', +] + + +def _get_latent_gan_model_fn(generator_fn, discriminator_fn, loss_fn, + optimizer): + """Sets up a model function that wraps around a given GAN.""" + def model_fn(features, labels, mode, params): + """Model function defining an inpainting estimator.""" + batch_size = params['batch_size'] + z_shape = [batch_size] + params['z_shape'] + add_summaries = params['add_summaries'] + input_clip = params['input_clip'] + + z = variable_scope.get_variable( + name=INPUT_NAME, initializer=random_ops.truncated_normal(z_shape), + constraint=lambda x: clip_ops.clip_by_value(x, -input_clip, input_clip)) + + generator = functools.partial(generator_fn, mode=mode) + discriminator = functools.partial(discriminator_fn, mode=mode) + gan_model = tfgan_train.gan_model(generator_fn=generator, + discriminator_fn=discriminator, + real_data=labels, + generator_inputs=z, + check_shapes=False) + + loss = loss_fn(gan_model, features, labels, add_summaries) + + # Use a variable scope to make sure that estimator variables dont cause + # save/load problems when restoring from ckpts. + with variable_scope.variable_scope(OPTIMIZER_NAME): + opt = optimizer(learning_rate=params['learning_rate'], + **params['opt_kwargs']) + train_op = opt.minimize( + loss=loss, global_step=training_util.get_or_create_global_step(), + var_list=[z]) + + if add_summaries: + z_grads = gradients_impl.gradients(loss, z) + summary.scalar('z_loss/z_grads', clip_ops.global_norm(z_grads)) + summary.scalar('z_loss/loss', loss) + + return model_fn_lib.EstimatorSpec(mode=mode, + predictions=gan_model.generated_data, + loss=loss, + train_op=train_op) + return model_fn + + +def get_latent_gan_estimator(generator_fn, discriminator_fn, loss_fn, + optimizer, params, config, ckpt_dir, + warmstart_options=True): + """Gets an estimator that passes gradients to the input. + + This function takes in a generator and adds a trainable z variable that is + used as input to this generator_fn. The generator itself is treated as a black + box through which gradients can pass through without updating any weights. The + result is a trainable way to traverse the GAN latent space. The loss_fn is + used to actually train the z variable. The generator_fn and discriminator_fn + should be previously trained by the tfgan library (on reload, the variables + are expected to follow the tfgan format. It may be possible to use the + latent gan estimator with entirely custom GANs that do not use the tfgan + library as long as the appropriate variables are wired properly). + + Args: + generator_fn: a function defining a Tensorflow graph for a GAN generator. + The weights defined in this graph should already be defined in the given + checkpoint location. Should have 'mode' as an argument. + discriminator_fn: a function defining a Tensorflow graph for a GAN + discriminator. Should have 'mode' as an argument. + loss_fn: a function defining a Tensorflow graph for a GAN loss. Takes in a + GANModel tuple, features, labels, and add_summaries as inputs. + optimizer: a tf.Optimizer or a function that returns a tf.Optimizer with no + inputs. + params: An object containing the following parameters: + - batch_size: an int indicating the size of the training batch. + - z_shape: the desired shape of the input z values (not counting batch). + - learning_rate: a scalar or function defining a learning rate applied to + optimizer. + - input_clip: the amount to clip the x training variable by. + - add_summaries: whether or not to add summaries. + - opt_kwargs: optimizer kwargs. + config: tf.RunConfig. Should point model to output dir and should indicate + whether to save checkpoints (to avoid saving checkpoints, set + save_checkpoints_steps to a number larger than the number of train steps). + The model_dir field in the RunConfig should point to a directory WITHOUT + any saved checkpoints. + ckpt_dir: the directory where the model checkpoints live. The checkpoint is + used to warm start the underlying GAN. This should NOT be the same as + config.model_dir. + warmstart_options: boolean, None, or a WarmStartSettings object. If set to + True, uses a default WarmStartSettings object. If set to False or None, + does not use warm start. If using a custom WarmStartSettings object, make + sure that new variables are properly accounted for when reloading the + underlying GAN. Defaults to True. + Returns: + An estimator spec defining a GAN input training estimator. + """ + model_fn = _get_latent_gan_model_fn(generator_fn, discriminator_fn, + loss_fn, optimizer) + + if isinstance(warmstart_options, estimator.WarmStartSettings): + ws = warmstart_options + elif warmstart_options: + # Default WarmStart loads all variable names except INPUT_NAME and + # OPTIMIZER_NAME. + var_regex = '^(?!.*(%s|%s).*)' % (INPUT_NAME, OPTIMIZER_NAME) + ws = estimator.WarmStartSettings(ckpt_to_initialize_from=ckpt_dir, + vars_to_warm_start=var_regex) + else: + ws = None + + if 'opt_kwargs' not in params: + params['opt_kwargs'] = {} + + return estimator.Estimator(model_fn=model_fn, config=config, params=params, + warm_start_from=ws) diff --git a/tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator_test.py b/tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator_test.py new file mode 100644 index 0000000000..ac139e532e --- /dev/null +++ b/tensorflow/contrib/gan/python/estimator/python/latent_gan_estimator_test.py @@ -0,0 +1,119 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for latent_gan_estimator. + +See g3.tp.tensorflow.contrib.gan.python.estimator.python.latent_gan_estimator. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tempfile +import numpy as np +from tensorflow.contrib.gan.python.estimator.python import latent_gan_estimator +from tensorflow.python.estimator import run_config as run_config +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops.losses import losses +from tensorflow.python.platform import test +from tensorflow.python.training import training + + +class TrainInputEstimatorTest(test.TestCase): + + def test_get_input_training_estimator(self): + """Integration test to make sure the input_training_estimator works.""" + + # Create dummy test input tensors. + true_features = np.reshape(np.random.uniform(size=100), (10, 10)) + true_labels = np.reshape(np.random.uniform(size=100), (5, 20)) + expected_z_output = [[1, -1], [-1, 1]] + + # Fill out required parameters randomly, includes optimizer kwargs. + params = { + 'batch_size': 2, + 'z_shape': [2], + 'learning_rate': 1.0, + 'input_clip': 1.0, + 'add_summaries': False, + 'opt_kwargs': { + 'beta1': 0.1 + } + } + + input_z_shape = [params['batch_size']] + params['z_shape'] + + # Create dummy model functions that represent an underlying GANEstimator and + # the input training wrapper. Make sure that everything is wired up + # correctly in the internals of each dummy function. + def _generator(net, mode): + """The generator function will get the newly created z variable.""" + del mode + self.assertSequenceEqual(net.shape, input_z_shape) + gen_dummy_var = variable_scope.get_variable( + name='generator_dummy_variable', + initializer=array_ops.ones(input_z_shape)) + return net * gen_dummy_var + + def _discriminator(net, condition, mode): + """The discriminator function will get either the z variable or labels.""" + del condition, mode + try: + self.assertSequenceEqual(net.shape, true_labels.shape) + except AssertionError: + self.assertSequenceEqual(net.shape, input_z_shape) + return net + + def _loss(gan_model, features, labels, _): + """Make sure that features and labels are passed in from input.""" + self.assertTrue(np.array_equal(features, true_features)) + self.assertTrue(np.array_equal(labels, true_labels)) + return losses.absolute_difference(expected_z_output, + gan_model.generated_data) + + optimizer = training.AdamOptimizer + + # We are not loading checkpoints, so set the corresponding directory to a + # dummy directories. + tmp_dir = tempfile.mkdtemp() + config = run_config.RunConfig(model_dir=tmp_dir, + save_summary_steps=None, + save_checkpoints_steps=1, + save_checkpoints_secs=None) + + # Get the estimator. Disable warm start so that there is no attempted + # checkpoint reloading. + estimator = latent_gan_estimator.get_latent_gan_estimator( + _generator, _discriminator, _loss, optimizer, params, config, tmp_dir, + warmstart_options=None) + + # Train for a few steps. + def dummy_input(): + return true_features, true_labels + estimator.train(input_fn=dummy_input, steps=10) + + # Make sure the generator variables did not change, but the z variables did + # change. + self.assertTrue(np.array_equal( + estimator.get_variable_value('Generator/generator_dummy_variable'), + np.ones(input_z_shape))) + self.assertTrue(np.array_equal( + estimator.get_variable_value('new_var_z_input'), + expected_z_output)) + + +if __name__ == '__main__': + test.main() -- GitLab From df01a441b477b20346c5cbf8248018798b75ff0f Mon Sep 17 00:00:00 2001 From: Sergei Lebedev Date: Fri, 4 Jan 2019 10:04:36 -0800 Subject: [PATCH 0186/2345] Decoupled ResourceVariable from RefVariable * RefVariable._try_guard_against_uninitialized_dependencies and related methods are now functions used by both variables subclasses * Removed implicit chaining of ResourceVariable ->Tensor convertor with that of RefVariable. Previously, ResourceVariable._dense_var_to_tensor would return NotImplemented if called with a different dtype. This effectively delegated the conversion to RefVariable._TensorConversionFunction which allowed compatible but not exactly matching dtypes. The change makes this behavior explicit in ResourceVariable._dense_var_to_tensor. PiperOrigin-RevId: 227872550 --- .../feature_column/feature_column_v2_test.py | 8 +- .../python/ops/resource_variable_ops.py | 109 ++++-- tensorflow/python/ops/variables.py | 316 +++++++++--------- 3 files changed, 251 insertions(+), 182 deletions(-) diff --git a/tensorflow/python/feature_column/feature_column_v2_test.py b/tensorflow/python/feature_column/feature_column_v2_test.py index a247425369..253e4c322b 100644 --- a/tensorflow/python/feature_column/feature_column_v2_test.py +++ b/tensorflow/python/feature_column/feature_column_v2_test.py @@ -2015,7 +2015,7 @@ class LinearModelTest(test.TestCase): } model(features) for var in model.variables: - self.assertTrue(isinstance(var, variables_lib.RefVariable)) + self.assertIsInstance(var, variables_lib.VariableV1) variable_names = [var.name for var in model.variables] self.assertItemsEqual([ 'linear_model/dense_feature_bucketized/weights:0', @@ -4010,7 +4010,7 @@ class FunctionalInputLayerTest(test.TestCase): self.assertEqual(0, len(cols_to_vars[dense_feature_bucketized])) self.assertEqual(1, len(cols_to_vars[some_embedding_column])) self.assertIsInstance(cols_to_vars[some_embedding_column][0], - variables_lib.Variable) + variables_lib.VariableV1) self.assertAllEqual(cols_to_vars[some_embedding_column][0].shape, [5, 10]) @test_util.run_deprecated_v1 @@ -6839,7 +6839,7 @@ class EmbeddingColumnTest(test.TestCase): self.assertItemsEqual(('dense_features/aaa_embedding/embedding_weights:0',), tuple([v.name for v in global_vars])) for v in global_vars: - self.assertTrue(isinstance(v, variables_lib.RefVariable)) + self.assertIsInstance(v, variables_lib.Variable) trainable_vars = ops.get_collection(ops.GraphKeys.TRAINABLE_VARIABLES) self.assertItemsEqual(('dense_features/aaa_embedding/embedding_weights:0',), tuple([v.name for v in trainable_vars])) @@ -7732,7 +7732,7 @@ class SharedEmbeddingColumnTest(test.TestCase): ['aaa_bbb_shared_embedding:0', 'ccc_ddd_shared_embedding:0'], tuple([v.name for v in global_vars])) for v in global_vars: - self.assertTrue(isinstance(v, variables_lib.RefVariable)) + self.assertIsInstance(v, variables_lib.Variable) trainable_vars = ops.get_collection(ops.GraphKeys.TRAINABLE_VARIABLES) if trainable: self.assertItemsEqual( diff --git a/tensorflow/python/ops/resource_variable_ops.py b/tensorflow/python/ops/resource_variable_ops.py index 3e26463ddd..ede4bc0078 100644 --- a/tensorflow/python/ops/resource_variable_ops.py +++ b/tensorflow/python/ops/resource_variable_ops.py @@ -36,6 +36,7 @@ from tensorflow.python.ops import gen_array_ops from tensorflow.python.ops import gen_resource_variable_ops from tensorflow.python.ops import gen_state_ops from tensorflow.python.ops import math_ops +from tensorflow.python.ops import state_ops from tensorflow.python.ops import variables # go/tf-wildcard-import # pylint: disable=wildcard-import @@ -160,8 +161,7 @@ def shape_safe_assign_variable_handle(handle, shape, value, name=None): name=name) -# TODO(apassos) make this be variables.Variable -class ResourceVariable(variables.RefVariable): +class ResourceVariable(variables.VariableV1): """Variable based on resource handles. See the [Variables How To](https://tensorflow.org/guide/variables) @@ -297,6 +297,15 @@ class ResourceVariable(variables.RefVariable): dtype=dtype, constraint=constraint) + def __repr__(self): + if context.executing_eagerly() and not self._in_graph_mode: + return "" % ( + self.name, self.get_shape(), self.dtype.name, + ops.numpy_text(self.read_value(), is_repr=True)) + else: + return "" % ( + self.name, self.get_shape(), self.dtype.name) + # pylint: disable=unused-argument def _init_from_args(self, initial_value=None, @@ -437,12 +446,15 @@ class ResourceVariable(variables.RefVariable): gen_resource_variable_ops.var_is_initialized_op(self._handle)) if initial_value is not None: with ops.name_scope("Assign") as n, ops.colocate_with(self._handle): + # pylint: disable=protected-access self._initializer_op = ( gen_resource_variable_ops.assign_variable_op( self._handle, - self._try_guard_against_uninitialized_dependencies( + variables._try_guard_against_uninitialized_dependencies( + name, initial_value), name=n)) + # pylint: enable=protected-access with ops.name_scope("Read"), ops.colocate_with(self._handle): # Manually assign reads to the handle's device to avoid log # messages. @@ -683,6 +695,10 @@ class ResourceVariable(variables.RefVariable): """The op for this variable.""" return self._handle.op + @property + def trainable(self): + return self._trainable + def eval(self, session=None): """Evaluates and returns the value of this variable.""" if context.executing_eagerly(): @@ -818,10 +834,6 @@ class ResourceVariable(variables.RefVariable): return ResourceVariable( variable_def=variable_def, import_scope=import_scope) - def _ref(self): - """Unsupported.""" - raise NotImplementedError("ResourceVariable does not implement _ref()") - def set_shape(self, shape): """Unsupported.""" raise NotImplementedError("ResourceVariable does not implement set_shape()") @@ -994,6 +1006,55 @@ class ResourceVariable(variables.RefVariable): self.handle, sparse_delta.indices, ops.convert_to_tensor(sparse_delta.values, self.dtype), name=name)) + def batch_scatter_update(self, sparse_delta, use_locking=False, name=None): + """Assigns `IndexedSlices` to this variable batch-wise. + + Analogous to `batch_gather`. This assumes that this variable and the + sparse_delta IndexedSlices have a series of leading dimensions that are the + same for all of them, and the updates are performed on the last dimension of + indices. In other words, the dimensions should be the following: + + `num_prefix_dims = sparse_delta.indices.ndims - 1` + `batch_dim = num_prefix_dims + 1` + `sparse_delta.updates.shape = sparse_delta.indices.shape + var.shape[ + batch_dim:]` + + where + + `sparse_delta.updates.shape[:num_prefix_dims]` + `== sparse_delta.indices.shape[:num_prefix_dims]` + `== var.shape[:num_prefix_dims]` + + And the operation performed can be expressed as: + + `var[i_1, ..., i_n, + sparse_delta.indices[i_1, ..., i_n, j]] = sparse_delta.updates[ + i_1, ..., i_n, j]` + + When sparse_delta.indices is a 1D tensor, this operation is equivalent to + `scatter_update`. + + To avoid this operation one can looping over the first `ndims` of the + variable and using `scatter_update` on the subtensors that result of slicing + the first dimension. This is a valid option for `ndims = 1`, but less + efficient than this implementation. + + Args: + sparse_delta: `IndexedSlices` to be assigned to this variable. + use_locking: If `True`, use locking during the operation. + name: the name of the operation. + + Returns: + A `Tensor` that will hold the new value of this variable after + the scattered subtraction has completed. + + Raises: + ValueError: if `sparse_delta` is not an `IndexedSlices`. + """ + return self._lazy_read(state_ops.batch_scatter_update( + self, sparse_delta.indices, sparse_delta.values, + use_locking=use_locking, name=name)) + def scatter_nd_sub(self, indices, updates, name=None): """Applies sparse subtraction to individual values or slices in a Variable. @@ -1178,8 +1239,10 @@ class ResourceVariable(variables.RefVariable): def _dense_var_to_tensor(self, dtype=None, name=None, as_ref=False): del name - if dtype is not None and dtype != self.dtype: - return NotImplemented + 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)) if as_ref: return self.read_value().op.inputs[0] else: @@ -1231,6 +1294,12 @@ def _dense_var_to_tensor(var, dtype=None, name=None, as_ref=False): return var._dense_var_to_tensor(dtype=dtype, name=name, as_ref=as_ref) # pylint: disable=protected-access +# Register a conversion function which reads the value of the variable, +# allowing instances of the class to be used as tensors. +ops.register_tensor_conversion_function(ResourceVariable, _dense_var_to_tensor) +ops.register_dense_tensor_like_type(ResourceVariable) + + class _UnreadVariable(ResourceVariable): """Represents a future for a read of a variable. @@ -1291,7 +1360,7 @@ class _UnreadVariable(ResourceVariable): """The op for this variable.""" return self._parent_op -ops.register_tensor_conversion_function(_UnreadVariable, _dense_var_to_tensor) + ops.register_dense_tensor_like_type(_UnreadVariable) @@ -1390,29 +1459,15 @@ class _MixedPrecisionVariable(ResourceVariable): def _dense_var_to_tensor(self, dtype=None, name=None, as_ref=False): del name - dtype = dtype or self.read_dtype - if dtype != self.read_dtype or as_ref: + if (dtype is not None and + not dtype.is_compatible_with(self.read_dtype) or as_ref): return NotImplemented - else: - res = self.value() - return res + return self.value() def _should_act_as_resource_variable(self): """To pass resource_variable_ops.is_resource_variable check.""" pass -# Register a conversion function which reads the value of the variable, -# allowing instances of the class to be used as tensors. - -# Note: registering for Variable after ResourceVariable because inheritance will -# otherwise lead to the wrong behavior. -ops.register_tensor_conversion_function(ResourceVariable, _dense_var_to_tensor) -ops.register_tensor_conversion_function( - variables.Variable, variables.Variable._TensorConversionFunction) # pylint: disable=protected-access - -# pylint: disable=protected-access -ops.register_dense_tensor_like_type(ResourceVariable) - @ops.RegisterGradient("ReadVariableOp") def _ReadGrad(_, grad): diff --git a/tensorflow/python/ops/variables.py b/tensorflow/python/ops/variables.py index 657f64deaa..a4967e1c0f 100644 --- a/tensorflow/python/ops/variables.py +++ b/tensorflow/python/ops/variables.py @@ -59,21 +59,6 @@ def _make_getter(captured_getter, captured_previous): return getter -def _has_cycle(op, path): - """Detect cycles in the dependencies of `initial_value`.""" - if op.name in path: - return True - path.add(op.name) - for op_input in op.inputs: - if _has_cycle(op_input.op, path): - return True - for op_control_input in op.control_inputs: - if _has_cycle(op_control_input, path): - return True - path.remove(op.name) - return False - - @tf_export("VariableSynchronization") class VariableSynchronization(enum.Enum): """Indicates when a distributed variable will be synced. @@ -1041,6 +1026,10 @@ class Variable(six.with_metaclass(VariableMetaclass, """Alias of `Variable.shape`.""" return self.shape + def _gather_saveables_for_checkpoint(self): + """For implementing `Checkpointable`. This object is saveable on its own.""" + return {checkpointable.VARIABLE_VALUE_KEY: self} + def to_proto(self, export_scope=None): """Converts a `Variable` to a `VariableDef` protocol buffer. @@ -1144,6 +1133,9 @@ class Variable(six.with_metaclass(VariableMetaclass, return None +Variable._OverloadAllOperators() # pylint: disable=protected-access + + @tf_export(v1=["Variable"]) class VariableV1(Variable): """See the [Variables Guide](https://tensorflow.org/guide/variables). @@ -1582,7 +1574,8 @@ class RefVariable(VariableV1): # using their initialized_value() method. self._initializer_op = state_ops.assign( self._variable, - self._try_guard_against_uninitialized_dependencies( + _try_guard_against_uninitialized_dependencies( + name, self._initial_value), validate_shape=validate_shape).op @@ -2161,134 +2154,6 @@ class RefVariable(VariableV1): else: return v.value() - def _gather_saveables_for_checkpoint(self): - """For implementing `Checkpointable`. This object is saveable on its own.""" - return {checkpointable.VARIABLE_VALUE_KEY: self} - - def _try_guard_against_uninitialized_dependencies(self, initial_value): - """Attempt to guard against dependencies on uninitialized variables. - - Replace references to variables in `initial_value` with references to the - variable's initialized values. The initialized values are essentially - conditional TensorFlow graphs that return a variable's value if it is - initialized or its `initial_value` if it hasn't been initialized. This - replacement is done on a best effort basis: - - - If the `initial_value` graph contains cycles, we don't do any - replacements for that graph. - - If the variables that `initial_value` depends on are not present in the - `GLOBAL_VARIABLES` or `LOCAL_VARIABLES` we don't replace them. - - In these cases, it is up to the caller to ensure that the `initial_value` - graph uses initialized variables or that they guard access to variables - using their `initialized_value` method. - - Args: - initial_value: `Tensor`. The initial value. - Returns: - A `Tensor` suitable to initialize a variable. - Raises: - TypeError: If `initial_value` is not a `Tensor`. - """ - if not isinstance(initial_value, ops.Tensor): - raise TypeError("initial_value needs to be a Tensor: %s" % initial_value) - - # Don't modify initial_value if it contains any cyclic dependencies. - if _has_cycle(initial_value.op, path=set()): - return initial_value - - return self._safe_initial_value_from_tensor(initial_value, op_cache={}) - - def _safe_initial_value_from_tensor(self, tensor, op_cache): - """Replace dependencies on variables with their initialized values. - - Args: - tensor: A `Tensor`. The tensor to replace. - op_cache: A dict mapping operation names to `Operation`s. Used to memoize - the results so as to avoid creating redundant operations. - Returns: - A `Tensor` compatible with `tensor`. Any inputs that lead to variable - values will be replaced with a corresponding graph that uses the - variable's initialized values. This is done on a best-effort basis. If no - modifications need to be made then `tensor` will be returned unchanged. - """ - op = tensor.op - new_op = op_cache.get(op.name) - if new_op is None: - new_op = self._safe_initial_value_from_op(op, op_cache) - op_cache[op.name] = new_op - return new_op.outputs[tensor.value_index] - - def _safe_initial_value_from_op(self, op, op_cache): - """Replace dependencies on variables with their initialized values. - - Args: - op: An `Operation`. The operation to replace. - op_cache: A dict mapping operation names to `Operation`s. Used to memoize - the results so as to avoid creating redundant operations. - Returns: - An `Operation` compatible with `op`. Any inputs that lead to variable - values will be replaced with a corresponding graph that uses the - variable's initialized values. This is done on a best-effort basis. If no - modifications need to be made then `op` will be returned unchanged. - """ - op_type = op.node_def.op - if op_type in ("IsVariableInitialized", "VarIsInitializedOp", - "ReadVariableOp"): - return op - - # Attempt to find the initialized_value of any variable reference / handles. - # TODO(b/70206927): Fix handling of ResourceVariables. - if op_type in ("Variable", "VariableV2", "VarHandleOp"): - initialized_value = self._find_initialized_value_for_variable(op) - return op if initialized_value is None else initialized_value.op - - # Recursively build initializer expressions for inputs. - modified = False - new_op_inputs = [] - for op_input in op.inputs: - new_op_input = self._safe_initial_value_from_tensor(op_input, op_cache) - new_op_inputs.append(new_op_input) - modified = modified or (new_op_input != op_input) - - # If at least one input was modified, replace the op. - if modified: - new_op_type = op_type - if new_op_type == "RefSwitch": - new_op_type = "Switch" - new_op_name = op.node_def.name + "_" + self.name - new_op_name = new_op_name.replace(":", "_") - return self.graph.create_op( - new_op_type, new_op_inputs, - op._output_types, # pylint: disable=protected-access - name=new_op_name, attrs=op.node_def.attr) - - return op - - def _find_initialized_value_for_variable(self, variable_op): - """Find the initialized value for a variable op. - - To do so, lookup the variable op in the variables collection. - - Args: - variable_op: A variable `Operation`. - Returns: - A `Tensor` representing the initialized value for the variable or `None` - if the initialized value could not be found. - """ - try: - var_names = [variable_op.node_def.name, variable_op.node_def.name + ":0"] - for collection_name in (ops.GraphKeys.GLOBAL_VARIABLES, - ops.GraphKeys.LOCAL_VARIABLES): - for var in self.graph.get_collection(collection_name): - if var.name in var_names: - return var.initialized_value() - except AttributeError: - # Return None when an incomplete user-defined variable type was put in - # the collection. - return None - return None - # NOTE(mrry): This enables the Variable's overloaded "right" binary # operators to run when the left operand is an ndarray, because it # accords the Variable class higher priority than an ndarray, or a @@ -2441,6 +2306,151 @@ class RefVariable(VariableV1): return self._save_slice_info +def _try_guard_against_uninitialized_dependencies(name, initial_value): + """Attempt to guard against dependencies on uninitialized variables. + + Replace references to variables in `initial_value` with references to the + variable's initialized values. The initialized values are essentially + conditional TensorFlow graphs that return a variable's value if it is + initialized or its `initial_value` if it hasn't been initialized. This + replacement is done on a best effort basis: + + - If the `initial_value` graph contains cycles, we don't do any + replacements for that graph. + - If the variables that `initial_value` depends on are not present in the + `GLOBAL_VARIABLES` or `LOCAL_VARIABLES` we don't replace them. + + In these cases, it is up to the caller to ensure that the `initial_value` + graph uses initialized variables or that they guard access to variables + using their `initialized_value` method. + + Args: + name: Variable name. + initial_value: `Tensor`. The initial value. + Returns: + A `Tensor` suitable to initialize a variable. + Raises: + TypeError: If `initial_value` is not a `Tensor`. + """ + if not isinstance(initial_value, ops.Tensor): + raise TypeError("initial_value needs to be a Tensor: %s" % initial_value) + + # Don't modify initial_value if it contains any cyclic dependencies. + if _has_cycle(initial_value.op, path=set()): + return initial_value + return _safe_initial_value_from_tensor(name, initial_value, op_cache={}) + + +def _has_cycle(op, path): + """Detect cycles in the dependencies of `initial_value`.""" + if op.name in path: + return True + path.add(op.name) + for op_input in op.inputs: + if _has_cycle(op_input.op, path): + return True + for op_control_input in op.control_inputs: + if _has_cycle(op_control_input, path): + return True + path.remove(op.name) + return False + + +def _safe_initial_value_from_tensor(name, tensor, op_cache): + """Replace dependencies on variables with their initialized values. + + Args: + name: Variable name. + tensor: A `Tensor`. The tensor to replace. + op_cache: A dict mapping operation names to `Operation`s. Used to memoize + the results so as to avoid creating redundant operations. + Returns: + A `Tensor` compatible with `tensor`. Any inputs that lead to variable + values will be replaced with a corresponding graph that uses the + variable's initialized values. This is done on a best-effort basis. If no + modifications need to be made then `tensor` will be returned unchanged. + """ + op = tensor.op + new_op = op_cache.get(op.name) + if new_op is None: + new_op = _safe_initial_value_from_op(name, op, op_cache) + op_cache[op.name] = new_op + return new_op.outputs[tensor.value_index] + + +def _safe_initial_value_from_op(name, op, op_cache): + """Replace dependencies on variables with their initialized values. + + Args: + name: Variable name. + op: An `Operation`. The operation to replace. + op_cache: A dict mapping operation names to `Operation`s. Used to memoize + the results so as to avoid creating redundant operations. + Returns: + An `Operation` compatible with `op`. Any inputs that lead to variable + values will be replaced with a corresponding graph that uses the + variable's initialized values. This is done on a best-effort basis. If no + modifications need to be made then `op` will be returned unchanged. + """ + op_type = op.node_def.op + if op_type in ("IsVariableInitialized", "VarIsInitializedOp", + "ReadVariableOp"): + return op + + # Attempt to find the initialized_value of any variable reference / handles. + # TODO(b/70206927): Fix handling of ResourceVariables. + if op_type in ("Variable", "VariableV2", "VarHandleOp"): + initialized_value = _find_initialized_value_for_variable(op) + return op if initialized_value is None else initialized_value.op + + # Recursively build initializer expressions for inputs. + modified = False + new_op_inputs = [] + for op_input in op.inputs: + new_op_input = _safe_initial_value_from_tensor(name, op_input, op_cache) + new_op_inputs.append(new_op_input) + modified = modified or (new_op_input != op_input) + + # If at least one input was modified, replace the op. + if modified: + new_op_type = op_type + if new_op_type == "RefSwitch": + new_op_type = "Switch" + new_op_name = op.node_def.name + "_" + name + new_op_name = new_op_name.replace(":", "_") + return op.graph.create_op( + new_op_type, new_op_inputs, + op._output_types, # pylint: disable=protected-access + name=new_op_name, attrs=op.node_def.attr) + + return op + + +def _find_initialized_value_for_variable(variable_op): + """Find the initialized value for a variable op. + + To do so, lookup the variable op in the variables collection. + + Args: + variable_op: A variable `Operation`. + Returns: + A `Tensor` representing the initialized value for the variable or `None` + if the initialized value could not be found. + """ + try: + var_names = [variable_op.node_def.name, variable_op.node_def.name + ":0"] + for collection_name in (ops.GraphKeys.GLOBAL_VARIABLES, + ops.GraphKeys.LOCAL_VARIABLES): + for var in variable_op.graph.get_collection(collection_name): + if var.name in var_names: + return var.initialized_value() + except AttributeError: + # Return None when an incomplete user-defined variable type was put in + # the collection. + return None + return None + + class PartitionedVariable(object): """A container for partitioned `Variable` objects. @@ -2659,6 +2669,15 @@ class PartitionedVariable(object): return assign_list return [assign.op for assign in assign_list] + +# Register a conversion function which reads the value of the variable, +# allowing instances of the class to be used as tensors. +ops.register_tensor_conversion_function( + RefVariable, + RefVariable._TensorConversionFunction) # pylint: disable=protected-access +ops.register_dense_tensor_like_type(RefVariable) + + @tf_export(v1=["global_variables"]) def global_variables(scope=None): """Returns global variables. @@ -2982,12 +3001,7 @@ def report_uninitialized_variables(var_list=None, # uninitialized variables. return array_ops.boolean_mask(variable_names_tensor, variables_mask) -# pylint: disable=protected-access -Variable._OverloadAllOperators() ops.register_tensor_conversion_function( - PartitionedVariable, PartitionedVariable._TensorConversionFunction) -# pylint: enable=protected-access - - -ops.register_dense_tensor_like_type(Variable) + PartitionedVariable, + PartitionedVariable._TensorConversionFunction) # pylint: disable=protected-access -- GitLab From 2a3d6401a3f21e4c9d043d90da48444d6e24e057 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 10:09:12 -0800 Subject: [PATCH 0187/2345] Remove NHWC format restriction while using batch_group_count for convolutions of depthwise backprop filters PiperOrigin-RevId: 227873314 --- tensorflow/compiler/tf2xla/kernels/conv_op_helpers.cc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/conv_op_helpers.cc b/tensorflow/compiler/tf2xla/kernels/conv_op_helpers.cc index b0bc764030..6b049f653c 100644 --- a/tensorflow/compiler/tf2xla/kernels/conv_op_helpers.cc +++ b/tensorflow/compiler/tf2xla/kernels/conv_op_helpers.cc @@ -428,11 +428,8 @@ xla::StatusOr MakeXlaBackpropFilterConvOp( int n_dim = GetTensorBatchDimIndex(num_dims, attrs.data_format); int c_dim = GetTensorFeatureDimIndex(num_dims, attrs.data_format); - // The conversion logic below assumes that the data format is NHWC, so we also - // check that here. bool use_batch_group_count = - filter_tensor_shape.dim_size(num_dims - 1) == 1 && attrs.depthwise && - attrs.data_format == FORMAT_NHWC; + filter_tensor_shape.dim_size(num_dims - 1) == 1 && attrs.depthwise; std::vector> padding(attrs.num_spatial_dims); std::vector rhs_dilation(attrs.num_spatial_dims); -- GitLab From 07816cc1e13c02d7ad579d1279a5fca883126c35 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 10:09:49 -0800 Subject: [PATCH 0188/2345] Fix markdown rendering. PiperOrigin-RevId: 227873426 --- tensorflow/python/ops/variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/ops/variables.py b/tensorflow/python/ops/variables.py index a4967e1c0f..f9fc72a6da 100644 --- a/tensorflow/python/ops/variables.py +++ b/tensorflow/python/ops/variables.py @@ -306,6 +306,7 @@ class Variable(six.with_metaclass(VariableMetaclass, Here replacing adding `use_resource=True` when constructing the variable will fix any nondeterminism issues: + ``` v = tf.Variable(True, use_resource=True) tf.cond(v, lambda: v.assign(False), my_false_fn) -- GitLab From daa75aff18fd42598d1fb68f13da13042e886c07 Mon Sep 17 00:00:00 2001 From: Saurabh Saxena Date: Fri, 4 Jan 2019 10:17:31 -0800 Subject: [PATCH 0189/2345] Allows TensorListScatter to scatter at non-contiguous indices to make it consistent with TensorArray.scatter. PiperOrigin-RevId: 227874653 --- tensorflow/core/kernels/list_kernels.h | 31 ++++++++++---- .../python/kernel_tests/list_ops_test.py | 41 +++++++++++++++++++ .../kernel_tests/tensor_array_ops_test.py | 5 +-- tensorflow/python/ops/list_ops.py | 14 +++++-- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/tensorflow/core/kernels/list_kernels.h b/tensorflow/core/kernels/list_kernels.h index 99b4f42084..559e14b03c 100644 --- a/tensorflow/core/kernels/list_kernels.h +++ b/tensorflow/core/kernels/list_kernels.h @@ -521,14 +521,31 @@ class TensorListScatter : public OpKernel { "Specified a list with shape ", element_shape.DebugString(), " from a tensor with shape ", output_shape.DebugString())); output_list.element_shape = element_shape; - output_list.tensors.reserve(indices.NumElements()); + + OP_REQUIRES(c, indices.NumElements() == input_tensor.shape().dim_size(0), + errors::InvalidArgument( + "Invalid number of rows in input tensor. Expected: ", + indices.NumElements(), + " Actual: ", input_tensor.shape().dim_size(0))); + + // Validate indices and resize output_list.tensors to fit the highest index. + { + size_t list_size = 0; + for (int index = 0; index < indices.NumElements(); ++index) { + const int i = indices.flat()(index); + OP_REQUIRES(c, i >= 0, + errors::InvalidArgument( + "Indices in TensorListScatter must all be positive.")); + if (i >= list_size) { + list_size = i + 1; + } + } + output_list.tensors.resize(list_size, Tensor(DT_INVALID)); + } + for (int index = 0; index < indices.NumElements(); ++index) { const int i = indices.flat()(index); - OP_REQUIRES(c, i < input_tensor.shape().dim_size(0), - errors::InvalidArgument( - "Trying to scatter index ", i, " from tensor with ", - input_tensor.shape().dim_size(0), " rows.")); - Tensor tmp = input_tensor.Slice(i, i + 1); + Tensor tmp = input_tensor.Slice(index, index + 1); TensorShape tmp_shape = tmp.shape(); tmp_shape.RemoveDim(0); OP_REQUIRES(c, tmp.CopyFrom(tmp, tmp_shape), @@ -541,7 +558,7 @@ class TensorListScatter : public OpKernel { // many small ondes. aligned.flat().device(c->eigen_device()) = tmp.unaligned_flat(); - output_list.tensors.push_back(aligned); + std::swap(output_list.tensors[i], aligned); } output_tensor->scalar()() = std::move(output_list); } diff --git a/tensorflow/python/kernel_tests/list_ops_test.py b/tensorflow/python/kernel_tests/list_ops_test.py index 054ac36755..fc0e270667 100644 --- a/tensorflow/python/kernel_tests/list_ops_test.py +++ b/tensorflow/python/kernel_tests/list_ops_test.py @@ -290,6 +290,47 @@ class ListOpsTest(test_util.TensorFlowTestCase, parameterized.TestCase): t = list_ops.tensor_list_gather(l, [], element_dtype=dtypes.float32) self.evaluate(t) + def testGatherGradWithNonContiguousIndices(self): + with backprop.GradientTape(persistent=True) as tape: + t = constant_op.constant([1.0, 2.0, 3.0]) + l = list_ops.tensor_list_from_tensor(t, element_shape=[]) + c = constant_op.constant(5.0) + tape.watch(c) + l = list_ops.tensor_list_set_item(l, 1, c) + t = list_ops.tensor_list_gather(l, [1], element_dtype=dtypes.float32) + self.assertAllEqual(self.evaluate(t), [5.0]) + s = t[0] * t[0] + dt = tape.gradient(s, c) + self.assertAllEqual(self.evaluate(dt), 10.0) + dl = tape.gradient(t, l) + dl_length = list_ops.tensor_list_length(dl) + self.assertAllEqual(self.evaluate(dl_length), 3) + + def testScatterOutputListSize(self): + c0 = constant_op.constant([1.0, 2.0]) + l = list_ops.tensor_list_scatter( + c0, [1, 3], ops.convert_to_tensor([], dtype=dtypes.int32)) + # TensorListScatter should return a list with size largest index + 1. + self.assertEqual(self.evaluate(list_ops.tensor_list_length(l)), 4) + + def testScatterWithInvalidRowsInInputTensorFails(self): + c0 = constant_op.constant([1.0, 2.0]) + with self.assertRaisesRegexp( + errors.InvalidArgumentError, + "Invalid number of rows in input tensor. Expected: 3 Actual: 2"): + l = list_ops.tensor_list_scatter( + c0, [1, 0, 2], ops.convert_to_tensor([], dtype=dtypes.int32)) + self.evaluate(l) + + def testScatterWithNegativeIndicesFails(self): + c0 = constant_op.constant([1.0, 2.0]) + with self.assertRaisesRegexp( + errors.InvalidArgumentError, + "Indices in TensorListScatter must all be positive."): + l = list_ops.tensor_list_scatter( + c0, [-1, -2], ops.convert_to_tensor([], dtype=dtypes.int32)) + self.evaluate(l) + def testScatterGrad(self): with backprop.GradientTape() as tape: c0 = constant_op.constant([1.0, 2.0]) diff --git a/tensorflow/python/kernel_tests/tensor_array_ops_test.py b/tensorflow/python/kernel_tests/tensor_array_ops_test.py index 9fb5791048..303e9a006a 100644 --- a/tensorflow/python/kernel_tests/tensor_array_ops_test.py +++ b/tensorflow/python/kernel_tests/tensor_array_ops_test.py @@ -1359,7 +1359,6 @@ class TensorArrayTest(test.TestCase): def testSkipEagerTensorArrayEvalEmptyWithDefault(self): self._testTensorArrayEvalEmptyWithDefault() - @test_util.disable_control_flow_v2("b/117943286") @test_util.run_v1_only("b/117943489") def testSkipEagerTensorArrayScatterReadAndGradients(self): with self.session(use_gpu=True) as session: @@ -1387,8 +1386,8 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual([10.0, -10.0], read_vals[1]) self.assertAllEqual([[2.0, 3.0], [4.0, 5.0]], grad_vals[0]) - @test_util.disable_control_flow_v2("b/117943286") - @test_util.run_v1_only("b/117943286") + @test_util.disable_control_flow_v2("b/118890905") + @test_util.run_v1_only("b/118890905") def testTensorArrayWriteGatherAndGradients(self): with self.session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( diff --git a/tensorflow/python/ops/list_ops.py b/tensorflow/python/ops/list_ops.py index 7bb9ca89e0..df928ea85d 100644 --- a/tensorflow/python/ops/list_ops.py +++ b/tensorflow/python/ops/list_ops.py @@ -200,10 +200,16 @@ def _TensorListResizeGrad(op, dlist): @ops.RegisterGradient("TensorListGather") def _TensorListGatherGrad(op, dtensor): - _, indices = op.inputs - return gen_list_ops.tensor_list_scatter( - tensor=dtensor, indices=indices, - element_shape=ops.convert_to_tensor(-1, dtype=dtypes.int32)), None + input_list, indices = op.inputs + dlist = gen_list_ops.tensor_list_scatter( + tensor=dtensor, + indices=indices, + element_shape=ops.convert_to_tensor(-1, dtype=dtypes.int32)) + # TensorListScatter returns a list with size `max(indices) + 1` + # so we manually resize it to match the size of the input list. + input_list_size = gen_list_ops.tensor_list_length(input_list) + dlist = gen_list_ops.tensor_list_resize(dlist, input_list_size) + return dlist, None @ops.RegisterGradient("TensorListScatter") -- GitLab From d183818d4d5e85a8f469402d273f8f03fa21f4b6 Mon Sep 17 00:00:00 2001 From: Scott Zhu Date: Fri, 4 Jan 2019 10:24:02 -0800 Subject: [PATCH 0190/2345] Update the math formula for LogLoss to match the parameters. PiperOrigin-RevId: 227875597 --- tensorflow/python/keras/losses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index e6db795bb8..6a428b28a9 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -503,7 +503,7 @@ class CategoricalHinge(Loss): class LogLoss(Loss): """Computes the log loss between `y_true` and `y_pred`. - logloss = -y log(p) - (1-y) log(1-p) + logloss = - y_true * log(y_pred) - (1 - y_true) * log(1 - y_pred) Usage: -- GitLab From 59d5fbcc1a5e677387aac2bbf919faddf0e43377 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Fri, 4 Jan 2019 10:36:25 -0800 Subject: [PATCH 0191/2345] Automated rollback of commit 64596a68235709e7f3246d0b120a4146521a998b PiperOrigin-RevId: 227877471 --- tensorflow/lite/experimental/swift/BUILD | 100 +++++ tensorflow/lite/experimental/swift/LICENSE | 202 ++++++++++ tensorflow/lite/experimental/swift/README.md | 54 +++ .../swift/Sources/Interpreter.swift | 265 ++++++++++++++ .../swift/Sources/InterpreterError.swift | 99 +++++ .../swift/Sources/InterpreterOptions.swift | 29 ++ .../experimental/swift/Sources/Model.swift | 40 ++ .../Sources/QuantizationParameters.swift | 38 ++ .../experimental/swift/Sources/Tensor.swift | 138 +++++++ .../Configs/TensorFlowLite.tulsigen | 57 +++ .../project.tulsiconf | 14 + .../project.pbxproj | 345 ++++++++++++++++++ .../TensorFlowLiteApp/AppDelegate.swift | 24 ++ .../Array+TensorFlowLite.swift | 22 ++ .../AppIcon.appiconset/Contents.json | 98 +++++ .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 44 +++ .../Base.lproj/Main.storyboard | 95 +++++ .../Data+TensorFlowLite.swift | 13 + .../TensorFlowLiteApp/Info.plist | 46 +++ .../TensorFlowLiteApp/ViewController.swift | 299 +++++++++++++++ .../swift/Tests/InterpreterOptionsTests.swift | 54 +++ .../swift/Tests/InterpreterTests.swift | 315 ++++++++++++++++ .../experimental/swift/Tests/ModelTests.swift | 59 +++ .../Tests/QuantizationParametersTests.swift | 43 +++ .../swift/Tests/TensorTests.swift | 83 +++++ .../tools/pip_package/pip_smoke_test.py | 36 +- 27 files changed, 2603 insertions(+), 15 deletions(-) create mode 100644 tensorflow/lite/experimental/swift/BUILD create mode 100644 tensorflow/lite/experimental/swift/LICENSE create mode 100644 tensorflow/lite/experimental/swift/README.md create mode 100644 tensorflow/lite/experimental/swift/Sources/Interpreter.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/InterpreterError.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/Model.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift create mode 100644 tensorflow/lite/experimental/swift/Sources/Tensor.swift create mode 100644 tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen create mode 100644 tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist create mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/ModelTests.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift create mode 100644 tensorflow/lite/experimental/swift/Tests/TensorTests.swift diff --git a/tensorflow/lite/experimental/swift/BUILD b/tensorflow/lite/experimental/swift/BUILD new file mode 100644 index 0000000000..3bd288a1e1 --- /dev/null +++ b/tensorflow/lite/experimental/swift/BUILD @@ -0,0 +1,100 @@ +# TensorFlow Lite for Swift. + +package(default_visibility = ["//visibility:private"]) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_unit_test") +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +MINIMUM_OS_VERSION = "9.0" + +SWIFT_COPTS = [ + "-wmo", +] + +swift_library( + name = "TensorFlowLite", + srcs = glob(["Sources/*.swift"]), + copts = SWIFT_COPTS, + module_name = "TensorFlowLite", + tags = ["manual"], + deps = [ + "//tensorflow/lite/experimental/c:c_api", + ], +) + +ios_unit_test( + name = "TensorFlowLiteTests", + size = "small", + minimum_os_version = MINIMUM_OS_VERSION, + tags = [ + "manual", + # DISABLED: Following sanitizer tests are not supported by iOS test targets. + "noasan", + "nomsan", + "notsan", + ], + deps = [":TensorFlowLiteTestsLib"], +) + +swift_library( + name = "TensorFlowLiteTestsLib", + testonly = 1, + srcs = glob(["Tests/*.swift"]), + copts = SWIFT_COPTS, + tags = ["manual"], + deps = [ + ":TensorFlowLite", + ":TestResources", + ], +) + +objc_library( + name = "TestResources", + resources = [ + "//tensorflow/lite:testdata/add.bin", + "//tensorflow/lite:testdata/add_quantized.bin", + "//tensorflow/lite:testdata/multi_add.bin", + ], + tags = ["manual"], +) + +ios_application( + name = "TensorFlowLiteApp", + app_icons = glob(["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/**"]), + bundle_id = "com.tensorflow.lite.swift.TensorFlowLite", + families = [ + "ipad", + "iphone", + ], + infoplists = ["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist"], + launch_storyboard = "TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard", + minimum_os_version = MINIMUM_OS_VERSION, + sdk_frameworks = [ + "CoreGraphics", + ], + tags = ["manual"], + deps = [":TensorFlowLiteAppLib"], +) + +swift_library( + name = "TensorFlowLiteAppLib", + srcs = glob(["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/*.swift"]), + tags = ["manual"], + deps = [ + ":TensorFlowLite", + ":TensorFlowLiteAppResources", + ], +) + +objc_library( + name = "TensorFlowLiteAppResources", + storyboards = glob([ + "TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/*.storyboard", + ]), + tags = ["manual"], + deps = [":TestResources"], +) diff --git a/tensorflow/lite/experimental/swift/LICENSE b/tensorflow/lite/experimental/swift/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/tensorflow/lite/experimental/swift/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tensorflow/lite/experimental/swift/README.md b/tensorflow/lite/experimental/swift/README.md new file mode 100644 index 0000000000..deb16c1e91 --- /dev/null +++ b/tensorflow/lite/experimental/swift/README.md @@ -0,0 +1,54 @@ +# TensorFlow Lite for Swift + +[TensorFlow Lite](https://www.tensorflow.org/lite/) is TensorFlow's lightweight +solution for Swift developers. It enables low-latency inference of on-device +machine learning models with a small binary size and fast performance supporting +hardware acceleration. + +## Getting Started + +### Bazel + +In your `BUILD` file, add the `TensorFlowLite` dependency: + +```python +swift_library( + # ... + deps = [ + "//tensorflow/lite/swift:TensorFlowLite", + ], +) +``` + +In your Swift files, import the module: + +```swift +import TensorFlowLite +``` + +### Tulsi + +Open the `TensorFlowLite.tulsiproj` using the [TulsiApp](https://github.com/bazelbuild/tulsi) or by +running the [`generate_xcodeproj.sh`](https://github.com/bazelbuild/tulsi/blob/master/src/tools/generate_xcodeproj.sh) +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 +``` diff --git a/tensorflow/lite/experimental/swift/Sources/Interpreter.swift b/tensorflow/lite/experimental/swift/Sources/Interpreter.swift new file mode 100644 index 0000000000..a14b5966b1 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/Interpreter.swift @@ -0,0 +1,265 @@ +// Copyright 2018 Google Inc. 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 Foundation +import TensorFlowLiteCAPI + +/// A TensorFlow Lite interpreter that performs inference from a given model. +public final class Interpreter { + + /// The `TFL_Interpreter` C pointer type represented as an `UnsafePointer`. + private typealias CInterpreter = OpaquePointer + + /// Total number of input tensors associated with the model. + public var inputTensorCount: Int { + return Int(TFL_InterpreterGetInputTensorCount(cInterpreter)) + } + + /// Total number of output tensors associated with the model. + public var outputTensorCount: Int { + return Int(TFL_InterpreterGetOutputTensorCount(cInterpreter)) + } + + /// The underlying `TFL_Interpreter` C pointer. + private var cInterpreter: CInterpreter? + + /// Creates a new model interpreter instance. + /// + /// - Parameters: + /// - modelPath: Local file path to a TensorFlow Lite model. + /// - options: Custom configurations for the interpreter. The default is `nil` indicating that + /// interpreter will determine the configuration options. + /// - Throws: An error if the model could not be loaded or the interpreter could not be created. + public init(modelPath: String, options: InterpreterOptions? = nil) throws { + guard let model = Model(filePath: modelPath) else { throw InterpreterError.failedToLoadModel } + + let cInterpreterOptions: OpaquePointer? = try options.map { options in + guard let cOptions = TFL_NewInterpreterOptions() else { + throw InterpreterError.failedToCreateInterpreter + } + if let threadCount = options.threadCount, threadCount > 0 { + TFL_InterpreterOptionsSetNumThreads(cOptions, Int32(threadCount)) + } + if options.isErrorLoggingEnabled { + TFL_InterpreterOptionsSetErrorReporter( + cOptions, + { (_, format, arguments) in + guard let cFormat = format, + let message = String(cFormat: cFormat, arguments: arguments) + else { + return + } + print(String(describing: InterpreterError.tensorFlowLiteError(message))) + }, + nil + ) + } + return cOptions + } + defer { TFL_DeleteInterpreterOptions(cInterpreterOptions) } + + guard let cInterpreter = TFL_NewInterpreter(model.cModel, cInterpreterOptions) else { + throw InterpreterError.failedToCreateInterpreter + } + self.cInterpreter = cInterpreter + } + + deinit { + TFL_DeleteInterpreter(cInterpreter) + } + + /// Invokes the interpreter to perform inference from the loaded graph. + /// + /// - Throws: An error if the model was not ready because tensors were not allocated. + public func invoke() throws { + guard TFL_InterpreterInvoke(cInterpreter) == kTfLiteOk else { + // TODO(b/117510052): Determine which error to throw. + throw InterpreterError.allocateTensorsRequired + } + } + + /// Returns the input tensor at the given index. + /// + /// - Parameters: + /// - index: The index for the input tensor. + /// - Throws: An error if the index is invalid or the tensors have not been allocated. + /// - Returns: The input tensor at the given index. + public func input(at index: Int) throws -> Tensor { + let maxIndex = inputTensorCount - 1 + guard case 0...maxIndex = index else { + throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) + } + guard let cTensor = TFL_InterpreterGetInputTensor(cInterpreter, Int32(index)), + let bytes = TFL_TensorData(cTensor), + let nameCString = TFL_TensorName(cTensor) + else { + throw InterpreterError.allocateTensorsRequired + } + guard let dataType = TensorDataType(type: TFL_TensorType(cTensor)) else { + throw InterpreterError.invalidTensorDataType + } + + let name = String(cString: nameCString) + let rank = TFL_TensorNumDims(cTensor) + let dimensions = (0.. Tensor { + let maxIndex = outputTensorCount - 1 + guard case 0...maxIndex = index else { + throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) + } + guard let cTensor = TFL_InterpreterGetOutputTensor(cInterpreter, Int32(index)), + let bytes = TFL_TensorData(cTensor), + let nameCString = TFL_TensorName(cTensor) + else { + // TODO(b/117510052): Determine which error to throw. + throw InterpreterError.invokeInterpreterRequired + } + guard let dataType = TensorDataType(type: TFL_TensorType(cTensor)) else { + throw InterpreterError.invalidTensorDataType + } + + let name = String(cString: nameCString) + let rank = TFL_TensorNumDims(cTensor) + let dimensions = (0.. Tensor { + let maxIndex = inputTensorCount - 1 + guard case 0...maxIndex = index else { + throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) + } + guard let cTensor = TFL_InterpreterGetInputTensor(cInterpreter, Int32(index)) else { + throw InterpreterError.allocateTensorsRequired + } + + let byteCount = TFL_TensorByteSize(cTensor) + guard data.count == byteCount else { + throw InterpreterError.invalidTensorDataCount(provided: data.count, required: byteCount) + } + + let status = data.withUnsafeBytes { TFL_TensorCopyFromBuffer(cTensor, $0, data.count) } + guard status == kTfLiteOk else { throw InterpreterError.failedToCopyDataToInputTensor } + return try input(at: index) + } + + /// Allocates memory for all input tensors based on their `TensorShape`s. + /// + /// - Note: This is a relatively expensive operation and should only be called after creating the + /// interpreter and/or resizing any input tensors. + /// - Throws: An error if memory could not be allocated for the input tensors. + public func allocateTensors() throws { + guard TFL_InterpreterAllocateTensors(cInterpreter) == kTfLiteOk else { + throw InterpreterError.failedToAllocateTensors + } + } +} + +// MARK: - Extensions + +extension String { + /// Returns a new `String` initialized by using the given format C array as a template into which + /// the remaining argument values are substituted according to the user’s default locale. + /// + /// - Note: Returns `nil` if a new `String` could not be constructed from the given values. + /// - Parameters: + /// - cFormat: The format C array as a template for substituting values. + /// - arguments: A C pointer to a `va_list` of arguments to substitute into `cFormat`. + init?(cFormat: UnsafePointer, arguments: CVaListPointer) { + var buffer: UnsafeMutablePointer? + guard vasprintf(&buffer, cFormat, arguments) != 0, let cString = buffer else { return nil } + self.init(validatingUTF8: cString) + } +} diff --git a/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift b/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift new file mode 100644 index 0000000000..5de58b997a --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift @@ -0,0 +1,99 @@ +// Copyright 2018 Google Inc. 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 Foundation + +/// TensorFlow Lite interpreter errors. +public enum InterpreterError: Error { + case invalidTensorIndex(index: Int, maxIndex: Int) + case invalidTensorDataCount(provided: Int, required: Int) + case invalidTensorDataType + case failedToLoadModel + case failedToCreateInterpreter + case failedToResizeInputTensor(index: Int) + case failedToCopyDataToInputTensor + case failedToAllocateTensors + case allocateTensorsRequired + case invokeInterpreterRequired + case tensorFlowLiteError(String) +} + +// MARK: - Extensions + +extension InterpreterError: LocalizedError { + /// Localized description of the interpreter error. + public var errorDescription: String? { + switch self { + case .invalidTensorIndex(let index, let maxIndex): + return "Invalid tensor index \(index), max index is \(maxIndex)." + case .invalidTensorDataCount(let providedCount, let requiredCount): + return "Provided data count \(providedCount) must match the required count \(requiredCount)." + case .invalidTensorDataType: + return "Tensor data type is unsupported or could not be determined because of a model error." + case .failedToLoadModel: + return "Failed to load the given model." + case .failedToCreateInterpreter: + return "Failed to create the interpreter." + case .failedToResizeInputTensor(let index): + return "Failed to resize input tesnor at index \(index)." + case .failedToCopyDataToInputTensor: + return "Failed to copy data to input tensor." + case .failedToAllocateTensors: + return "Failed to allocate memory for input tensors." + case .allocateTensorsRequired: + return "Must call allocateTensors()." + case .invokeInterpreterRequired: + return "Must call invoke()." + case .tensorFlowLiteError(let message): + return "TensorFlow Lite Error: \(message)" + } + } +} + +extension InterpreterError: CustomStringConvertible { + /// Textual representation of the TensorFlow Lite interpreter error. + public var description: String { + return errorDescription ?? "Unknown error." + } +} + +#if swift(>=4.2) +extension InterpreterError: Equatable {} +#else +extension InterpreterError: Equatable { + public static func == (lhs: InterpreterError, rhs: InterpreterError) -> Bool { + switch (lhs, rhs) { + case (.invalidTensorDataType, .invalidTensorDataType), + (.failedToLoadModel, .failedToLoadModel), + (.failedToCreateInterpreter, .failedToCreateInterpreter), + (.failedToAllocateTensors, .failedToAllocateTensors), + (.allocateTensorsRequired, .allocateTensorsRequired), + (.invokeInterpreterRequired, .invokeInterpreterRequired): + return true + case (.invalidTensorIndex(let lhsIndex, let lhsMaxIndex), + .invalidTensorIndex(let rhsIndex, let rhsMaxIndex)): + return lhsIndex == rhsIndex && lhsMaxIndex == rhsMaxIndex + case (.invalidTensorDataCount(let lhsProvidedCount, let lhsRequiredCount), + .invalidTensorDataCount(let rhsProvidedCount, let rhsRequiredCount)): + return lhsProvidedCount == rhsProvidedCount && lhsRequiredCount == rhsRequiredCount + case (.failedToResizeInputTensor(let lhsIndex), .failedToResizeInputTensor(let rhsIndex)): + return lhsIndex == rhsIndex + case (.tensorFlowLiteError(let lhsMessage), .tensorFlowLiteError(let rhsMessage)): + return lhsMessage == rhsMessage + default: + return false + } + } +} +#endif // swift(>=4.2) diff --git a/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift b/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift new file mode 100644 index 0000000000..2365fd7ade --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift @@ -0,0 +1,29 @@ +// Copyright 2018 Google Inc. 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 Foundation + +/// Custom configuration options for a TensorFlow Lite interpreter. +public struct InterpreterOptions: Equatable { + + /// Maximum number of CPU threads that the interpreter should run on. Default is `nil` which + /// indicates that the `Interpreter` will decide the number of threads to use. + public var threadCount: Int? = nil + + /// Whether error logging to the console is enabled. The default is `false`. + public var isErrorLoggingEnabled = false + + /// Creates a new instance of interpreter options. + public init() {} +} diff --git a/tensorflow/lite/experimental/swift/Sources/Model.swift b/tensorflow/lite/experimental/swift/Sources/Model.swift new file mode 100644 index 0000000000..e8c49ff1ae --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/Model.swift @@ -0,0 +1,40 @@ +// Copyright 2018 Google Inc. 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 Foundation +import TensorFlowLiteCAPI + +/// A TensorFlow Lite model used by the 'Interpreter` to perform inference. +final class Model { + + /// The `TFL_Model` C pointer type represented as an `UnsafePointer`. + typealias CModel = OpaquePointer + + /// The underlying `TFL_Model` C pointer. + let cModel: CModel? + + /// Creates a new model instance. + /// + /// - Precondition: Initialization can fail if the given `filePath` is invalid. + /// - Parameters: + /// - filePath: Local file path to a TensorFlow Lite model. + init?(filePath: String) { + guard !filePath.isEmpty, let cModel = TFL_NewModelFromFile(filePath) else { return nil } + self.cModel = cModel + } + + deinit { + TFL_DeleteModel(cModel) + } +} diff --git a/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift b/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift new file mode 100644 index 0000000000..f367875644 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift @@ -0,0 +1,38 @@ +// Copyright 2018 Google Inc. 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 Foundation + +/// Parameters that determine the mapping of quantized values to real values. Quantized values can +/// be mapped to float values using the following conversion: +/// `realValue = scale * (quantizedValue - zeroPoint)`. +public struct QuantizationParameters { + + /// Difference between real values corresponding to consecutive quantized values differing by 1. + /// For example, the range of quantized values for `UInt8` data type is [0, 255]. + public let scale: Float + + /// Quantized value that corresponds to the real 0 value. + public let zeroPoint: Int + + /// Creates a new quantization parameters instance. + /// + /// - Parameters: + /// - scale: Scale value for asymmetric quantization. + /// - zeroPoint: Zero point for asymmetric quantization. + init(scale: Float, zeroPoint: Int) { + self.scale = scale + self.zeroPoint = zeroPoint + } +} diff --git a/tensorflow/lite/experimental/swift/Sources/Tensor.swift b/tensorflow/lite/experimental/swift/Sources/Tensor.swift new file mode 100644 index 0000000000..b738d87549 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Sources/Tensor.swift @@ -0,0 +1,138 @@ +// Copyright 2018 Google Inc. 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 Foundation +import TensorFlowLiteCAPI + +/// An input or output tensor in a TensorFlow Lite graph. +public struct Tensor { + + /// Name of the tensor. + public let name: String + + /// Data type of the tensor. + public let dataType: TensorDataType + + /// Shape of the tensor. + public let shape: TensorShape + + /// Data in the input or output tensor. + public let data: Data + + /// Quantization parameters for the tensor if using a quantized model. + public let quantizationParameters: QuantizationParameters? + + /// Creates a new input or output tensor instance. + /// + /// - Parameters: + /// - name: Name of the tensor. + /// - dataType: Data type of the tensor. + /// - data: Data in the input tensor. + /// - quantizationParameters Quantization parameters for the tensor if using a quantized model. + /// The default is `nil`. + init( + name: String, + dataType: TensorDataType, + shape: TensorShape, + data: Data, + quantizationParameters: QuantizationParameters? = nil + ) { + self.name = name + self.dataType = dataType + self.shape = shape + self.data = data + self.quantizationParameters = quantizationParameters + } +} + +/// Supported TensorFlow Lite tensor data types. +public enum TensorDataType: Equatable { + /// 32-bit single precision floating point tensor data type. + case float32 + /// 8-bit unsigned integer tensor data type. + case uInt8 + /// 16-bit signed integer tensor data type. + case int16 + /// 32-bit signed integer tensor data type. + case int32 + /// 64-bit signed integer tensor data type. + case int64 + /// Boolean tensor data type. + case bool + + /// Creates a new tensor data type from the given `TFL_Type` or `nil` if the data type is + /// unsupported or could not be determined because there was an error. + /// + /// - Parameter type: A data type supported by a tensor. + init?(type: TFL_Type) { + switch type { + case kTfLiteFloat32: + self = .float32 + case kTfLiteUInt8: + self = .uInt8 + case kTfLiteInt16: + self = .int16 + case kTfLiteInt32: + self = .int32 + case kTfLiteInt64: + self = .int64 + case kTfLiteBool: + self = .bool + case kTfLiteNoType: + fallthrough + default: + return nil + } + } +} + +/// The shape of a TensorFlow Lite tensor. +public struct TensorShape { + + /// The number of dimensions of the tensor. + public let rank: Int + + /// Array of dimensions for the tensor. + public let dimensions: [Int] + + /// Array of `Int32` dimensions for the tensor. + var int32Dimensions: [Int32] { return dimensions.map(Int32.init) } + + /// Creates a new tensor shape instance with the given array of dimensions. + /// + /// - Parameters: + /// - dimensions: Dimensions for the tensor. + public init(_ dimensions: [Int]) { + self.rank = dimensions.count + self.dimensions = dimensions + } + + /// Creates a new tensor shape instance with the given elements representing the dimensions. + /// + /// - Parameters: + /// - elements: Dimensions for the tensor. + public init(_ elements: Int...) { + self.init(elements) + } +} + +extension TensorShape: ExpressibleByArrayLiteral { + /// Creates a new tensor shape instance with the given array literal representing the dimensions. + /// + /// - Parameters: + /// - arrayLiteral: Dimensions for the tensor. + public init(arrayLiteral: Int...) { + self.init(arrayLiteral) + } +} diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen new file mode 100644 index 0000000000..4010fab49e --- /dev/null +++ b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen @@ -0,0 +1,57 @@ +{ + "sourceFilters" : [ + "third_party/tensorflow/lite/experimental/c", + "third_party/tensorflow/lite/experimental/swift", + "third_party/tensorflow/lite/experimental/swift/Sources", + "third_party/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp", + "third_party/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj", + "third_party/tensorflow/lite/experimental/swift/Tests", + ], + "buildTargets" : [ + "//third_party/tensorflow/lite/experimental/swift:TensorFlowLite", + "//third_party/tensorflow/lite/experimental/swift:TensorFlowLiteApp", + "//third_party/tensorflow/lite/experimental/swift:TensorFlowLiteTests", + ], + "projectName" : "TensorFlowLite", + "optionSet" : { + "LaunchActionPreActionScript" : { + "p" : "$(inherited)" + }, + "BazelBuildStartupOptionsRelease" : { + "p" : "$(inherited)" + }, + "BazelBuildOptionsRelease" : { + "p" : "$(inherited)" + }, + "BazelBuildOptionsDebug" : { + "p" : "$(inherited)" + }, + "EnvironmentVariables" : { + "p" : "$(inherited)" + }, + "BuildActionPreActionScript" : { + "p" : "$(inherited)" + }, + "CommandlineArguments" : { + "p" : "$(inherited)" + }, + "TestActionPreActionScript" : { + "p" : "$(inherited)" + }, + "BazelBuildStartupOptionsDebug" : { + "p" : "$(inherited)" + }, + "BuildActionPostActionScript" : { + "p" : "$(inherited)" + }, + "TestActionPostActionScript" : { + "p" : "$(inherited)" + }, + "LaunchActionPostActionScript" : { + "p" : "$(inherited)" + } + }, + "additionalFilePaths" : [ + "third_party/tensorflow/lite/experimental/swift/BUILD" + ] +} diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf new file mode 100644 index 0000000000..14cff94453 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf @@ -0,0 +1,14 @@ +{ + "configDefaults" : { + "optionSet" : { + "ProjectPrioritizesSwift" : { + "p" : "YES" + } + } + }, + "projectName" : "TensorFlowLite", + "packages" : [ + "third_party/tensorflow/lite/experimental/swift" + ], + "workspaceRoot" : "../../../../../.." +} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..fbbf9a1de2 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj @@ -0,0 +1,345 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 4A7304B421500B8400C90B21 /* Data+TensorFlowLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */; }; + 4AA72B732146ED64006C3AEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA72B722146ED64006C3AEF /* AppDelegate.swift */; }; + 4AA72B752146ED64006C3AEF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA72B742146ED64006C3AEF /* ViewController.swift */; }; + 4AA72B782146ED64006C3AEF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B762146ED64006C3AEF /* Main.storyboard */; }; + 4AA72B7A2146ED66006C3AEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B792146ED66006C3AEF /* Assets.xcassets */; }; + 4AA72B7D2146ED66006C3AEF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */; }; + 4ADDE0CE2176600E00FF07A2 /* Array+TensorFlowLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+TensorFlowLite.swift"; sourceTree = ""; }; + 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TensorFlowLiteApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AA72B722146ED64006C3AEF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 4AA72B742146ED64006C3AEF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 4AA72B772146ED64006C3AEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 4AA72B792146ED66006C3AEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 4AA72B7C2146ED66006C3AEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 4AA72B7E2146ED66006C3AEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+TensorFlowLite.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4AA72B6C2146ED64006C3AEF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4AA72B662146ED64006C3AEF = { + isa = PBXGroup; + children = ( + 4AA72B712146ED64006C3AEF /* TensorFlowLiteApp */, + 4AA72B702146ED64006C3AEF /* Products */, + ); + sourceTree = ""; + }; + 4AA72B702146ED64006C3AEF /* Products */ = { + isa = PBXGroup; + children = ( + 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 4AA72B712146ED64006C3AEF /* TensorFlowLiteApp */ = { + isa = PBXGroup; + children = ( + 4AA72B722146ED64006C3AEF /* AppDelegate.swift */, + 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */, + 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */, + 4AA72B742146ED64006C3AEF /* ViewController.swift */, + 4AA72B762146ED64006C3AEF /* Main.storyboard */, + 4AA72B792146ED66006C3AEF /* Assets.xcassets */, + 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */, + 4AA72B7E2146ED66006C3AEF /* Info.plist */, + ); + path = TensorFlowLiteApp; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4AA72B6E2146ED64006C3AEF /* TensorFlowLiteApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4AA72B812146ED66006C3AEF /* Build configuration list for PBXNativeTarget "TensorFlowLiteApp" */; + buildPhases = ( + 4AA72B6B2146ED64006C3AEF /* Sources */, + 4AA72B6C2146ED64006C3AEF /* Frameworks */, + 4AA72B6D2146ED64006C3AEF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TensorFlowLiteApp; + productName = TensorFlowLiteApp; + productReference = 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4AA72B672146ED64006C3AEF /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0940; + LastUpgradeCheck = 0940; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 4AA72B6E2146ED64006C3AEF = { + CreatedOnToolsVersion = 9.4.1; + }; + }; + }; + buildConfigurationList = 4AA72B6A2146ED64006C3AEF /* Build configuration list for PBXProject "TensorFlowLiteApp" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4AA72B662146ED64006C3AEF; + productRefGroup = 4AA72B702146ED64006C3AEF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4AA72B6E2146ED64006C3AEF /* TensorFlowLiteApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4AA72B6D2146ED64006C3AEF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AA72B7D2146ED66006C3AEF /* LaunchScreen.storyboard in Resources */, + 4AA72B7A2146ED66006C3AEF /* Assets.xcassets in Resources */, + 4AA72B782146ED64006C3AEF /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4AA72B6B2146ED64006C3AEF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AA72B732146ED64006C3AEF /* AppDelegate.swift in Sources */, + 4ADDE0CE2176600E00FF07A2 /* Array+TensorFlowLite.swift in Sources */, + 4A7304B421500B8400C90B21 /* Data+TensorFlowLite.swift in Sources */, + 4AA72B752146ED64006C3AEF /* ViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 4AA72B762146ED64006C3AEF /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 4AA72B772146ED64006C3AEF /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 4AA72B7C2146ED66006C3AEF /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 4AA72B7F2146ED66006C3AEF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 4AA72B802146ED66006C3AEF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 4AA72B822146ED66006C3AEF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = TensorFlowLiteApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tensorflow.lite.swift.TensorFlowLite; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4AA72B832146ED66006C3AEF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = TensorFlowLiteApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tensorflow.lite.swift.TensorFlowLite; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4AA72B6A2146ED64006C3AEF /* Build configuration list for PBXProject "TensorFlowLiteApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AA72B7F2146ED66006C3AEF /* Debug */, + 4AA72B802146ED66006C3AEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4AA72B812146ED66006C3AEF /* Build configuration list for PBXNativeTarget "TensorFlowLiteApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AA72B822146ED66006C3AEF /* Debug */, + 4AA72B832146ED66006C3AEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4AA72B672146ED64006C3AEF /* Project object */; +} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift new file mode 100644 index 0000000000..ffa90a06ad --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift @@ -0,0 +1,24 @@ +import UIKit + +@UIApplicationMain + +final class AppDelegate: UIResponder, UIApplicationDelegate { + + /// The main window of the app. + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + return true + } +} + +// MARK: - Extensions + +#if !swift(>=4.2) +extension UIApplication { + typealias LaunchOptionsKey = UIApplicationLaunchOptionsKey +} +#endif // !swift(>=4.2) diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift new file mode 100644 index 0000000000..56df1ce659 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift @@ -0,0 +1,22 @@ +import Foundation + +extension Array { + /// Creates a new array from the bytes of the given unsafe data. + /// + /// - Warning: The array's `Element` type must be trivial in that it can be copied bit for bit + /// with no indirection or reference-counting operations; otherwise, copying the raw bytes in + /// the `unsafeData`'s buffer to a new array returns an unsafe copy. + /// - Note: Returns `nil` if `unsafeData.count` is not a multiple of + /// `MemoryLayout.stride`. + /// - Parameter unsafeData: The data containing the bytes to turn into an array. + init?(unsafeData: Data) { + guard unsafeData.count % MemoryLayout.stride == 0 else { return nil } + let elements = unsafeData.withUnsafeBytes { + UnsafeBufferPointer( + start: $0, + count: unsafeData.count / MemoryLayout.stride + ) + } + self.init(elements) + } +} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..a07a1321be --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..d0e91d63c4 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift new file mode 100644 index 0000000000..bc8a70c848 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift @@ -0,0 +1,13 @@ +import Foundation + +extension Data { + /// Creates a new buffer by copying the buffer pointer of the given array. + /// + /// - Warning: The given array's element type `T` must be trivial in that it can be copied bit + /// for bit with no indirection or reference-counting operations; otherwise, reinterpreting + /// data from the resulting buffer has undefined behavior. + /// - Parameter array: An array with elements of type `T`. + init(copyingBufferOf array: [T]) { + self = array.withUnsafeBufferPointer(Data.init) + } +} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist new file mode 100644 index 0000000000..3ca3875f04 --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist @@ -0,0 +1,46 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 0.0.1 + LSRequiresIPhoneOS + + NSCameraUsageDescription + NSCameraUsageDescription + NSPhotoLibraryUsageDescription + Select a photo to detect objects in. + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift new file mode 100644 index 0000000000..73c74fd19c --- /dev/null +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift @@ -0,0 +1,299 @@ +import TensorFlowLite +import UIKit + +class ViewController: UIViewController { + + // MARK: - Properties + + /// TensorFlowLite interpreter object for performing inference from a given model. + private var interpreter: Interpreter? + + /// Serial dispatch queue for managing `Interpreter` calls. + private let interpreterQueue = DispatchQueue( + label: Constant.dispatchQueueLabel, + qos: .userInitiated + ) + + /// The currently selected model. + private var currentModel: Model { + guard let currentModel = Model(rawValue: modelControl.selectedSegmentIndex) else { + preconditionFailure("Invalid model for selected segment index.") + } + return currentModel + } + + /// A description of the current model. + private var modelDescription: String { + guard let interpreter = interpreter else { return "" } + let inputCount = interpreter.inputTensorCount + let outputCount = interpreter.outputTensorCount + let inputTensors = (0.. String = { + guard let results = [Float32](unsafeData: outputTensor.data) else { return "No results." } + return resultsText + results.description + } + self.updateResultsText(results()) + } catch let error { + self.updateResultsText( + "Failed to invoke the interpreter with error: \(error.localizedDescription)" + ) + return + } + } + } + + private func invokeAddQuantized() { + interpreterQueue.async { + guard let interpreter = self.interpreter else { + self.updateResultsText(Constant.nilInterpreterErrorMessage) + return + } + do { + try interpreter.resizeInput(at: 0, to: [2]) + try interpreter.allocateTensors() + let input: [UInt8] = [1, 3] + let resultsText = self.modelDescription + "\n\n" + + "Performing 2 add operations on quantized input \(input.description) equals: " + self.updateResultsText(resultsText) + let data = Data(input) + try interpreter.copy(data, toInputAt: 0) + try interpreter.invoke() + let outputTensor = try interpreter.output(at: 0) + let results: () -> String = { + guard let quantizationParameters = outputTensor.quantizationParameters else { + return "No results." + } + let quantizedResults = [UInt8](outputTensor.data) + let dequantizedResults = quantizedResults.map { + quantizationParameters.scale * Float(Int($0) - quantizationParameters.zeroPoint) + } + return resultsText + quantizedResults.description + + ", dequantized results: " + dequantizedResults.description + } + self.updateResultsText(results()) + } catch let error { + self.updateResultsText( + "Failed to invoke the interpreter with error: \(error.localizedDescription)" + ) + return + } + } + } + + private func invokeMultiAdd() { + interpreterQueue.async { + guard let interpreter = self.interpreter else { + self.updateResultsText(Constant.nilInterpreterErrorMessage) + return + } + do { + let shape = TensorShape(2) + try (0.. [Float32] in + let input = [Float32(index + 1), Float32(index + 2)] + let data = Data(copyingBufferOf: input) + try interpreter.copy(data, toInputAt: index) + return input + } + let resultsText = self.modelDescription + "\n\n" + + "Performing 3 add operations on inputs \(inputs.description) equals: " + self.updateResultsText(resultsText) + try interpreter.invoke() + let results = try (0.. [Float32] in + let tensor = try interpreter.output(at: index) + return [Float32](unsafeData: tensor.data) ?? [] + } + self.updateResultsText(resultsText + results.description) + } catch let error { + self.updateResultsText( + "Failed to invoke the interpreter with error: \(error.localizedDescription)" + ) + return + } + } + } + + private func updateResultsText(_ text: String? = nil) { + safeDispatchOnMain { self.resultsTextView.text = text } + } +} + +// MARK: - Constants + +private enum Constant { + static let dispatchQueueLabel = "TensorFlowLiteInterpreterQueue" + static let nilInterpreterErrorMessage = + "Failed to invoke the interpreter because the interpreter was nil." +} + +/// Models that can be loaded by the TensorFlow Lite `Interpreter`. +private enum Model: Int, CustomStringConvertible { + /// A float model that performs two add operations on one input tensor and returns the result in + /// one output tensor. + case add = 0 + /// A quantized model that performs two add operations on one input tensor and returns the result + /// in one output tensor. + case addQuantized = 1 + /// A float model that performs three add operations on four input tensors and returns the results + /// in 2 output tensors. + case multiAdd = 2 + + var fileInfo: (name: String, extension: String) { + switch self { + case .add: + return Add.fileInfo + case .addQuantized: + return AddQuantized.fileInfo + case .multiAdd: + return MultiAdd.fileInfo + } + } + + // MARK: - CustomStringConvertible + + var description: String { + switch self { + case .add: + return Add.name + case .addQuantized: + return AddQuantized.name + case .multiAdd: + return MultiAdd.name + } + } +} + +/// Values for the `Add` model. +private enum Add { + static let name = "Add" + static let fileInfo = (name: "add", extension: "bin") +} + +/// Values for the `AddQuantized` model. +private enum AddQuantized { + static let name = "AddQuantized" + static let fileInfo = (name: "add_quantized", extension: "bin") +} + +/// Values for the `MultiAdd` model. +private enum MultiAdd { + static let name = "MultiAdd" + static let fileInfo = (name: "multi_add", extension: "bin") +} + +// MARK: - Fileprivate + +/// Safely dispatches the given block on the main queue. If the current thread is `main`, the block +/// is executed synchronously; otherwise, the block is executed asynchronously on the main thread. +fileprivate func safeDispatchOnMain(_ block: @escaping () -> Void) { + if Thread.isMainThread { block(); return } + DispatchQueue.main.async { block() } +} diff --git a/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift b/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift new file mode 100644 index 0000000000..54b4f59b28 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift @@ -0,0 +1,54 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class InterpreterOptionsTests: XCTestCase { + + func testInterpreterOptions_InitWithDefaultValues() { + let options = InterpreterOptions() + XCTAssertNil(options.threadCount) + XCTAssertFalse(options.isErrorLoggingEnabled) + } + + func testInterpreterOptions_InitWithCustomValues() { + var options = InterpreterOptions() + options.threadCount = 2 + XCTAssertEqual(options.threadCount, 2) + options.isErrorLoggingEnabled = true + XCTAssertTrue(options.isErrorLoggingEnabled) + } + + func testInterpreterOptions_Equatable() { + var options1 = InterpreterOptions() + var options2 = InterpreterOptions() + XCTAssertEqual(options1, options2) + + options1.threadCount = 2 + options2.threadCount = 2 + XCTAssertEqual(options1, options2) + + options2.threadCount = 3 + XCTAssertNotEqual(options1, options2) + options2.threadCount = 2 + + options1.isErrorLoggingEnabled = true + options2.isErrorLoggingEnabled = true + XCTAssertEqual(options1, options2) + + options2.isErrorLoggingEnabled = false + XCTAssertNotEqual(options1, options2) + } +} diff --git a/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift b/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift new file mode 100644 index 0000000000..e98da5f951 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift @@ -0,0 +1,315 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class InterpreterTests: XCTestCase { + + var interpreter: Interpreter! + + override func setUp() { + super.setUp() + + interpreter = try! Interpreter(modelPath: AddModel.path) + } + + override func tearDown() { + interpreter = nil + + super.tearDown() + } + + func testInterpreter_InitWithModelPath() { + XCTAssertNoThrow(try Interpreter(modelPath: AddModel.path)) + } + + func testInterpreter_Init_ThrowsFailedToLoadModel() { + XCTAssertThrowsError(try Interpreter(modelPath: "/invalid/path")) { error in + self.assertEqualErrors(actual: error, expected: .failedToLoadModel) + } + } + + func testInterpreter_InitWithModelPathAndOptions() { + var options = InterpreterOptions() + options.threadCount = 2 + XCTAssertNoThrow(try Interpreter(modelPath: AddModel.path, options: options)) + } + + func testInterpreter_InputTensorCount() { + XCTAssertEqual(interpreter.inputTensorCount, AddModel.inputTensorCount) + } + + func testInterpreter_OutputTensorCount() { + XCTAssertEqual(interpreter.outputTensorCount, AddModel.outputTensorCount) + } + + func testInterpreter_Invoke() throws { + try interpreter.allocateTensors() + XCTAssertNoThrow(try interpreter.invoke()) + } + + func testInterpreter_Invoke_ThrowsAllocateTensorsRequired_ModelNotReady() { + XCTAssertThrowsError(try interpreter.invoke()) { error in + self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired) + } + } + + func testInterpreter_InputTensorAtIndex() throws { + try setUpAddModelInputTensor() + let inputTensor = try interpreter.input(at: AddModel.validIndex) + XCTAssertEqual(inputTensor, AddModel.inputTensor) + } + + func testInterpreter_InputTensorAtIndex_QuantizedModel() throws { + interpreter = try Interpreter(modelPath: AddQuantizedModel.path) + try setUpAddQuantizedModelInputTensor() + let inputTensor = try interpreter.input(at: AddQuantizedModel.inputOutputIndex) + XCTAssertEqual(inputTensor, AddQuantizedModel.inputTensor) + } + + func testInterpreter_InputTensorAtIndex_ThrowsInvalidIndex() throws { + try interpreter.allocateTensors() + XCTAssertThrowsError(try interpreter.input(at: AddModel.invalidIndex)) { error in + let maxIndex = AddModel.inputTensorCount - 1 + self.assertEqualErrors( + actual: error, + expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) + ) + } + } + + func testInterpreter_InputTensorAtIndex_ThrowsAllocateTensorsRequired() { + XCTAssertThrowsError(try interpreter.input(at: AddModel.validIndex)) { error in + self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired) + } + } + + func testInterpreter_OutputTensorAtIndex() throws { + try setUpAddModelInputTensor() + try interpreter.invoke() + let outputTensor = try interpreter.output(at: AddModel.validIndex) + XCTAssertEqual(outputTensor, AddModel.outputTensor) + let expectedResults = [Float32](unsafeData: outputTensor.data) + XCTAssertEqual(expectedResults, AddModel.results) + } + + func testInterpreter_OutputTensorAtIndex_QuantizedModel() throws { + interpreter = try Interpreter(modelPath: AddQuantizedModel.path) + try setUpAddQuantizedModelInputTensor() + try interpreter.invoke() + let outputTensor = try interpreter.output(at: AddQuantizedModel.inputOutputIndex) + XCTAssertEqual(outputTensor, AddQuantizedModel.outputTensor) + let expectedResults = [UInt8](outputTensor.data) + XCTAssertEqual(expectedResults, AddQuantizedModel.results) + } + + func testInterpreter_OutputTensorAtIndex_ThrowsInvalidIndex() throws { + try interpreter.allocateTensors() + try interpreter.invoke() + XCTAssertThrowsError(try interpreter.output(at: AddModel.invalidIndex)) { error in + let maxIndex = AddModel.outputTensorCount - 1 + self.assertEqualErrors( + actual: error, + expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) + ) + } + } + + func testInterpreter_OutputTensorAtIndex_ThrowsInvokeInterpreterRequired() { + XCTAssertThrowsError(try interpreter.output(at: AddModel.validIndex)) { error in + self.assertEqualErrors(actual: error, expected: .invokeInterpreterRequired) + } + } + + func testInterpreter_ResizeInputTensorAtIndexToShape() { + XCTAssertNoThrow(try interpreter.resizeInput(at: AddModel.validIndex, to: [2, 2, 3])) + XCTAssertNoThrow(try interpreter.allocateTensors()) + } + + func testInterpreter_ResizeInputTensorAtIndexToShape_ThrowsInvalidIndex() { + XCTAssertThrowsError(try interpreter.resizeInput( + at: AddModel.invalidIndex, + to: [2, 2, 3] + )) { error in + let maxIndex = AddModel.inputTensorCount - 1 + self.assertEqualErrors( + actual: error, + expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) + ) + } + } + + func testInterpreter_CopyDataToInputTensorAtIndex() throws { + try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) + try interpreter.allocateTensors() + let inputTensor = try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex) + XCTAssertEqual(inputTensor.data, AddModel.inputData) + } + + func testInterpreter_CopyDataToInputTensorAtIndex_ThrowsInvalidIndex() { + XCTAssertThrowsError(try interpreter.copy( + AddModel.inputData, + toInputAt: AddModel.invalidIndex + )) { error in + let maxIndex = AddModel.inputTensorCount - 1 + self.assertEqualErrors( + actual: error, + expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) + ) + } + } + + func testInterpreter_CopyDataToInputTensorAtIndex_ThrowsInvalidDataCount() throws { + try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) + try interpreter.allocateTensors() + let invalidData = Data(count: AddModel.dataCount - 1) + XCTAssertThrowsError(try interpreter.copy( + invalidData, + toInputAt: AddModel.validIndex + )) { error in + self.assertEqualErrors( + actual: error, + expected: .invalidTensorDataCount(provided: invalidData.count, required: AddModel.dataCount) + ) + } + } + + func testInterpreter_AllocateTensors() { + XCTAssertNoThrow(try interpreter.allocateTensors()) + } + + // MARK: - Private + + private func setUpAddModelInputTensor() throws { + precondition(interpreter != nil) + try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) + try interpreter.allocateTensors() + try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex) + } + + private func setUpAddQuantizedModelInputTensor() throws { + precondition(interpreter != nil) + try interpreter.resizeInput(at: AddQuantizedModel.inputOutputIndex, to: AddQuantizedModel.shape) + try interpreter.allocateTensors() + try interpreter.copy(AddQuantizedModel.inputData, toInputAt: AddQuantizedModel.inputOutputIndex) + } + + private func assertEqualErrors(actual: Error, expected: InterpreterError) { + guard let actual = actual as? InterpreterError else { + XCTFail("Actual error should be of type InterpreterError.") + return + } + XCTAssertEqual(actual, expected) + } +} + +// MARK: - Constants + +/// Values for the `add.bin` model. +private enum AddModel { + static let info = (name: "add", extension: "bin") + static let inputTensorCount = 1 + static let outputTensorCount = 1 + static let invalidIndex = 1 + static let validIndex = 0 + static let shape: TensorShape = [2] + static let dataCount = inputData.count + static let inputData = Data(copyingBufferOf: [Float32(1.0), Float32(3.0)]) + static let outputData = Data(copyingBufferOf: [Float32(3.0), Float32(9.0)]) + static let results = [Float32(3.0), Float32(9.0)] + + static let inputTensor = Tensor( + name: "input", + dataType: .float32, + shape: shape, + data: inputData + ) + static let outputTensor = Tensor( + name: "output", + dataType: .float32, + shape: shape, + data: outputData + ) + + static var path: String = { + let bundle = Bundle(for: InterpreterTests.self) + guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" } + return path + }() +} + +/// Values for the `add_quantized.bin` model. +private enum AddQuantizedModel { + static let info = (name: "add_quantized", extension: "bin") + static let inputOutputIndex = 0 + static let shape: TensorShape = [2] + static let inputData = Data([1, 3]) + static let outputData = Data([3, 9]) + static let quantizationParameters = QuantizationParameters(scale: 0.003922, zeroPoint: 0) + static let results: [UInt8] = [3, 9] + + static let inputTensor = Tensor( + name: "input", + dataType: .uInt8, + shape: shape, + data: inputData, + quantizationParameters: quantizationParameters + ) + static let outputTensor = Tensor( + name: "output", + dataType: .uInt8, + shape: shape, + data: outputData, + quantizationParameters: quantizationParameters + ) + + static var path: String = { + let bundle = Bundle(for: InterpreterTests.self) + guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" } + return path + }() +} + +// MARK: - Extensions + +extension Array { + /// Creates a new array from the bytes of the given unsafe data. + /// + /// - Note: Returns `nil` if `unsafeData.count` is not a multiple of + /// `MemoryLayout.stride`. + /// - Parameter unsafeData: The data containing the bytes to turn into an array. + init?(unsafeData: Data) { + guard unsafeData.count % MemoryLayout.stride == 0 else { return nil } + let elements = unsafeData.withUnsafeBytes { + UnsafeBufferPointer( + start: $0, + count: unsafeData.count / MemoryLayout.stride + ) + } + self.init(elements) + } +} + +extension Data { + /// Creates a new buffer by copying the buffer pointer of the given array. + /// + /// - Warning: The given array's element type `T` must be trivial in that it can be copied bit + /// for bit with no indirection or reference-counting operations; otherwise, reinterpreting + /// data from the resulting buffer has undefined behavior. + /// - Parameter array: An array with elements of type `T`. + init(copyingBufferOf array: [T]) { + self = array.withUnsafeBufferPointer(Data.init) + } +} diff --git a/tensorflow/lite/experimental/swift/Tests/ModelTests.swift b/tensorflow/lite/experimental/swift/Tests/ModelTests.swift new file mode 100644 index 0000000000..025db18906 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/ModelTests.swift @@ -0,0 +1,59 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class ModelTests: XCTestCase { + + var modelPath: String! + + override func setUp() { + super.setUp() + + let bundle = Bundle(for: type(of: self)) + guard let modelPath = bundle.path( + forResource: Constant.modelInfo.name, + ofType: Constant.modelInfo.extension) + else { + XCTFail("Failed to get the model file path.") + return + } + self.modelPath = modelPath + } + + override func tearDown() { + modelPath = nil + + super.tearDown() + } + + func testModel_InitWithFilePath() { + XCTAssertNotNil(Model(filePath: modelPath)) + } + + func testModel_InitWithEmptyFilePath_FailsInitialization() { + XCTAssertNil(Model(filePath: "")) + } + + func testModel_InitWithInvalidFilePath_FailsInitialization() { + XCTAssertNil(Model(filePath: "invalid/path")) + } +} + +// MARK: - Constants + +private enum Constant { + static let modelInfo = (name: "add", extension: "bin") +} diff --git a/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift b/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift new file mode 100644 index 0000000000..65648c2698 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift @@ -0,0 +1,43 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class QuantizationParametersTests: XCTestCase { + + func testQuantizationParameters_InitWithCustomValues() { + let parameters = QuantizationParameters(scale: 0.5, zeroPoint: 1) + XCTAssertEqual(parameters.scale, 0.5) + XCTAssertEqual(parameters.zeroPoint, 1) + } + + func testQuantizationParameters_Equatable() { + let parameters1 = QuantizationParameters(scale: 0.5, zeroPoint: 1) + let parameters2 = QuantizationParameters(scale: 0.5, zeroPoint: 1) + XCTAssertEqual(parameters1, parameters2) + + let parameters3 = QuantizationParameters(scale: 0.4, zeroPoint: 1) + XCTAssertNotEqual(parameters1, parameters3) + XCTAssertNotEqual(parameters2, parameters3) + } +} + +// MARK: - Extensions + +extension QuantizationParameters: Equatable { + public static func == (lhs: QuantizationParameters, rhs: QuantizationParameters) -> Bool { + return lhs.scale == rhs.scale && lhs.zeroPoint == rhs.zeroPoint + } +} diff --git a/tensorflow/lite/experimental/swift/Tests/TensorTests.swift b/tensorflow/lite/experimental/swift/Tests/TensorTests.swift new file mode 100644 index 0000000000..4540043a16 --- /dev/null +++ b/tensorflow/lite/experimental/swift/Tests/TensorTests.swift @@ -0,0 +1,83 @@ +// Copyright 2018 Google Inc. 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. + +@testable import TensorFlowLite +import XCTest + +class TensorTests: XCTestCase { + + // MARK: - Tensor + + func testTensor_Init() { + let name = "InputTensor" + let dataType: TensorDataType = .uInt8 + let shape = TensorShape(Constant.dimensions) + guard let data = name.data(using: .utf8) else { XCTFail("Data should not be nil."); return } + let quantizationParameters = QuantizationParameters(scale: 0.5, zeroPoint: 1) + let inputTensor = Tensor( + name: name, + dataType: dataType, + shape: shape, + data: data, + quantizationParameters: quantizationParameters + ) + XCTAssertEqual(inputTensor.name, name) + XCTAssertEqual(inputTensor.dataType, dataType) + XCTAssertEqual(inputTensor.shape, shape) + XCTAssertEqual(inputTensor.data, data) + XCTAssertEqual(inputTensor.quantizationParameters, quantizationParameters) + } + + // MARK: - TensorShape + + func testTensorShape_InitWithArray() { + let shape = TensorShape(Constant.dimensions) + XCTAssertEqual(shape.rank, Constant.dimensions.count) + XCTAssertEqual(shape.dimensions, Constant.dimensions) + } + + func testTensorShape_InitWithElements() { + let shape = TensorShape(2, 2, 3) + XCTAssertEqual(shape.rank, Constant.dimensions.count) + XCTAssertEqual(shape.dimensions, Constant.dimensions) + } + + func testTensorShape_InitWithArrayLiteral() { + let shape: TensorShape = [2, 2, 3] + XCTAssertEqual(shape.rank, Constant.dimensions.count) + XCTAssertEqual(shape.dimensions, Constant.dimensions) + } +} + +// MARK: - Constants + +private enum Constant { + /// Array of 2 arrays of 2 arrays of 3 numbers: [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]]. + static let dimensions = [2, 2, 3] +} + +// MARK: - Extensions + +extension TensorShape: Equatable { + public static func == (lhs: TensorShape, rhs: TensorShape) -> Bool { + return lhs.rank == rhs.rank && lhs.dimensions == rhs.dimensions + } +} + +extension Tensor: Equatable { + public static func == (lhs: Tensor, rhs: Tensor) -> Bool { + return lhs.name == rhs.name && lhs.dataType == rhs.dataType && lhs.shape == rhs.shape && + lhs.data == rhs.data && lhs.quantizationParameters == rhs.quantizationParameters + } +} diff --git a/tensorflow/tools/pip_package/pip_smoke_test.py b/tensorflow/tools/pip_package/pip_smoke_test.py index 51d010c9e1..952c71c615 100644 --- a/tensorflow/tools/pip_package/pip_smoke_test.py +++ b/tensorflow/tools/pip_package/pip_smoke_test.py @@ -30,14 +30,19 @@ os.chdir(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))) PIP_PACKAGE_QUERY_EXPRESSION = ( "deps(//tensorflow/tools/pip_package:build_pip_package)") +# List of file paths containing BUILD files that should not be included for the +# pip smoke test. +BUILD_BLACKLIST = [ + "tensorflow/lite/examples/android", + "tensorflow/lite/experimental/swift", +] def GetBuild(dir_base): """Get the list of BUILD file all targets recursively startind at dir_base.""" items = [] for root, _, files in os.walk(dir_base): for name in files: - if (name == "BUILD" and - root.find("tensorflow/lite/examples/android") == -1): + if (name == "BUILD" and root not in BUILD_BLACKLIST): items.append("//" + root + ":all") return items @@ -67,9 +72,9 @@ def BuildPyTestDependencies(): PYTHON_TARGETS, PY_TEST_QUERY_EXPRESSION = BuildPyTestDependencies() -# Hard-coded blacklist of files if not included in pip package # TODO(amitpatankar): Clean up blacklist. -BLACKLIST = [ +# List of dependencies that should not included in the pip package. +DEPENDENCY_BLACKLIST = [ "//tensorflow/python:extra_py_tests_deps", "//tensorflow/cc/saved_model:saved_model_half_plus_two", "//tensorflow:no_tensorflow_py_deps", @@ -82,9 +87,7 @@ BLACKLIST = [ "//tensorflow/core/kernels/cloud:bigquery_reader_ops", "//tensorflow/python/feature_column:vocabulary_testdata", "//tensorflow/python:framework/test_file_system.so", - # contrib - "//tensorflow/contrib/session_bundle:session_bundle_half_plus_two", - "//tensorflow/contrib/keras:testing_utils", + # lite "//tensorflow/lite/experimental/examples/lstm:tflite_lstm", "//tensorflow/lite/experimental/examples/lstm:tflite_lstm.py", "//tensorflow/lite/experimental/examples/lstm:unidirectional_sequence_lstm_test", # pylint:disable=line-too-long @@ -93,6 +96,9 @@ BLACKLIST = [ "//tensorflow/lite/python:interpreter_test", "//tensorflow/lite/python:interpreter.py", "//tensorflow/lite/python:interpreter_test.py", + # contrib + "//tensorflow/contrib/session_bundle:session_bundle_half_plus_two", + "//tensorflow/contrib/keras:testing_utils", "//tensorflow/contrib/ffmpeg:test_data", "//tensorflow/contrib/fused_conv:fused_conv2d_bias_activation_op_test_base", "//tensorflow/contrib/hadoop:test_data", @@ -149,8 +155,8 @@ def main(): # File extensions and endings to ignore ignore_extensions = ["_test", "_test.py", "_test_gpu", "_test_gpu.py"] - ignored_files = 0 - blacklisted_files = len(BLACKLIST) + ignored_files_count = 0 + blacklisted_dependencies_count = len(DEPENDENCY_BLACKLIST) # Compare dependencies for dependency in tf_py_test_dependencies_list: if dependency and dependency.startswith("//tensorflow"): @@ -158,16 +164,16 @@ def main(): # Ignore extensions if any(dependency.endswith(ext) for ext in ignore_extensions): ignore = True - ignored_files += 1 + ignored_files_count += 1 - # Check if the dependency is in the pip package, the blacklist, or - # should be ignored because of its file extension + # Check if the dependency is in the pip package, the dependency blacklist, + # or should be ignored because of its file extension. if not (ignore or dependency in pip_package_dependencies_list or - dependency in BLACKLIST): + dependency in DEPENDENCY_BLACKLIST): missing_dependencies.append(dependency) - print("Ignored files: %d" % ignored_files) - print("Blacklisted files: %d" % blacklisted_files) + print("Ignored files count: %d" % ignored_files_count) + print("Blacklisted dependencies count: %d" % blacklisted_dependencies_count) if missing_dependencies: print("Missing the following dependencies from pip_packages:") for missing_dependency in missing_dependencies: -- GitLab From d28fddf859f0d9093a1279ceda52ebfb2d915788 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Fri, 4 Jan 2019 10:57:17 -0800 Subject: [PATCH 0192/2345] Fix some metric doc strings. PiperOrigin-RevId: 227880840 --- tensorflow/python/keras/metrics.py | 42 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index 73c116a745..7f13cc46e3 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -1382,8 +1382,8 @@ class MeanAbsoluteError(MeanMetricWrapper): Usage: ```python - mae = tf.metrics.MeanAbsoluteError() - mae.update_state([0., 0., 1., 1.], [1., 1., 1., 0.]) + m = tf.metrics.MeanAbsoluteError() + m.update_state([0., 0., 1., 1.], [1., 1., 1., 0.]) print('Final result: ', m.result().numpy()) # Final result: 0.75 ``` @@ -1391,7 +1391,7 @@ class MeanAbsoluteError(MeanMetricWrapper): ```python model = keras.models.Model(inputs, outputs) - model.compile('sgd', loss=tf.keras.losses.MeanAbsoluteError()) + model.compile('sgd', metrics=[tf.keras.metrics.MeanAbsoluteError()]) ``` """ @@ -1416,8 +1416,8 @@ class MeanAbsolutePercentageError(MeanMetricWrapper): Usage: ```python - mape = tf.keras.losses.MeanAbsolutePercentageError() - mape.update_state([0., 0., 1., 1.], [1., 1., 1., 0.]) + m = tf.keras.metrics.MeanAbsolutePercentageError() + m.update_state([0., 0., 1., 1.], [1., 1., 1., 0.]) print('Final result: ', m.result().numpy()) # Final result: 5e+08 ``` @@ -1425,7 +1425,7 @@ class MeanAbsolutePercentageError(MeanMetricWrapper): ```python model = keras.models.Model(inputs, outputs) - model.compile('sgd', loss=tf.keras.losses.MeanAbsolutePercentageError()) + model.compile('sgd', metrics=[tf.keras.metrics.MeanAbsolutePercentageError()]) ``` """ @@ -1450,8 +1450,8 @@ class MeanSquaredError(MeanMetricWrapper): Usage: ```python - mape = tf.keras.losses.MeanSquaredError() - mape.update_state([0., 0., 1., 1.], [1., 1., 1., 0.]) + m = tf.keras.metrics.MeanSquaredError() + m.update_state([0., 0., 1., 1.], [1., 1., 1., 0.]) print('Final result: ', m.result().numpy()) # Final result: 0.75 ``` @@ -1459,7 +1459,7 @@ class MeanSquaredError(MeanMetricWrapper): ```python model = keras.models.Model(inputs, outputs) - model.compile('sgd', loss=tf.keras.losses.MeanSquaredError()) + model.compile('sgd', metrics=[tf.keras.metrics.MeanSquaredError()]) ``` """ @@ -1484,8 +1484,8 @@ class MeanSquaredLogarithmicError(MeanMetricWrapper): Usage: ```python - msle = tf.keras.losses.MeanSquaredLogarithmicError() - msle.update_state([0., 0., 1., 1.], [1., 1., 1., 0.]) + m = tf.keras.metrics.MeanSquaredLogarithmicError() + m.update_state([0., 0., 1., 1.], [1., 1., 1., 0.]) print('Final result: ', m.result().numpy()) # Final result: 0.36034 ``` @@ -1493,7 +1493,7 @@ class MeanSquaredLogarithmicError(MeanMetricWrapper): ```python model = keras.models.Model(inputs, outputs) - model.compile('sgd', loss=tf.keras.losses.MeanSquaredLogarithmicError()) + model.compile('sgd', metrics=[tf.keras.metrics.MeanSquaredLogarithmicError()]) ``` """ @@ -1518,8 +1518,8 @@ class Hinge(MeanMetricWrapper): Usage: ```python - h = tf.keras.metrics.Hinge() - h.update_state([0., 1., 1.], [1., 0., 1.]) + m = tf.keras.metrics.Hinge() + m.update_state([0., 1., 1.], [1., 0., 1.]) print('Final result: ', m.result().numpy()) # Final result: 0.66 ``` @@ -1527,7 +1527,7 @@ class Hinge(MeanMetricWrapper): ```python model = keras.models.Model(inputs, outputs) - model.compile('sgd', loss=tf.keras.metrics.Hinge()) + model.compile('sgd', metrics=[tf.keras.metrics.Hinge()]) ``` """ @@ -1551,8 +1551,8 @@ class SquaredHinge(MeanMetricWrapper): Usage: ```python - h = tf.keras.metrics.SquaredHinge() - h.update_state([0., 1., 1.], [1., 0., 1.]) + m = tf.keras.metrics.SquaredHinge() + m.update_state([0., 1., 1.], [1., 0., 1.]) print('Final result: ', m.result().numpy()) # Final result: 0.66 ``` @@ -1560,7 +1560,7 @@ class SquaredHinge(MeanMetricWrapper): ```python model = keras.models.Model(inputs, outputs) - model.compile('sgd', loss=tf.keras.metrics.SquaredHinge()) + model.compile('sgd', metrics=[tf.keras.metrics.SquaredHinge()]) ``` """ @@ -1584,8 +1584,8 @@ class CategoricalHinge(MeanMetricWrapper): Usage: ```python - h = tf.keras.metrics.CategoricalHinge() - h.update_state([0., 1., 1.], [1., 0., 1.]) + m = tf.keras.metrics.CategoricalHinge() + m.update_state([0., 1., 1.], [1., 0., 1.]) print('Final result: ', m.result().numpy()) # Final result: 1.0 ``` @@ -1593,7 +1593,7 @@ class CategoricalHinge(MeanMetricWrapper): ```python model = keras.models.Model(inputs, outputs) - model.compile('sgd', loss=tf.keras.metrics.CategoricalHinge()) + model.compile('sgd', metrics=[tf.keras.metrics.CategoricalHinge()]) ``` """ -- GitLab From 49d70dff868661bc7aa999227dad64bde2538899 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 10:58:35 -0800 Subject: [PATCH 0193/2345] Make tf.where documentation clearer. PiperOrigin-RevId: 227881065 --- tensorflow/python/ops/array_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index 07a7e180fb..81558db04f 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -3191,7 +3191,7 @@ def where(condition, x=None, y=None, name=None): Returns: A `Tensor` with the same type and shape as `x`, `y` if they are non-None. - A `Tensor` with shape `(num_true, dim_size(condition))`. + Otherwise, a `Tensor` with shape `(num_true, rank(condition))`. Raises: ValueError: When exactly one of `x` or `y` is non-None. -- GitLab From 9d72bb94c054b9d3a00f39e6a99c02995b207ae6 Mon Sep 17 00:00:00 2001 From: Bixia Zheng Date: Fri, 4 Jan 2019 11:04:06 -0800 Subject: [PATCH 0194/2345] [XLA] The HLO evaluator should preserve the layouts for instructions inside a fusion. When evaluating a fusion, the HLO evaluator makes a copy of the fusion and resets the layout of each instruction in the copy to default layout before evaluating the copy of the fusion. In doing that, it changes the layout of the fusion intputs and outputs and causes incorrect results. This change fixes the evaluator to not overwrite the layouts for the instructions inside a fusion. Add test cases for fusion inputs, single output fusion and multiple output fusion. PiperOrigin-RevId: 227882142 --- .../compiler/xla/service/hlo_evaluator.cc | 4 +- .../xla/service/hlo_evaluator_test.cc | 66 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.cc b/tensorflow/compiler/xla/service/hlo_evaluator.cc index e897d09e1b..9754b8906b 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.cc +++ b/tensorflow/compiler/xla/service/hlo_evaluator.cc @@ -1097,7 +1097,9 @@ Status HloEvaluator::HandleFusion(HloInstruction* fusion) { fusion->fused_instructions_computation()->Clone( /*suffix=*/"clone_with_layout", &context); for (auto* instruction : cloned_fused_computation->instructions()) { - LayoutUtil::SetToDefaultLayout(instruction->mutable_shape()); + if (!LayoutUtil::HasLayout(instruction->shape())) { + LayoutUtil::SetToDefaultLayout(instruction->mutable_shape()); + } } auto readded_computation = empty_hlo_module.AddEntryComputation(std::move(cloned_fused_computation)); diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc index 0298da2174..8056193d99 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc +++ b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc @@ -2924,5 +2924,71 @@ ENTRY main { "Expected 1 argument, but got 2."); } +TEST_F(HloEvaluatorTest, PreserveFusionInputLayout) { + constexpr absl::string_view hlo_text = R"( + HloModule FusionInputLayout + + fused_computation { + param_0 = f32[20,20]{0,1} parameter(0) + ROOT bitcast = f32[20,20]{1,0} bitcast(param_0) + } + + ENTRY kernel_entry { + parameter.0 = f32[20,20]{0,1} parameter(0) + ROOT fusion = f32[20,20]{1,0} fusion(parameter.0), + kind=kLoop, calls=fused_computation + })"; + + TF_ASSERT_OK_AND_ASSIGN(m_, ParseAndReturnVerifiedModule(hlo_text)); + auto args = MakeFakeArguments(m_.get()).ConsumeValueOrDie(); + Literal actual = Evaluate({&args[0]}); + EXPECT_TRUE(absl::c_equal(args[0].data(), actual.data())); +} + +TEST_F(HloEvaluatorTest, PreserveFusionOutputLayout) { + constexpr absl::string_view hlo_text = R"( + HloModule FusionOutputLayout + + fused_computation { + param_0 = f32[20,20]{1,0} parameter(0) + ROOT bitcast = f32[20,20]{0,1} bitcast(param_0) + } + + ENTRY kernel_entry { + parameter.0 = f32[20,20]{1,0} parameter(0) + ROOT fusion = f32[20,20]{0,1} fusion(parameter.0), + kind=kLoop, calls=fused_computation + })"; + + TF_ASSERT_OK_AND_ASSIGN(m_, ParseAndReturnVerifiedModule(hlo_text)); + auto args = MakeFakeArguments(m_.get()).ConsumeValueOrDie(); + Literal actual = Evaluate({&args[0]}); + EXPECT_TRUE(absl::c_equal(args[0].data(), actual.data())); +} + +TEST_F(HloEvaluatorTest, PreserveMOFusionOutputLayout) { + constexpr absl::string_view hlo_text = R"( + HloModule MOFusionOutputLayout + + fused_computation { + param_0 = f32[20,20]{1,0} parameter(0) + bitcast = f32[20,20]{0,1} bitcast(param_0) + ROOT tuple = (f32[20,20]{0,1}) tuple(bitcast) + } + + ENTRY kernel_entry { + parameter.0 = f32[20,20]{1,0} parameter(0) + ROOT fusion = (f32[20,20]{0,1}) fusion(parameter.0), + kind=kLoop, calls=fused_computation + })"; + + TF_ASSERT_OK_AND_ASSIGN(m_, ParseAndReturnVerifiedModule(hlo_text)); + auto args = MakeFakeArguments(m_.get()).ConsumeValueOrDie(); + Literal actual_tuple = Evaluate({&args[0]}); + std::vector actual_literals = actual_tuple.DecomposeTuple(); + EXPECT_TRUE( + absl::c_equal(args[0].data(), actual_literals[0].data())); +} + } // namespace } // namespace xla -- GitLab From d76c2d0a570495854617c318a2708636f5530f27 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Fri, 4 Jan 2019 11:10:12 -0800 Subject: [PATCH 0195/2345] Improve the error message for model fit/eval/predict in scope PiperOrigin-RevId: 227883249 --- tensorflow/contrib/distribute/python/keras_test.py | 2 +- .../python/keras/engine/distributed_training_utils.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/distribute/python/keras_test.py b/tensorflow/contrib/distribute/python/keras_test.py index 4cfcc0375f..1cb5fa30a3 100644 --- a/tensorflow/contrib/distribute/python/keras_test.py +++ b/tensorflow/contrib/distribute/python/keras_test.py @@ -1196,7 +1196,7 @@ class TestDistributionStrategyValidation(test.TestCase, def test_loop_in_scope(self, distribution): with self.cached_session(): with self.assertRaisesRegexp( - RuntimeError, 'should not be run inside the distribution strategy'): + RuntimeError, 'should not be run inside the tf.distribute.Strategy'): with distribution.scope(): x = keras.layers.Input(shape=(3,), name='input') y = keras.layers.Dense(4, name='dense')(x) diff --git a/tensorflow/python/keras/engine/distributed_training_utils.py b/tensorflow/python/keras/engine/distributed_training_utils.py index 9e3bdd26cb..1678d84307 100644 --- a/tensorflow/python/keras/engine/distributed_training_utils.py +++ b/tensorflow/python/keras/engine/distributed_training_utils.py @@ -48,9 +48,9 @@ def validate_not_in_strategy_scope(): if distribution_strategy_context.has_distribution_strategy(): if distribution_strategy_context.in_cross_replica_context(): raise RuntimeError( - 'Fit/Eval/Predict should not be run inside the distribution strategy ' - 'scope. Only model creation and compilation should be in ' - 'distribution strategy scope.') + 'Fit/Eval/Predict should not be run inside the tf.distribute.Strategy' + ' scope. Only model creation and compilation should be in ' + 'tf.distribute.Strategy scope.') def set_weights(distribution_strategy, dist_model, weights): -- GitLab From 735d26a821d9f69f58ad12ea841da9bc8c5a65ee Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 11:18:24 -0800 Subject: [PATCH 0196/2345] Make session_handle of DirectSession an unique identifier and accessible from OptimizationPassRegistry::PRE_PLACEMENT pass. PiperOrigin-RevId: 227884596 --- tensorflow/core/common_runtime/direct_session.cc | 11 +++++++---- tensorflow/core/common_runtime/direct_session.h | 1 + tensorflow/core/common_runtime/executor.cc | 3 +++ tensorflow/core/common_runtime/executor.h | 2 ++ .../core/common_runtime/graph_execution_state.cc | 3 +++ .../core/common_runtime/graph_execution_state.h | 4 ++++ .../core/common_runtime/optimization_registry.h | 1 + tensorflow/core/framework/op_kernel.h | 6 ++++++ 8 files changed, 27 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/common_runtime/direct_session.cc b/tensorflow/core/common_runtime/direct_session.cc index 51b2c68c76..36f1a92aab 100644 --- a/tensorflow/core/common_runtime/direct_session.cc +++ b/tensorflow/core/common_runtime/direct_session.cc @@ -59,6 +59,7 @@ limitations under the License. #include "tensorflow/core/lib/gtl/array_slice.h" #include "tensorflow/core/lib/gtl/stl_util.h" #include "tensorflow/core/lib/monitoring/counter.h" +#include "tensorflow/core/lib/random/random.h" #include "tensorflow/core/lib/strings/numbers.h" #include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/lib/strings/strcat.h" @@ -303,10 +304,8 @@ DirectSession::DirectSession(const SessionOptions& options, if (!status.ok()) { LOG(ERROR) << status.error_message(); } - // NOTE(mrry): We do not need to use a unique string for the session - // handle, because DirectSession owns its devices. This may change - // in future versions. - session_handle_ = "direct"; + session_handle_ = + strings::StrCat("direct", strings::FpToString(random::New64())); int devices_added = 0; if (options.config.log_device_placement()) { const string mapping_str = device_mgr_->DeviceMappingString(); @@ -371,6 +370,7 @@ Status DirectSession::MaybeInitializeExecutionState( GraphExecutionStateOptions options; options.device_set = &device_set_; options.session_options = &options_; + options.session_handle = session_handle_; // TODO(mrry,suharshs): We explicitly copy `graph` so that // `MakeForBaseGraph()` can take ownership of its // contents. Previously this happened implicitly in calls to the @@ -533,6 +533,7 @@ Status DirectSession::RunInternal(int64 step_id, const RunOptions& run_options, CancellationManager step_cancellation_manager; args.cancellation_manager = &step_cancellation_manager; args.session_state = &session_state_; + args.session_handle = session_handle_; args.tensor_store = &run_state.tensor_store; args.step_container = &run_state.step_container; args.sync_on_finish = sync_on_finish_; @@ -888,6 +889,7 @@ Status DirectSession::PRunSetup(const std::vector& input_names, SchedClosure(pool, std::move(c)); }; args.session_state = &session_state_; + args.session_handle = session_handle_; args.tensor_store = &run_state->tensor_store; args.step_container = &run_state->step_container; if (LogMemory::IsEnabled()) { @@ -1465,6 +1467,7 @@ Status DirectSession::CreateGraphs( prune_options.device_set = &device_set_; prune_options.session_options = &options_; prune_options.stateful_placements = stateful_placements_; + prune_options.session_handle = session_handle_; TF_RETURN_IF_ERROR(GraphExecutionState::MakeForPrunedGraph( execution_state_->original_graph_def().library(), prune_options, execution_state_->original_graph_def(), subgraph_options, diff --git a/tensorflow/core/common_runtime/direct_session.h b/tensorflow/core/common_runtime/direct_session.h index 6754e9cfb7..bcac341544 100644 --- a/tensorflow/core/common_runtime/direct_session.h +++ b/tensorflow/core/common_runtime/direct_session.h @@ -317,6 +317,7 @@ class DirectSession : public Session { std::vector devices_; // not owned DeviceSet device_set_; + // Unique session identifier. string session_handle_; mutex graph_state_lock_; bool graph_created_ GUARDED_BY(graph_state_lock_) = false; diff --git a/tensorflow/core/common_runtime/executor.cc b/tensorflow/core/common_runtime/executor.cc index df2ee6c618..07c8c4a5d4 100644 --- a/tensorflow/core/common_runtime/executor.cc +++ b/tensorflow/core/common_runtime/executor.cc @@ -1244,6 +1244,7 @@ class ExecutorState { Rendezvous* rendezvous_; CollectiveExecutor* collective_executor_ = nullptr; SessionState* session_state_; + string session_handle_; TensorStore* tensor_store_; // Step-local container. ScopedStepContainer* step_container_; @@ -1371,6 +1372,7 @@ ExecutorState::ExecutorState(const Executor::Args& args, ExecutorImpl* impl) rendezvous_(args.rendezvous), collective_executor_(args.collective_executor), session_state_(args.session_state), + session_handle_(args.session_handle), tensor_store_(args.tensor_store), step_container_(args.step_container), stats_collector_(args.stats_collector), @@ -1616,6 +1618,7 @@ void ExecutorState::Process(TaggedNode tagged_node, int64 scheduled_nsec) { params.rendezvous = rendezvous_; params.collective_executor = collective_executor_; params.session_state = session_state_; + params.session_handle = session_handle_; params.tensor_store = tensor_store_; params.cancellation_manager = cancellation_manager_; params.call_frame = call_frame_; diff --git a/tensorflow/core/common_runtime/executor.h b/tensorflow/core/common_runtime/executor.h index 02930168a4..4be60c6771 100644 --- a/tensorflow/core/common_runtime/executor.h +++ b/tensorflow/core/common_runtime/executor.h @@ -88,6 +88,8 @@ class Executor { CallFrameInterface* call_frame = nullptr; CancellationManager* cancellation_manager = nullptr; SessionState* session_state = nullptr; + // Unique session identifier. Can be empty. + string session_handle; TensorStore* tensor_store = nullptr; ScopedStepContainer* step_container = nullptr; CollectiveExecutor* collective_executor = nullptr; diff --git a/tensorflow/core/common_runtime/graph_execution_state.cc b/tensorflow/core/common_runtime/graph_execution_state.cc index 04d658f047..9ecbc34f5f 100644 --- a/tensorflow/core/common_runtime/graph_execution_state.cc +++ b/tensorflow/core/common_runtime/graph_execution_state.cc @@ -59,6 +59,7 @@ GraphExecutionState::GraphExecutionState( : stateful_placements_(options.stateful_placements), device_set_(options.device_set), session_options_(options.session_options), + session_handle_(options.session_handle), flib_def_(new FunctionLibraryDefinition(OpRegistry::Global(), graph_def->library())), graph_(nullptr) { @@ -198,6 +199,7 @@ Status GraphExecutionState::Extend( GraphExecutionStateOptions combined_options; combined_options.device_set = device_set_; combined_options.session_options = session_options_; + combined_options.session_handle = session_handle_; combined_options.stateful_placements = stateful_placements_; // NOTE(mrry): `gdef` is no longer valid after the constructor @@ -558,6 +560,7 @@ Status GraphExecutionState::InitBaseGraph(const BuildGraphOptions& options) { RestoreStatefulNodes(new_graph.get()); GraphOptimizationPassOptions optimization_options; + optimization_options.session_handle = session_handle_; optimization_options.session_options = session_options_; optimization_options.graph = &new_graph; optimization_options.flib_def = flib_def_.get(); diff --git a/tensorflow/core/common_runtime/graph_execution_state.h b/tensorflow/core/common_runtime/graph_execution_state.h index 9cabe478a6..56315bb1ef 100644 --- a/tensorflow/core/common_runtime/graph_execution_state.h +++ b/tensorflow/core/common_runtime/graph_execution_state.h @@ -41,6 +41,8 @@ struct RewriteGraphMetadata; struct GraphExecutionStateOptions { const DeviceSet* device_set = nullptr; const SessionOptions* session_options = nullptr; + // Unique session identifier. Can be empty. + string session_handle; // A map from node name to device name, representing the unchangeable // placement of stateful nodes. std::unordered_map stateful_placements; @@ -192,6 +194,8 @@ class GraphExecutionState { GraphDef original_graph_def_; // Immutable after ctor. const DeviceSet* device_set_; // Not owned const SessionOptions* session_options_; // Not owned + // Unique session identifier. Can be empty. + string session_handle_; // Map from name to Node for the full graph in placed_. NodeNameToCostIdMap node_name_to_cost_id_map_; diff --git a/tensorflow/core/common_runtime/optimization_registry.h b/tensorflow/core/common_runtime/optimization_registry.h index 6fcd2afd27..19a76529bb 100644 --- a/tensorflow/core/common_runtime/optimization_registry.h +++ b/tensorflow/core/common_runtime/optimization_registry.h @@ -35,6 +35,7 @@ struct SessionOptions; // as a key into a state dictionary if it wants to keep state across // calls. struct GraphOptimizationPassOptions { + // Filled in by DirectSession for PRE_PLACEMENT optimizations. Can be empty. string session_handle; const SessionOptions* session_options = nullptr; const CostModel* cost_model = nullptr; diff --git a/tensorflow/core/framework/op_kernel.h b/tensorflow/core/framework/op_kernel.h index bccb2bf3c7..aa07cbd380 100644 --- a/tensorflow/core/framework/op_kernel.h +++ b/tensorflow/core/framework/op_kernel.h @@ -606,6 +606,9 @@ class OpKernelContext { // The session state for this op. SessionState* session_state = nullptr; + // Unique session identifier. Can be empty. + string session_handle; + // The tensor store for this op. TensorStore* tensor_store = nullptr; @@ -1034,6 +1037,9 @@ class OpKernelContext { // An op kernel can access the session state it belongs to. SessionState* session_state() const { return params_->session_state; } + // Unique identifier of the session it belongs to. Can be empty. + string session_handle() const { return params_->session_handle; } + // An op kernel can access the tensor store of the run it belongs to. TensorStore* tensor_store() const { return params_->tensor_store; } -- GitLab From 8c22259497e9914c37a7adcd33aebaf754473a02 Mon Sep 17 00:00:00 2001 From: Laurent Le Brun Date: Fri, 4 Jan 2019 20:37:31 +0100 Subject: [PATCH 0197/2345] Update dependency on rules_closure This makes this command succeed: bazel build //tensorflow/tools/pip_package:build_pip_package --incompatible_disallow_data_transition This is needed for a future change in Bazel. --- WORKSPACE | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 2277e83a3f..1c59686f16 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -4,11 +4,11 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file" http_archive( name = "io_bazel_rules_closure", - sha256 = "a38539c5b5c358548e75b44141b4ab637bba7c4dc02b46b1f62a96d6433f56ae", - strip_prefix = "rules_closure-dbb96841cc0a5fb2664c37822803b06dab20c7d1", + sha256 = "43c9b882fa921923bcba764453f4058d102bece35a37c9f6383c713004aacff1", + strip_prefix = "rules_closure-9889e2348259a5aad7e805547c1a0cf311cfcd91", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/rules_closure/archive/dbb96841cc0a5fb2664c37822803b06dab20c7d1.tar.gz", - "https://github.com/bazelbuild/rules_closure/archive/dbb96841cc0a5fb2664c37822803b06dab20c7d1.tar.gz", # 2018-04-13 + "https://mirror.bazel.build/github.com/bazelbuild/rules_closure/archive/9889e2348259a5aad7e805547c1a0cf311cfcd91.tar.gz", + "https://github.com/bazelbuild/rules_closure/archive/9889e2348259a5aad7e805547c1a0cf311cfcd91.tar.gz", # 2018-12-21 ], ) -- GitLab From 3e37a58b6615582cca0006be22d95edf4945f078 Mon Sep 17 00:00:00 2001 From: Pete Warden Date: Fri, 4 Jan 2019 11:44:16 -0800 Subject: [PATCH 0198/2345] Update documentation for Apollo3 board PiperOrigin-RevId: 227889248 --- tensorflow/lite/experimental/micro/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index dab7ae80f3..464c7b6ad7 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -187,7 +187,7 @@ You should see the following log with the magic string `~~~ALL TEST PASSED~~~`: 02:25:22.4253 [DEBUG] uart0: [+0.16ms host +0s virt 0.28s virt from start] Progam has exited with code:0x00000000 ``` -## Building for Apollo3 +## Building for Ambiq Micro Apollo3Blue EVB Follow these steps to get the pushbutton yes/no example working on Apollo 3: @@ -201,7 +201,8 @@ Follow these steps to get the pushbutton yes/no example working on Apollo 3: tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=apollo3evb pushbutton_cmsis_speech_test_bin 4. Install [Segger JLink tools](https://www.segger.com/downloads/jlink/) -5. Connect the Apollo3 EVB (with mic shield) to the computer and power it on +5. Connect the Apollo3 EVB (with mic shield in slot 3 of Microbus Shield board) + to the computer and power it on. 6. Start the GDB server in a new terminal with the following command: JLinkGDBServer -select USB -device AMA3B1KK-KBR -endian little -if SWD -speed 1000 -noir -noLocalhostOnly -- GitLab From 37afc40b6f33073193114c081bcda939289deca0 Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Fri, 4 Jan 2019 11:57:01 -0800 Subject: [PATCH 0199/2345] Roll-forward with fix: Fix dso_loader.cc's includes. tf_custom_op_library already adds core:stream_executor_headers_lib as a dep, so we must not add a duplicated dep here. PiperOrigin-RevId: 227891141 --- tensorflow/contrib/mpi_collectives/BUILD | 1 - tensorflow/stream_executor/platform/default/dso_loader.cc | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow/contrib/mpi_collectives/BUILD b/tensorflow/contrib/mpi_collectives/BUILD index ecac06354d..a7be92a35e 100644 --- a/tensorflow/contrib/mpi_collectives/BUILD +++ b/tensorflow/contrib/mpi_collectives/BUILD @@ -52,7 +52,6 @@ tf_custom_op_library( deps = [ ":mpi_defines", ":mpi_message_proto_cc", - "//tensorflow/stream_executor:stream_executor_headers_lib", "//third_party/mpi", ], ) diff --git a/tensorflow/stream_executor/platform/default/dso_loader.cc b/tensorflow/stream_executor/platform/default/dso_loader.cc index 0f0bce3253..668eeee3f3 100644 --- a/tensorflow/stream_executor/platform/default/dso_loader.cc +++ b/tensorflow/stream_executor/platform/default/dso_loader.cc @@ -28,7 +28,7 @@ limitations under the License. #include "tensorflow/stream_executor/lib/path.h" #include "tensorflow/stream_executor/lib/str_util.h" #include "tensorflow/stream_executor/lib/stringprintf.h" -#include "tensorflow/stream_executor/platform/dso_loader.h" +#include "tensorflow/stream_executor/platform/default/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/platform/port.h" -- GitLab From 45b2b27f6f30ec981300fb6987736f14caa45f29 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Fri, 4 Jan 2019 12:17:10 -0800 Subject: [PATCH 0200/2345] improve docs of depthwise conv --- tensorflow/python/ops/nn_impl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/ops/nn_impl.py b/tensorflow/python/ops/nn_impl.py index ec5ea44526..7abfde5149 100644 --- a/tensorflow/python/ops/nn_impl.py +++ b/tensorflow/python/ops/nn_impl.py @@ -467,7 +467,7 @@ def depthwise_conv2d(input, to `channel_multiplier` channels for each), then concatenates the results together. The output has `in_channels * channel_multiplier` channels. - In detail, + In detail, with the default NHWC format, output[b, i, j, k * channel_multiplier + q] = sum_{di, dj} filter[di, dj, k, q] * input[b, strides[1] * i + rate[0] * di, @@ -540,7 +540,7 @@ def depthwise_conv2d_v2(input, to `channel_multiplier` channels for each), then concatenates the results together. The output has `in_channels * channel_multiplier` channels. - In detail, + In detail, with the default NHWC format, output[b, i, j, k * channel_multiplier + q] = sum_{di, dj} filter[di, dj, k, q] * input[b, strides[1] * i + rate[0] * di, @@ -599,7 +599,7 @@ def separable_conv2d(input, between dimensions `[1, 2]` and `3`, not spatial separability between dimensions `1` and `2`. - In detail, + In detail, with the default NHWC format, output[b, i, j, k] = sum_{di, dj, q, r} input[b, strides[1] * i + di, strides[2] * j + dj, q] * @@ -699,7 +699,7 @@ def separable_conv2d_v2( between dimensions `[1, 2]` and `3`, not spatial separability between dimensions `1` and `2`. - In detail, + In detail, with the default NHWC format, output[b, i, j, k] = sum_{di, dj, q, r} input[b, strides[1] * i + di, strides[2] * j + dj, q] * -- GitLab From f1d4d18a622a3bb030ebc92b60763fe35a755bd5 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Fri, 4 Jan 2019 12:17:54 -0800 Subject: [PATCH 0201/2345] Add logcosh v2 loss and metric. PiperOrigin-RevId: 227894520 --- tensorflow/python/keras/losses.py | 27 +++++++++ tensorflow/python/keras/losses_test.py | 81 +++++++++++++++++++++++++ tensorflow/python/keras/metrics.py | 31 ++++++++++ tensorflow/python/keras/metrics_test.py | 43 +++++++++++++ 4 files changed, 182 insertions(+) diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index 6a428b28a9..1ff564054b 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -540,6 +540,33 @@ class LogLoss(Loss): return logloss(y_true, y_pred, epsilon=self.epsilon) +class Logcosh(Loss): + """Computes the logarithm of the hyperbolic cosine of the prediction error. + + logcosh = log((exp(x) + exp(-x))/2) where x is the error `y_pred` - `y_true`. + + Usage: + + ```python + l = tf.losses.Logcosh() + loss = l([0., 1., 1.], [1., 0., 1.]) + print('Loss: ', loss.numpy()) # Loss: 0.289 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile('sgd', loss=tf.losses.Logcosh()) + ``` + """ + + def call(self, y_true, y_pred): + y_pred = ops.convert_to_tensor(y_pred) + y_true = math_ops.cast(y_true, y_pred.dtype) + return logcosh(y_true, y_pred) + + @keras_export('keras.metrics.mean_squared_error', 'keras.metrics.mse', 'keras.metrics.MSE', diff --git a/tensorflow/python/keras/losses_test.py b/tensorflow/python/keras/losses_test.py index 76fadc4a24..aa00ba5a91 100644 --- a/tensorflow/python/keras/losses_test.py +++ b/tensorflow/python/keras/losses_test.py @@ -1094,5 +1094,86 @@ class LogLossTest(test.TestCase): self.assertAlmostEqual(self.evaluate(loss), 0., 3) +@test_util.run_all_in_graph_and_eager_modes +class LogcoshTest(test.TestCase): + + def setup(self): + y_pred = np.asarray([1, 9, 2, -5, -2, 6]).reshape((2, 3)) + y_true = np.asarray([4, 8, 12, 8, 1, 3]).reshape((2, 3)) + + self.batch_size = 6 + error = y_pred - y_true + self.expected_losses = np.log((np.exp(error) + np.exp(-error)) / 2) + + self.y_pred = constant_op.constant(y_pred, dtype=dtypes.float32) + self.y_true = constant_op.constant(y_true) + + def test_config(self): + logcosh_obj = keras.losses.Logcosh( + reduction=losses_impl.ReductionV2.SUM, name='logcosh_loss') + self.assertEqual(logcosh_obj.name, 'logcosh_loss') + self.assertEqual(logcosh_obj.reduction, losses_impl.ReductionV2.SUM) + + def test_unweighted(self): + self.setup() + logcosh_obj = keras.losses.Logcosh() + + loss = logcosh_obj(self.y_true, self.y_pred) + expected_loss = np.sum(self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_scalar_weighted(self): + self.setup() + logcosh_obj = keras.losses.Logcosh() + sample_weight = 2.3 + + loss = logcosh_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + expected_loss = sample_weight * np.sum( + self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + # Verify we get the same output when the same input is given + loss_2 = logcosh_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + self.assertAlmostEqual(self.evaluate(loss), self.evaluate(loss_2), 3) + + def test_sample_weighted(self): + self.setup() + logcosh_obj = keras.losses.Logcosh() + + sample_weight = constant_op.constant([1.2, 3.4], shape=(2, 1)) + loss = logcosh_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + + expected_loss = np.multiply( + self.expected_losses, + np.asarray([1.2, 1.2, 1.2, 3.4, 3.4, 3.4]).reshape((2, 3))) + expected_loss = np.sum(expected_loss) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_timestep_weighted(self): + self.setup() + logcosh_obj = keras.losses.Logcosh() + y_true = np.asarray([1, 9, 2, -5, -2, 6]).reshape(2, 3, 1) + y_pred = np.asarray([4, 8, 12, 8, 1, 3]).reshape(2, 3, 1) + error = y_pred - y_true + expected_losses = np.log((np.exp(error) + np.exp(-error)) / 2) + sample_weight = np.array([3, 6, 5, 0, 4, 2]).reshape((2, 3, 1)) + + y_pred = constant_op.constant(y_pred, dtype=dtypes.float32) + y_true = constant_op.constant(y_true) + loss = logcosh_obj( + y_true, + y_pred, + sample_weight=constant_op.constant(sample_weight, shape=(2, 3))) + expected_loss = np.sum(expected_losses * sample_weight) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_zero_weighted(self): + self.setup() + logcosh_obj = keras.losses.Logcosh() + sample_weight = 0 + loss = logcosh_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + self.assertAlmostEqual(self.evaluate(loss), 0., 3) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index 7f13cc46e3..2de0bc474c 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -1654,6 +1654,37 @@ class RootMeanSquaredError(Mean): return math_ops.sqrt(math_ops.div_no_nan(self.total, self.count)) +class Logcosh(MeanMetricWrapper): + """Computes the logarithm of the hyperbolic cosine of the prediction error. + + logcosh = log((exp(x) + exp(-x))/2) where x is the error `y_pred` - `y_true`. + + Usage: + + ```python + m = tf.keras.metrics.Logcosh() + m.update_state([0., 1., 1.], [1., 0., 1.]) + print('Final result: ', m.result().numpy()) # Final result: 0.289 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile('sgd', metrics=[tf.keras.metrics.Logcosh()]) + ``` + """ + + def __init__(self, name='logcosh', dtype=None): + super(Logcosh, self).__init__(logcosh, name, dtype=dtype) + + @classmethod + def from_config(cls, config): + if 'fn' in config: + config.pop('fn') + return super(Logcosh, cls).from_config(config) + + def accuracy(y_true, y_pred): y_pred.get_shape().assert_is_compatible_with(y_true.get_shape()) if y_true.dtype != y_pred.dtype: diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index fb635e7055..a9d88789ed 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -1467,6 +1467,49 @@ class SparseTopKCategoricalAccuracyTest(test.TestCase): self.assertEqual(0.5, self.evaluate(result)) # only 1 sample matches. +@test_util.run_all_in_graph_and_eager_modes +class LogcoshTest(test.TestCase): + + def setup(self): + y_pred = np.asarray([1, 9, 2, -5, -2, 6]).reshape((2, 3)) + y_true = np.asarray([4, 8, 12, 8, 1, 3]).reshape((2, 3)) + + self.batch_size = 6 + error = y_pred - y_true + self.expected_results = np.log((np.exp(error) + np.exp(-error)) / 2) + + self.y_pred = constant_op.constant(y_pred, dtype=dtypes.float32) + self.y_true = constant_op.constant(y_true) + + def test_config(self): + logcosh_obj = metrics.Logcosh(name='logcosh', dtype=dtypes.int32) + self.assertEqual(logcosh_obj.name, 'logcosh') + self.assertEqual(logcosh_obj._dtype, dtypes.int32) + + def test_unweighted(self): + self.setup() + logcosh_obj = metrics.Logcosh() + self.evaluate(variables.variables_initializer(logcosh_obj.variables)) + + update_op = logcosh_obj.update_state(self.y_true, self.y_pred) + self.evaluate(update_op) + result = logcosh_obj.result() + expected_result = np.sum(self.expected_results) / self.batch_size + self.assertAllClose(result, expected_result, atol=1e-3) + + def test_weighted(self): + self.setup() + logcosh_obj = metrics.Logcosh() + self.evaluate(variables.variables_initializer(logcosh_obj.variables)) + sample_weight = constant_op.constant([1.2, 3.4], shape=(2, 1)) + result = logcosh_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + + sample_weight = np.asarray([1.2, 1.2, 1.2, 3.4, 3.4, 3.4]).reshape((2, 3)) + expected_result = np.multiply(self.expected_results, sample_weight) + expected_result = np.sum(expected_result) / np.sum(sample_weight) + self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) + + def _get_model(compile_metrics): model_layers = [ layers.Dense(3, activation='relu', kernel_initializer='ones'), -- GitLab From f9bd1568aa6f0e0b8c40c10d73caeca80c3c19ab Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 4 Jan 2019 12:18:26 -0800 Subject: [PATCH 0202/2345] [XLA] Use absl::flat_hash_{map,set}::contains() instead of count(). contains() is a much better description of what we're trying to do! Also change a few std containers over to absl containers so we can advantage of this. PiperOrigin-RevId: 227894609 --- tensorflow/compiler/xla/BUILD | 1 + tensorflow/compiler/xla/client/xla_builder.cc | 8 +++-- tensorflow/compiler/xla/client/xla_builder.h | 3 +- tensorflow/compiler/xla/reference_util.cc | 25 ++++++++------ tensorflow/compiler/xla/service/BUILD | 9 +++++ .../xla/service/algebraic_simplifier.cc | 5 +-- tensorflow/compiler/xla/service/backend.cc | 8 ++--- tensorflow/compiler/xla/service/backend.h | 4 ++- .../compiler/xla/service/buffer_assignment.cc | 34 +++++++------------ .../compiler/xla/service/buffer_assignment.h | 7 ++-- .../xla/service/buffer_assignment_test.cc | 7 ++-- .../compiler/xla/service/channel_tracker.cc | 4 +-- .../compiler/xla/service/channel_tracker.h | 4 ++- .../xla/service/cpu/cpu_layout_assignment.cc | 3 +- .../compiler/xla/service/cpu/ir_emitter.cc | 12 +++---- .../compiler/xla/service/cpu/ir_emitter.h | 2 +- tensorflow/compiler/xla/service/gpu/BUILD | 7 +++- .../xla/service/gpu/buffer_allocations.cc | 11 +++--- .../xla/service/gpu/buffer_allocations.h | 4 ++- .../xla/service/gpu/hlo_to_ir_bindings.cc | 7 ++-- .../xla/service/gpu/hlo_to_ir_bindings.h | 6 ++-- .../xla/service/gpu/multi_output_fusion.cc | 2 +- .../xla/service/gpu/stream_assignment.cc | 9 ++--- .../xla/service/gpu/thunk_schedule.cc | 16 +++++---- .../compiler/xla/service/gpu/thunk_schedule.h | 12 ++++--- .../compiler/xla/service/heap_simulator.cc | 22 ++++++------ .../compiler/xla/service/hlo_computation.cc | 15 ++++---- .../xla/service/hlo_computation_test.cc | 5 +-- .../xla/service/hlo_dataflow_analysis.cc | 2 +- tensorflow/compiler/xla/service/hlo_dce.cc | 5 +-- .../compiler/xla/service/hlo_graph_dumper.cc | 16 ++++----- .../xla/service/hlo_instruction_test.cc | 33 +++++++++--------- .../xla/service/hlo_liveness_analysis.cc | 5 +-- tensorflow/compiler/xla/service/hlo_module.cc | 29 +++++++--------- .../xla/service/hlo_module_group_metadata.cc | 4 +-- .../xla/service/hlo_module_group_metadata.h | 2 +- .../compiler/xla/service/hlo_ordering.cc | 2 +- .../compiler/xla/service/hlo_pass_pipeline.cc | 2 +- .../compiler/xla/service/hlo_schedule.cc | 10 +++--- .../compiler/xla/service/hlo_schedule.h | 2 +- .../compiler/xla/service/hlo_sharding.cc | 5 +-- .../xla/service/hlo_sharding_metadata.cc | 4 +-- .../xla/service/indexed_array_analysis.cc | 6 ++-- .../compiler/xla/service/layout_assignment.cc | 2 +- .../compiler/xla/service/layout_assignment.h | 4 +-- tensorflow/compiler/xla/service/llvm_ir/BUILD | 1 + .../xla/service/llvm_ir/alias_analysis.h | 7 ++-- .../xla/service/llvm_ir/fused_ir_emitter.cc | 2 +- .../xla/service/llvm_ir/fused_ir_emitter.h | 6 ++-- .../xla/service/multi_output_fusion.cc | 2 +- .../xla/service/tuple_points_to_analysis.cc | 8 ++--- .../while_loop_invariant_code_motion.cc | 4 +-- .../xla/service/while_loop_simplifier.cc | 2 +- 53 files changed, 225 insertions(+), 192 deletions(-) diff --git a/tensorflow/compiler/xla/BUILD b/tensorflow/compiler/xla/BUILD index 722d137668..8a0e2db2b5 100644 --- a/tensorflow/compiler/xla/BUILD +++ b/tensorflow/compiler/xla/BUILD @@ -741,6 +741,7 @@ cc_library( "//tensorflow/compiler/xla/service:hlo_evaluator", "//tensorflow/compiler/xla/service:shape_inference", "//tensorflow/core:lib", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/memory", "@com_google_absl//absl/types:span", ], diff --git a/tensorflow/compiler/xla/client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_builder.cc index 4b752def1a..433033cae6 100644 --- a/tensorflow/compiler/xla/client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_builder.cc @@ -22,6 +22,8 @@ limitations under the License. #include #include "absl/algorithm/container.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" @@ -193,9 +195,9 @@ StatusOr XlaBuilder::GetProgramShape(XlaOp root) const { } void XlaBuilder::IsConstantVisitor(const int64 op_handle, - std::set* visited, + absl::flat_hash_set* visited, bool* is_constant) const { - if (visited->count(op_handle) != 0 || !*is_constant) { + if (visited->contains(op_handle) || !*is_constant) { return; } @@ -2415,7 +2417,7 @@ StatusOr XlaBuilder::IsConstant(const XlaOp& operand) const { TF_RETURN_IF_ERROR(LookUpInstruction(operand).status()); bool is_constant = true; - std::set visited; + absl::flat_hash_set visited; IsConstantVisitor(operand.handle(), &visited, &is_constant); return is_constant; } diff --git a/tensorflow/compiler/xla/client/xla_builder.h b/tensorflow/compiler/xla/client/xla_builder.h index 68ddf2cdd8..ebef8e0879 100644 --- a/tensorflow/compiler/xla/client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_builder.h @@ -727,7 +727,8 @@ class XlaBuilder { // operation such as `RngNormal` or `Infeed`. The visitor walks the // computation starting at a given operation and sets is_constant to false iff // a parameter or stateful operation is encountered. - void IsConstantVisitor(const int64 op_handle, std::set* visited, + void IsConstantVisitor(const int64 op_handle, + absl::flat_hash_set* visited, bool* is_constant) const; // Checks bounds for convolution parameters. diff --git a/tensorflow/compiler/xla/reference_util.cc b/tensorflow/compiler/xla/reference_util.cc index 033f6c9b0f..08b78ee244 100644 --- a/tensorflow/compiler/xla/reference_util.cc +++ b/tensorflow/compiler/xla/reference_util.cc @@ -18,6 +18,7 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/compiler/xla/literal_util.h" @@ -605,24 +606,26 @@ ReferenceUtil::ReduceToRowArray2D( const std::function& reduce_function) { std::vector result; CHECK_EQ(dims.size(), 3); - const std::set dim_set(dims.begin(), dims.end()); + const absl::flat_hash_set dim_set(dims.begin(), dims.end()); CHECK_EQ(dim_set.size(), 3); - for (int64 a0 = 0; a0 == 0 || (!dim_set.count(0) && a0 < array.n1()); ++a0) { - for (int64 a1 = 0; a1 == 0 || (!dim_set.count(1) && a1 < array.n2()); + for (int64 a0 = 0; a0 == 0 || (!dim_set.contains(0) && a0 < array.n1()); + ++a0) { + for (int64 a1 = 0; a1 == 0 || (!dim_set.contains(1) && a1 < array.n2()); ++a1) { - for (int64 a2 = 0; a2 == 0 || (!dim_set.count(2) && a2 < array.n3()); + for (int64 a2 = 0; a2 == 0 || (!dim_set.contains(2) && a2 < array.n3()); ++a2) { - for (int64 a3 = 0; a3 == 0 || (!dim_set.count(3) && a3 < array.n4()); + for (int64 a3 = 0; a3 == 0 || (!dim_set.contains(3) && a3 < array.n4()); ++a3) { float accumulator = init; - for (int64 i0 = 0; i0 == 0 || (dim_set.count(0) && i0 < array.n1()); - ++i0) { - for (int64 i1 = 0; i1 == 0 || (dim_set.count(1) && i1 < array.n2()); - ++i1) { + for (int64 i0 = 0; + i0 == 0 || (dim_set.contains(0) && i0 < array.n1()); ++i0) { + for (int64 i1 = 0; + i1 == 0 || (dim_set.contains(1) && i1 < array.n2()); ++i1) { for (int64 i2 = 0; - i2 == 0 || (dim_set.count(2) && i2 < array.n3()); ++i2) { + i2 == 0 || (dim_set.contains(2) && i2 < array.n3()); ++i2) { for (int64 i3 = 0; - i3 == 0 || (dim_set.count(3) && i3 < array.n4()); ++i3) { + i3 == 0 || (dim_set.contains(3) && i3 < array.n4()); + ++i3) { // Handle zero-sized arrays. if (array.n1() > 0 && array.n2() > 0 && array.n3() > 0 && array.n4() > 0) { diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index 755c477a12..85a21a8518 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -515,6 +515,7 @@ tf_cc_test( "//tensorflow/compiler/xla:window_util", "//tensorflow/compiler/xla/tests:hlo_test_base", "//tensorflow/compiler/xla/tests:xla_internal_test_main", + "@com_google_absl//absl/container:flat_hash_map", ], ) @@ -677,6 +678,7 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:stream_executor_no_cuda", "//third_party/eigen3", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", @@ -1002,6 +1004,7 @@ cc_library( "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", @@ -1136,6 +1139,7 @@ tf_cc_test( "//tensorflow/compiler/xla/tests:xla_internal_test_main", "//tensorflow/core:lib", "//tensorflow/core:test", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/memory", ], ) @@ -1580,6 +1584,7 @@ cc_library( "//tensorflow/core:lib", "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/container:inlined_vector", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", @@ -2116,6 +2121,7 @@ tf_cc_test( "//tensorflow/compiler/xla:test_helpers", "//tensorflow/compiler/xla/tests:hlo_test_base", "//tensorflow/compiler/xla/tests:xla_internal_test_main", + "@com_google_absl//absl/container:flat_hash_set", ], ) @@ -2288,6 +2294,7 @@ cc_library( "//tensorflow/compiler/xla:types", "//tensorflow/compiler/xla:util", "//tensorflow/core:lib", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", ], @@ -2548,6 +2555,7 @@ cc_library( "//tensorflow/compiler/xla:types", "//tensorflow/compiler/xla:util", "//tensorflow/core:lib", + "@com_google_absl//absl/container:flat_hash_set", ], ) @@ -3188,6 +3196,7 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:regexp_internal", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:optional", diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc index c573b60161..3d269dc04f 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc @@ -27,6 +27,7 @@ limitations under the License. #include "absl/algorithm/container.h" #include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/container/inlined_vector.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" @@ -2539,7 +2540,7 @@ Status AlgebraicSimplifierVisitor::HandleReduce(HloInstruction* reduce) { } if (can_move_reshape_into_reduce) { changed_ = true; - std::unordered_set dimensions_not_to_reduce; + absl::flat_hash_set dimensions_not_to_reduce; for (auto dim_pair : unmodified_dims) { if (arg_dim_in_output[dim_pair.second]) { dimensions_not_to_reduce.insert(dim_pair.first); @@ -2547,7 +2548,7 @@ Status AlgebraicSimplifierVisitor::HandleReduce(HloInstruction* reduce) { } std::vector new_reduce_dimensions; for (int64 i = 0; i < arg->operand(0)->shape().rank(); ++i) { - if (dimensions_not_to_reduce.count(i) == 0) { + if (!dimensions_not_to_reduce.contains(i)) { new_reduce_dimensions.push_back(i); } } diff --git a/tensorflow/compiler/xla/service/backend.cc b/tensorflow/compiler/xla/service/backend.cc index 2cf24a9dd5..215e8ced4b 100644 --- a/tensorflow/compiler/xla/service/backend.cc +++ b/tensorflow/compiler/xla/service/backend.cc @@ -115,12 +115,10 @@ StatusOr Backend::BorrowStream(int device_ordinal) { StatusOr Backend::BorrowStream(se::StreamExecutor* executor) { tensorflow::mutex_lock l(mu_); - if (0 == stream_pools_.count(executor)) { - stream_pools_.emplace(std::piecewise_construct, - std::forward_as_tuple(executor), - std::forward_as_tuple()); + if (!stream_pools_.contains(executor)) { + stream_pools_.emplace(executor, absl::make_unique()); } - return stream_pools_.at(executor).BorrowStream(executor); + return stream_pools_.at(executor)->BorrowStream(executor); } Backend::Backend(se::Platform* platform, Compiler* compiler, diff --git a/tensorflow/compiler/xla/service/backend.h b/tensorflow/compiler/xla/service/backend.h index 7ca993fb26..c35f033dc0 100644 --- a/tensorflow/compiler/xla/service/backend.h +++ b/tensorflow/compiler/xla/service/backend.h @@ -22,6 +22,7 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_map.h" #include "absl/strings/str_cat.h" #include "absl/types/span.h" #include "tensorflow/compiler/xla/service/compiler.h" @@ -175,7 +176,8 @@ class Backend { tensorflow::mutex mu_; // Mapping from stream executor to stream pools, used by `BorrowStream` above. - std::map stream_pools_ GUARDED_BY(mu_); + absl::flat_hash_map> + stream_pools_ GUARDED_BY(mu_); // The default memory allocator to use. std::unique_ptr memory_allocator_; diff --git a/tensorflow/compiler/xla/service/buffer_assignment.cc b/tensorflow/compiler/xla/service/buffer_assignment.cc index 6f4c1104f3..c997d594c4 100644 --- a/tensorflow/compiler/xla/service/buffer_assignment.cc +++ b/tensorflow/compiler/xla/service/buffer_assignment.cc @@ -138,8 +138,8 @@ Status GatherComputationsByAllocationType( worklist.pop_front(); const HloComputation* computation = worklist_front.first; bool is_thread_local = worklist_front.second; - bool in_thread_local_set = thread_local_set.count(computation) > 0; - bool in_global_set = global_set.count(computation) > 0; + bool in_thread_local_set = thread_local_set.contains(computation); + bool in_global_set = global_set.contains(computation); // If the computation has already been added to the respective set, then // nothing to do. @@ -207,9 +207,9 @@ Status GatherComputationsByAllocationType( // Add the computations to the vectors in post order. for (auto* computation : module->MakeComputationPostOrder()) { - if (thread_local_set.count(computation) > 0) { + if (thread_local_set.contains(computation)) { thread_local_computations->push_back(computation); - } else if (global_set.count(computation) > 0) { + } else if (global_set.contains(computation)) { global_computations->push_back(computation); } // If the computation is not reachable from the entry computation, then it @@ -219,13 +219,6 @@ Status GatherComputationsByAllocationType( return Status::OK(); } -size_t BufferAllocation::Slice::Hasher::operator()(Slice s) const { - uint64 h = std::hash()(s.index()); - h = tensorflow::Hash64Combine(h, std::hash()(s.offset())); - h = tensorflow::Hash64Combine(h, std::hash()(s.size())); - return h; -} - string BufferAllocation::Slice::ToString() const { return absl::StrCat("{index:", index(), ", offset:", offset_, ", size:", size_, "}"); @@ -240,7 +233,7 @@ BufferAllocation::Slice BufferAllocation::GetSlice( void BufferAllocation::AddAssignment(const LogicalBuffer& buffer, int64 offset, int64 size) { VLOG(4) << "Trying to add " << buffer << " to allocation #" << index(); - CHECK(assigned_buffers_.count(&buffer) == 0) + CHECK(!assigned_buffers_.contains(&buffer)) << "LogicalBuffer " << buffer << " already assigned to allocation " << index_; CHECK_LE(offset, size_) << "LogicalBuffer " << buffer @@ -346,7 +339,7 @@ const PointsToSet& BufferAssignment::GetPointsToSet( bool BufferAssignment::HasAllocation(const LogicalBuffer& buffer) const { TF_CHECK_OK(points_to_analysis().VerifyBuffer(buffer)); - return allocation_index_for_buffer_.count(&buffer) > 0; + return allocation_index_for_buffer_.contains(&buffer); } const BufferAllocation& BufferAssignment::GetAssignedAllocation( @@ -401,7 +394,7 @@ bool BufferAssignment::HasAllocationAt(const HloInstruction* instruction, const ShapeIndex& index) const { for (const LogicalBuffer* buffer : GetPointsToSet(instruction).element(index)) { - if (allocation_index_for_buffer_.count(buffer) > 0) { + if (allocation_index_for_buffer_.contains(buffer)) { return true; } } @@ -459,8 +452,7 @@ bool BufferAssignment::SharesSliceAtIndex( bool BufferAssignment::HaveDisjointSlices(const HloInstruction* hlo_a, const HloInstruction* hlo_b) const { - using SliceSet = - flat_hash_set; + using SliceSet = flat_hash_set; // Gets the slices all of instr's subshapes. If any subshape doesn't have an // assigned slice, returns the empty set. auto collect_slices = [&](const HloInstruction* instr) -> SliceSet { @@ -519,7 +511,7 @@ BufferAllocation* BufferAssignment::NewAllocation(const LogicalBuffer& buffer, void BufferAssignment::AddAssignment(BufferAllocation* allocation, const LogicalBuffer& buffer, int64 offset, int64 size) { - CHECK_EQ(0, allocation_index_for_buffer_.count(&buffer)) + CHECK(!allocation_index_for_buffer_.contains(&buffer)) << "LogicalBuffer " << buffer << " already has an allocation."; CHECK(allocation->is_reusable() || allocation->assigned_buffers().empty()) << "Non-reusable allocation already assigned a buffer: " @@ -988,7 +980,7 @@ Status BufferAssigner::AssignBuffersForComputation( std::vector allocation_indices; for (const LogicalBuffer* buffer : sorted_buffers) { VLOG(3) << "Assigning allocation to: " << *buffer; - if (colocated_buffers.count(buffer) > 0) { + if (colocated_buffers.contains(buffer)) { // Colocated buffers are currently assigned in an earlier pass. VLOG(3) << "Skipping colocated buffer: " << *buffer; continue; @@ -1056,7 +1048,7 @@ Status BufferAssigner::AssignBuffersForComputation( assignment->GetAllSlices(operand, /*index=*/{})) { BufferAllocation* allocation = assignment->GetMutableAllocation(operand_slice.index()); - if (colocated_allocations.count(allocation->index()) == 0) { + if (!colocated_allocations.contains(allocation->index())) { // TODO(b/32491382) Colocated buffers are currently assigned in an // earlier pass, and so can break the "increasing allocation size" // invariant in this function (causing this CHECK to fail). However, @@ -1087,7 +1079,7 @@ Status BufferAssigner::AssignBuffersForComputation( // Instructions are iterated in increasing buffer size, so any // previously create allocation must be large enough to hold this // instruction's output (with the exception of colocated buffers). - if (colocated_allocations.count(allocation->index()) == 0) { + if (!colocated_allocations.contains(allocation->index())) { // TODO(b/32491382) Colocated buffers are currently assigned in an // earlier pass, and so can break the "increasing allocation size" // invariant in this function (causing this CHECK to fail). However, @@ -1376,7 +1368,7 @@ void BufferAssigner::AddSetToColocatedBufferSets( std::vector overlap_set_indices; for (size_t index = 0; index < colocated_buffer_sets->size(); ++index) { for (const LogicalBuffer* buffer : colocated_set) { - if ((*colocated_buffer_sets)[index].count(buffer) > 0) { + if ((*colocated_buffer_sets)[index].contains(buffer)) { VLOG(5) << "Found overlap with existing set on buffer " << buffer->ToString() << "\n" << ColocatedBufferSetsToString((*colocated_buffer_sets)[index], diff --git a/tensorflow/compiler/xla/service/buffer_assignment.h b/tensorflow/compiler/xla/service/buffer_assignment.h index 0a9fdede80..4baab9b6ad 100644 --- a/tensorflow/compiler/xla/service/buffer_assignment.h +++ b/tensorflow/compiler/xla/service/buffer_assignment.h @@ -186,9 +186,10 @@ class BufferAllocation { end > other.offset_; } - struct Hasher { - size_t operator()(Slice s) const; - }; + template + friend H AbslHashValue(H h, const Slice& s) { + return H::combine(std::move(h), s.index(), s.offset(), s.size()); + } string ToString() const; diff --git a/tensorflow/compiler/xla/service/buffer_assignment_test.cc b/tensorflow/compiler/xla/service/buffer_assignment_test.cc index 8f482e6ba8..370fad7ffb 100644 --- a/tensorflow/compiler/xla/service/buffer_assignment_test.cc +++ b/tensorflow/compiler/xla/service/buffer_assignment_test.cc @@ -21,6 +21,7 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "tensorflow/compiler/xla/literal.h" #include "tensorflow/compiler/xla/service/buffer_value.h" @@ -309,7 +310,7 @@ class BufferAssignmentTest : public HloTestBase { static bool BuffersDistinct(const std::vector& a, const std::vector& b, const BufferAssignment& assignment) { - std::set a_slices; + absl::flat_hash_set a_slices; for (const HloInstruction* instruction : a) { if (assignment.HasTopLevelAllocation(instruction)) { a_slices.insert( @@ -319,8 +320,8 @@ static bool BuffersDistinct(const std::vector& a, for (const HloInstruction* instruction : b) { if (assignment.HasTopLevelAllocation(instruction)) { - if (a_slices.count(assignment.GetUniqueTopLevelSlice(instruction) - .ConsumeValueOrDie())) { + if (a_slices.contains(assignment.GetUniqueTopLevelSlice(instruction) + .ConsumeValueOrDie())) { return false; } } diff --git a/tensorflow/compiler/xla/service/channel_tracker.cc b/tensorflow/compiler/xla/service/channel_tracker.cc index 3c2d1ae6d8..b517495f2e 100644 --- a/tensorflow/compiler/xla/service/channel_tracker.cc +++ b/tensorflow/compiler/xla/service/channel_tracker.cc @@ -72,7 +72,7 @@ ChannelHandle ChannelTracker::AllocateHandle(ChannelHandle::ChannelType type) { } Status ChannelTracker::RegisterSendInternal(const ChannelHandle& handle) { - if (opaque_to_channel_.count(handle.handle()) == 0) { + if (!opaque_to_channel_.contains(handle.handle())) { return NotFound("channel handle not found: %d", handle.handle()); } Channel& channel = opaque_to_channel_[handle.handle()]; @@ -94,7 +94,7 @@ Status ChannelTracker::RegisterSendInternal(const ChannelHandle& handle) { } Status ChannelTracker::RegisterRecvInternal(const ChannelHandle& handle) { - if (opaque_to_channel_.count(handle.handle()) == 0) { + if (!opaque_to_channel_.contains(handle.handle())) { return NotFound("channel handle not found: %d", handle.handle()); } Channel& channel = opaque_to_channel_[handle.handle()]; diff --git a/tensorflow/compiler/xla/service/channel_tracker.h b/tensorflow/compiler/xla/service/channel_tracker.h index 52037bf9b5..89e17eba36 100644 --- a/tensorflow/compiler/xla/service/channel_tracker.h +++ b/tensorflow/compiler/xla/service/channel_tracker.h @@ -18,6 +18,7 @@ limitations under the License. #include +#include "absl/container/flat_hash_map.h" #include "absl/types/span.h" #include "tensorflow/compiler/xla/service/hlo_module.h" #include "tensorflow/compiler/xla/status.h" @@ -83,7 +84,8 @@ class ChannelTracker { // Mapping from ChannelHandle value to the corresponding registered // Channel object. - std::map opaque_to_channel_ GUARDED_BY(channel_mutex_); + absl::flat_hash_map opaque_to_channel_ + GUARDED_BY(channel_mutex_); TF_DISALLOW_COPY_AND_ASSIGN(ChannelTracker); }; diff --git a/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc b/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc index 1cb154e9ca..ff11409482 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc @@ -46,8 +46,7 @@ static bool ShouldMakeAllUsersColMajor(const HloInstruction* instruction) { for (auto* user : instruction->users()) { optional operand_idx = ProfitableToMakeDotOperandColumnMajor(*user); if (!operand_idx || user->operand(*operand_idx) != instruction || - std::count(user->operands().begin(), user->operands().end(), - instruction) != 1) { + absl::c_count(user->operands(), instruction) != 1) { return false; } } diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc index 169d628923..e4760aedf1 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc @@ -24,11 +24,9 @@ limitations under the License. #include #include +// IWYU pragma: no_include "llvm/IR/Intrinsics.gen.inc" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" -#include "tensorflow/core/lib/math/math_util.h" -#include "tensorflow/core/platform/logging.h" -// IWYU pragma: no_include "llvm/IR/Intrinsics.gen.inc" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/types/span.h" @@ -70,6 +68,8 @@ limitations under the License. #include "tensorflow/compiler/xla/window_util.h" #include "tensorflow/core/lib/core/bits.h" #include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/lib/math/math_util.h" +#include "tensorflow/core/platform/logging.h" namespace xla { @@ -1399,7 +1399,7 @@ static bool ReductionPreservesLayout(const HloInstruction& reduce) { int64 delta = 0; for (int64 i = 0; i < operand_shape.dimensions_size(); i++) { - if (reduced_dims.count(i)) { + if (reduced_dims.contains(i)) { delta++; } else { InsertOrDie(&unreduced_dim_map, i, i - delta); @@ -1412,7 +1412,7 @@ static bool ReductionPreservesLayout(const HloInstruction& reduce) { for (int64 operand_dim_idx = 0; operand_dim_idx < operand_shape.dimensions_size(); operand_dim_idx++) { int64 operand_dim = operand_shape.layout().minor_to_major(operand_dim_idx); - if (!reduced_dims.count(operand_dim)) { + if (!reduced_dims.contains(operand_dim)) { if (FindOrDie(unreduced_dim_map, operand_dim) != result_shape.layout().minor_to_major(result_dim_idx++)) { return false; @@ -1990,7 +1990,7 @@ Status IrEmitter::HandleSlice(HloInstruction* slice) { // The memcpy will copy elements that are logically this shape (allowed to be // scalar). const Shape logical_element_shape = ShapeUtil::FilterDimensions( - [&inner_dims](int64 dim) -> bool { return inner_dims.count(dim); }, + [&inner_dims](int64 dim) { return inner_dims.contains(dim); }, operand->shape()); const int64 primitive_elements_per_logical_element = diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.h b/tensorflow/compiler/xla/service/cpu/ir_emitter.h index db76de4bb2..a6fb11dcbf 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.h +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.h @@ -448,7 +448,7 @@ class IrEmitter : public DfsHloVisitorWithDefault, computation_to_profile_idx_; // Maps HLOs to Values emitted for them. - std::unordered_map emitted_value_; + absl::flat_hash_map emitted_value_; llvm_ir::AliasAnalysis alias_analysis_; diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index 6c23f921f4..d10a11d096 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -94,8 +94,8 @@ cc_library( "//tensorflow/compiler/xla:util", "//tensorflow/compiler/xla/service:hlo", "//tensorflow/compiler/xla/service:hlo_reachability", - "//tensorflow/core:lib", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/memory", ], ) @@ -135,6 +135,8 @@ cc_library( "//tensorflow/compiler/xla/service/llvm_ir:llvm_util", "//tensorflow/compiler/xla/service/llvm_ir:tuple_ops", "//tensorflow/core:lib", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", "@llvm//:core", @@ -263,7 +265,9 @@ cc_library( "//tensorflow/compiler/xla/service:buffer_assignment", "//tensorflow/compiler/xla/service:device_memory_allocator", "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", "//tensorflow/core:stream_executor_no_cuda", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/memory", "@com_google_absl//absl/types:span", ], @@ -362,6 +366,7 @@ cc_library( "//tensorflow/core/platform/default/build_config:stream_executor_cuda", # build_cleaner: keep "//tensorflow/stream_executor", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", diff --git a/tensorflow/compiler/xla/service/gpu/buffer_allocations.cc b/tensorflow/compiler/xla/service/gpu/buffer_allocations.cc index 528209abc7..eb59ee5a1d 100644 --- a/tensorflow/compiler/xla/service/gpu/buffer_allocations.cc +++ b/tensorflow/compiler/xla/service/gpu/buffer_allocations.cc @@ -24,6 +24,7 @@ limitations under the License. #include "tensorflow/compiler/xla/types.h" #include "tensorflow/compiler/xla/util.h" #include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/lib/gtl/map_util.h" #include "tensorflow/core/lib/strings/numbers.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/types.h" @@ -57,16 +58,16 @@ StatusOr> BufferAllocations::Builder::Build( // If buffer #i's address is already registered (e.g. external arguments or // result buffers), use that registered buffer. - if (registered_buffers_.count(i)) { - se::DeviceMemoryBase address = FindOrDie(registered_buffers_, i); - if (reinterpret_cast(address.opaque()) % expected_alignment != + if (se::DeviceMemoryBase* address = + tensorflow::gtl::FindOrNull(registered_buffers_, i)) { + if (reinterpret_cast(address->opaque()) % expected_alignment != 0) { return InternalError( "Address of registered buffer %d must be a multiple of %x, but " "was %p", - i, kEntryParameterAlignBytes, address.opaque()); + i, kEntryParameterAlignBytes, address->opaque()); } - buffer_allocations->SetBuffer(i, FindOrDie(registered_buffers_, i)); + buffer_allocations->SetBuffer(i, *address); continue; } diff --git a/tensorflow/compiler/xla/service/gpu/buffer_allocations.h b/tensorflow/compiler/xla/service/gpu/buffer_allocations.h index 14186b8faa..9413ac2cff 100644 --- a/tensorflow/compiler/xla/service/gpu/buffer_allocations.h +++ b/tensorflow/compiler/xla/service/gpu/buffer_allocations.h @@ -20,6 +20,7 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_map.h" #include "absl/types/span.h" #include "tensorflow/compiler/xla/service/buffer_assignment.h" #include "tensorflow/compiler/xla/service/device_memory_allocator.h" @@ -52,7 +53,8 @@ class BufferAllocations { DeviceMemoryAllocator* memory_allocator); private: - std::map registered_buffers_; + absl::flat_hash_map + registered_buffers_; }; ~BufferAllocations(); diff --git a/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.cc b/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.cc index 9634c92786..69aaaceca1 100644 --- a/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.cc +++ b/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.h" +#include "absl/container/flat_hash_set.h" #include "absl/strings/str_cat.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Function.h" @@ -45,10 +46,10 @@ void HloToIrBindings::EmitBasePointersForHlos( // An HLO can have duplicated operands. This data structure remembers which // operand HLOs are already bound to avoid rebinding the same HLO. - std::set already_bound_for_this_function; + absl::flat_hash_set already_bound_for_this_function; auto arg_iter = function->arg_begin(); for (const HloInstruction* io_hlo : io_hlos) { - if (!already_bound_for_this_function.count(io_hlo)) { + if (!already_bound_for_this_function.contains(io_hlo)) { if (!is_nested_ && io_hlo->opcode() == HloOpcode::kGetTupleElement) { BindHloToIrValue(*io_hlo, EmitGetTupleElement(io_hlo, &*arg_iter)); } else { @@ -63,7 +64,7 @@ void HloToIrBindings::EmitBasePointersForHlos( temp_buffer_base_->setName("temp_buffer"); for (const HloInstruction* non_io_hlo : non_io_hlos) { - if (already_bound_for_this_function.count(non_io_hlo)) { + if (already_bound_for_this_function.contains(non_io_hlo)) { continue; } already_bound_for_this_function.insert(non_io_hlo); diff --git a/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.h b/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.h index c0edae530c..f57b594e9c 100644 --- a/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.h +++ b/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.h @@ -18,6 +18,7 @@ limitations under the License. #include +#include "absl/container/flat_hash_map.h" #include "absl/types/span.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Value.h" @@ -61,7 +62,7 @@ class HloToIrBindings { // Returns whether `hlo` is bound to an LLVM IR value. bool BoundToIrValue(const HloInstruction& hlo) const { - return base_ptrs_.count(&hlo); + return base_ptrs_.contains(&hlo); } llvm::Value* GetTempBufferBase() const { return temp_buffer_base_; } @@ -110,7 +111,8 @@ class HloToIrBindings { // For an instruction that generates multiple outputs, the root will be a // tuple shape. The IrArray for each element output is stored in the subnode // in the ShapeTree. - std::unordered_map> base_ptrs_; + absl::flat_hash_map> + base_ptrs_; // The address of the memory block that contains all temporary buffers. llvm::Value* temp_buffer_base_ = nullptr; diff --git a/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc b/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc index 01fddcede6..02e1207f37 100644 --- a/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc +++ b/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc @@ -67,7 +67,7 @@ int64 GpuMultiOutputFusion::GetProfit(HloInstruction* instr1, } int64 profit = 0; for (auto instr : instr2->operands()) { - if (!IsProfitableOperand(instr) || in_list.count(instr) == 0) { + if (!IsProfitableOperand(instr) || !in_list.contains(instr)) { continue; } profit += ShapeUtil::ByteSizeOf(instr->shape()); diff --git a/tensorflow/compiler/xla/service/gpu/stream_assignment.cc b/tensorflow/compiler/xla/service/gpu/stream_assignment.cc index 4775baf44a..1dedbd3bef 100644 --- a/tensorflow/compiler/xla/service/gpu/stream_assignment.cc +++ b/tensorflow/compiler/xla/service/gpu/stream_assignment.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/gpu/stream_assignment.h" +#include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "tensorflow/compiler/xla/map_util.h" #include "tensorflow/compiler/xla/service/gpu/ir_emission_utils.h" @@ -25,7 +26,7 @@ namespace xla { namespace gpu { bool StreamAssignment::HasStreamAssigned(const HloInstruction& hlo) const { - return hlo_to_stream_number_.count(&hlo); + return hlo_to_stream_number_.contains(&hlo); } int StreamAssignment::StreamNumberForHlo(const HloInstruction& hlo) const { @@ -98,10 +99,10 @@ int ComputeStreamToAssign( // greedy approach. First, we compute as forbidden_stream_numbers the // streams assigned to GEMMs that are concurrent with `hlo`. Then, we assign // `hlo` a different stream. - std::set forbidden_stream_numbers; + absl::flat_hash_set forbidden_stream_numbers; for (const auto* seen_gemm : seen_gemms) { int stream_num = stream_assignment.StreamNumberForHlo(*seen_gemm); - if (!forbidden_stream_numbers.count(stream_num) && + if (!forbidden_stream_numbers.contains(stream_num) && CanRunConcurrently(*seen_gemm, hlo, reachability)) { forbidden_stream_numbers.insert(stream_num); } @@ -109,7 +110,7 @@ int ComputeStreamToAssign( for (int stream_num = 0; stream_num < stream_assignment.StreamCount(); ++stream_num) { - if (!forbidden_stream_numbers.count(stream_num)) { + if (!forbidden_stream_numbers.contains(stream_num)) { return stream_num; } } diff --git a/tensorflow/compiler/xla/service/gpu/thunk_schedule.cc b/tensorflow/compiler/xla/service/gpu/thunk_schedule.cc index 6b2d76764a..25bad67bab 100644 --- a/tensorflow/compiler/xla/service/gpu/thunk_schedule.cc +++ b/tensorflow/compiler/xla/service/gpu/thunk_schedule.cc @@ -14,17 +14,19 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/compiler/xla/service/gpu/thunk_schedule.h" +#include "absl/container/flat_hash_map.h" #include "tensorflow/compiler/xla/array2d.h" #include "tensorflow/compiler/xla/map_util.h" #include "tensorflow/compiler/xla/types.h" +#include "tensorflow/core/lib/gtl/map_util.h" namespace xla { namespace gpu { void ThunkSchedule::AddDependenciesOnTransitiveOperands( const Thunk& thunk, const HloInstruction& operand, - const std::unordered_map& hlo_to_thunk) { - if (hlo_to_thunk.count(&operand)) { + const absl::flat_hash_map& hlo_to_thunk) { + if (hlo_to_thunk.contains(&operand)) { // If `operand` is mapped to a thunk, adds `operand` to `thunk`'s dependency // list if `operand` is assigned to a different stream. As an optimization, // we skip `operand`'s operands because `operand` depends on them already. @@ -48,14 +50,14 @@ ThunkSchedule::ThunkSchedule( const std::vector& hlo_total_order) : thunks_(std::move(thunks)), stream_assignment_(std::move(stream_assignment)) { - std::unordered_map hlo_to_thunk; + absl::flat_hash_map hlo_to_thunk; for (const auto& thunk : *thunks_) { InsertOrDie(&hlo_to_thunk, thunk->hlo_instruction(), thunk.get()); } for (HloInstruction* hlo : hlo_total_order) { - if (hlo_to_thunk.count(hlo)) { - thunk_total_order_.push_back(FindOrDie(hlo_to_thunk, hlo)); + if (Thunk** thunk = tensorflow::gtl::FindOrNull(hlo_to_thunk, hlo)) { + thunk_total_order_.push_back(*thunk); } } @@ -106,7 +108,7 @@ void ThunkSchedule::RemoveRedundantDependencyEdges() { // redundant dependency edge. Array2D last_dependency(stream_count, stream_count, -1); for (const Thunk* dst : thunk_total_order_) { - if (!depends_on_.count(dst)) { + if (!depends_on_.contains(dst)) { continue; } @@ -134,7 +136,7 @@ void ThunkSchedule::RemoveRedundantDependencyEdges() { const std::list& ThunkSchedule::DependsOn( const Thunk* thunk) const { - if (depends_on_.count(thunk)) { + if (depends_on_.contains(thunk)) { return FindOrDie(depends_on_, thunk); } else { return empty_thunk_list_; diff --git a/tensorflow/compiler/xla/service/gpu/thunk_schedule.h b/tensorflow/compiler/xla/service/gpu/thunk_schedule.h index 43b628a1ba..549378debd 100644 --- a/tensorflow/compiler/xla/service/gpu/thunk_schedule.h +++ b/tensorflow/compiler/xla/service/gpu/thunk_schedule.h @@ -21,6 +21,8 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/compiler/xla/service/gpu/stream_assignment.h" #include "tensorflow/compiler/xla/service/gpu/thunk.h" #include "tensorflow/compiler/xla/service/hlo_instruction.h" @@ -54,7 +56,9 @@ class ThunkSchedule { // Thunks that `thunk` depends on. const std::list& DependsOn(const Thunk* thunk) const; // Whether `thunk` is depended by another thunk. - bool Depended(const Thunk* thunk) const { return depended_by_.count(thunk); } + bool Depended(const Thunk* thunk) const { + return depended_by_.contains(thunk); + } // Delegates to StreamAssignment. int StreamCount() const { return stream_assignment_->StreamCount(); } @@ -75,13 +79,13 @@ class ThunkSchedule { // thunk.hlo_instruction(). void AddDependenciesOnTransitiveOperands( const Thunk& thunk, const HloInstruction& operand, - const std::unordered_map& hlo_to_thunk); + const absl::flat_hash_map& hlo_to_thunk); std::unique_ptr thunks_; std::vector thunk_total_order_; - std::unordered_map> depends_on_; - std::set depended_by_; + absl::flat_hash_map> depends_on_; + absl::flat_hash_set depended_by_; std::list empty_thunk_list_; std::unique_ptr stream_assignment_; diff --git a/tensorflow/compiler/xla/service/heap_simulator.cc b/tensorflow/compiler/xla/service/heap_simulator.cc index 9220865867..70ae7078ae 100644 --- a/tensorflow/compiler/xla/service/heap_simulator.cc +++ b/tensorflow/compiler/xla/service/heap_simulator.cc @@ -199,7 +199,7 @@ Status HeapSimulator::RunComputation( // If the buffer has no users and isn't an entry parameter or output, it // must be a dead value. - if (live_buffers.count(buffer) == 0) { + if (!live_buffers.contains(buffer)) { dead_buffers_to_free.push_back(buffer); } } @@ -253,7 +253,7 @@ Status HeapSimulator::RunComputation( bool shared = false; if (options_.may_reuse_operand_buffers) { for (const BufferValue* operand_buffer : operand_buffers_to_free) { - if (reused_buffers.count(operand_buffer) != 0) { + if (reused_buffers.contains(operand_buffer)) { continue; } if (buffer->instruction()->IsUserOf(operand_buffer->instruction()) && @@ -374,15 +374,15 @@ bool HeapSimulator::IgnoreBuffer(const BufferValue* buffer) const { return true; } return options_.buffers_to_assign != nullptr && - options_.buffers_to_assign->count(buffer) == 0; + !options_.buffers_to_assign->contains(buffer); } // Alloc always calls the underlying heap algorithm. void HeapSimulator::Alloc(const BufferValue* buffer, const HloInstruction* instruction) { - CHECK(allocated_buffers_.count(buffer) == 0) + CHECK(!allocated_buffers_.contains(buffer)) << "Alloc called on allocated buffer: " << *buffer; - CHECK(freed_buffers_.count(buffer) == 0) + CHECK(!freed_buffers_.contains(buffer)) << "Alloc called on freed buffer: " << *buffer; allocated_buffers_.insert(buffer); @@ -411,9 +411,9 @@ void HeapSimulator::Free(const BufferValue* buffer, buffer = group->canonical; } - CHECK(allocated_buffers_.count(buffer) > 0) + CHECK(allocated_buffers_.contains(buffer)) << "Free called on non-allocated buffer: " << *buffer; - CHECK(freed_buffers_.count(buffer) == 0) + CHECK(!freed_buffers_.contains(buffer)) << "Free called on freed buffer: " << *buffer; freed_buffers_.insert(buffer); @@ -433,11 +433,11 @@ void HeapSimulator::ShareBuffer(const BufferValue* buffer, const HloInstruction* instruction) { CHECK_LE(size_fn_(*buffer), size_fn_(*shared)) << "ShareBuffer oversized buffer" << *buffer << " shared: " << *shared; - CHECK(allocated_buffers_.count(buffer) == 0) + CHECK(!allocated_buffers_.contains(buffer)) << "ShareBuffer called on allocated buffer: " << *buffer; - CHECK(freed_buffers_.count(buffer) == 0) + CHECK(!freed_buffers_.contains(buffer)) << "ShareBuffer called on freed buffer: " << *buffer; - CHECK(freed_buffers_.count(shared) == 0) + CHECK(!freed_buffers_.contains(shared)) << "ShareBuffer called on freed shared buffer: " << *shared; const BufferValue* canonical = nullptr; @@ -452,7 +452,7 @@ void HeapSimulator::ShareBuffer(const BufferValue* buffer, } else { // The 'shared' buffer doesn't have a group; it must be the canonical. Add // both 'buffer' and 'shared' to a new group. - CHECK(allocated_buffers_.count(shared) > 0) + CHECK(allocated_buffers_.contains(shared)) << "ShareBuffer called on non-allocated shared buffer: " << *shared; auto group = std::make_shared(); canonical = shared; diff --git a/tensorflow/compiler/xla/service/hlo_computation.cc b/tensorflow/compiler/xla/service/hlo_computation.cc index 52ca67afb8..10f0cf3a34 100644 --- a/tensorflow/compiler/xla/service/hlo_computation.cc +++ b/tensorflow/compiler/xla/service/hlo_computation.cc @@ -207,14 +207,14 @@ Status HloComputation::RemoveInstructionAndUnusedOperands( TF_RET_CHECK(instruction->user_count() == 0); TF_RET_CHECK(IsRemovable(instruction)) << "Cannot remove instruction: " << instruction->ToString(); - std::unordered_set removed; + absl::flat_hash_set removed; std::queue worklist; worklist.push(instruction); while (!worklist.empty()) { HloInstruction* item = worklist.front(); worklist.pop(); - if (removed.count(item) != 0 || item->user_count() != 0 || + if (removed.contains(item) || item->user_count() != 0 || item == root_instruction() || !IsRemovable(item) || (item->HasSideEffect() && item != instruction)) { continue; @@ -694,13 +694,14 @@ bool HloComputation::operator==(const HloComputation& other) const { if (this == &other) { return true; } - std::set> visited; + absl::flat_hash_set> + visited; std::function eq = [&visited, &eq](const HloInstruction* a, const HloInstruction* b) { // If are visited but not identical, the recursion should have // been aborted. So, if are visited at this point, they must be // identical. - if (visited.count(std::make_pair(a, b)) > 0) { + if (visited.contains(std::make_pair(a, b))) { return true; } visited.emplace(a, b); @@ -803,13 +804,13 @@ Status HloComputation::AcceptOrdered( << root->ToString(); } TF_RET_CHECK(order.size() == instruction_count()); - std::unordered_set visited; + absl::flat_hash_set visited; for (const HloInstruction* instruction : order) { VLOG(3) << "Visiting ordered: " << instruction->ToString(); - TF_RET_CHECK(instruction_iterators_.count(instruction) == 1) + TF_RET_CHECK(instruction_iterators_.contains(instruction)) << "Instruction " << instruction->name() << " is not in computation " << name(); - TF_RET_CHECK(visited.count(instruction) == 0) + TF_RET_CHECK(!visited.contains(instruction)) << "Instruction " << instruction->name() << " appears more than once in order"; HloInstruction* mutable_instruction = diff --git a/tensorflow/compiler/xla/service/hlo_computation_test.cc b/tensorflow/compiler/xla/service/hlo_computation_test.cc index 0361c87428..216d56b868 100644 --- a/tensorflow/compiler/xla/service/hlo_computation_test.cc +++ b/tensorflow/compiler/xla/service/hlo_computation_test.cc @@ -17,6 +17,7 @@ limitations under the License. #include +#include "absl/container/flat_hash_set.h" #include "tensorflow/compiler/xla/literal.h" #include "tensorflow/compiler/xla/service/dfs_hlo_visitor_with_default.h" #include "tensorflow/compiler/xla/service/hlo_instruction.h" @@ -226,7 +227,7 @@ TEST_F(HloComputationTest, VisitWithMultipleRoots) { : computation_(computation) {} Status DefaultAction(HloInstruction* hlo_instruction) override { - EXPECT_EQ(0, visited_set_.count(hlo_instruction)); + EXPECT_FALSE(visited_set_.contains(hlo_instruction)); visited_set_.insert(hlo_instruction); last_visited_ = hlo_instruction; return Status::OK(); @@ -239,7 +240,7 @@ TEST_F(HloComputationTest, VisitWithMultipleRoots) { } HloComputation* computation_; - std::set visited_set_; + absl::flat_hash_set visited_set_; int64 finish_visit_calls_ = 0; HloInstruction* last_visited_ = nullptr; }; diff --git a/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc b/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc index 1924204df0..0db452b85f 100644 --- a/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc @@ -107,7 +107,7 @@ bool HloDataflowAnalysis::AreTransitiveUsesElementwiseOrTuple( return false; } } - if (!visited.count(user)) { + if (!visited.contains(user)) { stack.push_back(user); } } diff --git a/tensorflow/compiler/xla/service/hlo_dce.cc b/tensorflow/compiler/xla/service/hlo_dce.cc index 7d35e251ca..a5a11f09cf 100644 --- a/tensorflow/compiler/xla/service/hlo_dce.cc +++ b/tensorflow/compiler/xla/service/hlo_dce.cc @@ -20,6 +20,7 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_set.h" #include "tensorflow/compiler/xla/service/hlo_computation.h" #include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/service/hlo_module.h" @@ -65,7 +66,7 @@ StatusOr HloDCE::Run(HloModule* module) { // Now DCE HloComputations. First, collect the computations that are // referenced by some remaining instruction. - std::unordered_set live_computations; + absl::flat_hash_set live_computations; if (HloComputation* entry_computation = module->entry_computation()) { live_computations.insert(entry_computation); } @@ -79,7 +80,7 @@ StatusOr HloDCE::Run(HloModule* module) { // Remove dead computations. for (auto* computation : module->MakeComputationPostOrder()) { - if (live_computations.count(computation) == 0) { + if (!live_computations.contains(computation)) { TF_RETURN_IF_ERROR(module->RemoveEmbeddedComputation(computation)); changed = true; } diff --git a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc index c6eaead8dd..22bcb2030b 100644 --- a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc +++ b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc @@ -24,9 +24,9 @@ limitations under the License. #include #include #include -#include #include +#include "absl/container/flat_hash_map.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -380,7 +380,7 @@ class HloDotDumper { // Each HloInstruction dumped gets a monotically-increasing node ID. This // must start at 1, because that's where graphviz's accounting starts. int64 next_node_id_ = 1; - std::unordered_map node_ids_; + absl::flat_hash_map node_ids_; // The "root" tag doesn't have an associated HloInstruction pointer, so we // need to store it outside the map. @@ -397,7 +397,7 @@ class HloDotDumper { // Each HloComputation that's emitted gets a monotonically-increasing ID. int64 next_cluster_id_ = 1; - std::unordered_map cluster_ids_; + absl::flat_hash_map cluster_ids_; // Edges to print from Footer(). Edges come at the end because graphviz is // unhappy if an edge from a subcomputation to a node in the outer computation @@ -407,7 +407,7 @@ class HloDotDumper { // When coloring by sharding information, we track the sharding string // representation to color association, by round-robin the color schemes. - std::unordered_map + absl::flat_hash_map sharding_colors_; int64 next_shard_color_ = 0; }; @@ -1286,7 +1286,7 @@ NodeFilter MakeNodeRadiusAroundFilter(const HloInstruction* root, int64 radius) { // First, find the neighborhood of nodes with distance from root <= radius. // These nodes are our initial set of "normal" nodes. - std::unordered_map nodes; + absl::flat_hash_map nodes; std::deque> worklist; worklist.push_back({root, 0}); while (!worklist.empty()) { @@ -1307,7 +1307,7 @@ NodeFilter MakeNodeRadiusAroundFilter(const HloInstruction* root, // are not interesting to the graph at hand. if (instr == root || instr->opcode() != HloOpcode::kTuple) { for (const HloInstruction* operand : instr->operands()) { - if (!nodes.count(operand)) { + if (!nodes.contains(operand)) { worklist.push_back({operand, depth + 1}); } } @@ -1335,7 +1335,7 @@ NodeFilter MakeNodeRadiusAroundFilter(const HloInstruction* root, continue; } for (const HloInstruction* user : instr->users()) { - if (!nodes.count(user)) { + if (!nodes.contains(user)) { worklist.push_back({user, depth + 1}); } } @@ -1344,7 +1344,7 @@ NodeFilter MakeNodeRadiusAroundFilter(const HloInstruction* root, auto is_displayed = [&](const HloInstruction* instr) { // Constants are displayed inline with their users; they're never omitted. // Nodes in subcomputations are always shown. - return nodes.count(instr) > 0 || instr->opcode() == HloOpcode::kConstant || + return nodes.contains(instr) || instr->opcode() == HloOpcode::kConstant || instr->parent() != root->parent(); }; diff --git a/tensorflow/compiler/xla/service/hlo_instruction_test.cc b/tensorflow/compiler/xla/service/hlo_instruction_test.cc index 8048e332cb..35f031f29a 100644 --- a/tensorflow/compiler/xla/service/hlo_instruction_test.cc +++ b/tensorflow/compiler/xla/service/hlo_instruction_test.cc @@ -20,6 +20,7 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_map.h" #include "tensorflow/compiler/xla/literal.h" #include "tensorflow/compiler/xla/protobuf_util.h" #include "tensorflow/compiler/xla/service/dfs_hlo_visitor_with_default.h" @@ -55,13 +56,13 @@ class OpAndUserCollectingVisitor : public DfsHloVisitorWithDefault { } Status HandleParameter(HloInstruction* parameter) override { - EXPECT_EQ(0, count_.count(parameter)); + EXPECT_FALSE(count_.contains(parameter)); count_[parameter] = GetCountsForNode(parameter); return Status::OK(); } Status HandleConstant(HloInstruction* constant) override { - EXPECT_EQ(0, count_.count(constant)); + EXPECT_FALSE(count_.contains(constant)); count_[constant] = GetCountsForNode(constant); return Status::OK(); } @@ -69,25 +70,25 @@ class OpAndUserCollectingVisitor : public DfsHloVisitorWithDefault { Status HandleAdd(HloInstruction* add) override { auto lhs = add->operand(0); auto rhs = add->operand(1); - EXPECT_EQ(0, count_.count(add)); - EXPECT_GT(count_.count(lhs), 0); - EXPECT_GT(count_.count(rhs), 0); + EXPECT_FALSE(count_.contains(add)); + EXPECT_TRUE(count_.contains(lhs)); + EXPECT_TRUE(count_.contains(rhs)); count_[add] = GetCountsForNode(add); return Status::OK(); } Status HandleNegate(HloInstruction* negate) override { auto operand = negate->operand(0); - EXPECT_EQ(0, count_.count(negate)); - EXPECT_GT(count_.count(operand), 0); + EXPECT_FALSE(count_.contains(negate)); + EXPECT_TRUE(count_.contains(operand)); count_[negate] = GetCountsForNode(negate); return Status::OK(); } Status HandleMap(HloInstruction* map) override { - EXPECT_EQ(0, count_.count(map)); + EXPECT_FALSE(count_.contains(map)); for (HloInstruction* arg : map->operands()) { - EXPECT_GT(count_.count(arg), 0); + EXPECT_TRUE(count_.contains(arg)); } count_[map] = GetCountsForNode(map); return Status::OK(); @@ -96,9 +97,9 @@ class OpAndUserCollectingVisitor : public DfsHloVisitorWithDefault { Status HandleReduce(HloInstruction* reduce) override { auto arg = reduce->operand(0); auto init_value = reduce->operand(1); - EXPECT_EQ(0, count_.count(reduce)); - EXPECT_GT(count_.count(arg), 0); - EXPECT_GT(count_.count(init_value), 0); + EXPECT_FALSE(count_.contains(reduce)); + EXPECT_TRUE(count_.contains(arg)); + EXPECT_TRUE(count_.contains(init_value)); count_[reduce] = GetCountsForNode(reduce); return Status::OK(); } @@ -128,7 +129,7 @@ class OpAndUserCollectingVisitor : public DfsHloVisitorWithDefault { } // Counters for HLOs. Maps HLO to a NumOpsAndUsers. - std::unordered_map count_; + absl::flat_hash_map count_; }; TEST_F(HloInstructionTest, BasicProperties) { @@ -137,7 +138,7 @@ TEST_F(HloInstructionTest, BasicProperties) { EXPECT_EQ(HloOpcode::kParameter, parameter->opcode()); EXPECT_TRUE(ShapeUtil::IsScalarWithElementType(parameter->shape(), F32)); EXPECT_FALSE(ShapeUtil::IsScalarWithElementType(parameter->shape(), S32)); - EXPECT_EQ(0, parameter->operand_count()); + EXPECT_FALSE(parameter->operand_count()); } TEST_F(HloInstructionTest, UserWithTwoOperands) { @@ -981,9 +982,9 @@ TEST_F(HloInstructionTest, FunctionVisitor) { module->AddEntryComputation(builder.Build()); int visit_num = 0; - std::unordered_map visit_order; + absl::flat_hash_map visit_order; EXPECT_IS_OK(add->Accept([&visit_num, &visit_order](HloInstruction* inst) { - EXPECT_EQ(0, visit_order.count(inst)); + EXPECT_FALSE(visit_order.contains(inst)); visit_order[inst] = visit_num; visit_num++; return Status::OK(); diff --git a/tensorflow/compiler/xla/service/hlo_liveness_analysis.cc b/tensorflow/compiler/xla/service/hlo_liveness_analysis.cc index 5bf055f3c0..e14bcfa7f6 100644 --- a/tensorflow/compiler/xla/service/hlo_liveness_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_liveness_analysis.cc @@ -17,6 +17,7 @@ limitations under the License. #include +#include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "tensorflow/compiler/xla/map_util.h" @@ -36,11 +37,11 @@ namespace xla { namespace { using Worklist = std::deque; -using Workset = std::unordered_set; +using Workset = absl::flat_hash_set; void AddToWorklist(const HloInstruction* instruction, Worklist* worklist, Workset* workset) { - if (workset->count(instruction) == 0) { + if (!workset->contains(instruction)) { worklist->push_back(instruction); workset->insert(instruction); VLOG(3) << "ADD instruction: " << instruction->name(); diff --git a/tensorflow/compiler/xla/service/hlo_module.cc b/tensorflow/compiler/xla/service/hlo_module.cc index fe8371384c..ed78189fed 100644 --- a/tensorflow/compiler/xla/service/hlo_module.cc +++ b/tensorflow/compiler/xla/service/hlo_module.cc @@ -392,15 +392,12 @@ namespace { // Returns whether `hlo` is used outside the given subcomputation. // `instructions_in_subcomputation` is the instruction set of the given // subcomputation. -bool IsUsedOutsideSubcomputation( - const HloInstruction& hlo, - const std::unordered_set& instructions_in_subcomputation) { - for (HloInstruction* user : hlo.users()) { - if (!instructions_in_subcomputation.count(user)) { - return true; - } - } - return false; +bool IsUsedOutsideSubcomputation(const HloInstruction& hlo, + const absl::flat_hash_set& + instructions_in_subcomputation) { + return absl::c_any_of(hlo.users(), [&](HloInstruction* user) { + return !instructions_in_subcomputation.contains(user); + }); } } // anonymous namespace @@ -411,9 +408,9 @@ HloInstruction* HloModule::OutlineExpressionFromComputation( // A map from original instructions to their counterparts in the new outlined // function. - std::unordered_map outlined_instructions; + absl::flat_hash_map outlined_instructions; // A set that contains all instructions to be outlined. - std::unordered_set instruction_set_to_outline( + absl::flat_hash_set instruction_set_to_outline( instructions_to_outline.begin(), instructions_to_outline.end()); std::vector arguments; std::vector outputs; @@ -502,7 +499,7 @@ std::vector HloModule::MakeComputationPostOrder() const { // First determine all root computations by building a set of nonroot // computations (computations which are called by an instruction in the // module). - std::set nonroot_computations; + absl::flat_hash_set nonroot_computations; for (auto& computation : computations_) { for (auto* instruction : computation->instructions()) { for (HloComputation* called_computation : @@ -515,19 +512,19 @@ std::vector HloModule::MakeComputationPostOrder() const { // Keep track of computations which have already been added to the post // order. This prevents duplication as an embedded computation may be called // from two different root computations. - std::set added_computations; + absl::flat_hash_set added_computations; std::vector post_order; for (auto& computation : computations_) { - if (nonroot_computations.count(computation.get()) == 0) { + if (!nonroot_computations.contains(computation.get())) { for (HloComputation* embedded_computation : computation->MakeEmbeddedComputationsList()) { - if (added_computations.count(embedded_computation) == 0) { + if (!added_computations.contains(embedded_computation)) { post_order.push_back(embedded_computation); added_computations.insert(embedded_computation); } } // Root computations should only be encountered once. - CHECK_EQ(0, added_computations.count(computation.get())); + CHECK(!added_computations.contains(computation.get())); post_order.push_back(computation.get()); added_computations.insert(computation.get()); } diff --git a/tensorflow/compiler/xla/service/hlo_module_group_metadata.cc b/tensorflow/compiler/xla/service/hlo_module_group_metadata.cc index 80f8ca2226..47734bc55c 100644 --- a/tensorflow/compiler/xla/service/hlo_module_group_metadata.cc +++ b/tensorflow/compiler/xla/service/hlo_module_group_metadata.cc @@ -199,7 +199,7 @@ bool HloModuleGroupMetadata::IsChannelInstruction( } bool HloModuleGroupMetadata::IsCompanionInstruction(HloInstruction* hlo) const { - return companion_set_index_.count(hlo) > 0; + return companion_set_index_.contains(hlo); } bool HloModuleGroupMetadata::InstructionCommunicates( @@ -510,7 +510,7 @@ Status HloModuleGroupMetadata::CheckCommunicatingInstruction( HloComputation* computation = instruction->parent(); const HloModule* module = computation->parent(); if (module->entry_computation() == computation || - tracked_instructions_.count(computation) > 0) { + tracked_instructions_.contains(computation)) { return Status::OK(); } return FailedPrecondition("channel is used in disallowed computation"); diff --git a/tensorflow/compiler/xla/service/hlo_module_group_metadata.h b/tensorflow/compiler/xla/service/hlo_module_group_metadata.h index 5cd0e38f38..3ed95c1050 100644 --- a/tensorflow/compiler/xla/service/hlo_module_group_metadata.h +++ b/tensorflow/compiler/xla/service/hlo_module_group_metadata.h @@ -178,7 +178,7 @@ class HloModuleGroupMetadata { // Precondition: IsCompanionWhile(instruction) is true. const std::vector& Companions( const HloInstruction* instruction) const { - CHECK_EQ(companion_set_index_.count(instruction), 1); + CHECK(companion_set_index_.contains(instruction)); return companion_set(companion_set_index_.at(instruction)); } diff --git a/tensorflow/compiler/xla/service/hlo_ordering.cc b/tensorflow/compiler/xla/service/hlo_ordering.cc index ca6a154809..0cec61c257 100644 --- a/tensorflow/compiler/xla/service/hlo_ordering.cc +++ b/tensorflow/compiler/xla/service/hlo_ordering.cc @@ -367,7 +367,7 @@ bool SequentialHloOrdering::ExecutesBeforeInSameComputation( const HloInstruction* a, const HloInstruction* b) const { CHECK_EQ(a->parent(), b->parent()); // If either instruction is not in the order, then 'a' and 'b' are unordered. - if (order_position_.count(a) == 0 || order_position_.count(b) == 0) { + if (!order_position_.contains(a) || !order_position_.contains(b)) { return false; } return order_position_.at(a) < order_position_.at(b); diff --git a/tensorflow/compiler/xla/service/hlo_pass_pipeline.cc b/tensorflow/compiler/xla/service/hlo_pass_pipeline.cc index 33ce7e23a8..ae8c08cf1d 100644 --- a/tensorflow/compiler/xla/service/hlo_pass_pipeline.cc +++ b/tensorflow/compiler/xla/service/hlo_pass_pipeline.cc @@ -89,7 +89,7 @@ std::vector HloPassPipeline::GetEnabledPasses( std::vector enabled_passes; for (auto& pass : passes_) { - if (disabled_pass_names.count(string(pass->name())) == 0) { + if (!disabled_pass_names.contains(pass->name())) { enabled_passes.push_back(pass.get()); } } diff --git a/tensorflow/compiler/xla/service/hlo_schedule.cc b/tensorflow/compiler/xla/service/hlo_schedule.cc index 8f6eb974c5..e75373501c 100644 --- a/tensorflow/compiler/xla/service/hlo_schedule.cc +++ b/tensorflow/compiler/xla/service/hlo_schedule.cc @@ -140,7 +140,7 @@ Status HloSchedule::UpdateComputationSchedule( std::queue worklist; for (HloInstruction* instruction : computation->instructions()) { - if (ids_in_schedule.count(instruction->unique_id()) == 0) { + if (!ids_in_schedule.contains(instruction->unique_id())) { // This is a newly added instruction which is not in the schedule. if (instruction->operands().empty()) { worklist.push(instruction); @@ -204,7 +204,7 @@ Status HloSchedule::Update() { std::vector nonfusion_computations = module_->MakeNonfusionComputations(); for (const HloComputation* computation : nonfusion_computations) { - TF_RET_CHECK(sequences_.count(computation->unique_id()) == 1) + TF_RET_CHECK(sequences_.contains(computation->unique_id())) << "Computation " << computation->name() << " not in HloSchedule."; } if (sequences_.size() > nonfusion_computations.size()) { @@ -215,7 +215,7 @@ Status HloSchedule::Update() { nonfusion_computations_ids.insert(computation->unique_id()); } for (auto it = sequences_.begin(); it != sequences_.end();) { - if (nonfusion_computations_ids.count(it->first) == 0) { + if (!nonfusion_computations_ids.contains(it->first)) { sequences_.erase(it++); } else { ++it; @@ -244,7 +244,7 @@ Status HloSchedule::Verify() const { << "Schedule has " << sequences_.size() << " sequences, but module has " << nonfusion_computations.size() << " non-fusion computations"; for (const HloComputation* computation : nonfusion_computations) { - TF_RET_CHECK(sequences_.count(computation->unique_id()) == 1) + TF_RET_CHECK(sequences_.contains(computation->unique_id())) << "Computation " << computation->name() << " missing from HLO schedule."; } @@ -268,7 +268,7 @@ Status HloSchedule::Verify() const { << instruction_position.size() << " instructions, expected " << computation->instruction_count(); for (const HloInstruction* instruction : computation->instructions()) { - TF_RET_CHECK(instruction_position.count(instruction) == 1) + TF_RET_CHECK(instruction_position.contains(instruction)) << "Instruction " << instruction->name() << " is not in schedule"; } diff --git a/tensorflow/compiler/xla/service/hlo_schedule.h b/tensorflow/compiler/xla/service/hlo_schedule.h index 486ddbf499..a5f54ae2c3 100644 --- a/tensorflow/compiler/xla/service/hlo_schedule.h +++ b/tensorflow/compiler/xla/service/hlo_schedule.h @@ -110,7 +110,7 @@ class HloSchedule { // Returns true if the schedule has a sequence for the given computation. bool is_computation_scheduled(const HloComputation* computation) const { - return sequences_.count(computation->unique_id()) == 1; + return sequences_.contains(computation->unique_id()); } // Updates the schedule such that it is (again) a valid schedule for the diff --git a/tensorflow/compiler/xla/service/hlo_sharding.cc b/tensorflow/compiler/xla/service/hlo_sharding.cc index bdd6ec1169..6c9c32f179 100644 --- a/tensorflow/compiler/xla/service/hlo_sharding.cc +++ b/tensorflow/compiler/xla/service/hlo_sharding.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/hlo_sharding.h" +#include "absl/container/flat_hash_set.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "tensorflow/compiler/xla/overflow_util.h" @@ -316,7 +317,7 @@ Status HloSharding::ValidateNonTuple(const Shape& shape, // All tile assignments must be less than the number of available cores and // unique. Status status = Status::OK(); - std::set seen_cores; + absl::flat_hash_set seen_cores; tile_assignment_.Each( [&](absl::Span indices, int32 core) { // Don't overwrite a bad status, so we report the first error. @@ -324,7 +325,7 @@ Status HloSharding::ValidateNonTuple(const Shape& shape, if (core >= num_devices) { status = tensorflow::errors::InvalidArgument(StrCat( "core ", core, " > ", num_devices, " in tile assignment")); - } else if (seen_cores.count(core) != 0) { + } else if (seen_cores.contains(core)) { status = tensorflow::errors::InvalidArgument( StrCat("core ", core, " is not unique in tile assignment")); } diff --git a/tensorflow/compiler/xla/service/hlo_sharding_metadata.cc b/tensorflow/compiler/xla/service/hlo_sharding_metadata.cc index b414d2a663..094d98bc6e 100644 --- a/tensorflow/compiler/xla/service/hlo_sharding_metadata.cc +++ b/tensorflow/compiler/xla/service/hlo_sharding_metadata.cc @@ -99,7 +99,7 @@ std::vector LocatePassThroughDomainLinks( << "Instruction is not a kDomain: " << instruction->ToString(); for (HloInstruction* user : instruction->users()) { if (user->opcode() == HloOpcode::kDomain && - domain.exit_domains.count(user) != 0) { + domain.exit_domains.contains(user)) { pass_through.emplace_back(user, instruction); VLOG(2) << "Found passthrough domain link:"; VLOG(2) << " " << user->ToString(); @@ -253,7 +253,7 @@ StatusOr ApplyShardingFromUsers(HloInstruction* instruction, instruction->shape(), HloSharding::AssignDevice(kUnassignedDevice)); for (HloInstruction* user : instruction->users()) { if (user->opcode() == HloOpcode::kDomain && - domain.exit_domains.count(user) > 0) { + domain.exit_domains.contains(user)) { // If a user is a domain and it is registered in the domain exits, then // the instruction sharding is taken directly from the domain, and no // further users need to be visited. diff --git a/tensorflow/compiler/xla/service/indexed_array_analysis.cc b/tensorflow/compiler/xla/service/indexed_array_analysis.cc index a41cf714c5..76bf48870d 100644 --- a/tensorflow/compiler/xla/service/indexed_array_analysis.cc +++ b/tensorflow/compiler/xla/service/indexed_array_analysis.cc @@ -103,7 +103,7 @@ Status IndexedArrayAnalysis::TraverseAndPopulateCache( do { const HloInstruction* instr = stack.back(); - if (cache_.count(instr)) { + if (cache_.contains(instr)) { stack.pop_back(); continue; } @@ -111,9 +111,9 @@ Status IndexedArrayAnalysis::TraverseAndPopulateCache( switch (FindOrDie(dfs_state_map, instr)) { case kDiscovered: { for (const HloInstruction* operand : instr->operands()) { - if (!cache_.count(operand)) { + if (!cache_.contains(operand)) { stack.push_back(operand); - CHECK(!dfs_state_map.count(operand) || + CHECK(!dfs_state_map.contains(operand) || dfs_state_map[operand] == kDiscovered); dfs_state_map[operand] = kDiscovered; } diff --git a/tensorflow/compiler/xla/service/layout_assignment.cc b/tensorflow/compiler/xla/service/layout_assignment.cc index 09246db68e..406cd0a219 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.cc +++ b/tensorflow/compiler/xla/service/layout_assignment.cc @@ -2135,7 +2135,7 @@ Status LayoutAssignment::ClearPreviousPassSideEffects(HloModule* module) { for (HloInstruction* instruction : computation->MakeInstructionPostOrder()) { if (instruction->opcode() == HloOpcode::kCopy && - added_copies_.count(instruction) > 0) { + added_copies_.contains(instruction)) { VLOG(5) << "Removing added copy: " << instruction->ToString(); TF_RETURN_IF_ERROR( instruction->ReplaceAllUsesWith(instruction->mutable_operand(0))); diff --git a/tensorflow/compiler/xla/service/layout_assignment.h b/tensorflow/compiler/xla/service/layout_assignment.h index 3b081de3c7..5701cb5b02 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.h +++ b/tensorflow/compiler/xla/service/layout_assignment.h @@ -243,7 +243,7 @@ class ChannelLayoutConstraints { // Returns true if channel_id has a layout constraint. bool IsChannelConstrained(int64 channel_id) const { - return constraints_.count(channel_id) > 0; + return constraints_.contains(channel_id); } // Given `shape`, apply the layout for `channel_id`. `channel_id` must already @@ -276,7 +276,7 @@ class ChannelLayoutConstraints { } private: - std::unordered_map constraints_; + absl::flat_hash_map constraints_; }; // HLO pass which assigns layouts to all instructions in the HLO module while diff --git a/tensorflow/compiler/xla/service/llvm_ir/BUILD b/tensorflow/compiler/xla/service/llvm_ir/BUILD index 728a66b388..54e8fe1947 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/BUILD +++ b/tensorflow/compiler/xla/service/llvm_ir/BUILD @@ -169,6 +169,7 @@ cc_library( "//tensorflow/compiler/xla/service:elemental_ir_emitter", "//tensorflow/compiler/xla/service:hlo", "//tensorflow/core:lib", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/types:optional", "@com_google_absl//absl/types:span", "@llvm//:core", diff --git a/tensorflow/compiler/xla/service/llvm_ir/alias_analysis.h b/tensorflow/compiler/xla/service/llvm_ir/alias_analysis.h index 2b46b3c396..12e2f449e2 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/alias_analysis.h +++ b/tensorflow/compiler/xla/service/llvm_ir/alias_analysis.h @@ -76,15 +76,12 @@ class AliasAnalysis { // A map from a buffer slice to metadata corresponding to its alias.scope // metadata. The index kParameterAliasSet is used to hold aliasing // information for parameters. - absl::flat_hash_map + absl::flat_hash_map alias_scope_metadata_; // A map from a buffer slice to metadata corresponding to its noalias // metadata. - absl::flat_hash_map - noalias_metadata_; + absl::flat_hash_map noalias_metadata_; }; } // namespace llvm_ir diff --git a/tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.cc b/tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.cc index 03a475b40b..e440f05e2b 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.cc @@ -35,7 +35,7 @@ using llvm_ir::IrArray; Status FusedIrEmitter::DefaultAction(HloInstruction* hlo) { indexed_generators_[hlo] = [=](const IrArray::Index& index) -> StatusOr { - if (generated_value_cache_[hlo].count(index.multidim()) > 0) { + if (generated_value_cache_[hlo].contains(index.multidim())) { llvm::Value* generated_value = generated_value_cache_[hlo][index.multidim()]; llvm::BasicBlock* generated_value_bb = nullptr; diff --git a/tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.h b/tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.h index 1b9c61f670..e6d52a580c 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.h +++ b/tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.h @@ -19,6 +19,7 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_map.h" #include "absl/types/optional.h" #include "absl/types/span.h" #include "llvm/IR/IRBuilder.h" @@ -134,8 +135,9 @@ class FusedIrEmitter : public DfsHloVisitorWithDefault { // Cache of generated values, lest we regenerate an element of a node with // multiple outgoing edges - std::unordered_map, llvm::Value*>> + absl::flat_hash_map< + const HloInstruction*, + absl::flat_hash_map, llvm::Value*>> generated_value_cache_; }; diff --git a/tensorflow/compiler/xla/service/multi_output_fusion.cc b/tensorflow/compiler/xla/service/multi_output_fusion.cc index 9ccdd7d8d8..53d52d9a3d 100644 --- a/tensorflow/compiler/xla/service/multi_output_fusion.cc +++ b/tensorflow/compiler/xla/service/multi_output_fusion.cc @@ -198,7 +198,7 @@ void MultiOutputFusion::Update(HloInstruction* instr1, HloInstruction* instr2) { if (instr == fusion || is_fused(instr) || is_connected(fusion, instr)) { continue; } - if (in_list.count(instr) > 0) { + if (in_list.contains(instr)) { continue; } int64 profit = GetProfit(instr, fusion); diff --git a/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc b/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc index b1f0672c60..dec719b04a 100644 --- a/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc +++ b/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc @@ -19,6 +19,7 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -55,11 +56,10 @@ bool PointsToSet::IsAmbiguous() const { bool PointsToSet::IsDistinct() const { bool distinct = true; - std::set all_points_to; - ForEachElement([&distinct, &all_points_to](const ShapeIndex& /*index*/, - const BufferList& points_to) { + absl::flat_hash_set all_points_to; + ForEachElement([&](const ShapeIndex& /*index*/, const BufferList& points_to) { for (auto& buffer : points_to) { - if (all_points_to.count(buffer) != 0) { + if (all_points_to.contains(buffer)) { distinct = false; } all_points_to.insert(buffer); diff --git a/tensorflow/compiler/xla/service/while_loop_invariant_code_motion.cc b/tensorflow/compiler/xla/service/while_loop_invariant_code_motion.cc index a1c627a319..69cc8feb3f 100644 --- a/tensorflow/compiler/xla/service/while_loop_invariant_code_motion.cc +++ b/tensorflow/compiler/xla/service/while_loop_invariant_code_motion.cc @@ -89,7 +89,7 @@ static void CreateLoopInvariantCopy( HloInstruction* next_operand = frame->instruction->mutable_operand(frame->operand_index++); - if (hoisted_instructions->count(next_operand) || + if (hoisted_instructions->contains(next_operand) || next_operand == while_body_param) { continue; } @@ -241,7 +241,7 @@ WhileLoopInvariantCodeMotion::TryHoistingInvariantInstructionsFromWhileBody( auto is_invariant = [&](HloInstruction* op) { return hoisted_instructions.find(op) != hoisted_instructions.end() || - unhoisted_invariant_instructions.count(op) || + unhoisted_invariant_instructions.contains(op) || op->opcode() == HloOpcode::kConstant; }; diff --git a/tensorflow/compiler/xla/service/while_loop_simplifier.cc b/tensorflow/compiler/xla/service/while_loop_simplifier.cc index 772faa25e2..de884b037d 100644 --- a/tensorflow/compiler/xla/service/while_loop_simplifier.cc +++ b/tensorflow/compiler/xla/service/while_loop_simplifier.cc @@ -127,7 +127,7 @@ static StatusOr TryRemoveDeadWhileParams(HloInstruction* while_op) { // through to the while body's root, count that element as "used", since // removing that element would be observable. for (int64 i = 0; i < while_body_root->operand_count(); ++i) { - if (used_tuple_indices.count(i)) { + if (used_tuple_indices.contains(i)) { continue; } -- GitLab From b4813a0cff9f7ad1278aee376a5302f31c4558a2 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 4 Jan 2019 12:29:13 -0800 Subject: [PATCH 0203/2345] [XLA] Use absl::c_foo rather than std::foo. No functional change. PiperOrigin-RevId: 227896034 --- tensorflow/compiler/xla/BUILD | 1 + .../compiler/xla/client/lib/sorting_test.cc | 2 +- tensorflow/compiler/xla/layout_util.cc | 6 +- .../compiler/xla/metric_table_report.cc | 4 +- tensorflow/compiler/xla/service/BUILD | 1 + .../xla/service/algebraic_simplifier.cc | 7 +- .../compiler/xla/service/buffer_assignment.cc | 85 +++++++++---------- .../compiler/xla/service/call_graph_test.cc | 2 +- .../xla/service/computation_layout.cc | 4 +- .../compiler/xla/service/copy_insertion.cc | 18 ++-- .../compiler/xla/service/cpu/disassembler.cc | 21 +++-- .../compiler/xla/service/cpu/ir_emitter.cc | 12 +-- .../xla/service/gpu/ir_emission_utils.cc | 15 ++-- .../xla/service/gpu/ir_emitter_unnested.cc | 8 +- .../compiler/xla/service/heap_simulator.cc | 41 +++++---- .../xla/service/hlo_alias_analysis.cc | 30 +++---- tensorflow/compiler/xla/service/hlo_buffer.cc | 2 +- .../compiler/xla/service/hlo_computation.cc | 12 ++- .../xla/service/hlo_dataflow_analysis.cc | 36 +++----- .../compiler/xla/service/hlo_dce_test.cc | 4 +- .../compiler/xla/service/hlo_domain_map.cc | 8 +- .../compiler/xla/service/hlo_evaluator.cc | 3 +- .../xla/service/hlo_evaluator_typed_visitor.h | 3 +- .../compiler/xla/service/hlo_graph_dumper.cc | 42 +++++---- .../compiler/xla/service/hlo_instruction.cc | 37 ++++---- .../compiler/xla/service/hlo_instructions.cc | 18 ++-- tensorflow/compiler/xla/service/hlo_module.cc | 18 ++-- .../xla/service/hlo_module_dce_test.cc | 4 +- tensorflow/compiler/xla/service/hlo_parser.cc | 2 +- .../xla/service/hlo_profile_printer.cc | 10 +-- .../compiler/xla/service/hlo_reachability.cc | 2 +- .../xla/service/hlo_rematerialization.cc | 25 +++--- .../compiler/xla/service/hlo_sharding.cc | 9 +- .../compiler/xla/service/hlo_sharding.h | 9 +- .../xla/service/hlo_tfgraph_builder.cc | 3 +- tensorflow/compiler/xla/service/hlo_value.cc | 2 +- .../service/human_readable_profile_builder.cc | 6 +- .../xla/service/instruction_fusion.cc | 30 +++---- .../compiler/xla/service/layout_assignment.cc | 9 +- .../xla/service/llvm_ir/alias_analysis.cc | 4 +- .../compiler/xla/service/name_uniquer.cc | 2 +- .../compiler/xla/service/platform_util.cc | 4 +- .../compiler/xla/service/shape_inference.cc | 22 +++-- .../compiler/xla/service/transpose_folding.cc | 6 +- .../xla/service/tuple_points_to_analysis.cc | 26 +++--- .../service/tuple_points_to_analysis_test.cc | 5 +- .../xla/service/while_loop_simplifier.cc | 5 +- .../xla/service/while_loop_simplifier_test.cc | 11 ++- tensorflow/compiler/xla/shape.cc | 3 +- tensorflow/compiler/xla/shape_util.cc | 3 +- tensorflow/compiler/xla/sparse_index_array.h | 2 +- .../compiler/xla/tests/convolution_test.cc | 4 +- tensorflow/compiler/xla/util.cc | 2 +- tensorflow/compiler/xla/util.h | 3 +- tensorflow/compiler/xla/window_util.cc | 32 ++++--- 55 files changed, 305 insertions(+), 380 deletions(-) diff --git a/tensorflow/compiler/xla/BUILD b/tensorflow/compiler/xla/BUILD index 8a0e2db2b5..45623c2718 100644 --- a/tensorflow/compiler/xla/BUILD +++ b/tensorflow/compiler/xla/BUILD @@ -717,6 +717,7 @@ cc_library( ":types", ":xla_data_proto", "//tensorflow/core:lib", + "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], diff --git a/tensorflow/compiler/xla/client/lib/sorting_test.cc b/tensorflow/compiler/xla/client/lib/sorting_test.cc index 27ff36c749..0fbd138aca 100644 --- a/tensorflow/compiler/xla/client/lib/sorting_test.cc +++ b/tensorflow/compiler/xla/client/lib/sorting_test.cc @@ -77,7 +77,7 @@ XLA_TEST_F(SortingTest, TopKFullSort) { auto x = ConstantR1(&builder, inputs); xla::GetTupleElement(xla::TopK(x, kSize), 0); - std::sort(inputs.begin(), inputs.end(), std::greater()); + absl::c_sort(inputs, std::greater()); ComputeAndCompareR1(&builder, inputs, {}); } diff --git a/tensorflow/compiler/xla/layout_util.cc b/tensorflow/compiler/xla/layout_util.cc index 42649d6692..2fe9b56c6b 100644 --- a/tensorflow/compiler/xla/layout_util.cc +++ b/tensorflow/compiler/xla/layout_util.cc @@ -290,8 +290,8 @@ Layout CreateDefaultLayoutForRank(int64 rank) { /* static */ bool LayoutUtil::HasLayout(const Shape& shape) { if (shape.IsTuple()) { // Tuple shape: all subshapes must have a layout. - return std::all_of(shape.tuple_shapes().begin(), shape.tuple_shapes().end(), - [](const Shape& s) { return HasLayout(s); }); + return absl::c_all_of(shape.tuple_shapes(), + [](const Shape& s) { return HasLayout(s); }); } else if (!shape.IsArray()) { // Opaque, token types etc. ignore layout. return true; @@ -424,7 +424,7 @@ Status LayoutUtil::CopyLayoutBetweenShapes(const Shape& src, Shape* dst) { positions_in_layout.push_back( PositionInContainer(layout.minor_to_major(), dim)); } - std::sort(positions_in_layout.begin(), positions_in_layout.end()); + absl::c_sort(positions_in_layout); for (size_t i = 1; i < positions_in_layout.size(); ++i) { if (1 != positions_in_layout[i] - positions_in_layout[i - 1]) { return false; diff --git a/tensorflow/compiler/xla/metric_table_report.cc b/tensorflow/compiler/xla/metric_table_report.cc index 4eab4fa429..ad1699a1ae 100644 --- a/tensorflow/compiler/xla/metric_table_report.cc +++ b/tensorflow/compiler/xla/metric_table_report.cc @@ -55,7 +55,7 @@ string MetricTableReport::MakeReport(double expected_metric_sum) { const auto metric_greater = [](const Entry& a, const Entry& b) { return a.metric > b.metric; }; - std::sort(entries_.begin(), entries_.end(), metric_greater); + absl::c_sort(entries_, metric_greater); // Create the report AppendLine(); @@ -117,7 +117,7 @@ std::vector MetricTableReport::MakeCategories( auto metric_sum_greater = [](const Category& a, const Category& b) { return a.metric_sum > b.metric_sum; }; - std::sort(categories.begin(), categories.end(), metric_sum_greater); + absl::c_sort(categories, metric_sum_greater); return categories; } diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index 85a21a8518..3c18c450e6 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -3414,6 +3414,7 @@ cc_library( ":hlo_profile_printer_data", ":human_readable_profile_builder", "//tensorflow/compiler/xla:types", + "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/strings", ], ) diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc index 3d269dc04f..003cb7e791 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc @@ -2216,8 +2216,7 @@ Status AlgebraicSimplifierVisitor::HandleReverse(HloInstruction* reverse) { auto dim_is_one = [&](int64 i) -> bool { return reverse->shape().dimensions(i) == 1; }; - if (std::all_of(reverse->dimensions().begin(), reverse->dimensions().end(), - dim_is_one)) { + if (absl::c_all_of(reverse->dimensions(), dim_is_one)) { return ReplaceInstruction(reverse, reverse->mutable_operand(0)); } return Status::OK(); @@ -2492,9 +2491,9 @@ Status AlgebraicSimplifierVisitor::HandleReduce(HloInstruction* reduce) { // Create a new reduce with the combined reduction dimensions of both // reduces. std::vector arg_dims = arg->dimensions(); - std::sort(arg_dims.begin(), arg_dims.end()); + absl::c_sort(arg_dims); std::vector reduce_dims = reduce->dimensions(); - std::sort(reduce_dims.begin(), reduce_dims.end()); + absl::c_sort(reduce_dims); // Transform reduce_dims to the same rank as the operand of the operand. for (int64 arg_dim : arg_dims) { for (int64& dim : reduce_dims) { diff --git a/tensorflow/compiler/xla/service/buffer_assignment.cc b/tensorflow/compiler/xla/service/buffer_assignment.cc index c997d594c4..c3285f516b 100644 --- a/tensorflow/compiler/xla/service/buffer_assignment.cc +++ b/tensorflow/compiler/xla/service/buffer_assignment.cc @@ -86,10 +86,9 @@ std::vector ColorInterferenceGraph( // first, but it would be good to investigate other ordering heuristics too. std::vector nodes(node_count); std::iota(nodes.begin(), nodes.end(), 0); - std::sort(nodes.begin(), nodes.end(), - [&interference_map](const int64 i, const int64 j) { - return interference_map[i].size() > interference_map[j].size(); - }); + absl::c_sort(nodes, [&interference_map](const int64 i, const int64 j) { + return interference_map[i].size() > interference_map[j].size(); + }); const int64 kColorUnassigned = -1; std::vector assigned_colors(node_count, kColorUnassigned); @@ -272,11 +271,12 @@ BufferAllocationProto BufferAllocation::ToProto() const { proto_assigned->set_offset(buffer_offset_size.second.offset); proto_assigned->set_size(buffer_offset_size.second.size); } - std::sort(proto.mutable_assigned()->begin(), proto.mutable_assigned()->end(), - [](const BufferAllocationProto::Assigned& assign1, - const BufferAllocationProto::Assigned& assign2) { - return assign1.logical_buffer_id() < assign2.logical_buffer_id(); - }); + absl::c_sort(*proto.mutable_assigned(), + [](const BufferAllocationProto::Assigned& assign1, + const BufferAllocationProto::Assigned& assign2) { + return assign1.logical_buffer_id() < + assign2.logical_buffer_id(); + }); return proto; } @@ -308,10 +308,10 @@ string BufferAllocation::ToString() const { for (const auto& buffer_offset_size : assigned_buffers_) { sorted_buffers.push_back(buffer_offset_size.first); } - std::sort(sorted_buffers.begin(), sorted_buffers.end(), - [](const LogicalBuffer* a, const LogicalBuffer* b) { - return a->id() < b->id(); - }); + absl::c_sort(sorted_buffers, + [](const LogicalBuffer* a, const LogicalBuffer* b) { + return a->id() < b->id(); + }); for (const LogicalBuffer* buffer : sorted_buffers) { const OffsetSize& offset_size = FindOrDie(assigned_buffers_, buffer); StrAppend(&output, absl::StrFormat( @@ -479,10 +479,9 @@ bool BufferAssignment::HaveDisjointSlices(const HloInstruction* hlo_a, // didn't return the empty set) for both HLOs, and the two resulting sets of // slices are disjoint. return !slices_a.empty() && !slices_b.empty() && - std::none_of(slices_a.begin(), slices_a.end(), - [&](const BufferAllocation::Slice& slice) { - return slices_b.count(slice) > 0; - }); + absl::c_none_of(slices_a, [&](const BufferAllocation::Slice& slice) { + return slices_b.contains(slice); + }); } StatusOr @@ -952,28 +951,28 @@ Status BufferAssigner::AssignBuffersForComputation( // operands (assuming operands are the same/larger size) enabling the // important reuse case where an elementwise instruction reuses one of its // operand's buffer. This improves locality. - std::sort(sorted_buffers.begin(), sorted_buffers.end(), - [has_sequential_order, &liveness, &post_order_position, assignment]( - const LogicalBuffer* a, const LogicalBuffer* b) { - // Primary sort is by decreasing buffer size. - const int64 a_size = assignment->buffer_size_(*a); - const int64 b_size = assignment->buffer_size_(*b); - if (a_size != b_size) { - return a_size > b_size; // use ">" for decreasing size. - } - // Otherwise live out buffers come before others, if the - // instructions are sequentially ordered. - if (has_sequential_order) { - const bool a_live_out = liveness.MaybeLiveOut(*a); - const bool b_live_out = liveness.MaybeLiveOut(*b); - if (a_live_out != b_live_out) { - return a_live_out; - } - } - // Final tiebreaker is in instruction post order. - return post_order_position.at(a->instruction()) < - post_order_position.at(b->instruction()); - }); + absl::c_sort(sorted_buffers, + [has_sequential_order, &liveness, &post_order_position, + assignment](const LogicalBuffer* a, const LogicalBuffer* b) { + // Primary sort is by decreasing buffer size. + const int64 a_size = assignment->buffer_size_(*a); + const int64 b_size = assignment->buffer_size_(*b); + if (a_size != b_size) { + return a_size > b_size; // use ">" for decreasing size. + } + // Otherwise live out buffers come before others, if the + // instructions are sequentially ordered. + if (has_sequential_order) { + const bool a_live_out = liveness.MaybeLiveOut(*a); + const bool b_live_out = liveness.MaybeLiveOut(*b); + if (a_live_out != b_live_out) { + return a_live_out; + } + } + // Final tiebreaker is in instruction post order. + return post_order_position.at(a->instruction()) < + post_order_position.at(b->instruction()); + }); // BufferAllocations are necessarily created in decreasing size order. Keep // indices of previously created BufferAllocations in allocation_indices. @@ -1305,10 +1304,10 @@ std::vector ComputePeakMemoryLogicalBuffers( live_buffers.end()); // Stabily sort the live buffers. - std::sort(live_buffers_vector.begin(), live_buffers_vector.end(), - [](const LogicalBuffer* a, const LogicalBuffer* b) { - return a->id() < b->id(); - }); + absl::c_sort(live_buffers_vector, + [](const LogicalBuffer* a, const LogicalBuffer* b) { + return a->id() < b->id(); + }); return live_buffers_vector; } diff --git a/tensorflow/compiler/xla/service/call_graph_test.cc b/tensorflow/compiler/xla/service/call_graph_test.cc index 7924134200..5de724f892 100644 --- a/tensorflow/compiler/xla/service/call_graph_test.cc +++ b/tensorflow/compiler/xla/service/call_graph_test.cc @@ -384,7 +384,7 @@ TEST_F(CallGraphTest, ComplexGraph) { // Verify visitation order of some computations in the graph. auto index_of = [&visited](const HloComputation* comp) { - auto it = std::find(visited.begin(), visited.end(), comp); + auto it = absl::c_find(visited, comp); EXPECT_NE(it, visited.end()); return std::distance(visited.begin(), it); }; diff --git a/tensorflow/compiler/xla/service/computation_layout.cc b/tensorflow/compiler/xla/service/computation_layout.cc index efc893818d..92d1ca4ba5 100644 --- a/tensorflow/compiler/xla/service/computation_layout.cc +++ b/tensorflow/compiler/xla/service/computation_layout.cc @@ -42,8 +42,8 @@ void ComputationLayout::SetToDefaultLayout() { } bool ComputationLayout::LayoutIsSet() const { - return std::all_of(parameter_layouts_.begin(), parameter_layouts_.end(), - [](const ShapeLayout& s) { return s.LayoutIsSet(); }) && + return absl::c_all_of(parameter_layouts_, + [](const ShapeLayout& s) { return s.LayoutIsSet(); }) && result_layout_.LayoutIsSet(); } diff --git a/tensorflow/compiler/xla/service/copy_insertion.cc b/tensorflow/compiler/xla/service/copy_insertion.cc index e0165d557d..2b837901f0 100644 --- a/tensorflow/compiler/xla/service/copy_insertion.cc +++ b/tensorflow/compiler/xla/service/copy_insertion.cc @@ -539,10 +539,9 @@ class CopyRemover { } std::vector values = buffer.values(); - std::sort(values.begin(), values.end(), - [this](const HloValue* a, const HloValue* b) { - return ordering_.IsDefinedBefore(*a, *b); - }); + absl::c_sort(values, [this](const HloValue* a, const HloValue* b) { + return ordering_.IsDefinedBefore(*a, *b); + }); // Create a list containing all of the values in the buffer. AddValueList(values, &value_to_node); @@ -842,12 +841,11 @@ class CopyRemover { copy_value_node->next->prev = operand_node; // Patch up uses. Remove use of copy from operand_node uses. - auto it = - std::find_if(operand_node->uses.begin(), operand_node->uses.end(), - [copy_value_node](const HloUse* use) { - return use->instruction == - copy_value_node->value->defining_instruction(); - }); + auto it = absl::c_find_if( + operand_node->uses, [copy_value_node](const HloUse* use) { + return use->instruction == + copy_value_node->value->defining_instruction(); + }); CHECK(it != operand_node->uses.end()); operand_node->uses.erase(it); diff --git a/tensorflow/compiler/xla/service/cpu/disassembler.cc b/tensorflow/compiler/xla/service/cpu/disassembler.cc index 3ae64142cd..c3c6847b7b 100644 --- a/tensorflow/compiler/xla/service/cpu/disassembler.cc +++ b/tensorflow/compiler/xla/service/cpu/disassembler.cc @@ -77,17 +77,16 @@ StatusOr Disassembler::DisassembleObjectFile( } // Sort the symbols in increasing address order. - std::sort( - symbols.begin(), symbols.end(), - [](const llvm::object::SymbolRef& a, const llvm::object::SymbolRef& b) { - // getAddress returns a Expected object. Assert there is no error - // before extracting the address. - llvm::Expected a_address_or_error = a.getAddress(); - CHECK(a_address_or_error); - llvm::Expected b_address_or_error = b.getAddress(); - CHECK(b_address_or_error); - return a_address_or_error.get() < b_address_or_error.get(); - }); + absl::c_sort(symbols, [](const llvm::object::SymbolRef& a, + const llvm::object::SymbolRef& b) { + // getAddress returns a Expected object. Assert there is no error + // before extracting the address. + llvm::Expected a_address_or_error = a.getAddress(); + CHECK(a_address_or_error); + llvm::Expected b_address_or_error = b.getAddress(); + CHECK(b_address_or_error); + return a_address_or_error.get() < b_address_or_error.get(); + }); // Construct ArrayRef pointing to section contents. llvm::StringRef section_content_string; diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc index e4760aedf1..b352848824 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc @@ -1709,10 +1709,8 @@ StatusOr IrEmitter::EmitVectorizedReduce( vectorization_factor_in_bytes / ShapeUtil::ByteSizeOfPrimitiveType(reduce->shape().element_type()); - bool is_reduction_over_minor_dimension = - std::find(dimensions.begin(), dimensions.end(), - LayoutUtil::Minor(arg->shape().layout(), 0)) != - dimensions.end(); + bool is_reduction_over_minor_dimension = absl::c_linear_search( + dimensions, LayoutUtil::Minor(arg->shape().layout(), 0)); unsigned element_alignment = tensorflow::MathUtil::GCD( ShapeUtil::ByteSizeOfPrimitiveType(reduce->shape().element_type()), @@ -2401,8 +2399,7 @@ StatusOr IrEmitter::EmitFastConcatenate( int64 concat_dim = concatenate->dimensions(0); const Layout& output_layout = output_shape.layout(); auto output_min2maj = LayoutUtil::MinorToMajor(output_layout); - auto concat_dim_layout_itr = - std::find(output_min2maj.begin(), output_min2maj.end(), concat_dim); + auto concat_dim_layout_itr = absl::c_find(output_min2maj, concat_dim); std::vector inner_dims(output_min2maj.begin(), concat_dim_layout_itr); std::vector outer_dims(std::next(concat_dim_layout_itr), @@ -2956,8 +2953,7 @@ Status IrEmitter::ElementTypesSameAndSupported( TF_RET_CHECK(!operands.empty()); PrimitiveType primitive_type = operands[0]->shape().element_type(); - if (std::find(supported_types.begin(), supported_types.end(), - primitive_type) == supported_types.end()) { + if (!absl::c_linear_search(supported_types, primitive_type)) { return Unimplemented("unsupported operand type %s in op %s", PrimitiveType_Name(primitive_type), HloOpcodeString(instruction.opcode())); diff --git a/tensorflow/compiler/xla/service/gpu/ir_emission_utils.cc b/tensorflow/compiler/xla/service/gpu/ir_emission_utils.cc index 5d25a032a9..caccb18899 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emission_utils.cc +++ b/tensorflow/compiler/xla/service/gpu/ir_emission_utils.cc @@ -154,20 +154,17 @@ bool IsReductionToVector(const HloInstruction& reduce) { const HloInstruction* input = reduce.operand(0); std::vector dims_to_keep; for (int64 dim = 0; dim < input->shape().dimensions().size(); ++dim) { - if (!std::count(reduce.dimensions().begin(), reduce.dimensions().end(), - dim)) { + if (!absl::c_linear_search(reduce.dimensions(), dim)) { dims_to_keep.push_back(dim); } } return LayoutUtil::AreDimensionsConsecutive(input->shape().layout(), dims_to_keep) && - ShapeUtil::Equal(reduce.shape(), ShapeUtil::FilterDimensions( - [&dims_to_keep](int64 dim) { - return std::count( - dims_to_keep.begin(), - dims_to_keep.end(), dim); - }, - input->shape())); + ShapeUtil::Equal( + reduce.shape(), + ShapeUtil::FilterDimensions( + [&](int64 dim) { return absl::c_count(dims_to_keep, dim); }, + input->shape())); } // This emits a device-side call to diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc index 063d493d90..923b3d5945 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc @@ -1506,10 +1506,10 @@ std::unique_ptr IrEmitterUnnested::BuildKernelThunk( return !allocation->is_constant(); }); - std::sort(non_constant_buffers.begin(), non_constant_buffers.end(), - [](const BufferAllocation* a, const BufferAllocation* b) { - return a->index() < b->index(); - }); + absl::c_sort(non_constant_buffers, + [](const BufferAllocation* a, const BufferAllocation* b) { + return a->index() < b->index(); + }); llvm::Function* kernel = BuildKernelPrototype(*inst, non_constant_buffers); diff --git a/tensorflow/compiler/xla/service/heap_simulator.cc b/tensorflow/compiler/xla/service/heap_simulator.cc index 70ae7078ae..4fca981c6a 100644 --- a/tensorflow/compiler/xla/service/heap_simulator.cc +++ b/tensorflow/compiler/xla/service/heap_simulator.cc @@ -225,10 +225,10 @@ Status HeapSimulator::RunComputation( } } // Sort to get a deterministic iteration order. - std::sort(operand_buffers_to_free.begin(), operand_buffers_to_free.end(), - [](const BufferValue* x, const BufferValue* y) { - return x->id() < y->id(); - }); + absl::c_sort(operand_buffers_to_free, + [](const BufferValue* x, const BufferValue* y) { + return x->id() < y->id(); + }); // Allocate buffers defined by this instruction. This is the latest point // that we can allocate; right before the buffer is first used. This must @@ -335,10 +335,9 @@ Status HeapSimulator::RunComputation( to_free.push_back(buffer); } - std::sort(to_free.begin(), to_free.end(), - [](const BufferValue* x, const BufferValue* y) { - return x->id() < y->id(); - }); + absl::c_sort(to_free, [](const BufferValue* x, const BufferValue* y) { + return x->id() < y->id(); + }); for (const BufferValue* buffer : to_free) { VLOG(3) << "Freeing pending: " << buffer->ToString(); Free(buffer, root); @@ -596,7 +595,7 @@ void DecreasingSizeRunsHeap::CallAndDrainRun() { } // Call ops in the run sorted by decreasing size, breaking ties by buffer id. - std::sort(run_.begin(), run_.end(), [](const Op& a, const Op& b) { + absl::c_sort(run_, [](const Op& a, const Op& b) { if (a.size != b.size) { return a.size > b.size; } @@ -866,23 +865,23 @@ HeapSimulator::Result GlobalDecreasingSizeBestFitHeap::Finish() { for (auto& entry : buffer_intervals_) { sorted_buffer_intervals.push_back(entry.second); } - std::sort(sorted_buffer_intervals.begin(), sorted_buffer_intervals.end(), - [](const BufferInterval& x, const BufferInterval& y) { - if (x.size != y.size) { - return x.size > y.size; - } - if (x.end - x.start != y.end - y.start) { - return x.end - x.start > y.end - y.start; - } - return x.buffer->id() < y.buffer->id(); - }); + absl::c_sort(sorted_buffer_intervals, + [](const BufferInterval& x, const BufferInterval& y) { + if (x.size != y.size) { + return x.size > y.size; + } + if (x.end - x.start != y.end - y.start) { + return x.end - x.start > y.end - y.start; + } + return x.buffer->id() < y.buffer->id(); + }); BufferIntervalTree interval_tree(sorted_buffer_intervals.size()); for (auto& buffer_interval : sorted_buffer_intervals) { auto chunks_overlapping_in_time = interval_tree.ChunksOverlappingInTime( buffer_interval.start, buffer_interval.end); - std::sort( - chunks_overlapping_in_time.begin(), chunks_overlapping_in_time.end(), + absl::c_sort( + chunks_overlapping_in_time, [](const Chunk& x, const Chunk& y) { return x.offset < y.offset; }); // Find the minimum free chunk that can hold this buffer. diff --git a/tensorflow/compiler/xla/service/hlo_alias_analysis.cc b/tensorflow/compiler/xla/service/hlo_alias_analysis.cc index 68094d8907..4c46ea595e 100644 --- a/tensorflow/compiler/xla/service/hlo_alias_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_alias_analysis.cc @@ -117,7 +117,7 @@ class BufferValueMap { for (const auto& pair : buffers_) { buffer_numbers.push_back(pair.first); } - std::sort(buffer_numbers.begin(), buffer_numbers.end()); + absl::c_sort(buffer_numbers); return buffer_numbers; } @@ -319,7 +319,7 @@ class BufferValueMap { ComputeWhileAliasedBuffers(value, &aliased_buffers); ComputeConditionalAliasedBuffers(value, &aliased_buffers); // Uniquify aliased buffers. - std::sort(aliased_buffers.begin(), aliased_buffers.end()); + absl::c_sort(aliased_buffers); aliased_buffers.erase( std::unique(aliased_buffers.begin(), aliased_buffers.end()), aliased_buffers.end()); @@ -367,7 +367,7 @@ std::vector HloAliasAnalysis::ComputeBuffersAt( } // Sort and uniquify vector before returning. - std::sort(buffers.begin(), buffers.end(), HloBuffer::IdLessThan); + absl::c_sort(buffers, HloBuffer::IdLessThan); buffers.erase(std::unique(buffers.begin(), buffers.end()), buffers.end()); return buffers; @@ -430,8 +430,7 @@ Status HloAliasAnalysis::Verify() const { for (const auto& pair : value_to_buffer_) { const HloValue* value = pair.first; const HloBuffer& buffer = *pair.second; - TF_RET_CHECK(std::find(buffer.values().begin(), buffer.values().end(), - value) != buffer.values().end()); + TF_RET_CHECK(absl::c_linear_search(buffer.values(), value)); } for (HloBuffer::Id id = 0; id < buffers_.size(); ++id) { @@ -515,7 +514,7 @@ StatusOr> HloAliasAnalysis::Run( auto& value_set = buffer_map.GetValuesInBuffer(buffer_number); std::vector sorted_values(value_set.begin(), value_set.end()); - std::sort(sorted_values.begin(), sorted_values.end(), HloValue::IdLessThan); + absl::c_sort(sorted_values, HloValue::IdLessThan); alias_analysis->buffers_.emplace_back(next_id++, sorted_values); for (const HloValue* value : sorted_values) { alias_analysis->value_to_buffer_[value] = @@ -547,16 +546,15 @@ bool HloAliasAnalysis::HasLiveRangeInterference( // tie-break using value ID. The tie-break is necessary because we need a // strict weak order for std::sort. std::vector values = buffer.values(); - std::sort(values.begin(), values.end(), - [&ordering](const HloValue* a, const HloValue* b) { - if (ordering.IsDefinedBefore(*a, *b)) { - return true; - } else if (ordering.IsDefinedBefore(*b, *a)) { - return false; - } else { - return a->id() < b->id(); - } - }); + absl::c_sort(values, [&ordering](const HloValue* a, const HloValue* b) { + if (ordering.IsDefinedBefore(*a, *b)) { + return true; + } else if (ordering.IsDefinedBefore(*b, *a)) { + return false; + } else { + return a->id() < b->id(); + } + }); // Walk through the ordered vector of values. First verify that the values // are totally ordered with respect to 'ordering', then check that no diff --git a/tensorflow/compiler/xla/service/hlo_buffer.cc b/tensorflow/compiler/xla/service/hlo_buffer.cc index 9c3aa0e64d..32e48651b3 100644 --- a/tensorflow/compiler/xla/service/hlo_buffer.cc +++ b/tensorflow/compiler/xla/service/hlo_buffer.cc @@ -49,7 +49,7 @@ std::vector HloBuffer::ComputePositions() const { value->positions().end()); } // Remove duplicates and sort positions. - std::sort(positions.begin(), positions.end()); + absl::c_sort(positions); positions.erase(std::unique(positions.begin(), positions.end()), positions.end()); return positions; diff --git a/tensorflow/compiler/xla/service/hlo_computation.cc b/tensorflow/compiler/xla/service/hlo_computation.cc index 10f0cf3a34..9e15722633 100644 --- a/tensorflow/compiler/xla/service/hlo_computation.cc +++ b/tensorflow/compiler/xla/service/hlo_computation.cc @@ -531,11 +531,10 @@ HloComputation::CreateFromProto( HloInstruction* root = instruction_map.at(proto.root_id()); // Sort the instructions in the proto id's order. - std::sort(instructions.begin(), instructions.end(), - [&](const std::unique_ptr& a, - const std::unique_ptr& b) { - return to_proto_id[a.get()] < to_proto_id[b.get()]; - }); + absl::c_sort(instructions, [&](const std::unique_ptr& a, + const std::unique_ptr& b) { + return to_proto_id[a.get()] < to_proto_id[b.get()]; + }); TF_RETURN_IF_ERROR([&]() -> Status { std::vector parameters_seen(parameter_count); @@ -800,8 +799,7 @@ Status HloComputation::AcceptOrdered( absl::Span order) const { VLOG(3) << "Accepting visitor with order."; for (HloInstruction* root : CollectUnreachableRoots()) { - TF_RET_CHECK(std::find(order.begin(), order.end(), root) != order.end()) - << root->ToString(); + TF_RET_CHECK(absl::c_linear_search(order, root)) << root->ToString(); } TF_RET_CHECK(order.size() == instruction_count()); absl::flat_hash_set visited; diff --git a/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc b/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc index 0db452b85f..3144a84805 100644 --- a/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc @@ -256,7 +256,7 @@ bool HloDataflowAnalysis::Phi( input_value_ids.push_back(value->id()); } } - std::sort(input_value_ids.begin(), input_value_ids.end()); + absl::c_sort(input_value_ids); input_value_ids.erase( std::unique(input_value_ids.begin(), input_value_ids.end()), input_value_ids.end()); @@ -271,8 +271,7 @@ bool HloDataflowAnalysis::Phi( if (current_value_defined_here) { VLOG(5) << "current_value_defined_here: " << current_value->ToString(); CHECK(current_value->is_phi()); - auto it = std::find(input_value_ids.begin(), input_value_ids.end(), - current_value->id()); + auto it = absl::c_find(input_value_ids, current_value->id()); if (it != input_value_ids.end()) { input_value_ids.erase(it); } @@ -921,8 +920,7 @@ StatusOr> HloDataflowAnalysis::Run( for (auto& pair : dataflow_analysis->values_) { dataflow_analysis->values_vector_.push_back(&pair.second); } - std::sort(dataflow_analysis->values_vector_.begin(), - dataflow_analysis->values_vector_.end(), HloValue::IdLessThan); + absl::c_sort(dataflow_analysis->values_vector_, HloValue::IdLessThan); TF_DCHECK_OK(dataflow_analysis->Verify()); @@ -937,9 +935,7 @@ Status HloDataflowAnalysis::Verify() const { for (const HloValue* value : values()) { for (const HloPosition& position : value->positions()) { const HloValueSet& value_set = GetValueSet(position); - TF_RET_CHECK(std::find(value_set.values().begin(), - value_set.values().end(), - value) != value_set.values().end()) + TF_RET_CHECK(absl::c_linear_search(value_set.values(), value)) << "Value set at position " << position << " does not contain value " << value->ToShortString(); } @@ -954,9 +950,7 @@ Status HloDataflowAnalysis::Verify() const { const HloValueSet& value_set = pair.second; const HloPosition position{instruction, index}; for (const HloValue* value : value_set.values()) { - TF_RET_CHECK(std::find(value->positions().begin(), - value->positions().end(), - position) != value->positions().end()) + TF_RET_CHECK(absl::c_linear_search(value->positions(), position)) << "Value set at position " << position << " unexpectedly contains value " << value->ToShortString(); } @@ -1041,11 +1035,10 @@ bool HloDataflowAnalysis::CanShareOperandBufferWithUser( // Check if one operand of kAdd fused root is kDot or kConvolution. auto* add = user->fused_expression_root(); auto add_operand_it = - std::find_if(add->operands().begin(), add->operands().end(), - [&](HloInstruction* operand) { - return operand->opcode() == HloOpcode::kConvolution || - operand->opcode() == HloOpcode::kDot; - }); + absl::c_find_if(add->operands(), [&](HloInstruction* operand) { + return operand->opcode() == HloOpcode::kConvolution || + operand->opcode() == HloOpcode::kDot; + }); if (add_operand_it == add->operands().end()) { return false; } @@ -1100,16 +1093,15 @@ bool HloDataflowAnalysis::CanShareOperandBufferWithUser( // *) The root instruction of the called computation is element-wise on // 'operand'. const bool found_caller_use = - std::find_if(uses.begin(), uses.end(), [user](const HloUse& use) { + absl::c_find_if(uses, [user](const HloUse& use) { return use.instruction == user; }) != uses.end(); auto* callee_root = user->to_apply()->root_instruction(); const bool found_elementwise_callee_use = - std::find_if( - uses.begin(), uses.end(), [callee_root](const HloUse& use) { - return use.instruction == callee_root && - callee_root->IsElementwiseOnOperand(use.operand_number); - }) != uses.end(); + absl::c_find_if(uses, [callee_root](const HloUse& use) { + return use.instruction == callee_root && + callee_root->IsElementwiseOnOperand(use.operand_number); + }) != uses.end(); return uses.size() == 2 && found_caller_use && found_elementwise_callee_use; } diff --git a/tensorflow/compiler/xla/service/hlo_dce_test.cc b/tensorflow/compiler/xla/service/hlo_dce_test.cc index 1fa4259a3e..b5d72b386f 100644 --- a/tensorflow/compiler/xla/service/hlo_dce_test.cc +++ b/tensorflow/compiler/xla/service/hlo_dce_test.cc @@ -43,9 +43,7 @@ class HloDceTest : public HloTestBase { // Returns whether the given instruction exists in the given computation. bool HasInstruction(const HloComputation& computation, const HloInstruction* instruction) { - return std::find(computation.instructions().begin(), - computation.instructions().end(), - instruction) != computation.instructions().end(); + return absl::c_linear_search(computation.instructions(), instruction); } }; diff --git a/tensorflow/compiler/xla/service/hlo_domain_map.cc b/tensorflow/compiler/xla/service/hlo_domain_map.cc index c6d02f9f67..7cdb7f6bdf 100644 --- a/tensorflow/compiler/xla/service/hlo_domain_map.cc +++ b/tensorflow/compiler/xla/service/hlo_domain_map.cc @@ -230,10 +230,10 @@ HloDomainMap::MakeNonDomainInstructions( } } // sort instructions according to instructions_order - std::sort(instructions.begin(), instructions.end(), - [&instructions_order](HloInstruction* a, HloInstruction* b) { - return instructions_order.at(a) < instructions_order.at(b); - }); + absl::c_sort(instructions, + [&instructions_order](HloInstruction* a, HloInstruction* b) { + return instructions_order.at(a) < instructions_order.at(b); + }); return instructions; } diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.cc b/tensorflow/compiler/xla/service/hlo_evaluator.cc index 9754b8906b..3a97b56c66 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.cc +++ b/tensorflow/compiler/xla/service/hlo_evaluator.cc @@ -1248,8 +1248,7 @@ StatusOr EvaluateSortInternal(HloInstruction* sort, // Extract a slice from the keys and values literals that correspond to // exactly the row in dimension 'sort_dim'. std::vector limit_indices(indices.begin(), indices.end()); - std::for_each(limit_indices.begin(), limit_indices.end(), - [](int64& index) { ++index; }); + absl::c_for_each(limit_indices, [](int64& index) { ++index; }); limit_indices[sort_dim] = sort_dim_elements; TF_ASSIGN_OR_RETURN(auto keys_to_sort, keys_literal.Slice(indices, limit_indices) diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h index cf3509d0aa..cc5f6cc682 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h +++ b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h @@ -1673,8 +1673,7 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { // Extract a slice from the literal that corresponds to exactly the // row in dimension 'sort_dim'. std::vector limit_indices(indices.begin(), indices.end()); - std::for_each(limit_indices.begin(), limit_indices.end(), - [](int64& index) { ++index; }); + absl::c_for_each(limit_indices, [](int64& index) { ++index; }); limit_indices[sort_dim] = sort_dim_elements; TF_ASSIGN_OR_RETURN(auto row_to_sort, keys_literal.Slice(indices, limit_indices) diff --git a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc index 22bcb2030b..4c7f5e9e7d 100644 --- a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc +++ b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc @@ -561,8 +561,8 @@ bool HloDotDumper::ShouldShowSubcomputation(const HloComputation* subcomp) { } // Show the subcomputation if we're showing any of its members. - return std::any_of( - subcomp->instructions().begin(), subcomp->instructions().end(), + return absl::c_any_of( + subcomp->instructions(), [&](const HloInstruction* instr) { return filter_.Show(instr); }); } @@ -735,15 +735,14 @@ bool HloDotDumper::ShouldMergeIntoUsers(const HloInstruction* instr) const { const int kMinUsersToOmit = 3; return instr->opcode() == HloOpcode::kParameter && instr->shape().IsTuple() && !instr->IsFused() && - std::count_if(instr->users().begin(), instr->users().end(), - [&](const HloInstruction* user) { - return filter_.Show(user); - }) > kMinUsersToOmit && - std::all_of(instr->users().begin(), instr->users().end(), - [&](const HloInstruction* user) { - return !filter_.Show(user) || - user->opcode() == HloOpcode::kGetTupleElement; - }); + absl::c_count_if(instr->users(), + [&](const HloInstruction* user) { + return filter_.Show(user); + }) > kMinUsersToOmit && + absl::c_all_of(instr->users(), [&](const HloInstruction* user) { + return !filter_.Show(user) || + user->opcode() == HloOpcode::kGetTupleElement; + }); } string HloDotDumper::DumpInstruction(const HloInstruction* instr) { @@ -900,12 +899,11 @@ ColorScheme HloDotDumper::GetInstructionColor(const HloInstruction* instr) { // the same color as a parameter. Unless the merged-in parameter is a // parameter to a fusion node that is bound to a constant -- these aren't // "real" parameters from the user's perspective. - if (std::any_of(instr->operands().begin(), instr->operands().end(), - [&](const HloInstruction* operand) { - return operand->opcode() == HloOpcode::kParameter && - ShouldMergeIntoUsers(operand) && - TryGetFusionParameterConstant(operand) == nullptr; - })) { + if (absl::c_any_of(instr->operands(), [&](const HloInstruction* operand) { + return operand->opcode() == HloOpcode::kParameter && + ShouldMergeIntoUsers(operand) && + TryGetFusionParameterConstant(operand) == nullptr; + })) { return parameter_color; } @@ -1355,12 +1353,11 @@ NodeFilter MakeNodeRadiusAroundFilter(const HloInstruction* root, NodeFilterResult& filter_result = kv.second; const auto& operands = instr->operands(); - if (std::any_of(operands.begin(), operands.end(), is_displayed) && - !std::all_of(operands.begin(), operands.end(), is_displayed)) { + if (absl::c_any_of(operands, is_displayed) && + !absl::c_all_of(operands, is_displayed)) { // Mark nodes with some operands omitted appropriately. filter_result = kSomeOperandsOmitted; - } else if (!operands.empty() && - std::none_of(operands.begin(), operands.end(), is_displayed)) { + } else if (!operands.empty() && absl::c_none_of(operands, is_displayed)) { // Mark nodes with *all* operands omitted appropriately. filter_result = kOmitNodeOperands; } @@ -1368,8 +1365,7 @@ NodeFilter MakeNodeRadiusAroundFilter(const HloInstruction* root, // Promote nodes with type kSomeUsersOmitted to kNormalNode if all of their // users made it into the graph. if (filter_result == kSomeUsersOmitted && - std::all_of(instr->users().begin(), instr->users().end(), - is_displayed)) { + absl::c_all_of(instr->users(), is_displayed)) { filter_result = kNormalNode; } } diff --git a/tensorflow/compiler/xla/service/hlo_instruction.cc b/tensorflow/compiler/xla/service/hlo_instruction.cc index 66bc73740f..029d170317 100644 --- a/tensorflow/compiler/xla/service/hlo_instruction.cc +++ b/tensorflow/compiler/xla/service/hlo_instruction.cc @@ -83,15 +83,14 @@ StatusOr> HloInstruction::CreateFromProto( return computation_map.at(proto.called_computation_ids(index)); }; - TF_RET_CHECK(std::all_of( - proto.operand_ids().begin(), proto.operand_ids().end(), - [&instruction_map](int64 id) { return instruction_map.contains(id); })) + TF_RET_CHECK( + absl::c_all_of(proto.operand_ids(), + [&](int64 id) { return instruction_map.contains(id); })) << proto.name() << " instruction contains invalid operand id(s)"; - TF_RET_CHECK(std::all_of( - proto.called_computation_ids().begin(), - proto.called_computation_ids().end(), - [&computation_map](int64 id) { return computation_map.contains(id); })) + TF_RET_CHECK( + absl::c_all_of(proto.called_computation_ids(), + [&](int64 id) { return computation_map.contains(id); })) << proto.name() << " instruction references invalid computation id(s)"; Shape shape(proto.shape()); @@ -1599,12 +1598,10 @@ HloInstruction::InstructionVector HloInstruction::unique_operands() const { Status HloInstruction::AddControlDependencyTo(HloInstruction* instruction) { TF_RET_CHECK(instruction->parent() == parent()); - if (std::find(control_successors_.begin(), control_successors_.end(), - instruction) == control_successors_.end()) { + if (!absl::c_linear_search(control_successors_, instruction)) { control_successors_.push_back(instruction); - TF_RET_CHECK(std::find(instruction->control_predecessors_.begin(), - instruction->control_predecessors_.end(), - this) == instruction->control_predecessors_.end()); + TF_RET_CHECK( + !absl::c_linear_search(instruction->control_predecessors_, this)); instruction->control_predecessors_.push_back(this); } return Status::OK(); @@ -1853,7 +1850,7 @@ void HloInstruction::RemoveUser(HloInstruction* user) { user_set_.erase(set_it); // This is linear in the number of the users, but a vector provides a stable // iteration order and much faster traversal. - auto vec_it = std::find(users_.begin(), users_.end(), user); + auto vec_it = absl::c_find(users_, user); CHECK(vec_it != users_.end()); users_.erase(vec_it); } @@ -1871,8 +1868,7 @@ Status HloInstruction::ReplaceUseWith(HloInstruction* user, RemoveUser(user); - TF_RET_CHECK( - std::count(user->operands_.begin(), user->operands_.end(), this) >= 0); + TF_RET_CHECK(absl::c_count(user->operands_, this) >= 0); std::replace(user->operands_.begin(), user->operands_.end(), this, new_producer); new_producer->AddUser(user); @@ -1907,8 +1903,7 @@ Status HloInstruction::ReplaceOperandWithDifferentShape( VLOG(3) << "Replacing operand " << operand_num << " of " << name() << " with " << new_operand->name() << ", was " << old_operand->name(); - if (std::find(operands_.begin(), operands_.end(), old_operand) == - operands_.end()) { + if (!absl::c_linear_search(operands_, old_operand)) { old_operand->RemoveUser(this); } new_operand->AddUser(this); @@ -2945,10 +2940,10 @@ StatusOr StringToFusionKind( string PaddingConfigToString(const PaddingConfig& padding) { bool has_interior_padding = - std::any_of(padding.dimensions().begin(), padding.dimensions().end(), - [](const PaddingConfig::PaddingConfigDimension& dim) { - return dim.interior_padding() != 0; - }); + absl::c_any_of(padding.dimensions(), + [](const PaddingConfig::PaddingConfigDimension& dim) { + return dim.interior_padding() != 0; + }); return StrJoin( padding.dimensions(), "x", [&](string* out, const PaddingConfig::PaddingConfigDimension& dim) { diff --git a/tensorflow/compiler/xla/service/hlo_instructions.cc b/tensorflow/compiler/xla/service/hlo_instructions.cc index 7170dd7d81..c24bf41ff8 100644 --- a/tensorflow/compiler/xla/service/hlo_instructions.cc +++ b/tensorflow/compiler/xla/service/hlo_instructions.cc @@ -42,11 +42,9 @@ using absl::StrJoin; bool IsInstructionElementwiseOnOperand(const HloInstruction* instruction, const HloInstruction* operand) { std::vector operand_indices = instruction->OperandIndices(operand); - return std::all_of( - operand_indices.begin(), operand_indices.end(), - [instruction](int64 operand_index) { - return instruction->IsElementwiseOnOperand(operand_index); - }); + return absl::c_all_of(operand_indices, [instruction](int64 operand_index) { + return instruction->IsElementwiseOnOperand(operand_index); + }); } string PrecisionConfigToString(const PrecisionConfig& precision_config) { @@ -814,8 +812,7 @@ std::vector HloSliceInstruction::ExtraAttributesToStringImpl( std::vector bounds; bounds.reserve(slice_starts_.size()); const bool omit_stride = - std::all_of(slice_strides_.begin(), slice_strides_.end(), - [](int64 stride) { return stride == 1; }); + absl::c_all_of(slice_strides_, [](int64 stride) { return stride == 1; }); for (int i = 0; i < slice_starts_.size(); ++i) { string stride_str = omit_stride ? "" : StrCat(":", slice_strides_[i]); bounds.push_back( @@ -1051,8 +1048,7 @@ HloInstruction* HloFusionInstruction::AddFusionOperand( void HloFusionInstruction::MergeFusionInstruction( HloFusionInstruction* instruction_to_merge) { - CHECK(std::find(operands().begin(), operands().end(), instruction_to_merge) != - operands().end()); + CHECK(absl::c_linear_search(operands(), instruction_to_merge)); // Clone the instruction from which to merge fused instructions. std::unique_ptr cloned = instruction_to_merge->Clone(); HloFusionInstruction* cloned_fusion = @@ -1219,8 +1215,8 @@ HloInstruction* HloFusionInstruction::CloneAndFuseInternal( // corresponding fused parameter instruction. Renumber parameters as // necessary to make parameter numbers consistent with their index in the // fused_parameter_ vector. - bool in_operand_list = std::find(operands().begin(), operands().end(), - instruction_to_fuse) != operands().end(); + bool in_operand_list = + absl::c_linear_search(operands(), instruction_to_fuse); CHECK(add_output || in_operand_list); if (instruction_to_fuse->opcode() == HloOpcode::kTuple) { // We assume all uses of a kTuple operation are GTE ops, not another diff --git a/tensorflow/compiler/xla/service/hlo_module.cc b/tensorflow/compiler/xla/service/hlo_module.cc index ed78189fed..258f918f47 100644 --- a/tensorflow/compiler/xla/service/hlo_module.cc +++ b/tensorflow/compiler/xla/service/hlo_module.cc @@ -107,11 +107,10 @@ HloComputation* HloModule::AddEntryComputation( } Status HloModule::RemoveEmbeddedComputation(HloComputation* to_remove) { - auto it = - std::find_if(computations_.begin(), computations_.end(), - [&to_remove](const std::unique_ptr& comp) { - return comp.get() == to_remove; - }); + auto it = absl::c_find_if( + computations_, [&to_remove](const std::unique_ptr& comp) { + return comp.get() == to_remove; + }); TF_RET_CHECK(it->get() == to_remove); computations_.erase(it); return Status::OK(); @@ -304,11 +303,10 @@ StatusOr> HloModule::CreateFromProto( auto module = absl::make_unique(proto.name(), module_config); // Sort the computations in the proto id's order. - std::sort(computations.begin(), computations.end(), - [&](const std::unique_ptr& a, - const std::unique_ptr& b) { - return to_proto_id[a.get()] < to_proto_id[b.get()]; - }); + absl::c_sort(computations, [&](const std::unique_ptr& a, + const std::unique_ptr& b) { + return to_proto_id[a.get()] < to_proto_id[b.get()]; + }); // Add sorted computations to the module. for (auto& computation : computations) { diff --git a/tensorflow/compiler/xla/service/hlo_module_dce_test.cc b/tensorflow/compiler/xla/service/hlo_module_dce_test.cc index e535b7d749..f6e2866204 100644 --- a/tensorflow/compiler/xla/service/hlo_module_dce_test.cc +++ b/tensorflow/compiler/xla/service/hlo_module_dce_test.cc @@ -38,9 +38,7 @@ class HloModuleDceTest : public HloTestBase { // Returns whether the given instruction exists in the given computation. bool HasInstruction(const HloComputation& computation, const HloInstruction* instruction) { - return std::find(computation.instructions().begin(), - computation.instructions().end(), - instruction) != computation.instructions().end(); + return absl::c_linear_search(computation.instructions(), instruction); } // Returns whether the while instruction with name 'while_name' in diff --git a/tensorflow/compiler/xla/service/hlo_parser.cc b/tensorflow/compiler/xla/service/hlo_parser.cc index 730d39500b..638396308c 100644 --- a/tensorflow/compiler/xla/service/hlo_parser.cc +++ b/tensorflow/compiler/xla/service/hlo_parser.cc @@ -2746,7 +2746,7 @@ bool HloParser::ParseConvolutionDimensionNumbers( } auto is_unique = [](string str) -> bool { - std::sort(str.begin(), str.end()); + absl::c_sort(str); return std::unique(str.begin(), str.end()) == str.end(); }; diff --git a/tensorflow/compiler/xla/service/hlo_profile_printer.cc b/tensorflow/compiler/xla/service/hlo_profile_printer.cc index 5eb707a957..9cc202aa9f 100644 --- a/tensorflow/compiler/xla/service/hlo_profile_printer.cc +++ b/tensorflow/compiler/xla/service/hlo_profile_printer.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/hlo_profile_printer.h" +#include "absl/algorithm/container.h" #include "absl/strings/str_cat.h" #include "tensorflow/compiler/xla/service/human_readable_profile_builder.h" @@ -34,11 +35,10 @@ string PrintHloProfile(const HloProfilePrinterData& hlo_profile_printer_data, for (const HloComputationInfo& computation_info : hlo_profile_printer_data.computation_infos()) { const auto& instruction_infos = computation_info.instruction_infos(); - bool any_instruction_profiled = - std::any_of(instruction_infos.begin(), instruction_infos.end(), - [&](const HloInstructionInfo& instruction_info) { - return counters[instruction_info.profile_index()] != 0; - }); + bool any_instruction_profiled = absl::c_any_of( + instruction_infos, [&](const HloInstructionInfo& instruction_info) { + return counters[instruction_info.profile_index()] != 0; + }); if (!any_instruction_profiled) { continue; diff --git a/tensorflow/compiler/xla/service/hlo_reachability.cc b/tensorflow/compiler/xla/service/hlo_reachability.cc index edaa4c59e2..0fced7f15b 100644 --- a/tensorflow/compiler/xla/service/hlo_reachability.cc +++ b/tensorflow/compiler/xla/service/hlo_reachability.cc @@ -49,7 +49,7 @@ void HloReachabilityMap::SetReachabilityToUnionHelper( absl::Span inputs, const HloInstruction* instruction, BitVector* bit_vector) { // If instruction is part of inputs, don't reset the bit_vector. - if (std::find(inputs.begin(), inputs.end(), instruction) == inputs.end()) { + if (!absl::c_linear_search(inputs, instruction)) { bit_vector->SetToZero(); } bit_vector->Set(GetIndex(instruction)); diff --git a/tensorflow/compiler/xla/service/hlo_rematerialization.cc b/tensorflow/compiler/xla/service/hlo_rematerialization.cc index ac74e2432f..9ca14ca18a 100644 --- a/tensorflow/compiler/xla/service/hlo_rematerialization.cc +++ b/tensorflow/compiler/xla/service/hlo_rematerialization.cc @@ -235,8 +235,7 @@ class InstructionList { } // Now scan forwards until we find one of the before_instructions. - while (std::find(before_instructions.begin(), before_instructions.end(), - min_position_item) == before_instructions.end()) { + while (!absl::c_linear_search(before_instructions, min_position_item)) { min_position_item = min_position_item->next; } return InsertBefore(to_insert, min_position_item); @@ -302,7 +301,7 @@ ItemList GetUsers(const InstructionList& instruction_list, // A buffer may be used by the instruction via more than one alias. For // example, a buffer which appears in more than one element of a tuple. Item* user_item = instruction_list.GetItem(user); - if (std::find(users.begin(), users.end(), user_item) == users.end()) { + if (!absl::c_linear_search(users, user_item)) { users.push_back(user_item); } } @@ -456,8 +455,7 @@ class MemoryUsageTracker { return false; } const BufferIdList& in_progress_uses = in_progress_item_->buffers_used; - return std::find(in_progress_uses.begin(), in_progress_uses.end(), - buffer_id) != in_progress_uses.end(); + return absl::c_linear_search(in_progress_uses, buffer_id); } // Returns whether the given instruction is live at the current program @@ -535,8 +533,7 @@ MemoryUsageTracker::MemoryUsageTracker( bool unused; for (Item* user_item : GetUsers(instruction_list_, logical_buffer, points_to_analysis, &unused)) { - if (std::find(buffer->users.begin(), buffer->users.end(), - user_item) == buffer->users.end()) { + if (!absl::c_linear_search(buffer->users, user_item)) { buffer->users.push_back(user_item); buffer->unfinished_user_count++; user_item->buffers_used.push_back(buffer->id); @@ -784,8 +781,7 @@ bool MemoryUsageTracker::Check() const { for (const Buffer& buffer : buffers_) { if (buffer.defining_instruction->instruction == instruction) { - CHECK(std::find(defined_buffers.begin(), defined_buffers.end(), - buffer.id) != defined_buffers.end()) + CHECK(absl::c_linear_search(defined_buffers, buffer.id)) << "Instruction " << instruction->name() << " defined buffers is missing: " << buffer.ToString(); } @@ -808,8 +804,7 @@ bool MemoryUsageTracker::Check() const { int64 unfinished_uses = 0; for (Item* user : buffer.users) { const BufferIdList& used_buffers = user->buffers_used; - CHECK(std::find(used_buffers.begin(), used_buffers.end(), buffer.id) != - used_buffers.end()) + CHECK(absl::c_linear_search(used_buffers, buffer.id)) << "Instruction " << user->instruction->name() << " used buffers is missing " << buffer.ToString(); if (!IsFinished(user)) { @@ -836,10 +831,10 @@ int64 RematerializationCost(const HloInstruction* instruction, // If none of the users of 'instruction' have been placed in the sequence (as // tracked by memory_tracker), then rematerialization of 'instruction' is a // zero-cost move of 'instruction' in the sequence. - if (!std::any_of(instruction->users().begin(), instruction->users().end(), - [&memory_tracker](const HloInstruction* inst) { - return memory_tracker.IsPlaced(inst); - })) { + if (!absl::c_any_of(instruction->users(), + [&memory_tracker](const HloInstruction* inst) { + return memory_tracker.IsPlaced(inst); + })) { return 0; } diff --git a/tensorflow/compiler/xla/service/hlo_sharding.cc b/tensorflow/compiler/xla/service/hlo_sharding.cc index 6c9c32f179..37cc146bd7 100644 --- a/tensorflow/compiler/xla/service/hlo_sharding.cc +++ b/tensorflow/compiler/xla/service/hlo_sharding.cc @@ -107,13 +107,12 @@ string HloSharding::ToString() const { bool HloSharding::UsesDevice(int64 device) const { if (IsTuple()) { - return std::any_of( - tuple_elements_.begin(), tuple_elements_.end(), - [&](const HloSharding& s) { return s.UsesDevice(device); }); + return absl::c_any_of(tuple_elements_, [&](const HloSharding& s) { + return s.UsesDevice(device); + }); } const auto& devices = tile_assignment_; - return replicated_ || - std::find(devices.begin(), devices.end(), device) != devices.end(); + return replicated_ || absl::c_linear_search(devices, device); } std::map HloSharding::UsedDevices(int64* count) const { diff --git a/tensorflow/compiler/xla/service/hlo_sharding.h b/tensorflow/compiler/xla/service/hlo_sharding.h index 9775505f86..5789ae0998 100644 --- a/tensorflow/compiler/xla/service/hlo_sharding.h +++ b/tensorflow/compiler/xla/service/hlo_sharding.h @@ -101,8 +101,8 @@ class HloSharding { if (!IsTuple()) { return replicated_; } - return std::all_of(tuple_elements_.begin(), tuple_elements_.end(), - [](const HloSharding& s) { return s.IsReplicated(); }); + return absl::c_all_of( + tuple_elements_, [](const HloSharding& s) { return s.IsReplicated(); }); } // Returns true if the tile size is the same as the input size. @@ -110,8 +110,9 @@ class HloSharding { if (!IsTuple()) { return maximal_; } - return std::all_of(tuple_elements_.begin(), tuple_elements_.end(), - [](const HloSharding& s) { return s.IsTileMaximal(); }); + return absl::c_all_of(tuple_elements_, [](const HloSharding& s) { + return s.IsTileMaximal(); + }); } // Returns true if the sharding defines an operation on the given device. diff --git a/tensorflow/compiler/xla/service/hlo_tfgraph_builder.cc b/tensorflow/compiler/xla/service/hlo_tfgraph_builder.cc index 4ea39a7628..c1f69db74e 100644 --- a/tensorflow/compiler/xla/service/hlo_tfgraph_builder.cc +++ b/tensorflow/compiler/xla/service/hlo_tfgraph_builder.cc @@ -61,8 +61,7 @@ void CleanNodeName(string* name) { name->erase(std::remove(name->begin(), name->end(), '%'), name->end()); const string chars_to_replace = "<>[]"; auto pred = [&](char c) { - return std::find(chars_to_replace.begin(), chars_to_replace.end(), c) != - chars_to_replace.end(); + return absl::c_linear_search(chars_to_replace, c); }; std::replace_if(name->begin(), name->end(), pred, '_'); } diff --git a/tensorflow/compiler/xla/service/hlo_value.cc b/tensorflow/compiler/xla/service/hlo_value.cc index d409df06be..218b33b2ac 100644 --- a/tensorflow/compiler/xla/service/hlo_value.cc +++ b/tensorflow/compiler/xla/service/hlo_value.cc @@ -209,7 +209,7 @@ std::ostream& operator<<(std::ostream& out, const HloValue& value) { } void HloValueSet::SortAndUniquifyValues() { - std::sort(values_.begin(), values_.end(), HloValue::IdLessThan); + absl::c_sort(values_, HloValue::IdLessThan); values_.erase(std::unique(values_.begin(), values_.end(), HloValue::IdEqual), values_.end()); } diff --git a/tensorflow/compiler/xla/service/human_readable_profile_builder.cc b/tensorflow/compiler/xla/service/human_readable_profile_builder.cc index 90904ac001..88fc62bd1e 100644 --- a/tensorflow/compiler/xla/service/human_readable_profile_builder.cc +++ b/tensorflow/compiler/xla/service/human_readable_profile_builder.cc @@ -128,9 +128,9 @@ string HumanReadableProfileBuilder::ToString() const { // Sort ops in decreasing order of cycles, and print them. std::vector sorted_ops(op_infos_); - std::sort( - sorted_ops.begin(), sorted_ops.end(), - [](const OpInfo& a, const OpInfo& b) { return a.cycles > b.cycles; }); + absl::c_sort(sorted_ops, [](const OpInfo& a, const OpInfo& b) { + return a.cycles > b.cycles; + }); for (const auto& op : sorted_ops) { print_op(op); } diff --git a/tensorflow/compiler/xla/service/instruction_fusion.cc b/tensorflow/compiler/xla/service/instruction_fusion.cc index f6f25c4413..b97060535d 100644 --- a/tensorflow/compiler/xla/service/instruction_fusion.cc +++ b/tensorflow/compiler/xla/service/instruction_fusion.cc @@ -178,19 +178,18 @@ bool InstructionFusion::EffectivelyAtMostUnary(HloInstruction* hlo) { output_rank = std::max(output_rank, ShapeUtil::TrueRank(subshape)); } }); - return std::count_if(hlo->operands().begin(), hlo->operands().end(), - [output_rank](HloInstruction* operand) { - if (operand->opcode() == HloOpcode::kBroadcast || - operand->opcode() == HloOpcode::kIota) { - return false; - } - if (operand->opcode() == HloOpcode::kConstant && - ShapeUtil::IsEffectiveScalar(operand->shape())) { - return false; - } - return ShapeUtil::TrueRank(operand->shape()) >= - output_rank; - }) <= 1; + return absl::c_count_if( + hlo->operands(), [output_rank](HloInstruction* operand) { + if (operand->opcode() == HloOpcode::kBroadcast || + operand->opcode() == HloOpcode::kIota) { + return false; + } + if (operand->opcode() == HloOpcode::kConstant && + ShapeUtil::IsEffectiveScalar(operand->shape())) { + return false; + } + return ShapeUtil::TrueRank(operand->shape()) >= output_rank; + }) <= 1; } bool InstructionFusion::CanFuseOnAllPaths( @@ -409,9 +408,8 @@ class ReversePostOrderFusionQueue : public FusionQueue { } sorted_operand_numbers.push_back(i); } - std::sort( - sorted_operand_numbers.begin(), sorted_operand_numbers.end(), - [&](int64 i, int64 j) { + absl::c_sort( + sorted_operand_numbers, [&](int64 i, int64 j) { // Instructions with higher priority in the queue come first. return ( FindOrDie(post_order_index_, instruction->mutable_operand(i)) > diff --git a/tensorflow/compiler/xla/service/layout_assignment.cc b/tensorflow/compiler/xla/service/layout_assignment.cc index 406cd0a219..10ff7bb6d4 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.cc +++ b/tensorflow/compiler/xla/service/layout_assignment.cc @@ -147,12 +147,9 @@ bool LayoutConstraints::OperandBufferForwarded( PointsToSet::BufferSet* output_buffers = GetBufferSet(instruction); PointsToSet::BufferSet* operand_buffers = GetBufferSet(instruction->operand(operand_no)); - for (const LogicalBuffer* output_buffer : *output_buffers) { - if (operand_buffers->count(output_buffer) > 0) { - return true; - } - } - return false; + return absl::c_any_of(*output_buffers, [&](const LogicalBuffer* b) { + return operand_buffers->count(b) > 0; + }); } Status LayoutConstraints::SetBufferLayout(const Layout& layout, diff --git a/tensorflow/compiler/xla/service/llvm_ir/alias_analysis.cc b/tensorflow/compiler/xla/service/llvm_ir/alias_analysis.cc index 643ecd0fba..ce3d922ca7 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/alias_analysis.cc +++ b/tensorflow/compiler/xla/service/llvm_ir/alias_analysis.cc @@ -81,9 +81,7 @@ void AliasAnalysis::AddAliasingInformationToIrArray(const HloInstruction& hlo, if (hlo.opcode() == HloOpcode::kParameter) { const std::vector& parameter_instructions = module_.entry_computation()->parameter_instructions(); - if (std::find(parameter_instructions.begin(), - parameter_instructions.end(), - &hlo) != parameter_instructions.end()) { + if (absl::c_linear_search(parameter_instructions, &hlo)) { array->MarkInvariantOverWholeProgram(context_); } } diff --git a/tensorflow/compiler/xla/service/name_uniquer.cc b/tensorflow/compiler/xla/service/name_uniquer.cc index daa718879d..f6feed2993 100644 --- a/tensorflow/compiler/xla/service/name_uniquer.cc +++ b/tensorflow/compiler/xla/service/name_uniquer.cc @@ -34,7 +34,7 @@ bool IsAllowed(char character) { } // namespace NameUniquer::NameUniquer(const string& separator) { - CHECK(std::all_of(separator.begin(), separator.end(), IsAllowed)) + CHECK(absl::c_all_of(separator, IsAllowed)) << "separator should comprises allowed characters only"; separator_ = separator; } diff --git a/tensorflow/compiler/xla/service/platform_util.cc b/tensorflow/compiler/xla/service/platform_util.cc index 896b73cda4..0491f641fc 100644 --- a/tensorflow/compiler/xla/service/platform_util.cc +++ b/tensorflow/compiler/xla/service/platform_util.cc @@ -260,8 +260,8 @@ PlatformUtil::GetStreamExecutors( // Block here in thread_pool destructor until all devices are initialized. } VLOG(1) << "Device initialization complete"; - if (std::all_of(stream_executors.begin(), stream_executors.end(), - [](se::StreamExecutor* s) { return s == nullptr; })) { + if (absl::c_all_of(stream_executors, + [](se::StreamExecutor* s) { return s == nullptr; })) { return InternalError("no supported devices found for platform %s", platform->Name()); } diff --git a/tensorflow/compiler/xla/service/shape_inference.cc b/tensorflow/compiler/xla/service/shape_inference.cc index 4a2dd09742..d711c8ef0d 100644 --- a/tensorflow/compiler/xla/service/shape_inference.cc +++ b/tensorflow/compiler/xla/service/shape_inference.cc @@ -534,9 +534,8 @@ Status ValidateDotDimensionNumbers( absl::Span contracting_dims, absl::Span batch_dims) -> bool { auto in_range = [&rank](int64 i) -> bool { return 0 <= i && i < rank; }; - return std::all_of(contracting_dims.begin(), contracting_dims.end(), - in_range) && - std::all_of(batch_dims.begin(), batch_dims.end(), in_range); + return absl::c_all_of(contracting_dims, in_range) && + absl::c_all_of(batch_dims, in_range); }; absl::Span lhs_contracting_dimensions = @@ -563,9 +562,8 @@ Status ValidateDotDimensionNumbers( auto is_unique = [&dim_set](int64 i) -> bool { return dim_set.insert(i).second; }; - return std::all_of(contracting_dims.begin(), contracting_dims.end(), - is_unique) && - std::all_of(batch_dims.begin(), batch_dims.end(), is_unique); + return absl::c_all_of(contracting_dims, is_unique) && + absl::c_all_of(batch_dims, is_unique); }; if (!dims_unique(lhs_contracting_dimensions, lhs_batch_dimensions) || @@ -1589,29 +1587,29 @@ ShapeInference::InferDegenerateDimensionBroadcastShape(HloOpcode operation, input_dnums[1] = dnums.input_feature_dimension(); std::copy(dnums.input_spatial_dimensions().begin(), dnums.input_spatial_dimensions().end(), input_dnums.begin() + 2); - std::sort(input_dnums.begin(), input_dnums.end()); + absl::c_sort(input_dnums); std::vector window_dnums(num_dims); window_dnums[0] = dnums.kernel_input_feature_dimension(); window_dnums[1] = dnums.kernel_output_feature_dimension(); std::copy(dnums.kernel_spatial_dimensions().begin(), dnums.kernel_spatial_dimensions().end(), window_dnums.begin() + 2); - std::sort(window_dnums.begin(), window_dnums.end()); + absl::c_sort(window_dnums); std::vector output_dnums(num_dims); output_dnums[0] = dnums.output_batch_dimension(); output_dnums[1] = dnums.output_feature_dimension(); std::copy(dnums.output_spatial_dimensions().begin(), dnums.output_spatial_dimensions().end(), output_dnums.begin() + 2); - std::sort(output_dnums.begin(), output_dnums.end()); + absl::c_sort(output_dnums); std::vector expected_dnums(num_dims); std::iota(expected_dnums.begin(), expected_dnums.end(), 0); const auto in_range = [num_dims](int64 i) { return 0 <= i && i < num_dims; }; - if (!std::all_of(input_dnums.begin(), input_dnums.end(), in_range) || - !std::all_of(window_dnums.begin(), window_dnums.end(), in_range) || - !std::all_of(output_dnums.begin(), output_dnums.end(), in_range)) { + if (!absl::c_all_of(input_dnums, in_range) || + !absl::c_all_of(window_dnums, in_range) || + !absl::c_all_of(output_dnums, in_range)) { return InvalidArgument( "A dimension number is out of range in convolution: %s.", dnums.DebugString()); diff --git a/tensorflow/compiler/xla/service/transpose_folding.cc b/tensorflow/compiler/xla/service/transpose_folding.cc index 15eb46bac0..a95ca2bf2a 100644 --- a/tensorflow/compiler/xla/service/transpose_folding.cc +++ b/tensorflow/compiler/xla/service/transpose_folding.cc @@ -130,8 +130,7 @@ bool FoldTransposeIntoConvolution(InstructionOperandsPair pair) { HloInstruction* new_lhs; const int64 kLhsIdx = 0; - if (std::find(operand_indices.begin(), operand_indices.end(), kLhsIdx) != - operand_indices.end()) { + if (absl::c_linear_search(operand_indices, kLhsIdx)) { HloInstruction& transpose = *convolution.mutable_operand(kLhsIdx); const auto& transpose_dimensions = transpose.dimensions(); HloInstruction& transpose_operand = *transpose.mutable_operand(0); @@ -154,8 +153,7 @@ bool FoldTransposeIntoConvolution(InstructionOperandsPair pair) { HloInstruction* new_rhs; const int64 kRhsIdx = 1; - if (std::find(operand_indices.begin(), operand_indices.end(), kRhsIdx) != - operand_indices.end()) { + if (absl::c_linear_search(operand_indices, kRhsIdx)) { HloInstruction& transpose = *convolution.mutable_operand(kRhsIdx); const auto& transpose_dimensions = transpose.dimensions(); HloInstruction& transpose_operand = *transpose.mutable_operand(0); diff --git a/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc b/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc index dec719b04a..5e505aaf02 100644 --- a/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc +++ b/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc @@ -87,9 +87,7 @@ bool PointsToSet::ContainsBuffer(const LogicalBuffer& buffer) const { bool found = false; ForEachElement([&found, &buffer](const ShapeIndex& /*index*/, const BufferList& pointed_to_buffers) { - if (!found && - std::find(pointed_to_buffers.begin(), pointed_to_buffers.end(), - &buffer) != pointed_to_buffers.end()) { + if (!found && absl::c_linear_search(pointed_to_buffers, &buffer)) { found = true; } }); @@ -99,8 +97,7 @@ bool PointsToSet::ContainsBuffer(const LogicalBuffer& buffer) const { bool PointsToSet::ContainsBufferAtIndex(const LogicalBuffer& buffer, const ShapeIndex& index) const { const auto& pointed_to_buffers = element(index); - return std::find(pointed_to_buffers.begin(), pointed_to_buffers.end(), - &buffer) != pointed_to_buffers.end(); + return absl::c_linear_search(pointed_to_buffers, &buffer); } void PointsToSet::AddPointedToBuffer(const LogicalBuffer& buffer, @@ -604,9 +601,8 @@ bool TuplePointsToAnalysis::DoesNotUseOperandBuffer( } else if (user->opcode() == HloOpcode::kFusion && user->fusion_kind() == HloInstruction::FusionKind::kLoop) { // Find fusion parameter associated with 'operand'. - auto it = std::find_if( - user->fused_parameters().begin(), user->fused_parameters().end(), - [=](HloInstruction* fused_param) { + auto it = absl::c_find_if( + user->fused_parameters(), [&](HloInstruction* fused_param) { return user->operand(fused_param->parameter_number()) == operand; }); CHECK(it != user->fused_parameters().end()); @@ -672,9 +668,8 @@ bool TuplePointsToAnalysis::HasUniqueFusedUseOfOperandAt( } // Find fusion parameter associated with 'operand'. const auto& fused_params = fusion->fused_parameters(); - auto fused_param_it = std::find_if( - fused_params.begin(), fused_params.end(), - [&](HloInstruction* fused_param) { + auto fused_param_it = + absl::c_find_if(fused_params, [&](HloInstruction* fused_param) { return fusion->operand(fused_param->parameter_number()) == operand; }); if (fused_param_it == fused_params.end()) { @@ -743,11 +738,10 @@ bool TuplePointsToAnalysis::CanShareOperandBufferWithUser( // Check if one operand of kAdd fused root is kDot or kConvolution. auto* add = user->fused_expression_root(); auto add_operand_it = - std::find_if(add->operands().begin(), add->operands().end(), - [&](HloInstruction* operand) { - return operand->opcode() == HloOpcode::kConvolution || - operand->opcode() == HloOpcode::kDot; - }); + absl::c_find_if(add->operands(), [&](HloInstruction* operand) { + return operand->opcode() == HloOpcode::kConvolution || + operand->opcode() == HloOpcode::kDot; + }); if (add_operand_it == add->operands().end()) { return false; } diff --git a/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc b/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc index d8875ca747..7599e1e6ad 100644 --- a/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc +++ b/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc @@ -721,9 +721,8 @@ class FusionPointsToAnalysisTest : public TuplePointsToAnalysisTest { // to fusion 'operand'. HloInstruction* GetFusionParameterForOperand(HloInstruction* fusion, HloInstruction* operand) { - auto it = std::find_if( - fusion->fused_instructions().begin(), - fusion->fused_instructions().end(), [=](const HloInstruction* fused) { + auto it = absl::c_find_if( + fusion->fused_instructions(), [&](const HloInstruction* fused) { return fused->opcode() == HloOpcode::kParameter && fusion->operand(fused->parameter_number()) == operand; }); diff --git a/tensorflow/compiler/xla/service/while_loop_simplifier.cc b/tensorflow/compiler/xla/service/while_loop_simplifier.cc index de884b037d..09d5409571 100644 --- a/tensorflow/compiler/xla/service/while_loop_simplifier.cc +++ b/tensorflow/compiler/xla/service/while_loop_simplifier.cc @@ -109,8 +109,7 @@ static StatusOr TryRemoveDeadWhileParams(HloInstruction* while_op) { // operand appears in, but it may appear more than once! if (user->user_count() == 1 && user->users().front() == while_body_root && while_body_root->operand_index(user) == user->tuple_index() && - std::count(while_body_root->operands().begin(), - while_body_root->operands().end(), user) == 1) { + absl::c_count(while_body_root->operands(), user) == 1) { continue; } @@ -158,7 +157,7 @@ static StatusOr TryRemoveDeadWhileParams(HloInstruction* while_op) { // Build up maps from the old/new to the new/old tuple indices. std::vector new_to_old_tuple_idx(used_tuple_indices.begin(), used_tuple_indices.end()); - std::sort(new_to_old_tuple_idx.begin(), new_to_old_tuple_idx.end()); + absl::c_sort(new_to_old_tuple_idx); absl::flat_hash_map old_to_new_tuple_idx; for (int64 new_idx = 0; new_idx < new_to_old_tuple_idx.size(); ++new_idx) { diff --git a/tensorflow/compiler/xla/service/while_loop_simplifier_test.cc b/tensorflow/compiler/xla/service/while_loop_simplifier_test.cc index 3713989ca2..ecca76b1e8 100644 --- a/tensorflow/compiler/xla/service/while_loop_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/while_loop_simplifier_test.cc @@ -407,13 +407,12 @@ TEST_F(WhileLoopSimplifierTest, RemoveUnusedLoopOperands) { // The original while instruction is still left in the module as a dead // instruction, find a while instruction with a different name as the new // while instruction. + const auto& instrs = m->entry_computation()->instructions(); HloInstruction* new_while_op = - *std::find_if(m->entry_computation()->instructions().begin(), - m->entry_computation()->instructions().end(), - [&](const HloInstruction* instr) { - return (instr->opcode() == HloOpcode::kWhile && - instr->name() != "while"); - }); + *absl::c_find_if(instrs, [&](const HloInstruction* instr) { + return (instr->opcode() == HloOpcode::kWhile && + instr->name() != "while"); + }); auto scalar_s32 = ShapeUtil::MakeShape(S32, {}); EXPECT_TRUE( diff --git a/tensorflow/compiler/xla/shape.cc b/tensorflow/compiler/xla/shape.cc index f05d816268..ea4f722ef4 100644 --- a/tensorflow/compiler/xla/shape.cc +++ b/tensorflow/compiler/xla/shape.cc @@ -87,8 +87,7 @@ bool Shape::is_static() const { } } } - return !std::any_of(dynamic_dimensions_.begin(), dynamic_dimensions_.end(), - [](bool b) { return b; }); + return !absl::c_any_of(dynamic_dimensions_, [](bool b) { return b; }); } void Shape::DeleteDimension(int64 dim_to_delete) { diff --git a/tensorflow/compiler/xla/shape_util.cc b/tensorflow/compiler/xla/shape_util.cc index 7f68c0ae1f..4726ee6a5b 100644 --- a/tensorflow/compiler/xla/shape_util.cc +++ b/tensorflow/compiler/xla/shape_util.cc @@ -405,8 +405,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( } /* static */ bool ShapeUtil::IsNestedTuple(const Shape& shape) { - return IsTuple(shape) && std::any_of(shape.tuple_shapes().begin(), - shape.tuple_shapes().end(), IsTuple); + return IsTuple(shape) && absl::c_any_of(shape.tuple_shapes(), IsTuple); } /* static */ bool ShapeUtil::IsEmptyTuple(const Shape& shape) { diff --git a/tensorflow/compiler/xla/sparse_index_array.h b/tensorflow/compiler/xla/sparse_index_array.h index a96d483462..0c25355467 100644 --- a/tensorflow/compiler/xla/sparse_index_array.h +++ b/tensorflow/compiler/xla/sparse_index_array.h @@ -135,7 +135,7 @@ void SparseIndexArray::SortWithValues(absl::Span values) { auto sort_order_less = [this](int64 lhs, int64 rhs) { return IndexUtil::CompareIndices(At(lhs), At(rhs)) < 0; }; - std::sort(sort_order.begin(), sort_order.end(), sort_order_less); + absl::c_sort(sort_order, sort_order_less); // Reorder the array elements according to sort_order. Work through the array // and follow cycles so we can do the reorder in-place. diff --git a/tensorflow/compiler/xla/tests/convolution_test.cc b/tensorflow/compiler/xla/tests/convolution_test.cc index 2496938912..9db9f2563b 100644 --- a/tensorflow/compiler/xla/tests/convolution_test.cc +++ b/tensorflow/compiler/xla/tests/convolution_test.cc @@ -467,8 +467,8 @@ XLA_TEST_F(ConvolutionTest, Convolve3D_1x4x2x3x3_2x2x2x3x3_Valid) { // servers. The error message is missing the operator ++. template void iota_int_init_value(std::vector& values, int init_value) { - std::for_each(values.begin(), values.end(), - [&](T& value) { value = static_cast(init_value++); }); + absl::c_for_each(values, + [&](T& value) { value = static_cast(init_value++); }); } template diff --git a/tensorflow/compiler/xla/util.cc b/tensorflow/compiler/xla/util.cc index 68cab7387c..34b73b5206 100644 --- a/tensorflow/compiler/xla/util.cc +++ b/tensorflow/compiler/xla/util.cc @@ -86,7 +86,7 @@ bool IsPermutation(absl::Span permutation, int64 rank) { CHECK_LT(index, rank); output[index] = 0; } - return std::find(output.begin(), output.end(), -1) == output.end(); + return !absl::c_linear_search(output, -1); } std::vector InversePermutation( diff --git a/tensorflow/compiler/xla/util.h b/tensorflow/compiler/xla/util.h index 6722641e9d..f2fd17dc99 100644 --- a/tensorflow/compiler/xla/util.h +++ b/tensorflow/compiler/xla/util.h @@ -324,8 +324,7 @@ bool IsIdentityPermutation(absl::Span permutation); template int64 PositionInContainer(const Container& container, int64 value) { - return std::distance(container.begin(), - std::find(container.begin(), container.end(), value)); + return std::distance(container.begin(), absl::c_find(container, value)); } // Formats the container as a comma-separated string. StrAppend must support diff --git a/tensorflow/compiler/xla/window_util.cc b/tensorflow/compiler/xla/window_util.cc index 51c73b3d17..e001cc35f9 100644 --- a/tensorflow/compiler/xla/window_util.cc +++ b/tensorflow/compiler/xla/window_util.cc @@ -17,6 +17,7 @@ limitations under the License. #include +#include "absl/algorithm/container.h" #include "absl/strings/str_cat.h" #include "tensorflow/compiler/xla/types.h" #include "tensorflow/compiler/xla/xla_data.pb.h" @@ -137,25 +138,23 @@ bool HasPadding(const Window& window) { } bool HasSymmetricPadding(const Window& window) { - return std::all_of(window.dimensions().begin(), window.dimensions().end(), - [](const WindowDimension& dim) { - return dim.padding_low() == dim.padding_high(); - }); + return absl::c_all_of(window.dimensions(), [](const WindowDimension& dim) { + return dim.padding_low() == dim.padding_high(); + }); } bool HasSymmetricPadding(const PaddingConfig& padding_config) { - return std::all_of(padding_config.dimensions().begin(), - padding_config.dimensions().end(), - [](const PaddingConfig::PaddingConfigDimension& dim) { - return dim.edge_padding_low() == dim.edge_padding_high(); - }); + return absl::c_all_of(padding_config.dimensions(), + [](const PaddingConfig::PaddingConfigDimension& dim) { + return dim.edge_padding_low() == + dim.edge_padding_high(); + }); } bool HasNegativePadding(const Window& window) { - return std::any_of(window.dimensions().begin(), window.dimensions().end(), - [](const WindowDimension& dim) { - return dim.padding_low() < 0 || dim.padding_high() < 0; - }); + return absl::c_any_of(window.dimensions(), [](const WindowDimension& dim) { + return dim.padding_low() < 0 || dim.padding_high() < 0; + }); } bool HasBaseDilation(const Window& window) { @@ -190,10 +189,9 @@ bool AllOrNoneReversed(const Window& window) { return true; } bool reversed = window.dimensions()[0].window_reversal(); - return std::all_of(window.dimensions().begin(), window.dimensions().end(), - [&](const WindowDimension& dim) { - return dim.window_reversal() == reversed; - }); + return absl::c_all_of(window.dimensions(), [&](const WindowDimension& dim) { + return dim.window_reversal() == reversed; + }); } bool HasDilation(const Window& window) { -- GitLab From e2071f6a0502a5bc81abd0cb533f3b52ba0e7855 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Fri, 4 Jan 2019 12:59:45 -0800 Subject: [PATCH 0204/2345] Add poisson v2 loss and metric. PiperOrigin-RevId: 227900433 --- tensorflow/python/keras/losses.py | 47 +++++++++----- tensorflow/python/keras/losses_test.py | 81 +++++++++++++++++++++++++ tensorflow/python/keras/metrics.py | 31 ++++++++++ tensorflow/python/keras/metrics_test.py | 46 ++++++++++++++ 4 files changed, 189 insertions(+), 16 deletions(-) diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index 1ff564054b..46e729c197 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -519,25 +519,39 @@ class LogLoss(Loss): model = keras.models.Model(inputs, outputs) model.compile('sgd', loss=tf.losses.LogLoss()) ``` - - Args: - epsilon: A small increment to add to avoid taking a log of zero. - reduction: Type of `tf.losses.Reduction` to apply to loss. Default value is - `SUM_OVER_BATCH_SIZE`. - name: Optional name for the op. """ - def __init__(self, - epsilon=1e-7, - reduction=losses_impl.ReductionV2.SUM_OVER_BATCH_SIZE, - name=None): - super(LogLoss, self).__init__(reduction=reduction, name=name) - self.epsilon = epsilon + def call(self, y_true, y_pred): + y_pred = ops.convert_to_tensor(y_pred) + y_true = math_ops.cast(y_true, y_pred.dtype) + return logloss(y_true, y_pred) + + +class Poisson(Loss): + """Computes the poisson loss between `y_true` and `y_pred`. + + loss = y_pred - y_true * log(y_pred) + + Usage: + + ```python + p = tf.losses.Poisson() + loss = p([1, 9, 2], [4, 8, 12]) + print('Loss: ', loss.numpy()) # Loss: -4.63 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile('sgd', loss=tf.losses.Poisson()) + ``` + """ def call(self, y_true, y_pred): y_pred = ops.convert_to_tensor(y_pred) y_true = math_ops.cast(y_true, y_pred.dtype) - return logloss(y_true, y_pred, epsilon=self.epsilon) + return poisson(y_true, y_pred) class Logcosh(Loss): @@ -629,9 +643,10 @@ def categorical_hinge(y_true, y_pred): return math_ops.maximum(0., neg - pos + 1.) -def logloss(y_true, y_pred, epsilon=1e-7): - losses = math_ops.multiply(y_true, math_ops.log(y_pred + epsilon)) - losses += math_ops.multiply((1 - y_true), math_ops.log(1 - y_pred + epsilon)) +def logloss(y_true, y_pred): + losses = math_ops.multiply(y_true, math_ops.log(y_pred + K.epsilon())) + losses += math_ops.multiply((1 - y_true), + math_ops.log(1 - y_pred + K.epsilon())) return K.mean(-losses, axis=-1) diff --git a/tensorflow/python/keras/losses_test.py b/tensorflow/python/keras/losses_test.py index aa00ba5a91..f34a6caa18 100644 --- a/tensorflow/python/keras/losses_test.py +++ b/tensorflow/python/keras/losses_test.py @@ -1175,5 +1175,86 @@ class LogcoshTest(test.TestCase): self.assertAlmostEqual(self.evaluate(loss), 0., 3) +@test_util.run_all_in_graph_and_eager_modes +class PoissonTest(test.TestCase): + + def setup(self): + self.np_y_pred = np.asarray([1, 9, 2, 5, 2, 6]).reshape((2, 3)) + self.np_y_true = np.asarray([4, 8, 12, 8, 1, 3]).reshape((2, 3)) + + self.batch_size = 6 + self.expected_losses = self.np_y_pred - np.multiply(self.np_y_true, + np.log(self.np_y_pred)) + + self.y_pred = constant_op.constant(self.np_y_pred, dtype=dtypes.float32) + self.y_true = constant_op.constant(self.np_y_true) + + def test_config(self): + poisson_obj = keras.losses.Poisson( + reduction=losses_impl.ReductionV2.SUM, name='poisson') + self.assertEqual(poisson_obj.name, 'poisson') + self.assertEqual(poisson_obj.reduction, losses_impl.ReductionV2.SUM) + + def test_unweighted(self): + self.setup() + poisson_obj = keras.losses.Poisson() + + loss = poisson_obj(self.y_true, self.y_pred) + expected_loss = np.sum(self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_scalar_weighted(self): + self.setup() + poisson_obj = keras.losses.Poisson() + sample_weight = 2.3 + loss = poisson_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + + expected_loss = sample_weight * np.sum( + self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + # Verify we get the same output when the same input is given + loss_2 = poisson_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + self.assertAlmostEqual(self.evaluate(loss), self.evaluate(loss_2), 3) + + def test_sample_weighted(self): + self.setup() + poisson_obj = keras.losses.Poisson() + + sample_weight = constant_op.constant([1.2, 3.4], shape=(2, 1)) + loss = poisson_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + + expected_loss = np.multiply( + self.expected_losses, + np.asarray([1.2, 1.2, 1.2, 3.4, 3.4, 3.4]).reshape((2, 3))) + expected_loss = np.sum(expected_loss) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_timestep_weighted(self): + self.setup() + poisson_obj = keras.losses.Poisson() + y_true = self.np_y_true.reshape(2, 3, 1) + y_pred = self.np_y_pred.reshape(2, 3, 1) + sample_weight = np.asarray([3, 6, 5, 0, 4, 2]).reshape(2, 3, 1) + expected_losses = y_pred - np.multiply(y_true, np.log(y_pred)) + + y_pred = constant_op.constant(y_pred, dtype=dtypes.float32) + y_true = constant_op.constant(y_true) + + loss = poisson_obj( + y_true, + y_pred, + sample_weight=constant_op.constant(sample_weight, shape=(2, 3))) + expected_loss = np.sum(expected_losses * sample_weight) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_zero_weighted(self): + self.setup() + poisson_obj = keras.losses.Poisson() + loss = poisson_obj(self.y_true, self.y_pred, sample_weight=0) + self.assertAlmostEqual(self.evaluate(loss), 0., 3) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index 2de0bc474c..71568e3fc5 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -1685,6 +1685,37 @@ class Logcosh(MeanMetricWrapper): return super(Logcosh, cls).from_config(config) +class Poisson(MeanMetricWrapper): + """Computes the poisson metric between `y_true` and `y_pred`. + + metric = y_pred - y_true * log(y_pred) + + Usage: + + ```python + m = tf.keras.metrics.Poisson() + m.update_state([1, 9, 2], [4, 8, 12]) + print('Final result: ', m.result().numpy()) # Final result: -4.63 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile('sgd', metrics=[tf.keras.metrics.Poisson()]) + ``` + """ + + def __init__(self, name='poisson', dtype=None): + super(Poisson, self).__init__(poisson, name, dtype=dtype) + + @classmethod + def from_config(cls, config): + if 'fn' in config: + config.pop('fn') + return super(Poisson, cls).from_config(config) + + def accuracy(y_true, y_pred): y_pred.get_shape().assert_is_compatible_with(y_true.get_shape()) if y_true.dtype != y_pred.dtype: diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index a9d88789ed..88b763d569 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -1510,6 +1510,52 @@ class LogcoshTest(test.TestCase): self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) +@test_util.run_all_in_graph_and_eager_modes +class PoissonTest(test.TestCase): + + def setup(self): + y_pred = np.asarray([1, 9, 2, 5, 2, 6]).reshape((2, 3)) + y_true = np.asarray([4, 8, 12, 8, 1, 3]).reshape((2, 3)) + + self.batch_size = 6 + self.expected_results = y_pred - np.multiply(y_true, np.log(y_pred)) + + self.y_pred = constant_op.constant(y_pred, dtype=dtypes.float32) + self.y_true = constant_op.constant(y_true) + + def test_config(self): + poisson_obj = metrics.Poisson(name='poisson', dtype=dtypes.int32) + self.assertEqual(poisson_obj.name, 'poisson') + self.assertEqual(poisson_obj._dtype, dtypes.int32) + + poisson_obj2 = metrics.Poisson.from_config(poisson_obj.get_config()) + self.assertEqual(poisson_obj2.name, 'poisson') + self.assertEqual(poisson_obj2._dtype, dtypes.int32) + + def test_unweighted(self): + self.setup() + poisson_obj = metrics.Poisson() + self.evaluate(variables.variables_initializer(poisson_obj.variables)) + + update_op = poisson_obj.update_state(self.y_true, self.y_pred) + self.evaluate(update_op) + result = poisson_obj.result() + expected_result = np.sum(self.expected_results) / self.batch_size + self.assertAllClose(result, expected_result, atol=1e-3) + + def test_weighted(self): + self.setup() + poisson_obj = metrics.Poisson() + self.evaluate(variables.variables_initializer(poisson_obj.variables)) + sample_weight = constant_op.constant([1.2, 3.4], shape=(2, 1)) + + result = poisson_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + sample_weight = np.asarray([1.2, 1.2, 1.2, 3.4, 3.4, 3.4]).reshape((2, 3)) + expected_result = np.multiply(self.expected_results, sample_weight) + expected_result = np.sum(expected_result) / np.sum(sample_weight) + self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) + + def _get_model(compile_metrics): model_layers = [ layers.Dense(3, activation='relu', kernel_initializer='ones'), -- GitLab From d198ec5f99258f443af8e16825ae9c77e26c5db6 Mon Sep 17 00:00:00 2001 From: Yuefeng Zhou Date: Fri, 4 Jan 2019 13:11:46 -0800 Subject: [PATCH 0205/2345] Move ParameterServerStrategy to core. PiperOrigin-RevId: 227902368 --- tensorflow/contrib/distribute/python/BUILD | 15 +- .../python/parameter_server_strategy.py | 467 +--------------- .../python/parameter_server_strategy_test.py | 339 +++++++---- tensorflow/python/distribute/BUILD | 22 + .../distribute/parameter_server_strategy.py | 528 ++++++++++++++++++ 5 files changed, 813 insertions(+), 558 deletions(-) create mode 100644 tensorflow/python/distribute/parameter_server_strategy.py diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 78ad3214fb..de6b6f1f84 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -80,19 +80,10 @@ py_library( srcs = ["parameter_server_strategy.py"], visibility = ["//tensorflow:internal"], deps = [ - ":mirrored_strategy", - "//tensorflow/core:protos_all_py", - "//tensorflow/python:array_ops", - "//tensorflow/python:framework_ops", - "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:training", - "//tensorflow/python:util", - "//tensorflow/python/distribute:cross_device_ops", - "//tensorflow/python/distribute:input_lib", - "//tensorflow/python/distribute:multi_worker_util", - "//tensorflow/python/distribute:reduce_util", + "//tensorflow/python/distribute:distribute_lib", + "//tensorflow/python/distribute:parameter_server_strategy", "//tensorflow/python/distribute:values", - "//tensorflow/python/eager:context", + "//tensorflow/python/distribute/cluster_resolver:cluster_resolver_lib", ], ) diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy.py b/tensorflow/contrib/distribute/python/parameter_server_strategy.py index 461e1bca21..bb0b8eb992 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy.py @@ -18,35 +18,24 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import copy - -from tensorflow.contrib.distribute.python import mirrored_strategy -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 multi_worker_util -from tensorflow.python.distribute import values -from tensorflow.python.eager import context -from tensorflow.python.framework import device as tf_device -from tensorflow.python.framework import ops -from tensorflow.python.ops import array_ops -from tensorflow.python.ops import resource_variable_ops -from tensorflow.python.ops import variable_scope as vs -from tensorflow.python.platform import tf_logging as logging -from tensorflow.python.training import device_setter -from tensorflow.python.util import nest +from tensorflow.python.distribute import parameter_server_strategy +from tensorflow.python.distribute.cluster_resolver import SimpleClusterResolver +from tensorflow.python.distribute.cluster_resolver import TFConfigClusterResolver + +# pylint: disable=protected-access,invalid-name,line-too-long +CoreParameterServerStrategy = parameter_server_strategy.ParameterServerStrategy +CoreParameterServerExtended = parameter_server_strategy.ParameterServerStrategyExtended -_LOCAL_CPU = "/device:CPU:0" -_LOCAL_GPU_0 = "/device:GPU:0" +# pylint: enable=protected-access,invalid-name,line-too-long -# TODO(yuefengz): maybe cache variables on local CPU. -# TODO(yuefengz): we may want to set session options to disallow communication -# between workers. class ParameterServerStrategy(distribute_lib.DistributionStrategy): """A parameter server DistributionStrategy. + *** contrib version *** + This strategy class works for both local training and between-graph replicated training for multiple workers. If `cluster_spec` is specified, either passed in to __init__() method or parsed from the @@ -101,434 +90,24 @@ class ParameterServerStrategy(distribute_lib.DistributionStrategy): ParameterServerExtended(self, num_gpus_per_worker)) -class ParameterServerExtended(distribute_lib.DistributionStrategyExtended): +class ParameterServerExtended(CoreParameterServerExtended): """Implementation of ParameterServerStrategy.""" def __init__(self, container_strategy, num_gpus_per_worker): - super(ParameterServerExtended, self).__init__(container_strategy) - self._num_gpus_per_worker = num_gpus_per_worker - self._initialize_local(num_gpus_per_worker) - - # We typically don't need to do all-reduce in this strategy. - self._cross_device_ops = ( - cross_device_ops_lib.ReductionToOneDeviceCrossDeviceOps( - reduce_to_device=_LOCAL_CPU)) - - def _initialize_multi_worker(self, num_gpus_per_worker, cluster_spec, - task_type, task_id): - """Initialize devices for multiple workers. - - It creates variable devices and compute devices. Variables and operations - will be assigned to them respectively. We have one compute device per - replica. The variable device is a device function or device string. The - default variable device assigns variables to parameter servers in a - round-robin fashion. - - Args: - num_gpus_per_worker: number of local GPUs or GPUs per worker. - cluster_spec: a dict, ClusterDef or ClusterSpec object specifying the - cluster configurations. - task_type: the current task type. - task_id: the current task id. - - Raises: - ValueError: if the cluster_spec doesn't have ps jobs. - """ - assert cluster_spec - if not task_type or task_id is None: - raise ValueError("When `cluster_spec` is given, you must also specify " - "`task_type` and `task_id`") - cluster_spec = multi_worker_util.normalize_cluster_spec(cluster_spec) - - worker_device = "/job:%s/task:%d" % (self._task_type, self._task_id) - - # 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. - if num_gpus_per_worker > 0: - compute_devices = tuple( - "%s/device:GPU:%d" % (worker_device, i) - for i in range(num_gpus_per_worker) - ) - else: - compute_devices = (worker_device,) - - self._device_map = values.ReplicaDeviceMap(compute_devices) - self._input_workers = input_lib.InputWorkers( - self._device_map, [(worker_device, compute_devices)]) - - # In distributed mode, place variables on ps jobs in a round-robin fashion. - # Note that devices returned from `replica_device_setter` are not - # canonical and therefore we don't canonicalize all variable devices to - # make them consistent. - # TODO(yuefengz): support passing a strategy object to control variable - # assignment. - # TODO(yuefengz): merge the logic of replica_device_setter into this - # class. - num_ps_replicas = len(cluster_spec.as_dict().get("ps", [])) - if num_ps_replicas == 0: - raise ValueError("The cluster spec needs to have `ps` jobs.") - self._variable_device = device_setter.replica_device_setter( - ps_tasks=num_ps_replicas, - worker_device=worker_device, - merge_devices=True, - cluster=cluster_spec) - - # The `_parameter_devices` is needed for the `parameter_devices` property - # and is a list of all variable devices. Here parameter devices are all - # tasks of the "ps" job. - self._parameter_devices = tuple(map("/job:ps/task:{}".format, - range(num_ps_replicas))) - - # Add a default device so that ops without specified devices will not end up - # on other workers. - self._default_device = worker_device - - self._is_chief = multi_worker_util.is_chief(cluster_spec, task_type, - task_id) - self._cluster_spec = cluster_spec - self._task_type = task_type - self._task_id = task_id - - logging.info( - "Multi-worker ParameterServerStrategy with " - "cluster_spec = %r, task_type = %r, task_id = %r, " - "num_ps_replicas = %r, is_chief = %r, device_map = %r, " - "variable_device = %r", cluster_spec.as_dict(), task_type, task_id, - num_ps_replicas, self._is_chief, self._device_map, - self._variable_device) - - def _initialize_local(self, num_gpus_per_worker): - """Initialize internal devices for local training.""" - worker_device = device_util.canonicalize("/device:CPU: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. - if num_gpus_per_worker > 0: - compute_devices = tuple( - map("/device:GPU:{}".format, range(num_gpus_per_worker))) - else: - compute_devices = (_LOCAL_CPU,) - - self._device_map = values.ReplicaDeviceMap(compute_devices) - self._input_workers = input_lib.InputWorkers( - self._device_map, [(worker_device, compute_devices)]) - - # If there is only one GPU, put everything on that GPU. Otherwise, place - # variables on CPU. - if num_gpus_per_worker == 1: - assert len(compute_devices) == 1 - self._variable_device = _LOCAL_GPU_0 - self._parameter_devices = (_LOCAL_GPU_0,) - else: - self._variable_device = _LOCAL_CPU - self._parameter_devices = (_LOCAL_CPU,) - - self._is_chief = True - self._cluster_spec = None - self._task_type = None - self._task_id = None - - logging.info( - "ParameterServerStrategy with compute_devices = %r, " - "variable_device = %r", compute_devices, self._variable_device) - - def _validate_colocate_with_variable(self, colocate_with_variable): - values.validate_colocate(colocate_with_variable, self) - - def _distribute_dataset(self, dataset_fn): - """Distributes the dataset to each local GPU.""" - return input_lib.PerReplicaDataset( - self._call_dataset_fn(dataset_fn), self._input_workers, 0, - prefetch_on_device=True) + # Use TFConfigClusterResolver to parse TF_CONFIG. We don't want to change + # the constructor's interface to allow customized cluster resolver. Use + # SimpleClusterResolver to override num_accelerators. + tfconfig = TFConfigClusterResolver() + cluster_resolver = SimpleClusterResolver( + cluster_spec=tfconfig.cluster_spec(), + task_type=tfconfig.task_type, + task_index=tfconfig.task_index, + num_accelerators=num_gpus_per_worker) + super(ParameterServerExtended, self).__init__( + container_strategy, cluster_resolver=cluster_resolver) def _make_dataset_iterator(self, dataset): - 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): - """Distributes the dataset to each local GPU.""" - if self._cluster_spec: - input_pipeline_id = multi_worker_util.id_in_cluster( - self._cluster_spec, self._task_type, self._task_id) - num_input_pipelines = multi_worker_util.worker_count( - self._cluster_spec, self._task_type) - else: - input_pipeline_id = 0 - num_input_pipelines = 1 - input_context = distribute_lib.InputContext( - num_input_pipelines=num_input_pipelines, - input_pipeline_id=input_pipeline_id, - num_replicas_in_sync=self._num_replicas_in_sync) - return input_lib.InputFunctionIterator( - input_fn, self._input_workers, [input_context]) - - def _broadcast_to(self, tensor, destinations): - # This is both a fast path for Python constants, and a way to delay - # converting Python values to a tensor until we know what type it - # should be converted to. Otherwise we have trouble with: - # global_step.assign_add(1) - # since the `1` gets broadcast as an int32 but global_step is int64. - if isinstance(tensor, (float, int)): - return tensor - if not cross_device_ops_lib.check_destinations(destinations): - # TODO(josh11b): Use current logical device instead of 0 here. - destinations = values.LogicalDeviceSpec( - device_map=self._device_map, logical_device=0) - return self._cross_device_ops.broadcast(tensor, destinations) - - def _allow_variable_partition(self): - return not context.executing_eagerly() - - # TODO(yuefengz): not all ops in device_setter.STANDARD_PS_OPS will go through - # this creator, such as "MutableHashTable". - def _create_variable(self, next_creator, *args, **kwargs): - if self._num_replicas_in_sync > 1: - 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: " + aggregation + - " for variable: " + kwargs["name"]) - - def var_creator(*args, **kwargs): - """Create an AggregatingVariable and fix up collections.""" - # Record what collections this variable should be added to. - collections = kwargs.pop("collections", None) - if collections is None: - collections = [ops.GraphKeys.GLOBAL_VARIABLES] - kwargs["collections"] = [] - - # Create and wrap the variable. - v = next_creator(*args, **kwargs) - wrapped = values.AggregatingVariable( - self._container_strategy(), v, aggregation) - - # Add the wrapped variable to the requested collections. - # The handling of eager mode and the global step matches - # ResourceVariable._init_from_args(). - if not context.executing_eagerly(): - g = ops.get_default_graph() - # If "trainable" is True, next_creator() will add the contained - # variable to the TRAINABLE_VARIABLES collection, so we manually - # remove it and replace with the wrapper. We can't set "trainable" - # to False for next_creator() since that causes functions like - # implicit_gradients to skip those variables. - if kwargs.get("trainable", True): - collections.append(ops.GraphKeys.TRAINABLE_VARIABLES) - l = g.get_collection_ref(ops.GraphKeys.TRAINABLE_VARIABLES) - l.remove(v) - g.add_to_collections(collections, wrapped) - elif ops.GraphKeys.GLOBAL_STEP in collections: - ops.add_to_collections(ops.GraphKeys.GLOBAL_STEP, wrapped) - - return wrapped - else: - var_creator = next_creator - - if "colocate_with" in kwargs: - with ops.device(None): - with ops.colocate_with(kwargs["colocate_with"]): - return var_creator(*args, **kwargs) - - with ops.colocate_with(None, ignore_existing=True): - with ops.device(self._variable_device): - return var_creator(*args, **kwargs) - - def _call_for_each_replica(self, fn, args, kwargs): - # pylint: disable=protected-access - return mirrored_strategy._call_for_each_replica( - self._container_strategy(), self._device_map, fn, args, kwargs) - - def _verify_destinations_not_different_worker(self, destinations): - if not self._cluster_spec: - return - if destinations is None: - return - for d in cross_device_ops_lib.get_devices_from(destinations): - d_spec = tf_device.DeviceSpec.from_string(d) - if d_spec.job == self._task_type and d_spec.task != self._task_id: - raise ValueError( - "Cannot reduce to another worker: %r, current worker is %r" % - (d, self._input_workers.worker_devices[0])) - - def _reduce_to(self, reduce_op, value, destinations): - self._verify_destinations_not_different_worker(destinations) - if not isinstance(value, values.DistributedValues): - # pylint: disable=protected-access - return cross_device_ops_lib.reduce_non_distributed_value( - reduce_op, self._device_map, value, destinations) - return self._cross_device_ops.reduce( - reduce_op, value, destinations=destinations) - - def _batch_reduce_to(self, reduce_op, value_destination_pairs): - for _, destinations in value_destination_pairs: - self._verify_destinations_not_different_worker(destinations) - return self._cross_device_ops.batch_reduce(reduce_op, - value_destination_pairs) - - def _select_single_value(self, structured): - """Select any single values in `structured`.""" - - def _select_fn(x): # pylint: disable=g-missing-docstring - if isinstance(x, values.Mirrored): - if len(x.devices) == 1: - return x.primary - else: - raise ValueError( - "You cannot update variable with a Mirrored object with multiple " - "components %r when using ParameterServerStrategy. You must " - "specify a single value or a Mirrored with a single value." % x) - elif isinstance(x, values.PerReplica): - raise ValueError( - "You cannot update variable with a PerReplica object %r when using " - "ParameterServerStrategy. You must specify a single value or a " - "Mirrored with a single value" % x) - else: - return x - - return nest.map_structure(_select_fn, structured) - - def _update(self, var, fn, args, kwargs, group): - if isinstance(var, values.AggregatingVariable): - var = var.get() - if not isinstance(var, resource_variable_ops.ResourceVariable): - raise ValueError( - "You can not update `var` %r. It must be a Variable." % var) - with ops.colocate_with(var), distribute_lib.UpdateContext(var.device): - result = fn(var, *self._select_single_value(args), - **self._select_single_value(kwargs)) - if group: - return result - else: - return nest.map_structure(self._unwrap, result) - - # TODO(yuefengz): does it need to call _select_single_value? - def _update_non_slot(self, colocate_with, fn, args, kwargs, group): - with ops.device( - colocate_with.device), distribute_lib.UpdateContext(colocate_with): - result = fn(*args, **kwargs) - if group: - return result - else: - return nest.map_structure(self._unwrap, result) - - def _unwrap(self, val): - if isinstance(val, values.DistributedValues): - return val.values - return (val,) - - def value_container(self, val): - if (hasattr(val, "_aggregating_container") and - not isinstance(val, values.AggregatingVariable)): - wrapper = val._aggregating_container() # pylint: disable=protected-access - if wrapper is not None: - return wrapper - return val - - def read_var(self, var): - # No need to distinguish between normal variables and replica-local - # variables. - return array_ops.identity(var) - - def _configure(self, - session_config=None, - cluster_spec=None, - task_type=None, - task_id=None): - """Configures the strategy class. - - The strategy object will be re-initialized if `cluster_spec` is given but - was not passed in the constructor. - - Args: - session_config: not used currently. - cluster_spec: a dict, ClusterDef or ClusterSpec object specifying the - cluster configurations. - task_type: the current task type. - task_id: the current task id. - - Raises: - ValueError: if `cluster_spec` is given but `task_type` or `task_id` is - not. - """ - if not self._cluster_spec and cluster_spec: - # If a `cluster_spec` is already passed in, do nothing here. - # TODO(yuefengz): check `cluster_spec` is the same if this object has - # already been initialized with a `cluster_spec`. - if task_type is None or task_id is None: - raise ValueError("When `cluster_spec` is given, must also specify " - "`task_type` and `task_id`.") - self._cluster_spec = multi_worker_util.normalize_cluster_spec( - cluster_spec) - self._task_type = task_type - self._task_id = task_id - self._initialize_multi_worker(self._num_gpus_per_worker, - self._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) - if not self._cluster_spec: - updated_config.isolate_session_state = True - return updated_config - - updated_config.isolate_session_state = False - - assert self._task_type - assert self._task_id is not None - - # The device filters prevent communication between workers. - if self._task_type not in ["chief", "worker"]: - return updated_config - del updated_config.device_filters[:] - updated_config.device_filters.extend( - ["/job:%s/task:%d" % (self._task_type, self._task_id), "/job:ps"]) - return updated_config - - @property - def _num_replicas_in_sync(self): - return self._device_map.num_replicas_in_graph - - @property - def worker_devices(self): - return self._device_map.all_devices - - @property - def worker_devices_by_replica(self): - return self._device_map.devices_by_replica - - @property - def parameter_devices(self): - return self._parameter_devices - - def non_slot_devices(self, var_list): - return min(var_list, key=lambda x: x.name) - - @property - def experimental_between_graph(self): - # TODO(yuefengz): Should this return False in the local case? - return True - - @property - def experimental_should_init(self): - return self._is_chief - - @property - def should_checkpoint(self): - return self._is_chief - - @property - def should_save_summary(self): - return self._is_chief + return input_lib.DatasetIterator(dataset, self._input_workers) # TODO(priyag): Delete this once all strategies use global batch size. @property diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py index ce7065f220..d8c6c50db2 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py @@ -29,10 +29,13 @@ from tensorflow.contrib.distribute.python import strategy_test_lib from tensorflow.core.protobuf import config_pb2 from tensorflow.python.data.ops import dataset_ops from tensorflow.python.distribute import device_util +from tensorflow.python.distribute import distribute_lib from tensorflow.python.distribute import distribution_strategy_context as ds_context from tensorflow.python.distribute import multi_worker_util +from tensorflow.python.distribute import parameter_server_strategy as core_parameter_server_strategy from tensorflow.python.distribute import reduce_util from tensorflow.python.distribute import values +from tensorflow.python.distribute.cluster_resolver import SimpleClusterResolver from tensorflow.python.eager import backprop from tensorflow.python.eager import context from tensorflow.python.estimator import run_config @@ -50,6 +53,7 @@ from tensorflow.python.ops import variable_scope from tensorflow.python.ops import variables from tensorflow.python.platform import test from tensorflow.python.training import training_util +from tensorflow.python.training.server_lib import ClusterSpec CHIEF = run_config.TaskType.CHIEF WORKER = run_config.TaskType.WORKER @@ -63,6 +67,57 @@ def _get_replica_id_integer(): return replica_id +class MockCoreParameterServerStrategy(distribute_lib.DistributionStrategy): + """Mock the strategy to allow cluster resolver as an argument.""" + + def __init__(self, cluster_resolver): + super(MockCoreParameterServerStrategy, self).__init__( + core_parameter_server_strategy.ParameterServerStrategyExtended( + self, cluster_resolver=cluster_resolver)) + + +def create_test_objects(cluster_spec=None, + task_type=None, + task_id=None, + num_gpus=None, + sess_config=None, + use_core_strategy=False): + sess_config = sess_config or config_pb2.ConfigProto() + if num_gpus is None: + num_gpus = context.num_gpus() + if use_core_strategy: + if cluster_spec and task_type and task_id is not None: + cluster_resolver = SimpleClusterResolver( + cluster_spec=multi_worker_util.normalize_cluster_spec(cluster_spec), + task_type=task_type, + task_index=task_id, + num_accelerators=num_gpus) + target = 'grpc://' + cluster_spec[WORKER][task_id] + else: + cluster_resolver = SimpleClusterResolver( + ClusterSpec({}), num_accelerators=num_gpus) + target = '' + + distribution = MockCoreParameterServerStrategy(cluster_resolver) + sess_config = copy.deepcopy(sess_config) + sess_config = distribution.update_config_proto(sess_config) + else: + distribution = parameter_server_strategy.ParameterServerStrategy( + num_gpus_per_worker=num_gpus) + if task_type: + sess_config = copy.deepcopy(sess_config) + distribution.configure( + session_config=sess_config, + cluster_spec=cluster_spec, + task_type=task_type, + task_id=task_id) + target = 'grpc://' + cluster_spec[WORKER][task_id] + else: + target = '' + + return distribution, target, sess_config + + class ParameterServerStrategyTestBase( multi_worker_test_base.MultiWorkerTestBase): @@ -76,24 +131,27 @@ class ParameterServerStrategyTestBase( self._sess_config = config_pb2.ConfigProto(allow_soft_placement=True) super(ParameterServerStrategyTestBase, self).setUp() - def _get_test_objects(self, task_type, task_id, num_gpus): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=num_gpus) - if not task_type: - return distribution, '', self._sess_config - - sess_config = copy.deepcopy(self._sess_config) - distribution.configure( - session_config=sess_config, + def _get_test_objects(self, + task_type, + task_id, + num_gpus, + use_core_strategy=False): + return create_test_objects( cluster_spec=self._cluster_spec, task_type=task_type, - task_id=task_id) - return (distribution, 'grpc://' + self._cluster_spec[WORKER][task_id], - sess_config) - - def _test_device_assignment_distributed(self, task_type, task_id, num_gpus): + task_id=task_id, + num_gpus=num_gpus, + sess_config=self._sess_config, + use_core_strategy=use_core_strategy) + + def _test_device_assignment_distributed(self, + task_type, + task_id, + num_gpus, + use_core_strategy=False): worker_device = '/job:%s/replica:0/task:%d' % (task_type, task_id) - d, _, sess_config = self._get_test_objects(task_type, task_id, num_gpus) + d, _, sess_config = self._get_test_objects( + task_type, task_id, num_gpus, use_core_strategy=use_core_strategy) with ops.Graph().as_default(), \ self.cached_session(target=self._default_target, config=sess_config) as sess, \ @@ -191,8 +249,9 @@ class ParameterServerStrategyTestBase( self.assertEqual(f_val, 46.0) def _test_device_assignment_distributed_enable_partitioner( - self, task_type, task_id, num_gpus): - d, _, sess_config = self._get_test_objects(task_type, task_id, num_gpus) + self, task_type, task_id, num_gpus, use_core_strategy=False): + d, _, sess_config = self._get_test_objects( + task_type, task_id, num_gpus, use_core_strategy=use_core_strategy) num_shards = len(d.parameter_devices) partitioner = partitioned_variables.fixed_size_partitioner(num_shards) with ops.Graph().as_default(), \ @@ -340,9 +399,13 @@ class ParameterServerStrategyTestBase( self.assertEqual(z_val, 43.0) self.assertEqual(f_val, 46.0) - def _test_simple_increment(self, task_type, task_id, num_gpus): + def _test_simple_increment(self, + task_type, + task_id, + num_gpus, + use_core_strategy=False): d, master_target, sess_config = self._get_test_objects( - task_type, task_id, num_gpus) + task_type, task_id, num_gpus, use_core_strategy=use_core_strategy) if d.extended._cluster_spec: num_workers = len(d.extended._cluster_spec.as_dict().get(WORKER)) if 'chief' in d.extended._cluster_spec.as_dict(): @@ -410,9 +473,13 @@ class ParameterServerStrategyTestBase( y_val == 20.0 + 1.0 * num_workers * d.num_replicas_in_sync and z_val == 30.0 + 1.0 * num_workers) - def _test_minimize_loss_graph(self, task_type, task_id, num_gpus): + def _test_minimize_loss_graph(self, + task_type, + task_id, + num_gpus, + use_core_strategy=False): d, master_target, sess_config = self._get_test_objects( - task_type, task_id, num_gpus) + task_type, task_id, num_gpus, use_core_strategy=use_core_strategy) if task_type: # Multi-worker assert hasattr(d.extended, '_cluster_spec') and d.extended._cluster_spec @@ -498,10 +565,15 @@ class ParameterServerStrategyTestBase( self.assertLess(error_after, error_before) return error_after < error_before - def _test_input_fn_iterator(self, task_type, task_id, num_gpus, input_fn, - expected_values): + def _test_input_fn_iterator(self, + task_type, + task_id, + num_gpus, + input_fn, + expected_values, + use_core_strategy=False): distribution, master_target, config = self._get_test_objects( - task_type, task_id, num_gpus) + task_type, task_id, num_gpus, use_core_strategy=use_core_strategy) devices = distribution.extended.worker_devices with ops.Graph().as_default(), \ @@ -541,66 +613,93 @@ class ParameterServerStrategyTest(ParameterServerStrategyTestBase, num_workers=3, num_ps=2) cls._default_target = 'grpc://' + cls._cluster_spec[WORKER][0] - def test_num_replicas_in_sync(self): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=2) + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def test_num_replicas_in_sync(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=2, use_core_strategy=use_core_strategy) # All the devices on a given worker are in sync which in this case is the # number of gpus on each worker. - self.assertEqual(2, distribution.num_replicas_in_sync) + self.assertEqual(2, strategy.num_replicas_in_sync) - def testDeviceAssignmentLocalCPU(self): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=0) + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testDeviceAssignmentLocalCPU(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=0, use_core_strategy=use_core_strategy) self._test_device_assignment_local( - distribution, compute_device='CPU', variable_device='CPU', num_gpus=0) + strategy, compute_device='CPU', variable_device='CPU', num_gpus=0) - def testDeviceAssignmentLocalOneGPU(self): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=1) + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testDeviceAssignmentLocalOneGPU(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=1, use_core_strategy=use_core_strategy) self._test_device_assignment_local( - distribution, compute_device='GPU', variable_device='GPU', num_gpus=1) + strategy, compute_device='GPU', variable_device='GPU', num_gpus=1) - def testDeviceAssignmentLocalTwoGPUs(self): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=2) + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testDeviceAssignmentLocalTwoGPUs(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=2, use_core_strategy=use_core_strategy) self._test_device_assignment_local( - distribution, compute_device='GPU', variable_device='CPU', num_gpus=2) + strategy, compute_device='GPU', variable_device='CPU', num_gpus=2) @combinations.generate( - combinations.combine(mode=['graph'], num_gpus=[0, 1, 2])) - def testDeviceAssignmentDistributed(self, num_gpus): - self._test_device_assignment_distributed('worker', 1, num_gpus) + combinations.combine( + mode=['graph'], num_gpus=[0, 1, 2], use_core_strategy=[True, False])) + def testDeviceAssignmentDistributed(self, num_gpus, use_core_strategy): + self._test_device_assignment_distributed( + 'worker', 1, num_gpus, use_core_strategy=use_core_strategy) @combinations.generate( - combinations.combine(mode=['graph'], num_gpus=[0, 1, 2])) - def testDeviceAssignmentDistributedEnablePartitioner(self, num_gpus): + combinations.combine( + mode=['graph'], num_gpus=[0, 1, 2], use_core_strategy=[True, False])) + def testDeviceAssignmentDistributedEnablePartitioner(self, num_gpus, + use_core_strategy): self._test_device_assignment_distributed_enable_partitioner( - 'worker', 1, num_gpus) + 'worker', 1, num_gpus, use_core_strategy=use_core_strategy) - def testSimpleBetweenGraph(self): - self._run_between_graph_clients(self._test_simple_increment, - self._cluster_spec, context.num_gpus()) + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testSimpleBetweenGraph(self, use_core_strategy): + self._run_between_graph_clients( + self._test_simple_increment, + self._cluster_spec, + context.num_gpus(), + use_core_strategy=use_core_strategy) @combinations.generate( - combinations.combine(mode=['graph'], num_gpus=[0, 1, 2])) - def testLocalSimpleIncrement(self, num_gpus): - self._test_simple_increment(None, 0, num_gpus) + combinations.combine( + mode=['graph'], num_gpus=[0, 1, 2], use_core_strategy=[True, False])) + def testLocalSimpleIncrement(self, num_gpus, use_core_strategy): + self._test_simple_increment(None, 0, num_gpus, use_core_strategy) @combinations.generate( - combinations.combine(mode=['graph'], num_gpus=[0, 1, 2])) - def testMinimizeLossGraphDistributed(self, num_gpus): - self._run_between_graph_clients(self._test_minimize_loss_graph, - self._cluster_spec, num_gpus) + combinations.combine( + mode=['graph'], num_gpus=[0, 1, 2], use_core_strategy=[True, False])) + def testMinimizeLossGraphDistributed(self, num_gpus, use_core_strategy): + self._run_between_graph_clients( + self._test_minimize_loss_graph, + self._cluster_spec, + num_gpus, + use_core_strategy=use_core_strategy) @combinations.generate( - combinations.combine(mode=['graph'], num_gpus=[0, 1, 2])) - def testMinimizeLossGraphLocal(self, num_gpus): - self._test_minimize_loss_graph(None, None, num_gpus) + combinations.combine( + mode=['graph'], num_gpus=[0, 1, 2], use_core_strategy=[True, False])) + def testMinimizeLossGraphLocal(self, num_gpus, use_core_strategy): + self._test_minimize_loss_graph(None, None, num_gpus, use_core_strategy) # TODO(priyag): Refactor this and other multi worker tests. @combinations.generate( - combinations.combine(mode=['graph'], num_gpus=[1, 2], required_gpus=1)) - def testMakeInputFnIteratorDistributed(self, num_gpus): + combinations.combine( + mode=['graph'], + num_gpus=[1, 2], + required_gpus=1, + use_core_strategy=[True, False])) + def testMakeInputFnIteratorDistributed(self, num_gpus, use_core_strategy): if context.num_gpus() < num_gpus: self.skipTest('Not enough GPUs') dataset_fn = lambda: dataset_ops.Dataset.range(100) @@ -612,12 +711,21 @@ class ParameterServerStrategyTest(ParameterServerStrategyTestBase, expected_num_replicas_in_sync=num_gpus, expected_num_input_pipelines=3, expected_input_pipeline_id=1) # because task_id = 1 - self._test_input_fn_iterator('worker', 1, num_gpus, - input_fn, expected_values) + self._test_input_fn_iterator( + 'worker', + 1, + num_gpus, + input_fn, + expected_values, + use_core_strategy=use_core_strategy) @combinations.generate( - combinations.combine(mode=['graph'], num_gpus=[1, 2], required_gpus=1)) - def testMakeInputFnIteratorLocal(self, num_gpus): + combinations.combine( + mode=['graph'], + num_gpus=[1, 2], + required_gpus=1, + use_core_strategy=[True, False])) + def testMakeInputFnIteratorLocal(self, num_gpus, use_core_strategy): if context.num_gpus() < num_gpus: self.skipTest('Not enough GPUs') dataset_fn = lambda: dataset_ops.Dataset.range(100) @@ -629,23 +737,31 @@ class ParameterServerStrategyTest(ParameterServerStrategyTestBase, expected_num_replicas_in_sync=num_gpus, expected_num_input_pipelines=1, expected_input_pipeline_id=0) # only one worker and pipeline for local. - self._test_input_fn_iterator(None, None, num_gpus, - input_fn, expected_values) + self._test_input_fn_iterator( + None, + None, + num_gpus, + input_fn, + expected_values, + use_core_strategy=use_core_strategy) - def testGlobalStepUpdate(self): - strategy = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=context.num_gpus()) + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testGlobalStepUpdate(self, use_core_strategy): + strategy, _, _ = create_test_objects(use_core_strategy=use_core_strategy) self._test_global_step_update(strategy) - def testUpdateConfigProtoMultiWorker(self): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=2) - distribution.configure( + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testUpdateConfigProtoMultiWorker(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=2, use_core_strategy=use_core_strategy) + strategy.configure( cluster_spec=self._cluster_spec, task_type='worker', task_id=1) config_proto = config_pb2.ConfigProto(device_filters=['to_be_overridden']) - new_config = distribution.update_config_proto(config_proto) + new_config = strategy.update_config_proto(config_proto) # Verify device filters. self.assertEqual(['/job:worker/task:1', '/job:ps'], @@ -654,12 +770,14 @@ class ParameterServerStrategyTest(ParameterServerStrategyTestBase, # Verify isolate_session_state self.assertFalse(new_config.isolate_session_state) - def testUpdateConfigProtoLocal(self): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=2) + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testUpdateConfigProtoLocal(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=2, use_core_strategy=use_core_strategy) config_proto = config_pb2.ConfigProto() - new_config = distribution.update_config_proto(config_proto) + new_config = strategy.update_config_proto(config_proto) # Verify isolate_session_state self.assertTrue(new_config.isolate_session_state) @@ -674,20 +792,31 @@ class ParameterServerStrategyWithChiefTest(ParameterServerStrategyTestBase, num_workers=3, num_ps=2, has_chief=True) cls._default_target = 'grpc://' + cls._cluster_spec[CHIEF][0] - def testSimpleBetweenGraph(self): - self._run_between_graph_clients(self._test_simple_increment, - self._cluster_spec, context.num_gpus()) + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testSimpleBetweenGraph(self, use_core_strategy): + self._run_between_graph_clients( + self._test_simple_increment, + self._cluster_spec, + context.num_gpus(), + use_core_strategy=use_core_strategy) @combinations.generate( - combinations.combine(mode=['graph'], num_gpus=[0, 1, 2])) - def testMinimizeLossGraph(self, num_gpus): - self._run_between_graph_clients(self._test_minimize_loss_graph, - self._cluster_spec, num_gpus) + combinations.combine( + mode=['graph'], num_gpus=[0, 1, 2], use_core_strategy=[True, False])) + def testMinimizeLossGraph(self, num_gpus, use_core_strategy): + self._run_between_graph_clients( + self._test_minimize_loss_graph, + self._cluster_spec, + num_gpus, + use_core_strategy=use_core_strategy) - def testGlobalStepIsWrappedOnTwoGPUs(self): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=2) - with ops.Graph().as_default(), distribution.scope(): + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testGlobalStepIsWrappedOnTwoGPUs(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=2, use_core_strategy=use_core_strategy) + with ops.Graph().as_default(), strategy.scope(): created_step = training_util.create_global_step() get_step = training_util.get_global_step() self.assertEqual(created_step, get_step, @@ -696,12 +825,14 @@ class ParameterServerStrategyWithChiefTest(ParameterServerStrategyTestBase, id(get_step), get_step.__class__.__name__))) self.assertIs(values.AggregatingVariable, type(created_step)) self.assertIs(values.AggregatingVariable, type(get_step)) - self.assertIs(distribution, created_step.distribute_strategy) + self.assertIs(strategy, created_step.distribute_strategy) - def testGlobalStepIsNotWrappedOnOneGPU(self): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=1) - with ops.Graph().as_default(), distribution.scope(): + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testGlobalStepIsNotWrappedOnOneGPU(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=1, use_core_strategy=use_core_strategy) + with ops.Graph().as_default(), strategy.scope(): created_step = training_util.create_global_step() get_step = training_util.get_global_step() self.assertEqual(created_step, get_step, @@ -710,20 +841,24 @@ class ParameterServerStrategyWithChiefTest(ParameterServerStrategyTestBase, id(get_step), get_step.__class__.__name__))) self.assertIs(resource_variable_ops.ResourceVariable, type(created_step)) self.assertIs(resource_variable_ops.ResourceVariable, type(get_step)) - self.assertIs(distribution, created_step.distribute_strategy) + self.assertIs(strategy, created_step.distribute_strategy) + + @combinations.generate( + combinations.combine(mode=['graph'], use_core_strategy=[True, False])) + def testValueContainer(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=2, use_core_strategy=use_core_strategy) + with ops.Graph().as_default(), strategy.scope(): - def testValueContainer(self): - distribution = parameter_server_strategy.ParameterServerStrategy( - num_gpus_per_worker=2) - with ops.Graph().as_default(), distribution.scope(): def f(): with backprop.GradientTape() as tape: v = variable_scope.get_variable('v', initializer=10.0) _ = v * v v, = tape.watched_variables() - w = distribution.extended.value_container(v) + w = strategy.extended.value_container(v) self.assertIs(values.AggregatingVariable, type(w)) - distribution.extended.call_for_each_replica(f) + + strategy.extended.call_for_each_replica(f) if __name__ == '__main__': diff --git a/tensorflow/python/distribute/BUILD b/tensorflow/python/distribute/BUILD index 987fb00454..faaa61934a 100644 --- a/tensorflow/python/distribute/BUILD +++ b/tensorflow/python/distribute/BUILD @@ -242,6 +242,28 @@ py_library( ], ) +py_library( + name = "parameter_server_strategy", + srcs = ["parameter_server_strategy.py"], + visibility = ["//tensorflow:internal"], + deps = [ + ":input_lib", + ":mirrored_strategy", + "//tensorflow/core:protos_all_py", + "//tensorflow/python:array_ops", + "//tensorflow/python:framework_ops", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:training", + "//tensorflow/python:util", + "//tensorflow/python/distribute:cross_device_ops", + "//tensorflow/python/distribute:multi_worker_util", + "//tensorflow/python/distribute:reduce_util", + "//tensorflow/python/distribute:values", + "//tensorflow/python/distribute/cluster_resolver:cluster_resolver_lib", + "//tensorflow/python/eager:context", + ], +) + py_library( name = "multi_worker_util", srcs = [ diff --git a/tensorflow/python/distribute/parameter_server_strategy.py b/tensorflow/python/distribute/parameter_server_strategy.py new file mode 100644 index 0000000000..71fbffdc0d --- /dev/null +++ b/tensorflow/python/distribute/parameter_server_strategy.py @@ -0,0 +1,528 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Classes implementing a multi-worker ps DistributionStrategy.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy + +from tensorflow.contrib.distribute.python import mirrored_strategy +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 multi_worker_util +from tensorflow.python.distribute import values +from tensorflow.python.distribute.cluster_resolver import SimpleClusterResolver +from tensorflow.python.distribute.cluster_resolver import TFConfigClusterResolver +from tensorflow.python.eager import context +from tensorflow.python.framework import device as tf_device +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import variable_scope as vs +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.training import device_setter +from tensorflow.python.util import nest + +_LOCAL_CPU = "/device:CPU:0" +_LOCAL_GPU_0 = "/device:GPU:0" + + +# TODO(yuefengz): maybe cache variables on local CPU. +class ParameterServerStrategy(distribute_lib.DistributionStrategy): + """A parameter server DistributionStrategy. + + This strategy class works for both local training and between-graph replicated + training for multiple workers. It uses `TFConfigClusterResolver` to detect + configurations for multi-worker training. In multi-worker training mode, i.e. + `TFConfigClusterResolver` has detected 'TF_CONFIG' environment variable and + 'TF_CONFIG' has a cluster spec, variables and updates to those variables are + assigned to parameter servers and other operations are assigned to workers. + In local training mode, variables are assigned to local CPU or the only GPU. + When each worker has more than one GPU, operations will be replicated on these + GPUs. In both cases, operations are replicated but variables are not and these + workers share a common view for which paramater server a variable is assigned + to. + + This class assumes between-graph replication will be used and works on a graph + for a particular worker. Note that each graph and worker is independent. + This means that while each worker will synchronously compute a single gradient + update across all GPUs, updates between workers proceed asynchronously. + Operations that occur only on the first replica (such as incrementing the + global step), will occur on the first replica *of every worker*. + + It is expected to call `call_for_each_replica(fn, ...)` for any + operations which potentially can be replicated across replicas (i.e. multiple + GPUs) even if there is only CPU or one GPU. When defining the `fn`, extra + caution needs to be taken: + + 1) It is generally not recommended to open a device scope under the strategy's + scope. A device scope (i.e. calling `tf.device`) will be merged with or + override the device for operations but will not change the device for + variables. + + 2) It is also not recommended to open a colocation scope (i.e. calling + `tf.colocate_with`) under the strategy's scope. For colocating variables, + use `distribution.colocate_vars_with` instead. Colocation of ops will possibly + create conflicts of device assignment. + """ + + def __init__(self): + """Initializes this strategy with default TFConfigClusterResolver.""" + super(ParameterServerStrategy, self).__init__( + ParameterServerStrategyExtended(self)) + + +class ParameterServerStrategyExtended( + distribute_lib.DistributionStrategyExtended): + """Implementation of ParameterServerStrategy.""" + + def __init__(self, + container_strategy, + cluster_resolver=TFConfigClusterResolver()): + super(ParameterServerStrategyExtended, self).__init__(container_strategy) + self._initialize_strategy(cluster_resolver) + + # We typically don't need to do all-reduce in this strategy. + self._cross_device_ops = ( + cross_device_ops_lib.ReductionToOneDeviceCrossDeviceOps( + reduce_to_device=_LOCAL_CPU)) + + def _initialize_strategy(self, cluster_resolver): + if cluster_resolver.cluster_spec().as_dict(): + self._initialize_multi_worker(cluster_resolver) + else: + self._initialize_local(cluster_resolver) + # Save the num_gpus_per_worker for configure method. + self._num_gpus_per_worker = cluster_resolver.num_accelerators() + + def _initialize_multi_worker(self, cluster_resolver): + """Initialize devices for multiple workers. + + It creates variable devices and compute devices. Variables and operations + will be assigned to them respectively. We have one compute device per + replica. The variable device is a device function or device string. The + default variable device assigns variables to parameter servers in a + round-robin fashion. + + Args: + cluster_resolver: a descendant of `ClusterResolver` object. + + Raises: + ValueError: if the cluster doesn't have ps jobs. + """ + num_gpus = cluster_resolver.num_accelerators() + cluster_spec = cluster_resolver.cluster_spec() + task_type = cluster_resolver.task_type + task_id = cluster_resolver.task_index + if not task_type or task_id is None: + raise ValueError("When `cluster_spec` is given, you must also specify " + "`task_type` and `task_id`") + cluster_spec = multi_worker_util.normalize_cluster_spec(cluster_spec) + assert cluster_spec.as_dict() + + worker_device = "/job:%s/task:%d" % (task_type, task_id) + + # 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. + if num_gpus > 0: + compute_devices = tuple( + "%s/device:GPU:%d" % (worker_device, i) for i in range(num_gpus)) + else: + compute_devices = (worker_device,) + + self._device_map = values.ReplicaDeviceMap(compute_devices) + self._input_workers = input_lib.InputWorkers( + self._device_map, [(worker_device, compute_devices)]) + + # In distributed mode, place variables on ps jobs in a round-robin fashion. + # Note that devices returned from `replica_device_setter` are not + # canonical and therefore we don't canonicalize all variable devices to + # make them consistent. + # TODO(yuefengz): support passing a strategy object to control variable + # assignment. + # TODO(yuefengz): merge the logic of replica_device_setter into this + # class. + num_ps_replicas = len(cluster_spec.as_dict().get("ps", [])) + if num_ps_replicas == 0: + raise ValueError("The cluster spec needs to have `ps` jobs.") + self._variable_device = device_setter.replica_device_setter( + ps_tasks=num_ps_replicas, + worker_device=worker_device, + merge_devices=True, + cluster=cluster_spec) + + # The `_parameter_devices` is needed for the `parameter_devices` property + # and is a list of all variable devices. Here parameter devices are all + # tasks of the "ps" job. + self._parameter_devices = tuple(map("/job:ps/task:{}".format, + range(num_ps_replicas))) + + # Add a default device so that ops without specified devices will not end up + # on other workers. + self._default_device = worker_device + + self._is_chief = multi_worker_util.is_chief(cluster_spec, task_type, + task_id) + self._cluster_spec = cluster_spec + self._task_type = task_type + self._task_id = task_id + + logging.info( + "Multi-worker ParameterServerStrategy with " + "cluster_spec = %r, task_type = %r, task_id = %r, " + "num_ps_replicas = %r, is_chief = %r, device_map = %r, " + "variable_device = %r", cluster_spec.as_dict(), task_type, task_id, + num_ps_replicas, self._is_chief, self._device_map, + self._variable_device) + + def _initialize_local(self, cluster_resolver): + """Initialize internal devices for local training.""" + worker_device = device_util.canonicalize("/device:CPU:0") + num_gpus = cluster_resolver.num_accelerators() + # 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. + if num_gpus > 0: + compute_devices = tuple(map("/device:GPU:{}".format, range(num_gpus))) + else: + compute_devices = (_LOCAL_CPU,) + + self._device_map = values.ReplicaDeviceMap(compute_devices) + self._input_workers = input_lib.InputWorkers( + self._device_map, [(worker_device, compute_devices)]) + + # If there is only one GPU, put everything on that GPU. Otherwise, place + # variables on CPU. + if num_gpus == 1: + assert len(compute_devices) == 1 + self._variable_device = _LOCAL_GPU_0 + self._parameter_devices = (_LOCAL_GPU_0,) + else: + self._variable_device = _LOCAL_CPU + self._parameter_devices = (_LOCAL_CPU,) + + self._is_chief = True + self._cluster_spec = None + self._task_type = None + self._task_id = None + + logging.info( + "ParameterServerStrategy with compute_devices = %r, " + "variable_device = %r", compute_devices, self._variable_device) + + def _validate_colocate_with_variable(self, colocate_with_variable): + values.validate_colocate(colocate_with_variable, self) + + def _distribute_dataset(self, dataset_fn): + """Distributes the dataset to each local GPU.""" + return input_lib.PerReplicaDataset( + self._call_dataset_fn(dataset_fn), + self._input_workers, + 0, + prefetch_on_device=True) + + def _make_dataset_iterator(self, dataset): + 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): + """Distributes the dataset to each local GPU.""" + if self._cluster_spec: + input_pipeline_id = multi_worker_util.id_in_cluster( + self._cluster_spec, self._task_type, self._task_id) + num_input_pipelines = multi_worker_util.worker_count( + self._cluster_spec, self._task_type) + else: + input_pipeline_id = 0 + num_input_pipelines = 1 + input_context = distribute_lib.InputContext( + num_input_pipelines=num_input_pipelines, + input_pipeline_id=input_pipeline_id, + num_replicas_in_sync=self._num_replicas_in_sync) + return input_lib.InputFunctionIterator(input_fn, self._input_workers, + [input_context]) + + def _broadcast_to(self, tensor, destinations): + # This is both a fast path for Python constants, and a way to delay + # converting Python values to a tensor until we know what type it + # should be converted to. Otherwise we have trouble with: + # global_step.assign_add(1) + # since the `1` gets broadcast as an int32 but global_step is int64. + if isinstance(tensor, (float, int)): + return tensor + if not cross_device_ops_lib.check_destinations(destinations): + # TODO(josh11b): Use current logical device instead of 0 here. + destinations = values.LogicalDeviceSpec( + device_map=self._device_map, logical_device=0) + return self._cross_device_ops.broadcast(tensor, destinations) + + def _allow_variable_partition(self): + return not context.executing_eagerly() + + # TODO(yuefengz): not all ops in device_setter.STANDARD_PS_OPS will go through + # this creator, such as "MutableHashTable". + def _create_variable(self, next_creator, *args, **kwargs): + if self._num_replicas_in_sync > 1: + 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: " + aggregation + + " for variable: " + kwargs["name"]) + + def var_creator(*args, **kwargs): + """Create an AggregatingVariable and fix up collections.""" + # Record what collections this variable should be added to. + collections = kwargs.pop("collections", None) + if collections is None: + collections = [ops.GraphKeys.GLOBAL_VARIABLES] + kwargs["collections"] = [] + + # Create and wrap the variable. + v = next_creator(*args, **kwargs) + wrapped = values.AggregatingVariable( + self._container_strategy(), v, aggregation) + + # Add the wrapped variable to the requested collections. + # The handling of eager mode and the global step matches + # ResourceVariable._init_from_args(). + if not context.executing_eagerly(): + g = ops.get_default_graph() + # If "trainable" is True, next_creator() will add the contained + # variable to the TRAINABLE_VARIABLES collection, so we manually + # remove it and replace with the wrapper. We can't set "trainable" + # to False for next_creator() since that causes functions like + # implicit_gradients to skip those variables. + if kwargs.get("trainable", True): + collections.append(ops.GraphKeys.TRAINABLE_VARIABLES) + l = g.get_collection_ref(ops.GraphKeys.TRAINABLE_VARIABLES) + l.remove(v) + g.add_to_collections(collections, wrapped) + elif ops.GraphKeys.GLOBAL_STEP in collections: + ops.add_to_collections(ops.GraphKeys.GLOBAL_STEP, wrapped) + + return wrapped + else: + var_creator = next_creator + + if "colocate_with" in kwargs: + with ops.device(None): + with ops.colocate_with(kwargs["colocate_with"]): + return var_creator(*args, **kwargs) + + with ops.colocate_with(None, ignore_existing=True): + with ops.device(self._variable_device): + return var_creator(*args, **kwargs) + + def _call_for_each_replica(self, fn, args, kwargs): + # pylint: disable=protected-access + return mirrored_strategy._call_for_each_replica( + self._container_strategy(), self._device_map, fn, args, kwargs) + + def _verify_destinations_not_different_worker(self, destinations): + if not self._cluster_spec: + return + if destinations is None: + return + for d in cross_device_ops_lib.get_devices_from(destinations): + d_spec = tf_device.DeviceSpec.from_string(d) + if d_spec.job == self._task_type and d_spec.task != self._task_id: + raise ValueError( + "Cannot reduce to another worker: %r, current worker is %r" % + (d, self._input_workers.worker_devices[0])) + + def _reduce_to(self, reduce_op, value, destinations): + self._verify_destinations_not_different_worker(destinations) + if not isinstance(value, values.DistributedValues): + # pylint: disable=protected-access + return cross_device_ops_lib.reduce_non_distributed_value( + reduce_op, self._device_map, value, destinations) + return self._cross_device_ops.reduce( + reduce_op, value, destinations=destinations) + + def _batch_reduce_to(self, reduce_op, value_destination_pairs): + for _, destinations in value_destination_pairs: + self._verify_destinations_not_different_worker(destinations) + return self._cross_device_ops.batch_reduce(reduce_op, + value_destination_pairs) + + def _select_single_value(self, structured): + """Select any single values in `structured`.""" + + def _select_fn(x): # pylint: disable=g-missing-docstring + if isinstance(x, values.Mirrored): + if len(x.devices) == 1: + return x.primary + else: + raise ValueError( + "You cannot update variable with a Mirrored object with multiple " + "components %r when using ParameterServerStrategy. You must " + "specify a single value or a Mirrored with a single value." % x) + elif isinstance(x, values.PerReplica): + raise ValueError( + "You cannot update variable with a PerReplica object %r when using " + "ParameterServerStrategy. You must specify a single value or a " + "Mirrored with a single value" % x) + else: + return x + + return nest.map_structure(_select_fn, structured) + + def _update(self, var, fn, args, kwargs, group): + if isinstance(var, values.AggregatingVariable): + var = var.get() + if not isinstance(var, resource_variable_ops.ResourceVariable): + raise ValueError( + "You can not update `var` %r. It must be a Variable." % var) + with ops.colocate_with(var), distribute_lib.UpdateContext(var.device): + result = fn(var, *self._select_single_value(args), + **self._select_single_value(kwargs)) + if group: + return result + else: + return nest.map_structure(self._unwrap, result) + + # TODO(yuefengz): does it need to call _select_single_value? + def _update_non_slot(self, colocate_with, fn, args, kwargs, group): + with ops.device( + colocate_with.device), distribute_lib.UpdateContext(colocate_with): + result = fn(*args, **kwargs) + if group: + return result + else: + return nest.map_structure(self._unwrap, result) + + def _unwrap(self, val): + if isinstance(val, values.DistributedValues): + return val.values + return (val,) + + def value_container(self, val): + if (hasattr(val, "_aggregating_container") and + not isinstance(val, values.AggregatingVariable)): + wrapper = val._aggregating_container() # pylint: disable=protected-access + if wrapper is not None: + return wrapper + return val + + def read_var(self, var): + # No need to distinguish between normal variables and replica-local + # variables. + return array_ops.identity(var) + + def _configure(self, + session_config=None, + cluster_spec=None, + task_type=None, + task_id=None): + """Configures the strategy class. + + The strategy object will be re-initialized if `cluster_spec` is given but + was not passed in the constructor. + + Args: + session_config: not used currently. + cluster_spec: a dict, ClusterDef or ClusterSpec object specifying the + cluster configurations. + task_type: the current task type. + task_id: the current task id. + + Raises: + ValueError: if `cluster_spec` is given but `task_type` or `task_id` is + not. + """ + if cluster_spec: + # Use the num_gpus_per_worker recorded in constructor since _configure + # doesn't take num_gpus. + cluster_resolver = SimpleClusterResolver( + cluster_spec=multi_worker_util.normalize_cluster_spec(cluster_spec), + task_type=task_type, + task_index=task_id, + num_accelerators=self._num_gpus_per_worker) + self._initialize_multi_worker(cluster_resolver) + + 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) + if not self._cluster_spec: + updated_config.isolate_session_state = True + return updated_config + + updated_config.isolate_session_state = False + + assert self._task_type + assert self._task_id is not None + + # The device filters prevent communication between workers. + if self._task_type not in ["chief", "worker"]: + return updated_config + del updated_config.device_filters[:] + updated_config.device_filters.extend( + ["/job:%s/task:%d" % (self._task_type, self._task_id), "/job:ps"]) + return updated_config + + @property + def _num_replicas_in_sync(self): + return self._device_map.num_replicas_in_graph + + @property + def worker_devices(self): + return self._device_map.all_devices + + @property + def worker_devices_by_replica(self): + return self._device_map.devices_by_replica + + @property + def parameter_devices(self): + return self._parameter_devices + + def non_slot_devices(self, var_list): + return min(var_list, key=lambda x: x.name) + + @property + def experimental_between_graph(self): + # TODO(yuefengz): Should this return False in the local case? + return True + + @property + def experimental_should_init(self): + return self._is_chief + + @property + def should_checkpoint(self): + return self._is_chief + + @property + def should_save_summary(self): + return self._is_chief + + # TODO(priyag): Delete this once all strategies use global batch size. + @property + def _global_batch_size(self): + return True -- GitLab From cea2e9c4fab95890873114bf60a5f19facca0c2f Mon Sep 17 00:00:00 2001 From: Sergei Lebedev Date: Fri, 4 Jan 2019 13:17:37 -0800 Subject: [PATCH 0206/2345] Removed set_shape overrides from ResourceVariable subclasses. These are essentially no-ops and allow for reads returning tensors inconsistent with variable shape: >>> import tensorflow as tf >>> tf.enable_eager_execution() >>> v = tf.Variable(0) >>> t = v.assign(42) >>> t.set_shape((100500, )) >>> t.shape (100500, ) >>> tf.convert_to_tensor(t) PiperOrigin-RevId: 227903155 --- tensorflow/python/ops/resource_variable_ops.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tensorflow/python/ops/resource_variable_ops.py b/tensorflow/python/ops/resource_variable_ops.py index ede4bc0078..d2e5dd9cfe 100644 --- a/tensorflow/python/ops/resource_variable_ops.py +++ b/tensorflow/python/ops/resource_variable_ops.py @@ -1351,10 +1351,6 @@ class _UnreadVariable(ResourceVariable): return gen_resource_variable_ops.read_variable_op(self._handle, self._dtype) - def set_shape(self, shape): - self._shape = shape - self._cached_shape_as_list = None - @property def op(self): """The op for this variable.""" @@ -1443,10 +1439,6 @@ class _MixedPrecisionVariable(ResourceVariable): else: return res - def set_shape(self, shape): - self._shape = shape - self._cached_shape_as_list = None - @property def op(self): """The op for this variable.""" -- GitLab From 4614b04cc076c543501124670da8b8da417cbf9f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 13:49:49 -0800 Subject: [PATCH 0207/2345] One more refactoring of kernel.cc. This introduces OpInputs to match OpOutputs, which is slightly enhanced. PiperOrigin-RevId: 227908106 --- tensorflow/lite/delegates/flex/kernel.cc | 127 ++++++++++++++--------- 1 file changed, 80 insertions(+), 47 deletions(-) diff --git a/tensorflow/lite/delegates/flex/kernel.cc b/tensorflow/lite/delegates/flex/kernel.cc index 3012241f4f..0005b6e5b0 100644 --- a/tensorflow/lite/delegates/flex/kernel.cc +++ b/tensorflow/lite/delegates/flex/kernel.cc @@ -51,24 +51,57 @@ namespace tflite { namespace flex { namespace kernel { -// Controls the lifetime of tensor handles in a vector. +// A list of inputs of a given node of the TensorFlow/Eager graph. +class OpInputs { + public: + explicit OpInputs(const TfLiteIntArray* indexes) { + for (int index : TfLiteIntArrayView(indexes)) { + inputs_.push_back(index); + } + } + ~OpInputs() {} + + int Size() const { return inputs_.size(); } + + int TfLiteIndex(int i) const { return inputs_[i]; } + + private: + std::vector inputs_; +}; + +// A list of outputs of a given node of the TensorFlow/Eager graph, along with +// the actual outputs of the EagerOperation. class OpOutputs { public: - explicit OpOutputs(int num_elements) : vector_(num_elements, nullptr) {} + explicit OpOutputs(const TfLiteIntArray* indexes) { + for (int index : TfLiteIntArrayView(indexes)) { + outputs_.push_back(index); + } + vector_.resize(outputs_.size()); + } + ~OpOutputs() { ResetTensorHandles(); } + + int Size() const { return outputs_.size(); } + + int TfLiteIndex(int i) const { return outputs_[i]; } - ~OpOutputs() { - for (auto* handle : vector_) { - if (handle) handle->Unref(); + // Carefully unreference all the handles in the eager output vector. + void ResetTensorHandles() { + for (int i = 0; i < vector_.size(); ++i) { + if (vector_[i]) { + vector_[i]->Unref(); + vector_[i] = nullptr; + } } } - tensorflow::gtl::InlinedVector* GetVector() { + tensorflow::gtl::InlinedVector* + GetTensorHandles() { return &vector_; } - tensorflow::TensorHandle* GetHandle(int index) { return vector_[index]; } - private: + std::vector outputs_; tensorflow::gtl::InlinedVector vector_; }; @@ -76,7 +109,8 @@ class OpOutputs { // TensorFlow ops within a single TF Lite op. class OpNode { public: - OpNode() {} + OpNode(const TfLiteIntArray* inputs, const TfLiteIntArray* outputs) + : inputs_(inputs), outputs_(outputs) {} ~OpNode() {} const string& name() const { return name_; } @@ -87,19 +121,14 @@ class OpNode { const tensorflow::NodeDef& nodedef() const { return nodedef_; } - const std::vector& inputs() const { return inputs_; } - void InitializeInputs(const TfLiteIntArray* inputs) { - for (int index : TfLiteIntArrayView(inputs)) { - inputs_.push_back(index); - } - } + const OpInputs& inputs() const { return inputs_; } + OpInputs* mutable_inputs() { return &inputs_; } - const std::vector& outputs() const { return outputs_; } - void InitializeOutputs(const TfLiteIntArray* outputs) { - for (int index : TfLiteIntArrayView(outputs)) { - outputs_.push_back(index); - } - } + const OpOutputs& outputs() const { return outputs_; } + OpOutputs* mutable_outputs() { return &outputs_; } + + int NumInputs() const { return inputs_.Size(); } + int NumOutputs() const { return outputs_.Size(); } tensorflow::Status InitializeNodeDef(const void* custom_initial_data, int custom_initial_data_size) { @@ -162,7 +191,8 @@ class OpNode { tensorflow::Status BuildEagerInputs(BufferMap* buffer_map, tensorflow::EagerOperation* op) { - for (int input_index : inputs_) { + for (int i = 0; i < inputs_.Size(); ++i) { + int input_index = inputs_.TfLiteIndex(i); if (!buffer_map->HasTensor(input_index)) { return tensorflow::errors::Internal( "Cannot read from invalid tensor index ", input_index); @@ -181,17 +211,21 @@ class OpNode { return tensorflow::Status::OK(); } - tensorflow::Status PersistEagerOutputs(BufferMap* buffer_map, - OpOutputs* retvals) { - for (int i = 0; i < outputs_.size(); ++i) { + tensorflow::Status PersistEagerOutputs(BufferMap* buffer_map) { + auto* handles = outputs_.GetTensorHandles(); + for (int i = 0; i < outputs_.Size(); ++i) { const tensorflow::Tensor* tensor = nullptr; - TF_RETURN_IF_ERROR(retvals->GetHandle(i)->Tensor(&tensor)); - buffer_map->SetFromTensorFlow(outputs_[i], *tensor); + TF_RETURN_IF_ERROR(handles->at(i)->Tensor(&tensor)); + buffer_map->SetFromTensorFlow(outputs_.TfLiteIndex(i), *tensor); } + outputs_.ResetTensorHandles(); return tensorflow::Status::OK(); } private: + OpNode(const OpNode&) = delete; + OpNode& operator=(const OpNode&) = delete; + // The name of the TensorFlow op to execute. string name_; // Index of this node into TF Lite's operator list. @@ -199,9 +233,9 @@ class OpNode { // The corresponding NodeDef, containing the attributes for the op. tensorflow::NodeDef nodedef_; // List of inputs, as TF Lite tensor indices. - std::vector inputs_; + OpInputs inputs_; // List of outputs, as TF Lite tensor indices. - std::vector outputs_; + OpOutputs outputs_; }; // Executes the TensorFlow op given by 'op_name', with the attributes specified @@ -212,18 +246,18 @@ tensorflow::Status ExecuteFlexOp(tensorflow::EagerContext* eager_context, TF_RETURN_IF_ERROR(node_data->BuildEagerOp(eager_context, &op)); TF_RETURN_IF_ERROR(node_data->BuildEagerInputs(buffer_map, op.get())); - int num_retvals = node_data->outputs().size(); - OpOutputs retvals(num_retvals); + int num_retvals = node_data->NumOutputs(); TF_RETURN_WITH_CONTEXT_IF_ERROR( - EagerExecute(op.get(), retvals.GetVector(), &num_retvals), + EagerExecute(op.get(), node_data->mutable_outputs()->GetTensorHandles(), + &num_retvals), " (while executing '", node_data->name(), "' via Eager)"); - if (num_retvals != node_data->outputs().size()) { + if (num_retvals != node_data->NumOutputs()) { return tensorflow::errors::Internal( "Unexpected number of outputs from EagerExecute"); } - TF_RETURN_IF_ERROR(node_data->PersistEagerOutputs(buffer_map, &retvals)); + TF_RETURN_IF_ERROR(node_data->PersistEagerOutputs(buffer_map)); return tensorflow::Status::OK(); } @@ -232,7 +266,7 @@ tensorflow::Status ExecuteFlexOp(tensorflow::EagerContext* eager_context, struct OpData { tensorflow::EagerContext* eager_context; BufferMap* buffer_map; - std::vector nodes; + std::vector> nodes; std::vector subgraph_inputs; std::vector subgraph_outputs; }; @@ -261,6 +295,8 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { op_data->subgraph_inputs.push_back(tensor_index); } + op_data->nodes.reserve(params->nodes_to_replace->size); + CHECK(params->nodes_to_replace); tensorflow::Status status; for (auto node_index : TfLiteIntArrayView(params->nodes_to_replace)) { @@ -268,8 +304,8 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { TfLiteRegistration* reg; context->GetNodeAndRegistration(context, node_index, &node, ®); - op_data->nodes.push_back(OpNode()); - OpNode& node_data = op_data->nodes.back(); + op_data->nodes.emplace_back(new OpNode(node->inputs, node->outputs)); + OpNode& node_data = *op_data->nodes.back(); node_data.set_index(node_index); node_data.set_name(""); @@ -277,9 +313,6 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { status = node_data.InitializeNodeDef(node->custom_initial_data, node->custom_initial_data_size); if (!status.ok()) break; - - node_data.InitializeInputs(node->inputs); - node_data.InitializeOutputs(node->outputs); } if (ConvertStatus(context, status) != kTfLiteOk) { @@ -328,14 +361,14 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { } for (const auto& node_data : op_data->nodes) { - if (node_data.nodedef().op().empty()) { + if (node_data->nodedef().op().empty()) { context->ReportError(context, "Invalid NodeDef in Flex op '%s'", - node_data.name().c_str()); + node_data->name().c_str()); return kTfLiteError; } - for (int tensor_index : node_data.inputs()) { - ++tensor_ref_count[tensor_index]; + for (int i = 0; i < node_data->inputs().Size(); ++i) { + ++tensor_ref_count[node_data->inputs().TfLiteIndex(i)]; } } @@ -371,12 +404,12 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { } // Execute the TensorFlow Ops sequentially. - for (OpNode& node_data : op_data->nodes) { + for (auto& node_data : op_data->nodes) { SCOPED_TAGGED_OPERATOR_PROFILE( reinterpret_cast(context->profiler), - node_data.name().c_str(), node_data.index()); + node_data->name().c_str(), node_data->index()); - auto status = ExecuteFlexOp(eager_context, buffer_map, &node_data); + auto status = ExecuteFlexOp(eager_context, buffer_map, node_data.get()); TF_LITE_ENSURE_OK(context, ConvertStatus(context, status)); } -- GitLab From 962d8821ebb23e918bae3680bab5a7f6df32cd7f Mon Sep 17 00:00:00 2001 From: Michael Kuperstein Date: Fri, 4 Jan 2019 13:52:30 -0800 Subject: [PATCH 0208/2345] [XLA] Adapt HLO pipeline to scalar-index DS and DUS This and adapts HLO to work with the scalar DynamicSlice / DynamicUpdateSlice form. This means it also effectively switches the backends to use the new form. Also a bunch of test churn - tests that don't run optimizations (and, hence, don't trigger the splitter pass) needed to be converted. PiperOrigin-RevId: 227908542 --- .../xla/service/algebraic_simplifier.cc | 27 ++++----- .../xla/service/algebraic_simplifier_test.cc | 26 +++++---- .../xla/service/buffer_assignment_test.cc | 6 +- tensorflow/compiler/xla/service/cpu/BUILD | 18 +++--- .../compiler/xla/service/cpu/cpu_compiler.cc | 2 + tensorflow/compiler/xla/service/gpu/BUILD | 12 ++-- .../service/gpu/multi_output_fusion_test.cc | 5 +- .../xla/service/gpu/nvptx_compiler.cc | 2 + .../xla/service/hlo_creation_utils.cc | 37 ++++++++++-- .../xla/service/hlo_evaluator_typed_visitor.h | 22 +------- .../compiler/xla/service/hlo_verifier.cc | 19 +------ .../compiler/xla/service/hlo_verifier_test.cc | 5 +- .../compiler/xla/service/interpreter/BUILD | 9 +-- .../xla/service/interpreter/compiler.cc | 2 + .../xla/service/layout_assignment_test.cc | 7 ++- .../compiler/xla/service/pattern_matcher.h | 2 +- .../compiler/xla/service/shape_inference.cc | 10 ++-- .../compiler/xla/service/shape_inference.h | 4 +- tensorflow/compiler/xla/shape_util.cc | 1 + tensorflow/compiler/xla/tests/BUILD | 1 + tensorflow/compiler/xla/tests/test_utils.cc | 52 +++++++---------- .../compiler/xla/tests/test_utils_test.cc | 56 ++++++++++--------- 22 files changed, 162 insertions(+), 163 deletions(-) diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc index 003cb7e791..5ac746c9f3 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc @@ -1380,6 +1380,9 @@ StatusOr AlgebraicSimplifierVisitor::OptimizeDotOfGather( // => output dimensions: DS ({M x N}, {0, start}, {M, 1}) => {M x 1}. bool lhs_is_dynamic_slice = lhs->opcode() == HloOpcode::kDynamicSlice; + HloDynamicSliceInstruction* dynamic_slice = + lhs_is_dynamic_slice ? Cast(lhs) + : Cast(rhs); // ctA: HloInstruction* left_operand = @@ -1397,8 +1400,6 @@ StatusOr AlgebraicSimplifierVisitor::OptimizeDotOfGather( HloInstruction::CreateDot(memoized_shape, left_operand, right_operand, dnums, dot->precision_config())); // Get pair {start, 0} or {0, start}. - HloInstruction* original_start_indices = - lhs_is_dynamic_slice ? lhs->mutable_operand(1) : rhs->mutable_operand(1); // Position of start: int index_of_non_zero_start = lhs_is_dynamic_slice ? 1 - lhs_contracting_dimension @@ -1407,23 +1408,19 @@ StatusOr AlgebraicSimplifierVisitor::OptimizeDotOfGather( int index_of_zero_start = 1 - index_of_non_zero_start; // Slice out start and 0 components and reorder if necessary. - auto indices_type = original_start_indices->shape().element_type(); + auto indices_type = dynamic_slice->operand(1)->shape().element_type(); Shape s_shape = ShapeUtil::MakeShape(indices_type, {1}); Shape d_shape = ShapeUtil::MakeShape(indices_type, {2}); HloInstruction* non_zero_start = - computation_->AddInstruction(HloInstruction::CreateSlice( - s_shape, original_start_indices, {index_of_non_zero_start}, - {index_of_non_zero_start + 1}, {1})); + dynamic_slice->mutable_operand(1 + index_of_non_zero_start); HloInstruction* zero_start = - computation_->AddInstruction(HloInstruction::CreateSlice( - s_shape, original_start_indices, {index_of_zero_start}, - {index_of_zero_start + 1}, {1})); - HloInstruction* new_start_indices = - lhs_is_dynamic_slice - ? computation_->AddInstruction(HloInstruction::CreateConcatenate( - d_shape, {non_zero_start, zero_start}, 0)) - : computation_->AddInstruction(HloInstruction::CreateConcatenate( - d_shape, {zero_start, non_zero_start}, 0)); + dynamic_slice->mutable_operand(1 + index_of_zero_start); + std::vector new_start_indices; + if (lhs_is_dynamic_slice) { + new_start_indices = {non_zero_start, zero_start}; + } else { + new_start_indices = {zero_start, non_zero_start}; + } // Build DynamicSlice(ctA x ctB). const int new_slice_m = lhs_is_dynamic_slice ? 1 : m; diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc index 211c5bf05a..e39321f1f3 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc @@ -4535,14 +4535,17 @@ TEST_P(DotOfGatherSimplificationTest, ConstantRHS) { int32 start_row = (spec.lcd == 0) ? 0 : spec.s; int32 start_col = (spec.lcd == 0) ? spec.s : 0; - const auto start_indices = + std::vector start_indices = { builder.AddInstruction(HloInstruction::CreateConstant( - LiteralUtil::CreateR1({start_row, start_col}))); + LiteralUtil::CreateR0(start_row))), + builder.AddInstruction(HloInstruction::CreateConstant( + LiteralUtil::CreateR0(start_col)))}; int64 slice_row_size = (spec.lcd == 0) ? spec.k : 1; int64 slice_col_size = (spec.lcd == 0) ? 1 : spec.k; - Shape ds_shape = ShapeUtil::MakeShape(F32, {slice_row_size, slice_col_size}); + std::vector slice_sizes = {slice_row_size, slice_col_size}; + Shape ds_shape = ShapeUtil::MakeShape(F32, slice_sizes); auto* ds = builder.AddInstruction(HloInstruction::CreateDynamicSlice( - ds_shape, lhs, start_indices, {slice_row_size, slice_col_size})); + ds_shape, lhs, start_indices, slice_sizes)); int64 rhs_rows = (spec.rcd == 0) ? spec.k : spec.n; int64 rhs_cols = (spec.rcd == 0) ? spec.n : spec.k; @@ -4575,7 +4578,7 @@ TEST_P(DotOfGatherSimplificationTest, ConstantRHS) { } else { EXPECT_THAT(computation->root_instruction(), GmockMatch(m::DynamicSlice(m::Dot(m::Constant(), m::Constant()), - m::Concatenate()))); + m::Constant(), m::Constant()))); } } @@ -4613,14 +4616,17 @@ TEST_P(DotOfGatherSimplificationTest, ConstantLHS) { int32 start_row = (spec.rcd == 0) ? 0 : spec.s; int32 start_col = (spec.rcd == 0) ? spec.s : 0; - const auto start_indices = + std::vector start_indices = { + builder.AddInstruction(HloInstruction::CreateConstant( + LiteralUtil::CreateR0(start_row))), builder.AddInstruction(HloInstruction::CreateConstant( - LiteralUtil::CreateR1({start_row, start_col}))); + LiteralUtil::CreateR0(start_col)))}; int64 slice_row_size = (spec.rcd == 0) ? spec.k : 1; int64 slice_col_size = (spec.rcd == 0) ? 1 : spec.k; - Shape ds_shape = ShapeUtil::MakeShape(F32, {slice_row_size, slice_col_size}); + std::vector slice_sizes = {slice_row_size, slice_col_size}; + Shape ds_shape = ShapeUtil::MakeShape(F32, slice_sizes); auto* ds = builder.AddInstruction(HloInstruction::CreateDynamicSlice( - ds_shape, rhs, start_indices, {slice_row_size, slice_col_size})); + ds_shape, rhs, start_indices, slice_sizes)); DotDimensionNumbers dot_dnums; dot_dnums.add_lhs_contracting_dimensions(spec.lcd); @@ -4645,7 +4651,7 @@ TEST_P(DotOfGatherSimplificationTest, ConstantLHS) { } else { EXPECT_THAT(computation->root_instruction(), GmockMatch(m::DynamicSlice(m::Dot(m::Constant(), m::Constant()), - m::Concatenate()))); + m::Constant(), m::Constant()))); } } diff --git a/tensorflow/compiler/xla/service/buffer_assignment_test.cc b/tensorflow/compiler/xla/service/buffer_assignment_test.cc index 370fad7ffb..1b4e93a2f3 100644 --- a/tensorflow/compiler/xla/service/buffer_assignment_test.cc +++ b/tensorflow/compiler/xla/service/buffer_assignment_test.cc @@ -2486,9 +2486,9 @@ while_body { get-tuple-element.3 = s32[] get-tuple-element(state), index=0 constant.2 = s32[] constant(128) add.5 = s32[] add(get-tuple-element.3, constant.2) - constant.3 = s32[3]{0} constant({0, 0, 0}) - dynamic-update-slice.5 = f32[1280,1,128]{2,1,0} dynamic-update-slice(get-tuple-element.4, broadcast.6, constant.3) - dynamic-update-slice.9 = f32[1280,1,128]{2,1,0} dynamic-update-slice(dynamic-update-slice.5, broadcast.6, constant.3) + constant.3 = s32[] constant(0) + dynamic-update-slice.5 = f32[1280,1,128]{2,1,0} dynamic-update-slice(get-tuple-element.4, broadcast.6, constant.3, constant.3, constant.3) + dynamic-update-slice.9 = f32[1280,1,128]{2,1,0} dynamic-update-slice(dynamic-update-slice.5, broadcast.6, constant.3, constant.3, constant.3) ROOT tuple.85 = (s32[], s32[], s32[2]{0}, f32[1280,1,128]{2,1,0}) tuple(add.5, dynamic-update-slice.9) } diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index a4ca772c7c..5c72e1ff71 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -1,6 +1,14 @@ # Description: # LLVM-based CPU backend for XLA. +load("//tensorflow/compiler/xla:xla.bzl", "ORC_JIT_MEMORY_MAPPER_TARGETS") +load( + "//third_party/mkl:build_defs.bzl", + "mkl_deps", +) +load("//tensorflow:tensorflow.bzl", "tf_cc_binary", "tf_cc_test") +load(":build_defs.bzl", "runtime_copts") + licenses(["notice"]) # Apache 2.0 package( @@ -14,15 +22,6 @@ package_group( ], ) -load(":build_defs.bzl", "runtime_copts") -load("//tensorflow:tensorflow.bzl", "tf_cc_test") -load("//tensorflow:tensorflow.bzl", "tf_cc_binary") -load("//tensorflow/compiler/xla:xla.bzl", "ORC_JIT_MEMORY_MAPPER_TARGETS") -load( - "//third_party/mkl:build_defs.bzl", - "mkl_deps", -) - # Filegroup used to collect source files for dependency checking. filegroup( name = "c_srcs", @@ -114,6 +113,7 @@ cc_library( "//tensorflow/compiler/xla/service:conditional_simplifier", "//tensorflow/compiler/xla/service:convolution_group_converter", "//tensorflow/compiler/xla/service:dot_decomposer", + "//tensorflow/compiler/xla/service:dynamic_index_splitter", "//tensorflow/compiler/xla/service:executable", "//tensorflow/compiler/xla/service:flatten_call_graph", "//tensorflow/compiler/xla/service:hlo", diff --git a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc index dbab839aee..91c64e45b9 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc @@ -69,6 +69,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/cpu/simple_orc_jit.h" #include "tensorflow/compiler/xla/service/dfs_hlo_visitor_with_default.h" #include "tensorflow/compiler/xla/service/dot_decomposer.h" +#include "tensorflow/compiler/xla/service/dynamic_index_splitter.h" #include "tensorflow/compiler/xla/service/flatten_call_graph.h" #include "tensorflow/compiler/xla/service/hlo.pb.h" #include "tensorflow/compiler/xla/service/hlo_computation.h" @@ -244,6 +245,7 @@ Status CpuCompiler::RunHloPassesThroughLayoutAssn( HloPassPipeline pipeline("HLO passes through layout assignment"); pipeline.AddInvariantChecker(/*layout_sensitive=*/false, /*allow_mixed_precision=*/false); + pipeline.AddPass(); pipeline.AddPass(); ReducePrecisionInsertion::AddPasses( diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index d10a11d096..5e81281bae 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -3,6 +3,11 @@ load("//tensorflow/compiler/xla/tests:build_defs.bzl", "xla_test") load("//tensorflow/compiler/xla:xla.bzl", "xla_proto_library") +load( + "//tensorflow/core:platform/default/build_config_root.bzl", + "tf_cuda_tests_tags", +) +load("//tensorflow:tensorflow.bzl", "tf_cc_test") licenses(["notice"]) # Apache 2.0 @@ -24,12 +29,6 @@ filegroup( ]), ) -load("//tensorflow:tensorflow.bzl", "tf_cc_test") -load( - "//tensorflow/core:platform/default/build_config_root.bzl", - "tf_cuda_tests_tags", -) - xla_proto_library( name = "backend_configs", srcs = ["backend_configs.proto"], @@ -700,6 +699,7 @@ cc_library( "//tensorflow/compiler/xla/service:call_inliner", "//tensorflow/compiler/xla/service:conditional_simplifier", "//tensorflow/compiler/xla/service:convolution_group_converter", + "//tensorflow/compiler/xla/service:dynamic_index_splitter", "//tensorflow/compiler/xla/service:executable", "//tensorflow/compiler/xla/service:flatten_call_graph", "//tensorflow/compiler/xla/service:hlo", diff --git a/tensorflow/compiler/xla/service/gpu/multi_output_fusion_test.cc b/tensorflow/compiler/xla/service/gpu/multi_output_fusion_test.cc index d16c87ba5c..40b87b16a1 100644 --- a/tensorflow/compiler/xla/service/gpu/multi_output_fusion_test.cc +++ b/tensorflow/compiler/xla/service/gpu/multi_output_fusion_test.cc @@ -628,8 +628,7 @@ TEST_F(MultiOutputFusionTest, MultiOutputFusionDUS) { p.1 = s32[1]{0} parameter(1) p.2 = f16[1,96,1024]{2,1,0} parameter(2) c.0 = s32[] constant(0) - pad = s32[3]{0} pad(p.1, c.0), padding=0_2 - ROOT %dynamic-update-slice = f16[50,96,1024]{2,1,0} dynamic-update-slice(p.0, p.2, pad) + ROOT %dynamic-update-slice = f16[50,96,1024]{2,1,0} dynamic-update-slice(p.0, p.2, p.1, c.0, c.0) } fusion.2 { @@ -638,7 +637,7 @@ TEST_F(MultiOutputFusionTest, MultiOutputFusionDUS) { p.2 = f16[1,96,1024]{2,1,0} parameter(2) c.0 = s32[] constant(0) pad = s32[3]{0} pad(p.1, c.0), padding=0_2 - ROOT %dynamic-update-slice = f16[50,96,1024]{2,1,0} dynamic-update-slice(p.0, p.2, pad) + ROOT %dynamic-update-slice = f16[50,96,1024]{2,1,0} dynamic-update-slice(p.0, p.2, p.1, c.0, c.0) } ENTRY entry { diff --git a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc index cd369d5598..9be7609508 100644 --- a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc +++ b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc @@ -37,6 +37,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/call_inliner.h" #include "tensorflow/compiler/xla/service/conditional_simplifier.h" #include "tensorflow/compiler/xla/service/convolution_group_converter.h" +#include "tensorflow/compiler/xla/service/dynamic_index_splitter.h" #include "tensorflow/compiler/xla/service/flatten_call_graph.h" #include "tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.h" #include "tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.h" @@ -152,6 +153,7 @@ Status OptimizeHloModule(HloModule* hlo_module, se::StreamExecutor* stream_exec, HloPassPipeline pipeline("optimization"); pipeline.AddInvariantChecker(/*layout_sensitive=*/false, /*allow_mixed_precision=*/false); + pipeline.AddPass(); pipeline.AddPass(); ReducePrecisionInsertion::AddPasses( &pipeline, hlo_module->config().debug_options(), diff --git a/tensorflow/compiler/xla/service/hlo_creation_utils.cc b/tensorflow/compiler/xla/service/hlo_creation_utils.cc index 8cea95a73e..bb5d21c654 100644 --- a/tensorflow/compiler/xla/service/hlo_creation_utils.cc +++ b/tensorflow/compiler/xla/service/hlo_creation_utils.cc @@ -19,6 +19,7 @@ limitations under the License. #include "absl/strings/str_cat.h" #include "tensorflow/compiler/xla/literal.h" #include "tensorflow/compiler/xla/literal_util.h" +#include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/service/hlo_module.h" #include "tensorflow/compiler/xla/service/shape_inference.h" #include "tensorflow/compiler/xla/util.h" @@ -105,12 +106,26 @@ StatusOr MakeDynamicSliceHlo( absl::Span slice_sizes) { HloComputation* computation = operand->parent(); CHECK_EQ(computation, start_indices->parent()); + int64 rank = start_indices->shape().dimensions(0); + std::vector scalar_start_indices; + for (int i = 0; i < rank; ++i) { + // TODO(b/118437727): Update callers to provide scalars directly. + auto slice = computation->AddInstruction(HloInstruction::CreateSlice( + ShapeUtil::MakeShape(start_indices->shape().element_type(), {1}), + start_indices, {i}, {i + 1}, {1})); + scalar_start_indices.push_back( + computation->AddInstruction(HloInstruction::CreateReshape( + ShapeUtil::MakeShape(start_indices->shape().element_type(), {}), + slice))); + } + std::vector scalar_start_indices_shapes( + rank, ShapeUtil::MakeShape(start_indices->shape().element_type(), {})); TF_ASSIGN_OR_RETURN( Shape dynamic_slice_shape, ShapeInference::InferDynamicSliceShape( - operand->shape(), {start_indices->shape()}, slice_sizes)); + operand->shape(), scalar_start_indices_shapes, slice_sizes)); return computation->AddInstruction(HloInstruction::CreateDynamicSlice( - dynamic_slice_shape, operand, start_indices, slice_sizes)); + dynamic_slice_shape, operand, scalar_start_indices, slice_sizes)); } StatusOr MakeDynamicUpdateSliceHlo( @@ -119,12 +134,26 @@ StatusOr MakeDynamicUpdateSliceHlo( HloComputation* computation = operand->parent(); CHECK_EQ(computation, update->parent()); CHECK_EQ(computation, start_indices->parent()); + int64 rank = start_indices->shape().dimensions(0); + std::vector scalar_start_indices; + for (int i = 0; i < rank; ++i) { + // TODO(b/118437727): Update callers to provide scalars directly. + auto slice = computation->AddInstruction(HloInstruction::CreateSlice( + ShapeUtil::MakeShape(start_indices->shape().element_type(), {1}), + start_indices, {i}, {i + 1}, {1})); + scalar_start_indices.push_back( + computation->AddInstruction(HloInstruction::CreateReshape( + ShapeUtil::MakeShape(start_indices->shape().element_type(), {}), + slice))); + } + std::vector scalar_start_indices_shapes( + rank, ShapeUtil::MakeShape(start_indices->shape().element_type(), {})); TF_ASSIGN_OR_RETURN( Shape dynamic_update_slice_shape, ShapeInference::InferDynamicUpdateSliceShape( - operand->shape(), update->shape(), {start_indices->shape()})); + operand->shape(), update->shape(), scalar_start_indices_shapes)); return computation->AddInstruction(HloInstruction::CreateDynamicUpdateSlice( - dynamic_update_slice_shape, operand, update, start_indices)); + dynamic_update_slice_shape, operand, update, scalar_start_indices)); } StatusOr MakeBroadcastHlo( diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h index cc5f6cc682..698b177310 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h +++ b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h @@ -1410,22 +1410,12 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { auto operand = dynamic_slice->operand(0); auto start_indices = dynamic_slice->operand(1); auto result_shape = dynamic_slice->shape(); - // TODO(b/118437727): Remove all of this nonsense. - // We may get an instruction without a parent module. In this case, assume - // scalar indices are not allowed. - bool allow_scalar_index = false; - if (dynamic_slice->GetModule() != nullptr) { - allow_scalar_index = dynamic_slice->GetModule() - ->config() - .debug_options() - .xla_allow_scalar_index_dynamic_ops(); - } TF_ASSIGN_OR_RETURN( auto inferred_return_shape, ShapeInference::InferDynamicSliceShape( operand->shape(), Cast(dynamic_slice)->index_shapes(), - dynamic_slice->dynamic_slice_sizes(), allow_scalar_index)); + dynamic_slice->dynamic_slice_sizes())); TF_RET_CHECK(ShapeUtil::Compatible(result_shape, inferred_return_shape)) << "return shape is set to: " << ShapeUtil::HumanString(result_shape) << " but is inferred to be: " @@ -1483,20 +1473,12 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { auto update = dynamic_update_slice->operand(1); auto start_indices = dynamic_update_slice->operand(2); auto result_shape = dynamic_update_slice->shape(); - bool allow_scalar_index = false; - if (dynamic_update_slice->GetModule() != nullptr) { - allow_scalar_index = dynamic_update_slice->GetModule() - ->config() - .debug_options() - .xla_allow_scalar_index_dynamic_ops(); - } TF_ASSIGN_OR_RETURN( auto inferred_return_shape, ShapeInference::InferDynamicUpdateSliceShape( operand->shape(), update->shape(), Cast(dynamic_update_slice) - ->index_shapes(), - allow_scalar_index)); + ->index_shapes())); TF_RET_CHECK(ShapeUtil::Compatible(result_shape, inferred_return_shape)) << "return shape is set to: " << ShapeUtil::HumanString(result_shape) << " but is inferred to be: " diff --git a/tensorflow/compiler/xla/service/hlo_verifier.cc b/tensorflow/compiler/xla/service/hlo_verifier.cc index 9274d42280..2c69c27ce5 100644 --- a/tensorflow/compiler/xla/service/hlo_verifier.cc +++ b/tensorflow/compiler/xla/service/hlo_verifier.cc @@ -504,38 +504,23 @@ Status ShapeVerifier::HandleSlice(HloInstruction* slice) { } Status ShapeVerifier::HandleDynamicSlice(HloInstruction* dynamic_slice) { - const DebugOptions& debug_options = - dynamic_slice->GetModule()->config().debug_options(); - const bool allow_scalar_indices = - debug_options.xla_allow_scalar_index_dynamic_ops(); - if (!allow_scalar_indices) { - TF_RETURN_IF_ERROR(CheckOperandCount(dynamic_slice, 2)); - } return CheckShape( dynamic_slice, ShapeInference::InferDynamicSliceShape( dynamic_slice->operand(0)->shape(), Cast(dynamic_slice)->index_shapes(), - dynamic_slice->dynamic_slice_sizes(), allow_scalar_indices)); + dynamic_slice->dynamic_slice_sizes())); } Status ShapeVerifier::HandleDynamicUpdateSlice( HloInstruction* dynamic_update_slice) { - const DebugOptions& debug_options = - dynamic_update_slice->GetModule()->config().debug_options(); - const bool allow_scalar_indices = - debug_options.xla_allow_scalar_index_dynamic_ops(); - if (!allow_scalar_indices) { - TF_RETURN_IF_ERROR(CheckOperandCount(dynamic_update_slice, 3)); - } return CheckShape( dynamic_update_slice, ShapeInference::InferDynamicUpdateSliceShape( dynamic_update_slice->operand(0)->shape(), dynamic_update_slice->operand(1)->shape(), Cast(dynamic_update_slice) - ->index_shapes(), - allow_scalar_indices)); + ->index_shapes())); } Status ShapeVerifier::HandleTuple(HloInstruction* tuple) { diff --git a/tensorflow/compiler/xla/service/hlo_verifier_test.cc b/tensorflow/compiler/xla/service/hlo_verifier_test.cc index 9b2d884f79..d27c62a879 100644 --- a/tensorflow/compiler/xla/service/hlo_verifier_test.cc +++ b/tensorflow/compiler/xla/service/hlo_verifier_test.cc @@ -450,8 +450,9 @@ TEST_F(HloVerifierTestLayoutSensitive, SliceWithLayoutChangeNotAllowed) { HloModule SliceWithLayoutChange ENTRY SliceWithLayoutChange { par0 = f32[4,5]{0,1} parameter(0) - par1 = s32[2] parameter(1) - ROOT dslice0 = f32[3,4]{1,0} dynamic-slice(par0, par1), + par1 = s32[] parameter(1) + par2 = s32[] parameter(2) + ROOT dslice0 = f32[3,4]{1,0} dynamic-slice(par0, par1, par2), dynamic_slice_sizes={3,4} } )"; diff --git a/tensorflow/compiler/xla/service/interpreter/BUILD b/tensorflow/compiler/xla/service/interpreter/BUILD index 8999f38ccd..2e1d2cfbda 100644 --- a/tensorflow/compiler/xla/service/interpreter/BUILD +++ b/tensorflow/compiler/xla/service/interpreter/BUILD @@ -1,12 +1,12 @@ -licenses(["notice"]) # Apache 2.0 - -package(default_visibility = ["//visibility:public"]) - load( "//tensorflow/core:platform/default/build_config_root.bzl", "if_static", ) +licenses(["notice"]) # Apache 2.0 + +package(default_visibility = ["//visibility:public"]) + cc_library( name = "interpreter_transfer_manager", srcs = ["interpreter_transfer_manager.cc"], @@ -35,6 +35,7 @@ cc_library( "//tensorflow/compiler/xla/service:batchnorm_expander", "//tensorflow/compiler/xla/service:compiler", "//tensorflow/compiler/xla/service:computation_placer", + "//tensorflow/compiler/xla/service:dynamic_index_splitter", "//tensorflow/compiler/xla/service:executable", "//tensorflow/compiler/xla/service:flatten_call_graph", "//tensorflow/compiler/xla/service:hlo", diff --git a/tensorflow/compiler/xla/service/interpreter/compiler.cc b/tensorflow/compiler/xla/service/interpreter/compiler.cc index 69f611a094..4818b2dae0 100644 --- a/tensorflow/compiler/xla/service/interpreter/compiler.cc +++ b/tensorflow/compiler/xla/service/interpreter/compiler.cc @@ -21,6 +21,7 @@ limitations under the License. #include "absl/memory/memory.h" #include "tensorflow/compiler/xla/service/algebraic_simplifier.h" #include "tensorflow/compiler/xla/service/computation_placer.h" +#include "tensorflow/compiler/xla/service/dynamic_index_splitter.h" #include "tensorflow/compiler/xla/service/flatten_call_graph.h" #include "tensorflow/compiler/xla/service/hlo_constant_folding.h" #include "tensorflow/compiler/xla/service/hlo_cse.h" @@ -44,6 +45,7 @@ namespace interpreter { Status InterpreterCompiler::RunHloOptimization(HloModule* hlo_module) { HloPassPipeline pipeline("Interpreter"); + pipeline.AddPass(); pipeline.AddPass( hlo_module->mutable_entry_computation_layout(), LayoutAssignment::InstructionCanChangeLayout); diff --git a/tensorflow/compiler/xla/service/layout_assignment_test.cc b/tensorflow/compiler/xla/service/layout_assignment_test.cc index 387b385157..c8cf3c47d3 100644 --- a/tensorflow/compiler/xla/service/layout_assignment_test.cc +++ b/tensorflow/compiler/xla/service/layout_assignment_test.cc @@ -960,8 +960,9 @@ TEST_F(LayoutAssignmentTest, CopyDSliceOperandToAvoidImplicitLayoutChange) { ENTRY CopyDSliceOperandToAvoidImplicitLayoutChange { par0 = f32[3,4]{1,0} parameter(0) par1 = f32[4,5]{0,1} parameter(1) - par2 = s32[2] parameter(2) - dslice0 = f32[3,4] dynamic-slice(par1, par2), dynamic_slice_sizes={3,4} + par2 = s32[] parameter(2) + par3 = s32[] parameter(3) + dslice0 = f32[3,4] dynamic-slice(par1, par2, par3), dynamic_slice_sizes={3,4} ROOT add0 = f32[3,4]{1,0} add(par0,dslice0) } )"; @@ -982,7 +983,7 @@ TEST_F(LayoutAssignmentTest, CopyDSliceOperandToAvoidImplicitLayoutChange) { m::Parameter(), m::DynamicSlice( m::Copy(m::Parameter(1)).WithShapeEqualTo(&shape_copy), - m::Parameter(2))))); + m::Parameter(2), m::Parameter(3))))); } TEST_F(LayoutAssignmentTest, CopyConcatOperandToAvoidImplicitLayoutChange) { diff --git a/tensorflow/compiler/xla/service/pattern_matcher.h b/tensorflow/compiler/xla/service/pattern_matcher.h index 802b2c0c85..9e3d106021 100644 --- a/tensorflow/compiler/xla/service/pattern_matcher.h +++ b/tensorflow/compiler/xla/service/pattern_matcher.h @@ -2118,7 +2118,6 @@ XLA_BINOP_PATTERN(Divide) XLA_BINOP_PATTERN(Complex) XLA_BINOP_PATTERN(Convolution) XLA_BINOP_PATTERN(Dot) -XLA_BINOP_PATTERN(DynamicSlice) XLA_COMMUTATIVE_BINOP_PATTERN(Eq) XLA_BINOP_PATTERN(Gather) XLA_BINOP_PATTERN(Ge) @@ -2235,6 +2234,7 @@ inline auto WithOperands(Matcher&& m, int64 operand_num, FirstArg&& first_arg, XLA_VARIADIC_OP_PATTERN(AfterAll); XLA_VARIADIC_OP_PATTERN(Concatenate); XLA_VARIADIC_OP_PATTERN(CustomCall); +XLA_VARIADIC_OP_PATTERN(DynamicSlice) XLA_VARIADIC_OP_PATTERN(Map) XLA_VARIADIC_OP_PATTERN(Reduce); XLA_VARIADIC_OP_PATTERN(Sort); diff --git a/tensorflow/compiler/xla/service/shape_inference.cc b/tensorflow/compiler/xla/service/shape_inference.cc index d711c8ef0d..b729dd7660 100644 --- a/tensorflow/compiler/xla/service/shape_inference.cc +++ b/tensorflow/compiler/xla/service/shape_inference.cc @@ -2099,15 +2099,15 @@ ShapeInference::InferDegenerateDimensionBroadcastShape(HloOpcode operation, } const Shape& start_indices_shape = start_index_shapes[0]; - TF_RETURN_IF_ERROR( - ExpectArray(start_indices_shape, "start indices of dynamic slice")); - VLOG(2) << StrFormat( "slicing shape %s at dynamic start_indices %s with slice_sizes={%s}", ShapeUtil::HumanString(operand_shape), ShapeUtil::HumanString(start_indices_shape), StrJoin(slice_sizes, ", ")); + TF_RETURN_IF_ERROR( + ExpectArray(start_indices_shape, "start indices of dynamic slice")); + if (start_indices_shape.rank() != 1) { return InvalidArgument( "Dynamic slice start indices of rank %d must be rank1.", @@ -2151,7 +2151,7 @@ ShapeInference::InferDegenerateDimensionBroadcastShape(HloOpcode operation, "Dynamic slice start indices must be of integral type."); } for (const Shape& index_shape : start_index_shapes) { - if (!ShapeUtil::Equal(first_index_shape, index_shape)) { + if (!ShapeUtil::Compatible(first_index_shape, index_shape)) { return InvalidArgument( "Dynamic slice start indices must all have the same shape, got " "mismatching indices with shapes %s and %s.", @@ -2258,7 +2258,7 @@ ShapeInference::InferDegenerateDimensionBroadcastShape(HloOpcode operation, "Dynamic update slice start indices must be of integral type."); } for (const Shape& index_shape : start_index_shapes) { - if (!ShapeUtil::Equal(first_index_shape, index_shape)) { + if (!ShapeUtil::Compatible(first_index_shape, index_shape)) { return InvalidArgument( "Dynamic update slice start indices must all have the same " "shape, got mismatching indices with shapes %s and %s.", diff --git a/tensorflow/compiler/xla/service/shape_inference.h b/tensorflow/compiler/xla/service/shape_inference.h index e440e364c8..7d39ef38e0 100644 --- a/tensorflow/compiler/xla/service/shape_inference.h +++ b/tensorflow/compiler/xla/service/shape_inference.h @@ -177,14 +177,14 @@ class ShapeInference { // in 'slice_sizes', with dynamic start indices shape 'start_indices_shape'. static StatusOr InferDynamicSliceShape( const Shape& operand_shape, absl::Span start_index_shapes, - absl::Span slice_sizes, bool allow_scalar_indices = false); + absl::Span slice_sizes, bool allow_scalar_indices = true); // Infers the shape produced by a dynamic update slice operation based // on the shape of operand and update. static StatusOr InferDynamicUpdateSliceShape( const Shape& operand_shape, const Shape& update_shape, absl::Span start_index_shapes, - bool allow_scalar_indices = false); + bool allow_scalar_indices = true); // Infers the shape produced by doing a compile-time-constant indexing into // the given input shape. This is essential for operations on tuples, because diff --git a/tensorflow/compiler/xla/shape_util.cc b/tensorflow/compiler/xla/shape_util.cc index 4726ee6a5b..7f359311c3 100644 --- a/tensorflow/compiler/xla/shape_util.cc +++ b/tensorflow/compiler/xla/shape_util.cc @@ -112,6 +112,7 @@ bool CompareShapes(const Shape& lhs, const Shape& rhs, bool compare_layouts, if (compare_layouts) { if (lhs.layout().format() != rhs.layout().format()) { + VLOG(3) << "CompareShapes: lhs layout format != rhs layout format"; return false; } if (LayoutUtil::IsDenseArray(lhs)) { diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 2c11ba4283..9433264917 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -71,6 +71,7 @@ cc_library( "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/compiler/xla/service:hlo", + "//tensorflow/compiler/xla/service:hlo_casting_utils", "//tensorflow/compiler/xla/service:hlo_dataflow_analysis", "//tensorflow/compiler/xla/service:hlo_verifier", "//tensorflow/compiler/xla/service:transfer_manager", diff --git a/tensorflow/compiler/xla/tests/test_utils.cc b/tensorflow/compiler/xla/tests/test_utils.cc index 18df41c117..95c89b0ba6 100644 --- a/tensorflow/compiler/xla/tests/test_utils.cc +++ b/tensorflow/compiler/xla/tests/test_utils.cc @@ -19,6 +19,7 @@ limitations under the License. #include "absl/memory/memory.h" #include "tensorflow/compiler/xla/literal_util.h" #include "tensorflow/compiler/xla/primitive_util.h" +#include "tensorflow/compiler/xla/service/hlo_casting_utils.h" #include "tensorflow/compiler/xla/service/hlo_dataflow_analysis.h" #include "tensorflow/compiler/xla/service/hlo_verifier.h" #include "tensorflow/compiler/xla/service/transfer_manager.h" @@ -274,16 +275,9 @@ bool NeedsInitValue(const HloUse& use) { // Generate random values that are constrained to the input_shape minus the // output_shape so as not to produce wrapping slices, for instance. -Literal MakeRandomIndex(absl::Span index_space, - std::minstd_rand0* engine) { - std::vector start_indices(index_space.size()); - if (engine != nullptr) { - for (int i = 0; i < index_space.size(); ++i) { - std::uniform_int_distribution generator(0, index_space[i]); - start_indices[i] = generator(*engine); - } - } - return LiteralUtil::CreateR1(start_indices); +Literal MakeRandomIndex(int64 index_bound, std::minstd_rand0* engine) { + std::uniform_int_distribution generator(0, index_bound); + return LiteralUtil::CreateR0(generator(*engine)); } // Use dataflow analysis on each parameter to see if there are uses that would @@ -300,8 +294,8 @@ std::vector FindConstrainedUses( HloInstruction* instruction = use.instruction; const HloOpcode opcode = instruction->opcode(); const int64 op_num = use.operand_number; - if ((opcode == HloOpcode::kDynamicSlice && op_num == 1) || - (opcode == HloOpcode::kDynamicUpdateSlice && op_num == 2)) { + if ((opcode == HloOpcode::kDynamicSlice && op_num >= 1) || + (opcode == HloOpcode::kDynamicUpdateSlice && op_num >= 2)) { constrained_uses.push_back(instruction); } else if (opcode == HloOpcode::kFusion) { const HloInstruction* const to_analyze = @@ -336,7 +330,7 @@ std::vector FindConstrainedUses( StatusOr CreateLiteralForConstrainedUses( const absl::Span constrained_uses, const HloInstruction& param, std::minstd_rand0* engine) { - std::vector index_space; + int64 index_bound = INT64_MAX; bool no_duplicates = false; bool needs_constant = false; ConstantType constant_type = ConstantType::kUnknown; @@ -348,19 +342,16 @@ StatusOr CreateLiteralForConstrainedUses( const Shape& slice_shape = use->opcode() == HloOpcode::kDynamicSlice ? use->shape() : use->operand(1)->shape(); - const int64 rank = indexed_shape.rank(); - if (!index_space.empty()) { - TF_RET_CHECK(rank == index_space.size()); - for (int64 i = 0; i < rank; ++i) { - index_space[i] = std::min( - index_space[i], ShapeUtil::GetDimension(indexed_shape, i) - - ShapeUtil::GetDimension(slice_shape, i)); - } - } else { - index_space.resize(rank); - for (int64 i = 0; i < rank; ++i) { - index_space[i] = ShapeUtil::GetDimension(indexed_shape, i) - - ShapeUtil::GetDimension(slice_shape, i); + const int64 first_index = + Cast(use)->first_index_operand_number(); + for (int64 operand = first_index; operand < use->operand_count(); + ++operand) { + if (use->operand(operand) == ¶m) { + index_bound = std::min( + index_bound, + ShapeUtil::GetDimension(indexed_shape, operand - first_index) - + ShapeUtil::GetDimension(slice_shape, + operand - first_index)); } } break; @@ -388,16 +379,13 @@ StatusOr CreateLiteralForConstrainedUses( } int constraint_count = 0; constraint_count += no_duplicates ? 1 : 0; - constraint_count += !index_space.empty() ? 1 : 0; + constraint_count += (index_bound != INT64_MAX) ? 1 : 0; constraint_count += needs_constant ? 1 : 0; if (constraint_count > 1) { return Unimplemented("Conflicting operand generation constraints."); } - if (!index_space.empty()) { - // constrained_uses looks through bitcasts, so param and indexed_space may - // not have the same shape. (For example, param might be an R0 while - // indexed_space might have size 1.) - return MakeRandomIndex(index_space, engine) + if (index_bound != INT64_MAX) { + return MakeRandomIndex(index_bound, engine) .Reshape(param.shape().dimensions()); } else if (needs_constant) { switch (constant_type) { diff --git a/tensorflow/compiler/xla/tests/test_utils_test.cc b/tensorflow/compiler/xla/tests/test_utils_test.cc index 4b4f164d1b..591d6c1922 100644 --- a/tensorflow/compiler/xla/tests/test_utils_test.cc +++ b/tensorflow/compiler/xla/tests/test_utils_test.cc @@ -79,25 +79,26 @@ XLA_TEST_F(TestUtilsTest, MultipleIndexSpacesForDynamicSlices) { R"(HloModule index_space_module ENTRY IndexSpace { - index_param = s32[3]{0} parameter(0) - array_param.1 = f32[123,4,789]{0,1,2} parameter(1) - array_param.2 = f32[3,3000,5]{0,1,2} parameter(2) - dynamic-slice.1 = f32[1,2,3] dynamic-slice(array_param.1, index_param), dynamic_slice_sizes={1,2,3} - ROOT dynamic-slice.2 = f32[3,2,2] dynamic-slice(array_param.2, index_param), dynamic_slice_sizes={3,2,2} + index_param.0 = s32[] parameter(0) + index_param.1 = s32[] parameter(1) + index_param.2 = s32[] parameter(2) + array_param.1 = f32[123,4,789]{0,1,2} parameter(3) + array_param.2 = f32[3,3000,5]{0,1,2} parameter(4) + dynamic-slice.1 = f32[1,2,3] dynamic-slice(array_param.1, index_param.0, index_param.1, index_param.2), dynamic_slice_sizes={1,2,3} + ROOT dynamic-slice.2 = f32[3,2,2] dynamic-slice(array_param.2, index_param.0, index_param.1, index_param.2), dynamic_slice_sizes={3,2,2} })") .ValueOrDie(); TF_ASSERT_OK_AND_ASSIGN(std::vector args, MakeFakeArguments(module.get())); - ASSERT_EQ(args.size(), 3); - const Literal& index_arg = args[0]; + ASSERT_EQ(args.size(), 5); - EXPECT_EQ(index_arg.Get({0}), 0); + EXPECT_EQ(args[0].Get({}), 0); - EXPECT_GE(index_arg.Get({1}), 0); - EXPECT_LE(index_arg.Get({1}), 2); + EXPECT_GE(args[1].Get({}), 0); + EXPECT_LE(args[0].Get({}), 2); - EXPECT_GE(index_arg.Get({2}), 0); - EXPECT_LE(index_arg.Get({2}), 3); + EXPECT_GE(args[2].Get({}), 0); + EXPECT_LE(args[2].Get({}), 3); } XLA_TEST_F(TestUtilsTest, MultipleIndexSpacesForDynamicUpdateSlices) { @@ -105,28 +106,29 @@ XLA_TEST_F(TestUtilsTest, MultipleIndexSpacesForDynamicUpdateSlices) { R"(HloModule index_space_module ENTRY IndexSpace { - index_param = s32[3]{0} parameter(0) - array_param.1 = f32[123,4,789]{0,1,2} parameter(1) - array_param.2 = f32[3,3000,5]{0,1,2} parameter(2) - update_param.1 = f32[1,2,3]{0,1,2} parameter(3) - update_param.2 = f32[3,2,2]{0,1,2} parameter(4) - - dynamic-update-slice.1 = f32[123,4,789] dynamic-update-slice(array_param.1, update_param.1, index_param) - ROOT dynamic-update-slice.2 = f32[3,3000,5] dynamic-update-slice(array_param.2, update_param.2, index_param) + index_param.0 = s32[] parameter(0) + index_param.1 = s32[] parameter(1) + index_param.2 = s32[] parameter(2) + array_param.1 = f32[123,4,789]{0,1,2} parameter(3) + array_param.2 = f32[3,3000,5]{0,1,2} parameter(4) + update_param.1 = f32[1,2,3]{0,1,2} parameter(5) + update_param.2 = f32[3,2,2]{0,1,2} parameter(6) + + dynamic-update-slice.1 = f32[123,4,789] dynamic-update-slice(array_param.1, update_param.1, index_param.0, index_param.1, index_param.2) + ROOT dynamic-update-slice.2 = f32[3,3000,5] dynamic-update-slice(array_param.2, update_param.2, index_param.0, index_param.1, index_param.2) })") .ValueOrDie(); TF_ASSERT_OK_AND_ASSIGN(std::vector args, MakeFakeArguments(module.get())); - ASSERT_EQ(args.size(), 5); - const Literal& index_arg = args[0]; + ASSERT_EQ(args.size(), 7); - EXPECT_EQ(index_arg.Get({0}), 0); + EXPECT_EQ(args[0].Get({}), 0); - EXPECT_GE(index_arg.Get({1}), 0); - EXPECT_LE(index_arg.Get({1}), 2); + EXPECT_GE(args[1].Get({}), 0); + EXPECT_LE(args[0].Get({}), 2); - EXPECT_GE(index_arg.Get({2}), 0); - EXPECT_LE(index_arg.Get({2}), 3); + EXPECT_GE(args[2].Get({}), 0); + EXPECT_LE(args[2].Get({}), 3); } XLA_TEST_F(TestUtilsTest, NoDuplicatesFloats) { -- GitLab From efe565bc0981e80a52a97f3961cfba3e87023b42 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Fri, 4 Jan 2019 14:08:26 -0800 Subject: [PATCH 0209/2345] Make the initial tf.train.Checkpoint.restore() read Tensors in a batch Should be faster when reading from distributed file systems. Does not affect cases where restore-on-create is necessary, but as long as variable objects have been created and tracked before restore() their reads should be batched together. PiperOrigin-RevId: 227911381 --- .../python/training/checkpointable/util.py | 22 +++------ .../training/saving/functional_saver.py | 45 +++++++++++-------- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/tensorflow/python/training/checkpointable/util.py b/tensorflow/python/training/checkpointable/util.py index c890a7f440..a45263f5c6 100644 --- a/tensorflow/python/training/checkpointable/util.py +++ b/tensorflow/python/training/checkpointable/util.py @@ -176,25 +176,13 @@ class _CheckpointRestoreCoordinator(object): raise AssertionError( ("Saveable keys changed when validating. Got back %s, was " "expecting %s") % (tensor_saveables.keys(), validated_names)) - for saveable in validated_saveables: - if saveable.device: - device = saveable_object_util.set_cpu0(saveable.device) - else: - device = None - with ops.device(device): - tensors = [] - for spec in saveable.specs: - tensors.append( - io_ops.restore_v2( - self.save_path_tensor, - [spec.name], - [spec.slice_spec], - [spec.dtype])[0]) - restore_op = saveable.restore(tensors, restored_shapes=None) - if not context.executing_eagerly(): + new_restore_ops = functional_saver.restore_from_saveable_objects( + self.save_path_tensor, validated_saveables) + if not context.executing_eagerly(): + restore_ops.extend(new_restore_ops) + for saveable, restore_op in zip(validated_saveables, new_restore_ops): assert saveable.name not in self.restore_ops_by_name self.restore_ops_by_name[saveable.name] = restore_op - restore_ops.append(restore_op) return restore_ops diff --git a/tensorflow/python/training/saving/functional_saver.py b/tensorflow/python/training/saving/functional_saver.py index 51f618ddd3..4ff2742c2f 100644 --- a/tensorflow/python/training/saving/functional_saver.py +++ b/tensorflow/python/training/saving/functional_saver.py @@ -107,25 +107,32 @@ class Saver(object): A scalar string Tensor containing `file_prefix` with control dependencies on the restore ops. """ - restore_specs = [] - tensor_structure = [] - for saveable in self._saveable_objects: - saveable_tensor_structure = [] - tensor_structure.append(saveable_tensor_structure) - for spec in saveable.specs: - saveable_tensor_structure.append(spec.name) - restore_specs.append((spec.name, spec.slice_spec, spec.dtype)) - tensor_names, tensor_slices, tensor_dtypes = zip(*restore_specs) - with ops.device("cpu:0"): - restored_tensors = io_ops.restore_v2( - file_prefix, tensor_names, tensor_slices, tensor_dtypes) - structured_restored_tensors = nest.pack_sequence_as( - tensor_structure, restored_tensors) - restore_ops = [] - for saveable, restored_tensors in zip(self._saveable_objects, - structured_restored_tensors): - restore_ops.append(saveable.restore(restored_tensors, - restored_shapes=None)) + restore_ops = restore_from_saveable_objects( + file_prefix, self._saveable_objects) with ops.device("cpu:0"): with ops.control_dependencies(restore_ops): return array_ops.identity(file_prefix) + + +def restore_from_saveable_objects(file_prefix, saveable_objects): + """Reads from a checkpoint and returns restore ops for `saveable_objects`s.""" + restore_specs = [] + tensor_structure = [] + for saveable in saveable_objects: + saveable_tensor_structure = [] + tensor_structure.append(saveable_tensor_structure) + for spec in saveable.specs: + saveable_tensor_structure.append(spec.name) + restore_specs.append((spec.name, spec.slice_spec, spec.dtype)) + tensor_names, tensor_slices, tensor_dtypes = zip(*restore_specs) + with ops.device("cpu:0"): + restored_tensors = io_ops.restore_v2( + file_prefix, tensor_names, tensor_slices, tensor_dtypes) + structured_restored_tensors = nest.pack_sequence_as( + tensor_structure, restored_tensors) + restore_ops = [] + for saveable, restored_tensors in zip(saveable_objects, + structured_restored_tensors): + restore_ops.append(saveable.restore(restored_tensors, + restored_shapes=None)) + return restore_ops -- GitLab From 41e333f0193f89bc889a31f2a32b4119261aa222 Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Fri, 4 Jan 2019 14:10:40 -0800 Subject: [PATCH 0210/2345] [tf.data] Adding prefetching of input iterators for parallel interleave. PiperOrigin-RevId: 227911701 --- .../data/parallel_interleave_dataset_op.cc | 752 +++++++++++------- .../data/kernel_tests/interleave_test.py | 123 +-- 2 files changed, 511 insertions(+), 364 deletions(-) diff --git a/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc b/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc index 315f96d4b5..dc2663d1e0 100644 --- a/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc +++ b/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include #include +#include #include #include "tensorflow/core/common_runtime/function.h" @@ -21,6 +22,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/framework/types.h" #include "tensorflow/core/kernels/data/captured_function.h" #include "tensorflow/core/kernels/data/dataset_utils.h" #include "tensorflow/core/lib/core/threadpool.h" @@ -43,12 +45,7 @@ namespace { // // Furthermore, this class favors modularity over extended functionality. In // particular, it refrains from implementing configurable buffering of output -// elements and prefetching of input iterators, relying on other parts of -// tf.data to provide this functionality if necessary. -// -// The above design choices were made with automated optimizations in mind, -// isolating the degree of parallelism as the single tunable knob of this -// implementation. +// elements and prefetching of input iterators. class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { public: explicit ParallelInterleaveDatasetOp(OpKernelConstruction* ctx) @@ -237,27 +234,20 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { Status GetNextInternal(IteratorContext* ctx, std::vector* out_tensors, bool* end_of_sequence) override { - std::shared_ptr result; - do { - result.reset(); - { - mutex_lock l(*mu_); - EnsureRunnerThreadStarted(ctx); - while (ShouldWait(&result)) { - RecordStop(ctx); - cond_var_->wait(l); - RecordStart(ctx); - } - if (!result) { - *end_of_sequence = true; - return Status::OK(); - } + std::shared_ptr result; + { + mutex_lock l(*mu_); + EnsureThreadsStarted(ctx); + while (!Consume(&result)) { + RecordStop(ctx); + cond_var_->wait(l); + RecordStart(ctx); } - RecordStop(ctx); - result->notification.WaitForNotification(); - RecordStart(ctx); - } while (result->skip); - + } + if (!result) { + *end_of_sequence = true; + return Status::OK(); + } if (result->status.ok()) { *out_tensors = std::move(result->return_values); RecordBufferDequeue(ctx, *out_tensors); @@ -281,37 +271,22 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { while (num_calls_ > 0) { cond_var_->wait(l); } - CHECK_EQ(num_calls_, 0); + DCHECK_EQ(num_calls_, 0); TF_RETURN_IF_ERROR(SaveInput(writer, input_impl_)); - TF_RETURN_IF_ERROR(writer->WriteScalar( - full_name("invocation_results.size"), invocation_results_.size())); - for (size_t i = 0; i < invocation_results_.size(); i++) { - std::shared_ptr result = invocation_results_[i]; - TF_RETURN_IF_ERROR(WriteStatusLocked(writer, i, result->status)); - TF_RETURN_IF_ERROR(writer->WriteScalar( - full_name(strings::StrCat("invocation_results[", i, "].size")), - result->return_values.size())); - for (size_t j = 0; j < result->return_values.size(); j++) { - TF_RETURN_IF_ERROR(writer->WriteTensor( - full_name( - strings::StrCat("invocation_results[", i, "][", j, "]")), - result->return_values[j])); - } - if (result->skip) { - TF_RETURN_IF_ERROR(writer->WriteScalar( - full_name(strings::StrCat("invocation_results[", i, "].skip")), - "")); - } - } + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name("block_index"), block_index_)); TF_RETURN_IF_ERROR( writer->WriteScalar(full_name("cycle_index"), cycle_index_)); if (end_of_input_) { TF_RETURN_IF_ERROR( writer->WriteScalar(full_name("end_of_input"), "")); } + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name("element_id_counter"), + element_id_counter_)); TF_RETURN_IF_ERROR( writer->WriteScalar(full_name("num_open"), num_open_)); TF_RETURN_IF_ERROR(WriteCurrentElements(writer)); + TF_RETURN_IF_ERROR(WriteFutureElements(writer)); return Status::OK(); } @@ -319,114 +294,268 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { IteratorStateReader* reader) override { mutex_lock l(*mu_); TF_RETURN_IF_ERROR(RestoreInput(ctx, reader, input_impl_)); - int64 invocation_results_size; - TF_RETURN_IF_ERROR(reader->ReadScalar( - full_name("invocation_results.size"), &invocation_results_size)); - for (size_t i = 0; i < invocation_results_size; i++) { - std::shared_ptr result(new InvocationResult()); - invocation_results_.push_back(result); - TF_RETURN_IF_ERROR(ReadStatusLocked(reader, i, &result->status)); - size_t num_return_values; - { - int64 size; - TF_RETURN_IF_ERROR(reader->ReadScalar( - full_name(strings::StrCat("invocation_results[", i, "].size")), - &size)); - num_return_values = static_cast(size); - if (num_return_values != size) { - return errors::InvalidArgument(strings::StrCat( - full_name( - strings::StrCat("invocation_results[", i, "].size")), - ": ", size, " is not a valid value of type size_t.")); - } - } - result->return_values.reserve(num_return_values); - for (size_t j = 0; j < num_return_values; j++) { - result->return_values.emplace_back(); - TF_RETURN_IF_ERROR( - reader->ReadTensor(full_name(strings::StrCat( - "invocation_results[", i, "][", j, "]")), - &result->return_values.back())); - } - result->skip = reader->Contains( - full_name(strings::StrCat("invocation_results[", i, "].skip"))); - result->notification.Notify(); - } + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name("block_index"), &block_index_)); TF_RETURN_IF_ERROR( reader->ReadScalar(full_name("cycle_index"), &cycle_index_)); + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("element_id_counter"), + &element_id_counter_)); if (reader->Contains(full_name("end_of_input"))) end_of_input_ = true; TF_RETURN_IF_ERROR( reader->ReadScalar(full_name("num_open"), &num_open_)); TF_RETURN_IF_ERROR(ReadCurrentElements(ctx, reader)); + TF_RETURN_IF_ERROR(ReadFutureElements(ctx, reader)); return Status::OK(); } private: + // Represents the result of fetching an element from a dataset. + struct Result { + Status status; + std::vector return_values; + // Indicates whether the result is ready to be consumed. + bool is_ready = false; + }; + + // The interleave transformation repeatedly inputs elements, applies the + // user-provided function to transform the input elements to datasets, and + // interleaves the elements of these datasets as its output. + // + // This structure represents an input element and derived state. struct Element { + // Unique identifier, needed to support checkpointing. + int64 id; + // The actual input element. + std::vector inputs; + // Iterator created from the input element. std::unique_ptr iterator; - std::vector inputs; // inputs for creating the iterator - bool in_use; + mutex mu; + // Buffer for storing the outputs of `iterator`. + std::deque> results GUARDED_BY(mu); + // Indicates whether the element is used by a worker thread. + bool in_use = false; }; - struct InvocationResult { - Notification notification; // used for coordination with the consumer - Status status; // the invocation status - std::vector return_values; // the invocation result values - bool skip; // if set the result should be skipped - }; + // Advances the position in the interleave cycle to the next cycle + // element. + void AdvanceToNextInCycle() EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + block_index_ = 0; + cycle_index_ = (cycle_index_ + 1) % dataset()->cycle_length_; + } - void EnsureRunnerThreadStarted(IteratorContext* ctx) + // Advances the position in the interleave cycle by one. + void AdvancePosition() EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + ++block_index_; + if (block_index_ == dataset()->block_length_) { + AdvanceToNextInCycle(); + } + } + + // Consumes a result (if available), returning an indication of whether + // a result is available. If `true` is returned, `result` either + // points to a valid result or is null if end of input has been reached. + bool Consume(std::shared_ptr* result) EXCLUSIVE_LOCKS_REQUIRED(*mu_) { - if (!runner_thread_) { - std::shared_ptr new_ctx(new IteratorContext(*ctx)); - runner_thread_.reset(ctx->env()->StartThread( - {}, "tf_data_parallel_interleave_runner", - [this, new_ctx]() { RunnerThread(new_ctx); })); + if (!sloppy_) { + return ConsumeHelper(result); + } + // If we are allowed to be sloppy (i.e. return results out of order), + // try to find an element in the cycle that has a result available. + for (int i = 0; i < dataset()->cycle_length_; ++i) { + if (ConsumeHelper(result)) { + return true; + } + AdvanceToNextInCycle(); + } + return false; + } + + bool ConsumeHelper(std::shared_ptr* result) + EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + while (true) { + std::shared_ptr element = current_elements_[cycle_index_]; + if (element) { + mutex_lock l(element->mu); + if (!element->results.empty()) { + if (element->results.front()->is_ready) { + // We found a result. + std::swap(*result, element->results.front()); + element->results.pop_front(); + AdvancePosition(); + cond_var_->notify_all(); + return true; + } else { + // Wait for the result to become ready. + return false; + } + } else if (!element->iterator) { + // We reached the end of input for this element. Reset + // it and move on to the next cycle element. + current_elements_[cycle_index_].reset(); + AdvanceToNextInCycle(); + cond_var_->notify_all(); + continue; + } else { + // Wait for the iterator to produce a result. + return false; + } + } else { + if (!future_elements_.empty() || !end_of_input_) { + // Wait for an element to be created. + return false; + } + // No new elements will be created; try to find a + // non-empty element in the cycle. + for (int i = 0; i < dataset()->cycle_length_; ++i) { + AdvanceToNextInCycle(); + if (current_elements_[cycle_index_]) { + break; + } + } + if (current_elements_[cycle_index_]) { + continue; + } + // End of input has been reached. + return true; + } } } - // Fetches up to `results.size()` outputs from the cycle element at - // position `cycle_index`. + // Manages current cycle elements, creating new iterators as needed and + // asynchronously fetching results from existing iterators. // - // If end of input is encountered, the `skip` field of the invocation - // result is used to identify results that should be skipped. - void FetchOutputs( - const std::shared_ptr& ctx, IteratorBase* iterator, - int64 cycle_index, - const std::vector>& results) - LOCKS_EXCLUDED(*mu_) { + // This method runs in the `current_elements_manager_` background thread. + void CurrentElementsManager(const std::shared_ptr& ctx) { RecordStart(ctx.get()); auto cleanup = gtl::MakeCleanup([this, ctx] { RecordStop(ctx.get()); }); - bool end_of_input = false; - for (auto& result : results) { - if (!end_of_input) { - result->status = iterator->GetNext( - ctx.get(), &result->return_values, &end_of_input); + auto busy = [this]() EXCLUSIVE_LOCKS_REQUIRED(*mu_) -> bool { + const bool has_more_elements = + !future_elements_.empty() || !end_of_input_; + const int block_length = dataset()->block_length_; + bool all_elements_busy = true; + for (auto& element : current_elements_) { + if (!element) { + if (has_more_elements) { + all_elements_busy = false; + break; + } + } else { + mutex_lock l(element->mu); + if (!element->in_use && element->iterator && + element->results.size() < block_length) { + all_elements_busy = false; + break; + } + } } - if (end_of_input) { - result->skip = true; + return all_elements_busy || num_calls_ >= num_parallel_calls_->value; + }; + while (true) { + mutex_lock l(*mu_); + + // Wait until this thread is cancelled, the end of input has been + // reached. + while (!cancelled_ && (!end_of_input_ || num_open_ > 0) && busy()) { + RecordStop(ctx.get()); + cond_var_->wait(l); + RecordStart(ctx.get()); } - RecordBufferEnqueue(ctx.get(), result->return_values); - { - mutex_lock l(*mu_); - result->notification.Notify(); - cond_var_->notify_all(); + + if (cancelled_ || + (future_elements_.empty() && end_of_input_ && num_open_ == 0)) { + return; + } + + for (int i = 0; i < dataset()->cycle_length_; ++i) { + int idx = (cycle_index_ + i) % dataset()->cycle_length_; + if (!current_elements_[idx]) { + if (!future_elements_.empty()) { + current_elements_[idx] = std::move(future_elements_.back()); + future_elements_.pop_back(); + } else { + current_elements_[idx] = MakeElement(ctx); + if (!current_elements_[idx]) { + continue; + } + } + } + std::shared_ptr element = current_elements_[idx]; + if (!element->in_use && element->iterator) { + int64 num_results; + { + mutex_lock l(element->mu); + num_results = + dataset()->block_length_ - element->results.size(); + } + if (num_results > 0) { + num_calls_++; + element->in_use = true; + thread_pool_->Schedule( + std::bind(&ParallelInterleaveIterator::FetchResults, this, + ctx, std::move(element), num_results)); + } + } + } + const auto& stats_aggregator = ctx->stats_aggregator(); + if (stats_aggregator) { + stats_aggregator->AddScalar( + strings::StrCat(key_prefix_, "::thread_utilization"), + static_cast(num_calls_) / + static_cast(num_parallel_calls_->value)); } - if (!result->status.ok()) { + cond_var_->notify_all(); + } + } + + void EnsureThreadsStarted(IteratorContext* ctx) + EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + if (!current_elements_manager_) { + auto new_ctx = std::make_shared(*ctx); + current_elements_manager_ = + WrapUnique(ctx->env()->StartThread( + {}, "tf_data_parallel_interleave_current", + [this, new_ctx]() { CurrentElementsManager(new_ctx); })); + } + if (!future_elements_manager_) { + auto new_ctx = std::make_shared(*ctx); + future_elements_manager_ = WrapUnique(ctx->env()->StartThread( + {}, "tf_data_parallel_interleave_future", + [this, new_ctx]() { FutureElementsManager(new_ctx); })); + } + } + + // Fetches up to `dataset()->block_length_` results from `element`. + void FetchResults(const std::shared_ptr& ctx, + const std::shared_ptr& element, + int64 num_results) LOCKS_EXCLUDED(*mu_) { + RecordStart(ctx.get()); + auto cleanup = gtl::MakeCleanup([this, ctx] { RecordStop(ctx.get()); }); + bool end_of_input = false; + for (int64 i = 0; i < num_results; ++i) { + auto result = std::make_shared(); + result->status = element->iterator->GetNext( + ctx.get(), &result->return_values, &end_of_input); + if (end_of_input) { break; } + RecordBufferEnqueue(ctx.get(), result->return_values); + mutex_lock l(*mu_); + mutex_lock l2(element->mu); + element->results.push_back(result); + result->is_ready = true; + cond_var_->notify_all(); } mutex_lock l(*mu_); - current_elements_[cycle_index].in_use = false; + // Release the ownership of the cycle element iterator. + element->in_use = false; if (end_of_input) { - // Release the ownership of the cycle element iterator, closing the - // iterator if end of input was encountered. - current_elements_[cycle_index].iterator.reset(); - current_elements_[cycle_index].inputs.clear(); - num_open_--; + // Close the iterator if end of input was encountered. + element->iterator.reset(); + element->inputs.clear(); + --num_open_; } - num_calls_--; + --num_calls_; const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( @@ -437,82 +566,47 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { cond_var_->notify_all(); } - // Method responsible for 1) creating iterators out of input elements, 2) - // determining the order in which elements are fetched from the iterators, - // and 3) scheduling the fetching of the elements to a threadpool. + // Manages futures cycle elements, creating new iterators as needed and + // asynchronously fetching results from existing iterators. // - // This method runs in the `runner_thread` background thread. - void RunnerThread(const std::shared_ptr& ctx) { + // This method runs in the `future_elements_manager_` background thread. + void FutureElementsManager(const std::shared_ptr& ctx) { RecordStart(ctx.get()); auto cleanup = gtl::MakeCleanup([this, ctx] { RecordStop(ctx.get()); }); auto busy = [this]() EXCLUSIVE_LOCKS_REQUIRED(*mu_) -> bool { - return current_elements_[cycle_index_].in_use || - num_calls_ >= num_parallel_calls_->value || - invocation_results_.size() >= - dataset()->cycle_length_ * dataset()->block_length_; + return num_calls_ >= num_parallel_calls_->value || + future_elements_.size() >= dataset()->cycle_length_; }; while (true) { mutex_lock l(*mu_); + // Wait until this thread is cancelled, the end of input has been // reached, or the cycle element at the `cycle_index_` position is - // not in use and there is space in the `invocation_results_` queue. - while (!cancelled_ && (!end_of_input_ || num_open_ > 0) && busy()) { + // not in use. + while (!cancelled_ && !end_of_input_ && busy()) { RecordStop(ctx.get()); cond_var_->wait(l); RecordStart(ctx.get()); } - if (cancelled_ || (end_of_input_ && num_open_ == 0)) { + if (cancelled_ || end_of_input_) { return; } - while ((!end_of_input_ || num_open_ > 0) && !busy()) { - if (!current_elements_[cycle_index_].iterator) { - // Try to create a new iterator from the next input element. - Status status = input_impl_->GetNext( - ctx.get(), ¤t_elements_[cycle_index_].inputs, - &end_of_input_); - if (!status.ok()) { - invocation_results_.emplace_back(new InvocationResult()); - std::shared_ptr& result = - invocation_results_.back(); - result->status.Update(status); - result->notification.Notify(); - break; - } - if (!end_of_input_) { - Status status = MakeIteratorFromInputElement( - ctx.get(), current_elements_[cycle_index_].inputs, - cycle_index_, *instantiated_captured_func_, prefix(), - ¤t_elements_[cycle_index_].iterator); - if (!status.ok()) { - invocation_results_.emplace_back(new InvocationResult()); - std::shared_ptr& result = - invocation_results_.back(); - result->status.Update(status); - result->notification.Notify(); - break; - } - ++num_open_; - } + while (!end_of_input_ && !busy()) { + std::shared_ptr element = MakeElement(ctx); + if (!element) { + break; } - if (current_elements_[cycle_index_].iterator) { - // Pre-allocate invocation results for outputs to be fetched - // and then fetch the outputs asynchronously. - std::vector> results; - results.reserve(dataset()->block_length_); - for (int i = 0; i < dataset()->block_length_; ++i) { - invocation_results_.emplace_back(new InvocationResult()); - results.push_back(invocation_results_.back()); - } - num_calls_++; - current_elements_[cycle_index_].in_use = true; - thread_pool_->Schedule( - std::bind(&ParallelInterleaveIterator::FetchOutputs, this, - ctx, current_elements_[cycle_index_].iterator.get(), - cycle_index_, std::move(results))); + future_elements_.push_front(element); + if (!element->iterator) { + continue; } - cycle_index_ = (cycle_index_ + 1) % dataset()->cycle_length_; + ++num_calls_; + element->in_use = true; + thread_pool_->Schedule( + std::bind(&ParallelInterleaveIterator::FetchResults, this, ctx, + std::move(element), dataset()->block_length_)); } const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { @@ -525,56 +619,66 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { } } - // Determines whether the caller needs to wait for a result. Upon - // returning false, `result` will either be NULL if end of input has been - // reached or point to the result. - bool ShouldWait(std::shared_ptr* result) + // Creates a new element. + std::shared_ptr MakeElement( + const std::shared_ptr& ctx) EXCLUSIVE_LOCKS_REQUIRED(*mu_) { - if (sloppy_) { - for (auto it = invocation_results_.begin(); - it != invocation_results_.end(); ++it) { - if ((*it)->notification.HasBeenNotified()) { - std::swap(*result, *it); - invocation_results_.erase(it); - cond_var_->notify_all(); - return false; - } + auto element = std::make_shared(); + element->id = element_id_counter_++; + Status status = + input_impl_->GetNext(ctx.get(), &element->inputs, &end_of_input_); + if (!status.ok()) { + auto result = std::make_shared(); + result->is_ready = true; + result->status = status; + mutex_lock l(element->mu); + element->results.push_back(std::move(result)); + return element; + } + if (!end_of_input_) { + Status status = MakeIteratorFromInputElement( + ctx.get(), element->inputs, element->id, + *instantiated_captured_func_, prefix(), &element->iterator); + if (!status.ok()) { + auto result = std::make_shared(); + result->is_ready = true; + result->status = status; + mutex_lock l(element->mu); + element->results.push_back(std::move(result)); + return element; } - return !invocation_results_.empty() || - (!end_of_input_ || num_open_ > 0); + ++num_open_; } else { - if (!invocation_results_.empty()) { - std::swap(*result, invocation_results_.front()); - invocation_results_.pop_front(); - cond_var_->notify_all(); - return false; - } - return (!end_of_input_ || num_open_ > 0); + element.reset(); } + return element; } - Status WriteStatusLocked(IteratorStateWriter* writer, size_t index, + Status WriteStatusLocked(IteratorStateWriter* writer, + const string& key_prefix, size_t idx, const Status& status) EXCLUSIVE_LOCKS_REQUIRED(*mu_) { TF_RETURN_IF_ERROR(writer->WriteScalar( - CodeKey(index), static_cast(status.code()))); + CodeKey(key_prefix, idx), static_cast(status.code()))); if (!status.ok()) { - TF_RETURN_IF_ERROR(writer->WriteScalar(ErrorMessageKey(index), - status.error_message())); + TF_RETURN_IF_ERROR(writer->WriteScalar( + ErrorMessageKey(key_prefix, idx), status.error_message())); } return Status::OK(); } - Status ReadStatusLocked(IteratorStateReader* reader, size_t index, + Status ReadStatusLocked(IteratorStateReader* reader, + const string& key_prefix, size_t idx, Status* status) EXCLUSIVE_LOCKS_REQUIRED(*mu_) { int64 code_int; - TF_RETURN_IF_ERROR(reader->ReadScalar(CodeKey(index), &code_int)); + TF_RETURN_IF_ERROR( + reader->ReadScalar(CodeKey(key_prefix, idx), &code_int)); error::Code code = static_cast(code_int); if (code != error::Code::OK) { string error_message; - TF_RETURN_IF_ERROR( - reader->ReadScalar(ErrorMessageKey(index), &error_message)); + TF_RETURN_IF_ERROR(reader->ReadScalar( + ErrorMessageKey(key_prefix, idx), &error_message)); *status = Status(code, error_message); } else { *status = Status::OK(); @@ -582,64 +686,178 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { return Status::OK(); } - string CodeKey(size_t index) { + string CodeKey(const string& key_prefix, size_t idx) { return full_name( - strings::StrCat("invocation_results[", index, "].code")); + strings::StrCat(key_prefix, ".results[", idx, "].code")); } - string ErrorMessageKey(size_t index) { + string ErrorMessageKey(const string& key_prefix, size_t idx) { return full_name( - strings::StrCat("invocation_results[", index, "].error_message")); + strings::StrCat(key_prefix, ".results[", idx, "].error_message")); + } + + Status WriteElement(std::shared_ptr element, int idx, + const string& key_prefix, IteratorStateWriter* writer) + EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + if (element->iterator) { + TF_RETURN_IF_ERROR(SaveInput(writer, element->iterator)); + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(key_prefix, "[", idx, "].id")), + element->id)); + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(key_prefix, "[", idx, "].inputs.size")), + element->inputs.size())); + for (int i = 0; i < element->inputs.size(); i++) { + TF_RETURN_IF_ERROR(writer->WriteTensor( + full_name( + strings::StrCat(key_prefix, "[", idx, "].inputs[", i, "]")), + element->inputs[i])); + } + } + mutex_lock l(element->mu); + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(key_prefix, "[", idx, "].results.size")), + element->results.size())); + for (size_t i = 0; i < element->results.size(); i++) { + std::shared_ptr result = element->results[i]; + TF_RETURN_IF_ERROR(WriteStatusLocked( + writer, strings::StrCat(key_prefix, "[", idx, "]"), i, + result->status)); + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(key_prefix, "[", idx, "].results[", i, + "].size")), + result->return_values.size())); + for (size_t j = 0; j < result->return_values.size(); j++) { + TF_RETURN_IF_ERROR(writer->WriteTensor( + full_name(strings::StrCat(key_prefix, "[", idx, "].results[", i, + "][", j, "]")), + result->return_values[j])); + } + if (result->is_ready) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(key_prefix, "[", idx, "].results[", i, + "].is_ready")), + "")); + } + } + return Status::OK(); } Status WriteCurrentElements(IteratorStateWriter* writer) EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name("current_elements.size"), current_elements_.size())); for (int idx = 0; idx < current_elements_.size(); idx++) { - if (current_elements_[idx].iterator) { - TF_RETURN_IF_ERROR( - SaveInput(writer, current_elements_[idx].iterator)); - TF_RETURN_IF_ERROR(writer->WriteScalar( - full_name( - strings::StrCat("current_elements[", idx, "].inputs.size")), - current_elements_[idx].inputs.size())); - for (int i = 0; i < current_elements_[idx].inputs.size(); i++) { - TF_RETURN_IF_ERROR(writer->WriteTensor( - full_name(strings::StrCat("current_elements[", idx, - "].inputs[", i, "]")), - current_elements_[idx].inputs[i])); - } + if (current_elements_[idx]) { + TF_RETURN_IF_ERROR(WriteElement(current_elements_[idx], idx, + "current_elements", writer)); } } return Status::OK(); } + Status WriteFutureElements(IteratorStateWriter* writer) + EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name("future_elements.size"), future_elements_.size())); + for (int idx = 0; idx < future_elements_.size(); idx++) { + if (future_elements_[idx]) { + TF_RETURN_IF_ERROR(WriteElement(future_elements_[idx], idx, + "future_elements", writer)); + } + } + return Status::OK(); + } + + Status ReadElement(IteratorContext* ctx, IteratorStateReader* reader, + int idx, const string& key_prefix, + std::shared_ptr* out) + EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + if (!reader->Contains(full_name( + strings::StrCat(key_prefix, "[", idx, "].results.size")))) { + return Status::OK(); + } + auto element = std::make_shared(); + mutex_lock l(element->mu); + int64 results_size; + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(key_prefix, "[", idx, "].results.size")), + &results_size)); + element->results.resize(results_size); + for (size_t i = 0; i < results_size; i++) { + auto result = std::make_shared(); + TF_RETURN_IF_ERROR(ReadStatusLocked( + reader, strings::StrCat(key_prefix, "[", idx, "]"), i, + &result->status)); + int64 num_return_values; + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(key_prefix, "[", idx, "].results[", i, + "].size")), + &num_return_values)); + result->return_values.reserve(num_return_values); + for (size_t j = 0; j < num_return_values; j++) { + result->return_values.emplace_back(); + TF_RETURN_IF_ERROR(reader->ReadTensor( + full_name(strings::StrCat(key_prefix, "[", idx, "].results[", i, + "][", j, "]")), + &result->return_values.back())); + } + result->is_ready = reader->Contains(full_name(strings::StrCat( + key_prefix, "[", idx, "].results[", i, "].is_ready"))); + element->results[i] = std::move(result); + } + if (!reader->Contains(full_name( + strings::StrCat(key_prefix, "[", idx, "].inputs.size")))) { + element->iterator.reset(); + *out = std::move(element); + return Status::OK(); + } + int64 inputs_size; + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(key_prefix, "[", idx, "].inputs.size")), + &inputs_size)); + element->inputs.resize(inputs_size); + for (int i = 0; i < inputs_size; i++) { + TF_RETURN_IF_ERROR(reader->ReadTensor( + full_name( + strings::StrCat(key_prefix, "[", idx, "].inputs[", i, "]")), + &element->inputs[i])); + } + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(key_prefix, "[", idx, "].id")), + &element->id)); + TF_RETURN_IF_ERROR(MakeIteratorFromInputElement( + ctx, element->inputs, element->id, + *instantiated_captured_func_.get(), prefix(), &element->iterator)); + TF_RETURN_IF_ERROR(RestoreInput(ctx, reader, element->iterator)); + *out = std::move(element); + return Status::OK(); + } + Status ReadCurrentElements(IteratorContext* ctx, IteratorStateReader* reader) EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + int64 size; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name("current_elements.size"), &size)); + DCHECK_EQ(current_elements_.size(), size); for (int idx = 0; idx < current_elements_.size(); idx++) { - if (reader->Contains(full_name(strings::StrCat( - "current_elements[", idx, "].inputs.size")))) { - int64 inputs_size; - TF_RETURN_IF_ERROR(reader->ReadScalar( - full_name( - strings::StrCat("current_elements[", idx, "].inputs.size")), - &inputs_size)); - current_elements_[idx].inputs.resize(inputs_size); - for (int i = 0; i < inputs_size; i++) { - TF_RETURN_IF_ERROR(reader->ReadTensor( - full_name(strings::StrCat("current_elements[", idx, - "].inputs[", i, "]")), - ¤t_elements_[idx].inputs[i])); - } - TF_RETURN_IF_ERROR(MakeIteratorFromInputElement( - ctx, current_elements_[idx].inputs, idx, - *instantiated_captured_func_.get(), prefix(), - ¤t_elements_[idx].iterator)); - TF_RETURN_IF_ERROR( - RestoreInput(ctx, reader, current_elements_[idx].iterator)); - } else { - current_elements_[idx].iterator.reset(); - } + TF_RETURN_IF_ERROR(ReadElement(ctx, reader, idx, "current_elements", + ¤t_elements_[idx])); + } + return Status::OK(); + } + + Status ReadFutureElements(IteratorContext* ctx, + IteratorStateReader* reader) + EXCLUSIVE_LOCKS_REQUIRED(*mu_) { + int64 size; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name("future_elements.size"), &size)); + future_elements_.resize(size); + for (int idx = 0; idx < future_elements_.size(); idx++) { + TF_RETURN_IF_ERROR(ReadElement(ctx, reader, idx, "future_elements", + &future_elements_[idx])); } return Status::OK(); } @@ -648,12 +866,11 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { // the worker threads. const std::shared_ptr mu_; - // Used for coordination between the main thread, the runner thread, and - // the worker threads. In particular, the runner thread should only - // schedule new calls when the number of in-flight calls is less than the - // user specified level of parallelism, there are slots available in the - // `invocation_results_` buffer, the current cycle element is not in use, - // and there are elements left to be fetched. + // Used for coordination between the main thread, the manager threads, and + // the threadpool threads. In particular, the managers thread should only + // schedule new calls into the threadpool when the number of in-flight + // calls is less than the user specified level of parallelism and there + // are slots available in the element `results` buffer. const std::shared_ptr cond_var_; // Identifies the maximum number of parallel calls. @@ -665,18 +882,17 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { // Iterator for input elements. std::unique_ptr input_impl_ GUARDED_BY(*mu_); - // Identifies current cycle element. - int64 cycle_index_ = 0; + // Identifies position in the interleave cycle. + int64 block_index_ GUARDED_BY(*mu_) = 0; + int64 cycle_index_ GUARDED_BY(*mu_) = 0; - // Iterators for the current cycle elements. Concurrent access is - // protected by `element_in_use_`. - std::vector current_elements_ GUARDED_BY(*mu_); + // Elements of the current interleave cycle. + std::vector> current_elements_ GUARDED_BY(*mu_); - // Buffer for storing the invocation results. - std::deque> invocation_results_ - GUARDED_BY(*mu_); + // Elements to be used in the interleave cycle in the future. + std::deque> future_elements_ GUARDED_BY(*mu_); - // Identifies whether end of input has been reached. + // Identifies whether the global end of input has been reached. bool end_of_input_ GUARDED_BY(*mu_) = false; // Identifies the number of open iterators. @@ -686,9 +902,11 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { int64 num_calls_ GUARDED_BY(*mu_) = 0; std::unique_ptr thread_pool_; - std::unique_ptr runner_thread_ GUARDED_BY(*mu_); + std::unique_ptr current_elements_manager_ GUARDED_BY(*mu_); + std::unique_ptr future_elements_manager_ GUARDED_BY(*mu_); + int64 element_id_counter_ GUARDED_BY(*mu_) = 0; - // Identifies whether background activity should be cancelled. + // 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/python/data/kernel_tests/interleave_test.py b/tensorflow/python/data/kernel_tests/interleave_test.py index 4fb61b2daf..4b427ff5a4 100644 --- a/tensorflow/python/data/kernel_tests/interleave_test.py +++ b/tensorflow/python/data/kernel_tests/interleave_test.py @@ -17,19 +17,15 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import threading - from absl.testing import parameterized import numpy as np -from tensorflow.python.data.experimental.ops import threading_options from tensorflow.python.data.kernel_tests import test_base from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import errors from tensorflow.python.framework import sparse_tensor from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops -from tensorflow.python.ops import script_ops from tensorflow.python.ops import sparse_ops from tensorflow.python.platform import test @@ -78,47 +74,6 @@ def _interleave(lists, cycle_length, block_length): break -def _make_coordinated_sloppy_dataset(input_values, cycle_length, block_length, - num_parallel_calls): - """Produces a dataset iterator and events to control the order of elements. - - Args: - input_values: the values to generate lists to interleave from - cycle_length: the length of the interleave cycle - block_length: the length of the interleave block - num_parallel_calls: the degree of interleave parallelism - - Returns: - A dataset iterator (represented as `get_next` op) and events that can be - used to control the order of output elements. - """ - - # Set up threading events used to sequence when items are produced that - # are subsequently interleaved. These events allow us to deterministically - # simulate slowdowns and force sloppiness. - coordination_events = {i: threading.Event() for i in input_values} - - def map_py_fn(x): - coordination_events[x].wait() - coordination_events[x].clear() - return x * x - - def map_fn(x): - return script_ops.py_func(map_py_fn, [x], x.dtype) - - def interleave_fn(x): - dataset = dataset_ops.Dataset.from_tensors(x) - dataset = dataset.repeat(x) - return dataset.map(map_fn) - - options = dataset_ops.Options() - options.experimental_deterministic = False - dataset = dataset_ops.Dataset.from_tensor_slices(input_values).repeat( - 2).interleave(interleave_fn, cycle_length, block_length, - num_parallel_calls).with_options(options) - return dataset, coordination_events - - def _repeat(values, count): """Produces a list of lists suitable for testing interleave. @@ -252,63 +207,37 @@ class InterleaveTest(test_base.DatasetTestBase, parameterized.TestCase): self.evaluate(get_next()) @parameterized.named_parameters( - ("1", np.int64([4, 5, 6]), 2, 1, 1), - ("2", np.int64([4, 5, 6]), 2, 1, 2), - ("3", np.int64([4, 5, 6]), 2, 3, 1), - ("4", np.int64([4, 5, 6]), 2, 3, 2), - ("5", np.int64([4, 5, 6]), 3, 2, 1), - ("6", np.int64([4, 5, 6]), 3, 2, 2), - ("7", np.int64([4, 5, 6]), 3, 2, 3), - ("8", np.int64([4, 0, 6]), 2, 3, 1), - ("9", np.int64([4, 0, 6]), 2, 3, 2), + ("1", np.int64([4, 5, 6]), 1, 3, 1), + ("2", np.int64([4, 5, 6]), 2, 1, 1), + ("3", np.int64([4, 5, 6]), 2, 1, 2), + ("4", np.int64([4, 5, 6]), 2, 3, 1), + ("5", np.int64([4, 5, 6]), 2, 3, 2), + ("6", np.int64([4, 5, 6]), 7, 2, 1), + ("7", np.int64([4, 5, 6]), 7, 2, 3), + ("8", np.int64([4, 5, 6]), 7, 2, 5), + ("9", np.int64([4, 5, 6]), 7, 2, 7), + ("10", np.int64([4, 0, 6]), 2, 3, 1), + ("11", np.int64([4, 0, 6]), 2, 3, 2), ) - def testSloppyInterleaveInOrder(self, input_values, cycle_length, + def testSloppyInterleaveDataset(self, input_values, cycle_length, block_length, num_parallel_calls): - dataset, coordination_events = _make_coordinated_sloppy_dataset( - input_values, cycle_length, block_length, num_parallel_calls) - options = dataset_ops.Options() - options.experimental_threading = threading_options.ThreadingOptions() - options.experimental_threading.private_threadpool_size = ( - num_parallel_calls + 1) - dataset = dataset.with_options(options) - - get_next = self.getNext(dataset, requires_initialization=True) - for expected_element in _interleave( - _repeat(input_values, 2), cycle_length, block_length): - coordination_events[expected_element].set() - self.assertEqual(expected_element * expected_element, - self.evaluate(get_next())) - with self.assertRaises(errors.OutOfRangeError): - self.evaluate(get_next()) - - @parameterized.named_parameters( - ("1", np.int64([4, 5, 6]), 2, 1, 2), - ("2", np.int64([4, 5, 6]), 2, 3, 2), - ("3", np.int64([4, 5, 6]), 3, 2, 3), - ("4", np.int64([4, 0, 6]), 2, 3, 2), - ) - def testSloppyInterleaveOutOfOrder(self, input_values, cycle_length, - block_length, num_parallel_calls): - dataset, coordination_events = _make_coordinated_sloppy_dataset( - input_values, cycle_length, block_length, num_parallel_calls) + count = 2 + dataset = dataset_ops.Dataset.from_tensor_slices(input_values).repeat( + count).interleave( + lambda x: dataset_ops.Dataset.from_tensors(x).repeat(x), + cycle_length, block_length, num_parallel_calls) options = dataset_ops.Options() - options.experimental_threading = threading_options.ThreadingOptions() - options.experimental_threading.private_threadpool_size = ( - num_parallel_calls + 1) + options.experimental_deterministic = False dataset = dataset.with_options(options) - get_next = self.getNext(dataset, requires_initialization=True) - elements = [ - x for x in _interleave( - _repeat(input_values, 2), cycle_length, block_length) + expected_output = [ + element for element in _interleave( + _repeat(input_values, count), cycle_length, block_length) ] - for i in [1, 4, 7]: - elements[i], elements[i + 1] = elements[i + 1], elements[i] - - for element in elements: - coordination_events[element].set() - self.assertEqual(element * element, self.evaluate(get_next())) - with self.assertRaises(errors.OutOfRangeError): - self.evaluate(get_next()) + get_next = self.getNext(dataset) + actual_output = [] + for _ in range(len(expected_output)): + actual_output.append(self.evaluate(get_next())) + self.assertAllEqual(expected_output.sort(), actual_output.sort()) if __name__ == "__main__": -- GitLab From dba2134638812e6375a26012a485b55a24c45a19 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 14:26:00 -0800 Subject: [PATCH 0211/2345] Switch RBE image location for CUDA 9 to tensorflow-testing. PiperOrigin-RevId: 227914044 --- .../tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 | 4 ++-- third_party/toolchains/BUILD | 2 +- third_party/toolchains/preconfig/generate/containers.bzl | 2 +- third_party/toolchains/preconfig/generate/workspace.bzl | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 b/tensorflow/tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 index 9d5d51447c..4ce4214065 100644 --- a/tensorflow/tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 +++ b/tensorflow/tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 @@ -1,7 +1,7 @@ # To push a new version, run: # $ docker build -f Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04 \ -# --tag "gcr.io/asci-toolchain/nosla-cuda9.0-cudnn7-ubuntu14.04" . -# $ docker push gcr.io/asci-toolchain/nosla-cuda9.0-cudnn7-ubuntu14.04 +# --tag "gcr.io/tensorflow-testing/nosla-cuda9.0-cudnn7-ubuntu14.04" . +# $ docker push gcr.io/tensorflow-testing/nosla-cuda9.0-cudnn7-ubuntu14.04 # # TODO(klimek): Include clang in this image so we can also target clang # builds. diff --git a/third_party/toolchains/BUILD b/third_party/toolchains/BUILD index 28f1e6979c..f2248341c4 100644 --- a/third_party/toolchains/BUILD +++ b/third_party/toolchains/BUILD @@ -32,7 +32,7 @@ platform( remote_execution_properties = """ properties: { name: "container-image" - value:"docker://gcr.io/asci-toolchain/nosla-cuda9.0-cudnn7-ubuntu14.04@%s" + value:"docker://gcr.io/tensorflow-testing/nosla-cuda9.0-cudnn7-ubuntu14.04@%s" }""" % container_digests["cuda9.0-cudnn7-ubuntu14.04"], ) diff --git a/third_party/toolchains/preconfig/generate/containers.bzl b/third_party/toolchains/preconfig/generate/containers.bzl index 32d5f29b52..67e43485ed 100644 --- a/third_party/toolchains/preconfig/generate/containers.bzl +++ b/third_party/toolchains/preconfig/generate/containers.bzl @@ -1,4 +1,4 @@ container_digests = { - "cuda9.0-cudnn7-ubuntu14.04": "sha256:c43ed5341dd765042e0bbd1bf50fadeedd649d1e0c34d81999cb6ce30916cb95", + "cuda9.0-cudnn7-ubuntu14.04": "sha256:006a76ee1838122ff7f21ebac85f24c1ef350d4dd79b3ceff0e4fe649ed90d33", "cuda10.0-cudnn7-ubuntu14.04": "sha256:e36f05f1ff39e39ddf07122e37f2b1895948bb6f7acc3db37a3c496be5e66228", } diff --git a/third_party/toolchains/preconfig/generate/workspace.bzl b/third_party/toolchains/preconfig/generate/workspace.bzl index a3268585e1..eb74022c24 100644 --- a/third_party/toolchains/preconfig/generate/workspace.bzl +++ b/third_party/toolchains/preconfig/generate/workspace.bzl @@ -11,7 +11,7 @@ def _remote_config_workspace(): container_pull( name = "cuda9.0-cudnn7-ubuntu14.04", registry = "gcr.io", - repository = "asci-toolchain/nosla-cuda9.0-cudnn7-ubuntu14.04", + repository = "tensorflow-testing/nosla-cuda9.0-cudnn7-ubuntu14.04", digest = container_digests["cuda9.0-cudnn7-ubuntu14.04"], ) -- GitLab From 2f246ef177ad3cb8aba3b4de0a628822cbeab71d Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Fri, 4 Jan 2019 14:27:22 -0800 Subject: [PATCH 0212/2345] Upgrade ci docker images to cuda 10. These really should be gone already... PiperOrigin-RevId: 227914258 --- tensorflow/tools/ci_build/Dockerfile.gpu | 4 ++-- tensorflow/tools/ci_build/Dockerfile.rbe.gpu | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/tools/ci_build/Dockerfile.gpu b/tensorflow/tools/ci_build/Dockerfile.gpu index a4cad4b6c6..ff1fefe892 100644 --- a/tensorflow/tools/ci_build/Dockerfile.gpu +++ b/tensorflow/tools/ci_build/Dockerfile.gpu @@ -1,4 +1,4 @@ -FROM nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04 +FROM nvidia/cuda:10.0-cudnn7-devel-ubuntu16.04 LABEL maintainer="Jan Prach " @@ -24,7 +24,7 @@ COPY install/.bazelrc /etc/bazel.bazelrc ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH # Link NCCL libray and header where the build script expects them. -RUN mkdir /usr/local/cuda-9.0/lib && \ +RUN mkdir /usr/local/cuda/lib && \ ln -s /usr/lib/x86_64-linux-gnu/libnccl.so.2 /usr/local/cuda/lib/libnccl.so.2 && \ ln -s /usr/include/nccl.h /usr/local/cuda/include/nccl.h diff --git a/tensorflow/tools/ci_build/Dockerfile.rbe.gpu b/tensorflow/tools/ci_build/Dockerfile.rbe.gpu index b656205836..c4912a65b6 100644 --- a/tensorflow/tools/ci_build/Dockerfile.rbe.gpu +++ b/tensorflow/tools/ci_build/Dockerfile.rbe.gpu @@ -1,4 +1,4 @@ -FROM nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04 +FROM nvidia/cuda:10.0-cudnn7-devel-ubuntu16.04 LABEL maintainer="Nick Lopez " -- GitLab From 7cc1ff9072ce4c2c7f36b7011e24486071ad13ee Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Fri, 4 Jan 2019 14:35:17 -0800 Subject: [PATCH 0213/2345] Better exception for tf.constant(tf.constant(x)) PiperOrigin-RevId: 227915529 --- tensorflow/python/framework/constant_op.py | 4 ++++ tensorflow/python/kernel_tests/constant_op_test.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/tensorflow/python/framework/constant_op.py b/tensorflow/python/framework/constant_op.py index ade0797dcd..62a25f3fea 100644 --- a/tensorflow/python/framework/constant_op.py +++ b/tensorflow/python/framework/constant_op.py @@ -277,6 +277,10 @@ def _constant_impl( (num_t, shape, shape.num_elements())) g = ops.get_default_graph() tensor_value = attr_value_pb2.AttrValue() + if tensor_util.is_tensor(value): + raise ValueError( + ("Cannot create a tf.constant from symbolic Tensor %s. Did you mean " + "tf.convert_to_tensor?") % (value,)) tensor_value.tensor.CopyFrom( tensor_util.make_tensor_proto( value, dtype=dtype, shape=shape, verify_shape=verify_shape, diff --git a/tensorflow/python/kernel_tests/constant_op_test.py b/tensorflow/python/kernel_tests/constant_op_test.py index 583082c2aa..2b743ed4c4 100644 --- a/tensorflow/python/kernel_tests/constant_op_test.py +++ b/tensorflow/python/kernel_tests/constant_op_test.py @@ -24,6 +24,7 @@ from google.protobuf import text_format from tensorflow.core.framework import graph_pb2 from tensorflow.core.framework import tensor_pb2 +from tensorflow.python.eager import def_function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes as dtypes_lib from tensorflow.python.framework import errors_impl @@ -630,6 +631,16 @@ class OnesTest(test.TestCase): self.assertEqual([2, 3], z.get_shape()) self.assertAllEqual(z.eval(), np.ones([2, 3])) + @test_util.run_in_graph_and_eager_modes + def testIteratedConstantSensibleException(self): + + @def_function.function + def constant_on_constant(x): + return constant_op.constant(x) + + with self.assertRaisesRegexp(ValueError, "convert_to_tensor"): + constant_on_constant(constant_op.constant(2)) + class OnesLikeTest(test.TestCase): -- GitLab From 73b6e92b4b3faf0c20687af3ba63989fc5234f13 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Fri, 4 Jan 2019 14:35:23 -0800 Subject: [PATCH 0214/2345] Remove usage of the initialize and finalize api PiperOrigin-RevId: 227915547 --- .../distribute/python/metrics_v1_test.py | 3 --- .../distribute/python/minimize_loss_test.py | 19 +------------------ .../contrib/distribute/python/step_fn_test.py | 4 ---- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/tensorflow/contrib/distribute/python/metrics_v1_test.py b/tensorflow/contrib/distribute/python/metrics_v1_test.py index 32a0d19943..7472f6dde4 100644 --- a/tensorflow/contrib/distribute/python/metrics_v1_test.py +++ b/tensorflow/contrib/distribute/python/metrics_v1_test.py @@ -122,7 +122,6 @@ class MetricsV1Test(test.TestCase, parameterized.TestCase): batches_per_update = distribution.num_replicas_in_sync self.evaluate(iterator.initializer) - self.evaluate(distribution.initialize()) self.evaluate(variables.local_variables_initializer()) batches_consumed = 0 @@ -136,8 +135,6 @@ class MetricsV1Test(test.TestCase, parameterized.TestCase): if batches_consumed >= 4: # Consume 4 input batches in total. break - self.evaluate(distribution.finalize()) - @combinations.generate(all_combinations() + tpu_combinations()) def testMean(self, distribution): def _dataset_fn(): diff --git a/tensorflow/contrib/distribute/python/minimize_loss_test.py b/tensorflow/contrib/distribute/python/minimize_loss_test.py index 824c4b0937..b0e24a53f6 100644 --- a/tensorflow/contrib/distribute/python/minimize_loss_test.py +++ b/tensorflow/contrib/distribute/python/minimize_loss_test.py @@ -75,7 +75,6 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): return distribution.run_steps_on_dataset( step_fn, iterator, iterations=2).run_op - self.evaluate(distribution.initialize()) if not context.executing_eagerly(): with self.cached_session() as sess: run_step = sess.make_callable(run_step()) @@ -84,12 +83,9 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): weights, biases = [], [] for _ in range(5): run_step() - weights.append(self.evaluate(layer.kernel)) biases.append(self.evaluate(layer.bias)) - self.evaluate(distribution.finalize()) - error = abs(numpy.add(numpy.squeeze(weights), numpy.squeeze(biases)) - 1) is_not_increasing = all(y <= x for x, y in zip(error, error[1:])) self.assertTrue(is_not_increasing) @@ -152,7 +148,7 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): # `distribution.scope`. with variable_scope.variable_creator_scope( appending_creator), distribution.scope(): - model_fn, dataset_fn, layer = minimize_loss_example( + model_fn, dataset_fn, _ = minimize_loss_example( optimizer_fn, use_bias=True, use_callable_loss=True, @@ -169,16 +165,12 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): return distribution.run_steps_on_dataset( step_fn, iterator, iterations=1).run_op - self.evaluate(distribution.initialize()) if not context.executing_eagerly(): with self.cached_session() as sess: run_step = sess.make_callable(run_step()) self.evaluate(variables_lib.global_variables_initializer()) - run_step() - self.evaluate(distribution.finalize()) - def get_expected_variables(optimizer_fn, num_parameter_devices): variables_map = { "GradientDescent": ["dense/kernel", "dense/bias"], @@ -241,7 +233,6 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): return distribution.run_steps_on_dataset( step_fn, iterator, iterations=1).run_op - self.evaluate(distribution.initialize()) if not context.executing_eagerly(): with self.cached_session() as sess: run_step = sess.make_callable(run_step()) @@ -267,8 +258,6 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): expected_moving_mean - averaged_batch_mean(i)) * (1.0 - momentum)) self.assertNear(expected_moving_means[i], moving_means[i], 0.0001) - self.evaluate(distribution.finalize()) - @combinations.generate( combinations.times( combinations.combine( @@ -335,7 +324,6 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): return distribution.run_steps_on_dataset( step_fn, iterator, iterations=1).run_op - self.evaluate(distribution.initialize()) if not context.executing_eagerly(): with self.cached_session() as sess: run_step = sess.make_callable(run_step()) @@ -370,8 +358,6 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): # One of the mean loss reductions. self.assertNear(weight, 2 + 10.6, 0.0001) - self.evaluate(distribution.finalize()) - @combinations.generate( combinations.times( combinations.distributions_and_v1_optimizers(), @@ -458,7 +444,6 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): reduced=False, distribution=distribution) return (ctx.run_op, ctx.last_step_outputs["replica_loss_reduced"]) - self.evaluate(distribution.initialize()) if not context.executing_eagerly(): with self.cached_session() as sess: run_step = sess.make_callable(run_step()) @@ -471,8 +456,6 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): weights.append(self.evaluate(layer.kernel)) biases.append(self.evaluate(layer.bias)) - self.evaluate(distribution.finalize()) - loss_is_not_increasing = all(y <= x for x, y in zip(losses, losses[1:])) self.assertTrue(loss_is_not_increasing) diff --git a/tensorflow/contrib/distribute/python/step_fn_test.py b/tensorflow/contrib/distribute/python/step_fn_test.py index 1ff9b9ceec..a77d6d0bec 100644 --- a/tensorflow/contrib/distribute/python/step_fn_test.py +++ b/tensorflow/contrib/distribute/python/step_fn_test.py @@ -45,7 +45,6 @@ class SingleLossStepTest(test.TestCase, parameterized.TestCase): single_loss_step, layer = single_loss_example( optimizer_fn, distribution, use_bias=True, iterations_per_step=2) - self.evaluate(distribution.initialize()) if context.executing_eagerly(): run_step = single_loss_step else: @@ -57,12 +56,9 @@ class SingleLossStepTest(test.TestCase, parameterized.TestCase): weights, biases = [], [] for _ in range(5): run_step() - weights.append(self.evaluate(layer.kernel)) biases.append(self.evaluate(layer.bias)) - self.evaluate(distribution.finalize()) - error = abs(numpy.add(numpy.squeeze(weights), numpy.squeeze(biases)) - 1) is_not_increasing = all(y <= x for x, y in zip(error, error[1:])) self.assertTrue(is_not_increasing) -- GitLab From 1dee496b6addb67c269151d4a96b81608b1eb463 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 14:43:02 -0800 Subject: [PATCH 0215/2345] TOCO's default_range parameters are floats, not integers. PiperOrigin-RevId: 227916837 --- tensorflow/lite/python/tflite_convert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/python/tflite_convert.py b/tensorflow/lite/python/tflite_convert.py index 341b539bea..401a592273 100644 --- a/tensorflow/lite/python/tflite_convert.py +++ b/tensorflow/lite/python/tflite_convert.py @@ -343,13 +343,13 @@ def run_main(_): "floats. Used for quantized input tensors. (default None)")) parser.add_argument( "--default_ranges_min", - type=int, + type=float, help=("Default value for min bound of min/max range values used for all " "arrays without a specified range, Intended for experimenting with " "quantization via \"dummy quantization\". (default None)")) parser.add_argument( "--default_ranges_max", - type=int, + type=float, help=("Default value for max bound of min/max range values used for all " "arrays without a specified range, Intended for experimenting with " "quantization via \"dummy quantization\". (default None)")) -- GitLab From 58edbcb97b90596ee5f4cd907dfa7dd748c1843a Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Fri, 4 Jan 2019 15:03:28 -0800 Subject: [PATCH 0216/2345] Internal change. PiperOrigin-RevId: 227920137 --- tensorflow/tools/ci_build/builds/run_pip_tests.sh | 3 ++- tensorflow/tools/ci_build/ci_parameterized_build.sh | 9 ++++++--- tensorflow/tools/ci_build/osx/cpu/run_contrib.sh | 1 + tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tensorflow/tools/ci_build/builds/run_pip_tests.sh b/tensorflow/tools/ci_build/builds/run_pip_tests.sh index 7d5cf3f843..a095633a22 100755 --- a/tensorflow/tools/ci_build/builds/run_pip_tests.sh +++ b/tensorflow/tools/ci_build/builds/run_pip_tests.sh @@ -88,7 +88,8 @@ if [[ ${IS_GPU} == "1" ]]; then PIP_TEST_FILTER_TAG="-no_gpu,-no_pip_gpu,${PIP_TEST_FILTER_TAG}" fi if [[ ${IS_MAC} == "1" ]]; then - PIP_TEST_FILTER_TAG="-nomac,${PIP_TEST_FILTER_TAG}" + # TODO(b/122370901): Fix nomac, no_mac inconsistency. + PIP_TEST_FILTER_TAG="-nomac,-no_mac,${PIP_TEST_FILTER_TAG}" fi # Bazel flags we need for all tests: diff --git a/tensorflow/tools/ci_build/ci_parameterized_build.sh b/tensorflow/tools/ci_build/ci_parameterized_build.sh index 435ec7ca68..41b9f241d5 100755 --- a/tensorflow/tools/ci_build/ci_parameterized_build.sh +++ b/tensorflow/tools/ci_build/ci_parameterized_build.sh @@ -398,7 +398,8 @@ if [[ "${TF_BUILD_APPEND_ARGUMENTS}" == *"--test_tag_filters="* ]]; then NEW_ITEM="${NEW_ITEM},-benchmark-test" fi if [[ ${IS_MAC} == "1" ]] && [[ ${NEW_ITEM} != *"nomac"* ]]; then - NEW_ITEM="${NEW_ITEM},-nomac" + # TODO(b/122370901): Fix nomac, no_mac inconsistency. + NEW_ITEM="${NEW_ITEM},-nomac,-no_mac" fi EXTRA_ARGS="${EXTRA_ARGS} ${NEW_ITEM}" else @@ -408,11 +409,13 @@ if [[ "${TF_BUILD_APPEND_ARGUMENTS}" == *"--test_tag_filters="* ]]; then else EXTRA_ARGS="${EXTRA_ARGS} ${TF_BUILD_APPEND_ARGUMENTS} --test_tag_filters=-no_oss,-oss_serial,-benchmark-test" if [[ ${IS_MAC} == "1" ]]; then - EXTRA_ARGS="${EXTRA_ARGS},-nomac" + # TODO(b/122370901): Fix nomac, no_mac inconsistency. + EXTRA_ARGS="${EXTRA_ARGS},-nomac,-no_mac" fi EXTRA_ARGS="${EXTRA_ARGS} --build_tag_filters=-no_oss,-oss_serial,-benchmark-test" if [[ ${IS_MAC} == "1" ]]; then - EXTRA_ARGS="${EXTRA_ARGS},-nomac" + # TODO(b/122370901): Fix nomac, no_mac inconsistency. + EXTRA_ARGS="${EXTRA_ARGS},-nomac,-no_mac" fi fi diff --git a/tensorflow/tools/ci_build/osx/cpu/run_contrib.sh b/tensorflow/tools/ci_build/osx/cpu/run_contrib.sh index 3efd994d78..1184d4acec 100755 --- a/tensorflow/tools/ci_build/osx/cpu/run_contrib.sh +++ b/tensorflow/tools/ci_build/osx/cpu/run_contrib.sh @@ -31,6 +31,7 @@ export CC_OPT_FLAGS='-mavx' export PYTHON_BIN_PATH=$(which python2) yes "" | $PYTHON_BIN_PATH configure.py which bazel +# TODO(b/122370901): Fix nomac, no_mac inconsistency. bazel test --test_tag_filters=-no_oss,-gpu,-benchmark-test,-nomac,-no_mac \ --test_timeout 300,450,1200,3600 \ --test_size_filters=small,medium --config=opt \ diff --git a/tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh b/tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh index adee0d3171..d39340b1d8 100755 --- a/tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh +++ b/tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh @@ -32,6 +32,7 @@ export CC_OPT_FLAGS='-mavx' export PYTHON_BIN_PATH=$(which python2) yes "" | $PYTHON_BIN_PATH configure.py which bazel +# TODO(b/122370901): Fix nomac, no_mac inconsistency. bazel test --test_tag_filters=-no_oss,-gpu,-benchmark-test,-nomac,-no_mac \ --test_timeout 300,450,1200,3600 --config=opt \ --announce_rc \ -- GitLab From 07777005d9b3feff4c8d74348739ad17de510131 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 15:07:06 -0800 Subject: [PATCH 0217/2345] Implement memory prefetching for PresizedCuckooMap PiperOrigin-RevId: 227920727 --- tensorflow/core/BUILD | 1 + tensorflow/core/util/presized_cuckoo_map.h | 10 ++++ .../core/util/presized_cuckoo_map_test.cc | 49 ++++++++++++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 7c9decbd09..460353df41 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -3813,6 +3813,7 @@ tf_cc_tests( "//tensorflow/core/kernels:ops_util", "//third_party/eigen3", "@com_google_absl//absl/base", + "@com_google_absl//absl/time", ], ) diff --git a/tensorflow/core/util/presized_cuckoo_map.h b/tensorflow/core/util/presized_cuckoo_map.h index f88ad2faaf..1cdde34562 100644 --- a/tensorflow/core/util/presized_cuckoo_map.h +++ b/tensorflow/core/util/presized_cuckoo_map.h @@ -20,6 +20,7 @@ limitations under the License. #include #include "tensorflow/core/framework/types.h" #include "tensorflow/core/platform/macros.h" +#include "tensorflow/core/platform/prefetch.h" namespace tensorflow { @@ -132,6 +133,15 @@ class PresizedCuckooMap { FindInBucket(k, fast_map_to_buckets(h2(tk)), out); } + // Prefetch memory associated with the key k into cache levels specified by + // hint. + template + void PrefetchKey(const key_type k) const { + const uint64 tk = key_transform(k); + port::prefetch(&buckets_[fast_map_to_buckets(tk)].keys); + port::prefetch(&buckets_[fast_map_to_buckets(h2(tk))].keys); + } + int64 MemoryUsed() const { return sizeof(PresizedCuckooMap) + sizeof(CuckooPathQueue); } diff --git a/tensorflow/core/util/presized_cuckoo_map_test.cc b/tensorflow/core/util/presized_cuckoo_map_test.cc index f2be1e8a2f..8dbb2ec065 100644 --- a/tensorflow/core/util/presized_cuckoo_map_test.cc +++ b/tensorflow/core/util/presized_cuckoo_map_test.cc @@ -13,12 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/core/util/presized_cuckoo_map.h" #include + +#include "absl/time/clock.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/fingerprint.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/platform/test_benchmark.h" +#include "tensorflow/core/util/presized_cuckoo_map.h" namespace tensorflow { namespace { @@ -50,6 +52,51 @@ TEST(PresizedCuckooMapTest, Basic) { EXPECT_EQ(out, 2); } +TEST(PresizedCuckooMapTest, Prefetch) { + { + PresizedCuckooMap pscm(2); + EXPECT_TRUE(pscm.InsertUnique(1, 2)); + // Works for both present and absent keys. + pscm.PrefetchKey(1); + pscm.PrefetchKey(2); + } + + // Do not run in debug mode, when prefetch is not implemented, or when + // sanitizers are enabled. +#if defined(NDEBUG) && defined(__GNUC__) && !defined(ADDRESS_SANITIZER) && \ + !defined(MEMORY_SANITIZER) && !defined(THREAD_SANITIZER) && \ + !defined(UNDEFINED_BEHAVIOR_SANITIZER) + const auto now = [] { return absl::Now(); }; + + // Make size enough to not fit in L2 cache (16.7 Mb) + static constexpr int size = 1 << 22; + PresizedCuckooMap pscm(size); + for (int i = 0; i < size; ++i) { + pscm.InsertUnique(i, i); + } + + absl::Duration no_prefetch, prefetch; + int64 out; + for (int iter = 0; iter < 10; ++iter) { + auto time = now(); + for (int i = 0; i < size; ++i) { + testing::DoNotOptimize(pscm.Find(i, &out)); + } + no_prefetch += now() - time; + + time = now(); + for (int i = 0; i < size; ++i) { + pscm.PrefetchKey(i + 20); + testing::DoNotOptimize(pscm.Find(i, &out)); + } + prefetch += now() - time; + } + + // no_prefetch is at least 30% slower. + EXPECT_GE(1.0 * no_prefetch / prefetch, 1.3); +#endif +} + TEST(PresizedCuckooMapTest, TooManyItems) { static constexpr int kTableSize = 1000; PresizedCuckooMap pscm(kTableSize); -- GitLab From 620fa70346ef6cd2c7f13c9986625850d523cf0b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 15:23:23 -0800 Subject: [PATCH 0218/2345] Go: Update generated wrapper functions for TensorFlow ops. PiperOrigin-RevId: 227923029 --- tensorflow/go/op/wrappers.go | 1844 ++++++++++++++++++++++++++++++++-- 1 file changed, 1782 insertions(+), 62 deletions(-) diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index 6fe61f512f..208c15d75a 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -327,6 +327,192 @@ func FakeQuantWithMinMaxArgs(scope *Scope, inputs tf.Output, optional ...FakeQua return op.Output(0) } +// Subtracts sparse `updates` from an existing tensor according to `indices`. +// +// This operation creates a new tensor by subtracting sparse `updates` from the +// passed in `tensor`. +// This operation is very similar to `tf.scatter_nd_sub`, except that the updates +// are subtracted from an existing tensor (as opposed to a variable). If the memory +// for the existing tensor cannot be re-used, a copy is made and updated. +// +// `indices` is an integer tensor containing indices into a new tensor of shape +// `shape`. The last dimension of `indices` can be at most the rank of `shape`: +// +// indices.shape[-1] <= shape.rank +// +// The last dimension of `indices` corresponds to indices into elements +// (if `indices.shape[-1] = shape.rank`) or slices +// (if `indices.shape[-1] < shape.rank`) along dimension `indices.shape[-1]` of +// `shape`. `updates` is a tensor with shape +// +// indices.shape[:-1] + shape[indices.shape[-1]:] +// +// The simplest form of tensor_scatter_sub is to subtract individual elements +// from a tensor by index. For example, say we want to insert 4 scattered elements +// in a rank-1 tensor with 8 elements. +// +// In Python, this scatter subtract operation would look like this: +// +// ```python +// indices = tf.constant([[4], [3], [1], [7]]) +// updates = tf.constant([9, 10, 11, 12]) +// tensor = tf.ones([8], dtype=tf.int32) +// updated = tf.tensor_scatter_sub(tensor, indices, updates) +// with tf.Session() as sess: +// print(sess.run(scatter)) +// ``` +// +// The resulting tensor would look like this: +// +// [1, -10, 1, -9, -8, 1, 1, -11] +// +// We can also, insert entire slices of a higher rank tensor all at once. For +// example, if we wanted to insert two slices in the first dimension of a +// rank-3 tensor with two matrices of new values. +// +// In Python, this scatter add operation would look like this: +// +// ```python +// indices = tf.constant([[0], [2]]) +// updates = tf.constant([[[5, 5, 5, 5], [6, 6, 6, 6], +// [7, 7, 7, 7], [8, 8, 8, 8]], +// [[5, 5, 5, 5], [6, 6, 6, 6], +// [7, 7, 7, 7], [8, 8, 8, 8]]]) +// tensor = tf.ones([4, 4, 4]) +// updated = tf.tensor_scatter_sub(tensor, indices, updates) +// with tf.Session() as sess: +// print(sess.run(scatter)) +// ``` +// +// The resulting tensor would look like this: +// +// [[[-4, -4, -4, -4], [-5, -5, -5, -5], [-6, -6, -6, -6], [-7, -7, -7, -7]], +// [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], +// [[-4, -4, -4, -4], [-5, -5, -5, -5], [-6, -6, -6, -6], [-7, -7, -7, -7]], +// [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]] +// +// Note that on CPU, if an out of bound index is found, an error is returned. +// On GPU, if an out of bound index is found, the index is ignored. +// +// Arguments: +// tensor: Tensor to copy/update. +// indices: Index tensor. +// updates: Updates to scatter into output. +// +// Returns A new tensor copied from tensor and updates subtracted according to the indices. +func TensorScatterSub(scope *Scope, tensor tf.Output, indices tf.Output, updates tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorScatterSub", + Input: []tf.Input{ + tensor, indices, updates, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Scatter `updates` into an existing tensor according to `indices`. +// +// This operation creates a new tensor by applying sparse `updates` to the passed +// in `tensor`. +// This operation is very similar to `tf.scatter_nd`, except that the updates are +// scattered onto an existing tensor (as opposed to a zero-tensor). If the memory +// for the existing tensor cannot be re-used, a copy is made and updated. +// +// If `indices` contains duplicates, then their updates are accumulated (summed). +// +// **WARNING**: The order in which updates are applied is nondeterministic, so the +// output will be nondeterministic if `indices` contains duplicates -- because +// of some numerical approximation issues, numbers summed in different order +// may yield different results. +// +// `indices` is an integer tensor containing indices into a new tensor of shape +// `shape`. The last dimension of `indices` can be at most the rank of `shape`: +// +// indices.shape[-1] <= shape.rank +// +// The last dimension of `indices` corresponds to indices into elements +// (if `indices.shape[-1] = shape.rank`) or slices +// (if `indices.shape[-1] < shape.rank`) along dimension `indices.shape[-1]` of +// `shape`. `updates` is a tensor with shape +// +// indices.shape[:-1] + shape[indices.shape[-1]:] +// +// The simplest form of scatter is to insert individual elements in a tensor by +// index. For example, say we want to insert 4 scattered elements in a rank-1 +// tensor with 8 elements. +// +//
+// +//
+// +// In Python, this scatter operation would look like this: +// +// ```python +// indices = tf.constant([[4], [3], [1], [7]]) +// updates = tf.constant([9, 10, 11, 12]) +// tensor = tf.ones([8], dtype=tf.int32) +// updated = tf.tensor_scatter_update(tensor, indices, updates) +// with tf.Session() as sess: +// print(sess.run(scatter)) +// ``` +// +// The resulting tensor would look like this: +// +// [1, 11, 1, 10, 9, 1, 1, 12] +// +// We can also, insert entire slices of a higher rank tensor all at once. For +// example, if we wanted to insert two slices in the first dimension of a +// rank-3 tensor with two matrices of new values. +// +// In Python, this scatter operation would look like this: +// +// ```python +// indices = tf.constant([[0], [2]]) +// updates = tf.constant([[[5, 5, 5, 5], [6, 6, 6, 6], +// [7, 7, 7, 7], [8, 8, 8, 8]], +// [[5, 5, 5, 5], [6, 6, 6, 6], +// [7, 7, 7, 7], [8, 8, 8, 8]]]) +// tensor = tf.ones([4, 4, 4]) +// updated = tf.tensor_scatter_update(tensor, indices, updates) +// with tf.Session() as sess: +// print(sess.run(scatter)) +// ``` +// +// The resulting tensor would look like this: +// +// [[[5, 5, 5, 5], [6, 6, 6, 6], [7, 7, 7, 7], [8, 8, 8, 8]], +// [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], +// [[5, 5, 5, 5], [6, 6, 6, 6], [7, 7, 7, 7], [8, 8, 8, 8]], +// [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]] +// +// Note that on CPU, if an out of bound index is found, an error is returned. +// On GPU, if an out of bound index is found, the index is ignored. +// +// Arguments: +// tensor: Tensor to copy/update. +// indices: Index tensor. +// updates: Updates to scatter into output. +// +// Returns A new tensor with the given shape and updates applied according +// to the indices. +func TensorScatterUpdate(scope *Scope, tensor tf.Output, indices tf.Output, updates tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorScatterUpdate", + Input: []tf.Input{ + tensor, indices, updates, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Scatter `updates` into a new tensor according to `indices`. // // Creates a new tensor by applying sparse `updates` to individual values or @@ -334,6 +520,10 @@ func FakeQuantWithMinMaxArgs(scope *Scope, inputs tf.Output, optional ...FakeQua // the given `shape` according to indices. This operator is the inverse of the // `tf.gather_nd` operator which extracts values or slices from a given tensor. // +// This operation is similar to tensor_scatter_add, except that the tensor is +// zero-initialized. Calling `tf.scatter_nd(indices, values, shape)` is identical +// to `tensor_scatter_add(tf.zeros(shape, values.dtype), indices, values)` +// // If `indices` contains duplicates, then their updates are accumulated (summed). // // **WARNING**: The order in which updates are applied is nondeterministic, so the @@ -464,6 +654,15 @@ func QuantizeAndDequantizeV2RangeGiven(value bool) QuantizeAndDequantizeV2Attr { } // QuantizeAndDequantizeV2RoundMode sets the optional round_mode attribute to value. +// +// value: The 'round_mode' attribute controls which rounding tie-breaking algorithm is +// used when rounding float values to their quantized equivalents. The following +// rounding modes are currently supported: +// +// * HALF_TO_EVEN: this is the default round_mode. +// * HALF_UP: round towards positive. In this mode 7.5 rounds up to 8 and -7.5 +// rounds up to -7. +// // If not specified, defaults to "HALF_TO_EVEN" func QuantizeAndDequantizeV2RoundMode(value string) QuantizeAndDequantizeV2Attr { return func(m optionalAttr) { @@ -523,7 +722,7 @@ func QuantizeAndDequantizeV2RoundMode(value string) QuantizeAndDequantizeV2Attr // // output = round(clamp(value, input_min, input_max) * scale_factor) / scale_factor. // -// The above round function uses half to even rounding. +// The above round function rounds the value based on the given round_mode. // // // Arguments: @@ -3422,11 +3621,11 @@ func PopulationCount(scope *Scope, x tf.Output) (y tf.Output) { // bucketized values for a single feature. // // Arguments: -// float_values: float; List of Rank 2 Tensor each containing float values for a single feature. +// float_values: float; List of Rank 1 Tensor each containing float values for a single feature. // bucket_boundaries: float; List of Rank 1 Tensors each containing the bucket boundaries for a single // feature. // -// Returns int; List of Rank 2 Tensors each containing the bucketized values for a single feature. +// Returns int; List of Rank 1 Tensors each containing the bucketized values for a single feature. func BoostedTreesBucketize(scope *Scope, float_values []tf.Output, bucket_boundaries []tf.Output) (buckets []tf.Output) { if scope.Err() != nil { return @@ -3497,15 +3696,16 @@ func BoostedTreesQuantileStreamResourceFlush(scope *Scope, quantile_stream_resou // Makes the summary of quantiles for the batch. // -// An op that takes a list of tensors and outputs the quantile summaries for each tensor. +// An op that takes a list of tensors (one tensor per feature) and outputs the +// quantile summaries for each tensor. // // Arguments: -// float_values: float; List of Rank 2 Tensors each containing values for a single feature. +// float_values: float; List of Rank 1 Tensors each containing values for a single feature. // example_weights: float; Rank 1 Tensor with weights per instance. // epsilon: float; The required maximum approximation error. // -// Returns float; List of Rank 2 Tensors each containing the quantile summary (value, weight, -// min_rank, max_rank) of a single feature. +// Returns float; List of Rank 2 Tensors each containing the quantile summary +// (value, weight, min_rank, max_rank) of a single feature. func BoostedTreesMakeQuantileSummaries(scope *Scope, float_values []tf.Output, example_weights tf.Output, epsilon tf.Output) (summaries []tf.Output) { if scope.Err() != nil { return @@ -3806,6 +4006,70 @@ func BoostedTreesEnsembleResourceHandleOp(scope *Scope, optional ...BoostedTrees return op.Output(0) } +// Output the logits for the given input data +// +// Arguments: +// tree_handle: Handle to the tree resource. +// dense_features: Rank 2 dense features tensor. +// logits_dimension: Scalar, dimension of the logits. +// +// Returns The logits predictions from the tree for each instance in the batch. +func TensorForestTreePredict(scope *Scope, tree_handle tf.Output, dense_features tf.Output, logits_dimension int64) (logits tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"logits_dimension": logits_dimension} + opspec := tf.OpSpec{ + Type: "TensorForestTreePredict", + Input: []tf.Input{ + tree_handle, dense_features, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Get the number of nodes in a tree +// +// Arguments: +// tree_handle: Handle to the tree resource. +// +// Returns The size of the tree. +func TensorForestTreeSize(scope *Scope, tree_handle tf.Output) (tree_size tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorForestTreeSize", + Input: []tf.Input{ + tree_handle, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Creates a tree resource and returns a handle to it. +// +// Arguments: +// tree_handle: Handle to the tree resource to be created. +// tree_config: Serialized proto string of the boosted_trees.Tree. +// +// Returns the created operation. +func TensorForestCreateTreeVariable(scope *Scope, tree_handle tf.Output, tree_config tf.Output) (o *tf.Operation) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorForestCreateTreeVariable", + Input: []tf.Input{ + tree_handle, tree_config, + }, + } + return scope.AddOperation(opspec) +} + // ComputeAccidentalHitsAttr is an optional argument to ComputeAccidentalHits. type ComputeAccidentalHitsAttr func(optionalAttr) @@ -4829,6 +5093,119 @@ func CudnnRNNParamsToCanonical(scope *Scope, num_layers tf.Output, num_units tf. return weights, biases } +// CudnnRNNBackpropV3Attr is an optional argument to CudnnRNNBackpropV3. +type CudnnRNNBackpropV3Attr func(optionalAttr) + +// CudnnRNNBackpropV3RnnMode sets the optional rnn_mode attribute to value. +// If not specified, defaults to "lstm" +func CudnnRNNBackpropV3RnnMode(value string) CudnnRNNBackpropV3Attr { + return func(m optionalAttr) { + m["rnn_mode"] = value + } +} + +// CudnnRNNBackpropV3InputMode sets the optional input_mode attribute to value. +// If not specified, defaults to "linear_input" +func CudnnRNNBackpropV3InputMode(value string) CudnnRNNBackpropV3Attr { + return func(m optionalAttr) { + m["input_mode"] = value + } +} + +// CudnnRNNBackpropV3Direction sets the optional direction attribute to value. +// If not specified, defaults to "unidirectional" +func CudnnRNNBackpropV3Direction(value string) CudnnRNNBackpropV3Attr { + return func(m optionalAttr) { + m["direction"] = value + } +} + +// CudnnRNNBackpropV3Dropout sets the optional dropout attribute to value. +// If not specified, defaults to 0 +func CudnnRNNBackpropV3Dropout(value float32) CudnnRNNBackpropV3Attr { + return func(m optionalAttr) { + m["dropout"] = value + } +} + +// CudnnRNNBackpropV3Seed sets the optional seed attribute to value. +// If not specified, defaults to 0 +func CudnnRNNBackpropV3Seed(value int64) CudnnRNNBackpropV3Attr { + return func(m optionalAttr) { + m["seed"] = value + } +} + +// CudnnRNNBackpropV3Seed2 sets the optional seed2 attribute to value. +// If not specified, defaults to 0 +func CudnnRNNBackpropV3Seed2(value int64) CudnnRNNBackpropV3Attr { + return func(m optionalAttr) { + m["seed2"] = value + } +} + +// Backprop step of CudnnRNNV3. +// +// Compute the backprop of both data and weights in a RNN. Takes an extra +// "sequence_lengths" input than CudnnRNNBackprop. +// +// rnn_mode: Indicates the type of the RNN model. +// input_mode: Indicates whether there is a linear projection between the input and +// the actual computation before the first layer. 'skip_input' is only allowed +// when input_size == num_units; 'auto_select' implies 'skip_input' when +// input_size == num_units; otherwise, it implies 'linear_input'. +// direction: Indicates whether a bidirectional model will be used. Should be +// "unidirectional" or "bidirectional". +// dropout: Dropout probability. When set to 0., dropout is disabled. +// seed: The 1st part of a seed to initialize dropout. +// seed2: The 2nd part of a seed to initialize dropout. +// input: A 3-D tensor with the shape of [seq_length, batch_size, input_size]. +// input_h: A 3-D tensor with the shape of [num_layer * dir, batch_size, +// num_units]. +// input_c: For LSTM, a 3-D tensor with the shape of +// [num_layer * dir, batch, num_units]. For other models, it is ignored. +// params: A 1-D tensor that contains the weights and biases in an opaque layout. +// The size must be created through CudnnRNNParamsSize, and initialized +// separately. Note that they might not be compatible across different +// generations. So it is a good idea to save and restore +// sequence_lengths: a vector of lengths of each input sequence. +// output: A 3-D tensor with the shape of [seq_length, batch_size, +// dir * num_units]. +// output_h: The same shape has input_h. +// output_c: The same shape as input_c for LSTM. An empty tensor for other models. +// output_backprop: A 3-D tensor with the same shape as output in the forward pass. +// output_h_backprop: A 3-D tensor with the same shape as output_h in the forward +// pass. +// output_c_backprop: A 3-D tensor with the same shape as output_c in the forward +// pass. +// reserve_space: The same reserve_space produced in the forward operation. +// input_backprop: The backprop to input in the forward pass. Has the same shape +// as input. +// input_h_backprop: The backprop to input_h in the forward pass. Has the same +// shape as input_h. +// input_c_backprop: The backprop to input_c in the forward pass. Has the same +// shape as input_c. +// params_backprop: The backprop to the params buffer in the forward pass. Has the +// same shape as params. +func CudnnRNNBackpropV3(scope *Scope, input tf.Output, input_h tf.Output, input_c tf.Output, params tf.Output, sequence_lengths tf.Output, output tf.Output, output_h tf.Output, output_c tf.Output, output_backprop tf.Output, output_h_backprop tf.Output, output_c_backprop tf.Output, reserve_space tf.Output, host_reserved tf.Output, optional ...CudnnRNNBackpropV3Attr) (input_backprop tf.Output, input_h_backprop tf.Output, input_c_backprop tf.Output, params_backprop tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "CudnnRNNBackpropV3", + Input: []tf.Input{ + input, input_h, input_c, params, sequence_lengths, output, output_h, output_c, output_backprop, output_h_backprop, output_c_backprop, reserve_space, host_reserved, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1), op.Output(2), op.Output(3) +} + // CudnnRNNBackpropV2Attr is an optional argument to CudnnRNNBackpropV2. type CudnnRNNBackpropV2Attr func(optionalAttr) @@ -6697,6 +7074,34 @@ func Sign(scope *Scope, x tf.Output) (y tf.Output) { return op.Output(0) } +// Creates a dataset that passes a sliding window over `input_dataset`. +// +// Arguments: +// +// window_size: A scalar representing the number of elements in the +// sliding window. +// window_shift: A scalar representing the steps moving the sliding window +// forward in one iteration. It must be positive. +// window_stride: A scalar representing the stride of the input elements of the sliding window. +// It must be positive. +// +// +func ExperimentalSlidingWindowDataset(scope *Scope, input_dataset tf.Output, window_size tf.Output, window_shift tf.Output, window_stride tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "ExperimentalSlidingWindowDataset", + Input: []tf.Input{ + input_dataset, window_size, window_shift, window_stride, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Returns which elements of x are finite. // // @compatibility(numpy) @@ -7608,6 +8013,33 @@ func Inv(scope *Scope, x tf.Output) (y tf.Output) { return op.Output(0) } +// Creates a dataset that batches input elements into a SparseTensor. +// +// Arguments: +// input_dataset: A handle to an input dataset. Must have a single component. +// batch_size: A scalar representing the number of elements to accumulate in a +// batch. +// row_shape: A vector representing the dense shape of each row in the produced +// SparseTensor. The shape may be partially specified, using `-1` to indicate +// that a particular dimension should use the maximum size of all batch elements. +// +// +func ExperimentalDenseToSparseBatchDataset(scope *Scope, input_dataset tf.Output, batch_size tf.Output, row_shape tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "ExperimentalDenseToSparseBatchDataset", + Input: []tf.Input{ + input_dataset, batch_size, row_shape, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // ComplexAbsAttr is an optional argument to ComplexAbs. type ComplexAbsAttr func(optionalAttr) @@ -8442,6 +8874,26 @@ func ResourceSparseApplyAdadelta(scope *Scope, var_ tf.Output, accum tf.Output, return scope.AddOperation(opspec) } +// Checks whether a tree has been initialized. +// +// Arguments: +// tree_handle: Handle to the tree. +// +// Returns Whether the tree is initialized. +func TensorForestTreeIsInitializedOp(scope *Scope, tree_handle tf.Output) (is_initialized tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorForestTreeIsInitializedOp", + Input: []tf.Input{ + tree_handle, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Gets next element for the provided shard number. // // Arguments: @@ -10977,7 +11429,6 @@ func OneHotAxis(value int64) OneHotAttr { // ========= // // Suppose that -// // ``` // indices = [0, 2, -1, 1] // depth = 3 @@ -10987,16 +11438,15 @@ func OneHotAxis(value int64) OneHotAttr { // ``` // // Then output is `[4 x 3]`: -// -// ```output = -// [5.0 0.0 0.0] // one_hot(0) -// [0.0 0.0 5.0] // one_hot(2) -// [0.0 0.0 0.0] // one_hot(-1) -// [0.0 5.0 0.0] // one_hot(1) -// ``` +// ``` +// output = +// [5.0 0.0 0.0] // one_hot(0) +// [0.0 0.0 5.0] // one_hot(2) +// [0.0 0.0 0.0] // one_hot(-1) +// [0.0 5.0 0.0] // one_hot(1) +// ``` // // Suppose that -// // ``` // indices = [0, 2, -1, 1] // depth = 3 @@ -11006,19 +11456,19 @@ func OneHotAxis(value int64) OneHotAttr { // ``` // // Then output is `[3 x 4]`: +// ``` +// output = +// [0.0 3.0 3.0 3.0] +// [3.0 3.0 3.0 0.0] +// [3.0 3.0 3.0 3.0] +// [3.0 0.0 3.0 3.0] +// // ^ one_hot(0) +// // ^ one_hot(2) +// // ^ one_hot(-1) +// // ^ one_hot(1) +// ``` // -// ```output = -// [0.0 3.0 3.0 3.0] -// [3.0 3.0 3.0 0.0] -// [3.0 3.0 3.0 3.0] -// [3.0 0.0 3.0 3.0] -// // ^ one_hot(0) -// // ^ one_hot(2) -// // ^ one_hot(-1) -// // ^ one_hot(1) -// ``` // Suppose that -// // ``` // indices = [[0, 2], [1, -1]] // depth = 3 @@ -11028,15 +11478,16 @@ func OneHotAxis(value int64) OneHotAttr { // ``` // // Then output is `[2 x 2 x 3]`: -// -// ```output = -// [ -// [1.0, 0.0, 0.0] // one_hot(0) -// [0.0, 0.0, 1.0] // one_hot(2) -// ][ -// [0.0, 1.0, 0.0] // one_hot(1) -// [0.0, 0.0, 0.0] // one_hot(-1) -// ]``` +// ``` +// output = +// [ +// [1.0, 0.0, 0.0] // one_hot(0) +// [0.0, 0.0, 1.0] // one_hot(2) +// ][ +// [0.0, 1.0, 0.0] // one_hot(1) +// [0.0, 0.0, 0.0] // one_hot(-1) +// ] +// ``` // // Arguments: // indices: A tensor of indices. @@ -11594,8 +12045,8 @@ func ResizeBicubic(scope *Scope, images tf.Output, size tf.Output, optional ...R // Arguments: // params_nested_splits: The `nested_row_splits` tensors that define the row-partitioning for the // `params` RaggedTensor input. -// params_dense_values: The `inner_values` for the `params` RaggedTensor. There was a terminology change -// at the python level from dense_values to inner_values, so dense_values is the +// params_dense_values: The `flat_values` for the `params` RaggedTensor. There was a terminology change +// at the python level from dense_values to flat_values, so dense_values is the // deprecated name. // indices: Indices in the outermost dimension of `params` of the values that should be // gathered. @@ -11604,7 +12055,7 @@ func ResizeBicubic(scope *Scope, images tf.Output, size tf.Output, optional ...R // `indices.shape.ndims + params.ragged_rank - 1`. // // Returns The `nested_row_splits` tensors that define the row-partitioning for the -// returned RaggedTensor.The `inner_values` for the returned RaggedTensor. +// returned RaggedTensor.The `flat_values` for the returned RaggedTensor. func RaggedGather(scope *Scope, params_nested_splits []tf.Output, params_dense_values tf.Output, indices tf.Output, OUTPUT_RAGGED_RANK int64) (output_nested_splits []tf.Output, output_dense_values tf.Output) { if scope.Err() != nil { return @@ -11685,7 +12136,7 @@ func NonMaxSuppressionV2(scope *Scope, boxes tf.Output, scores tf.Output, max_ou // // Arguments: // rt_nested_splits: The `row_splits` for the `RaggedTensor`. -// rt_dense_values: The `inner_values` for the `RaggedTensor`. +// rt_dense_values: The `flat_values` for the `RaggedTensor`. // // Returns The indices for the `SparseTensor`.The values of the `SparseTensor`.`sparse_dense_shape` is a tight bounding box of the input `RaggedTensor`. func RaggedTensorToSparse(scope *Scope, rt_nested_splits []tf.Output, rt_dense_values tf.Output) (sparse_indices tf.Output, sparse_values tf.Output, sparse_dense_shape tf.Output) { @@ -12936,9 +13387,7 @@ func ResourceScatterNdAddUseLocking(value bool) ResourceScatterNdAddAttr { } } -// Adds sparse `updates` to individual values or slices within a given -// -// variable according to `indices`. +// Applies sparse addition to individual values or slices in a Variable. // // `ref` is a `Tensor` with rank `P` and `indices` is a `Tensor` of rank `Q`. // @@ -12952,24 +13401,24 @@ func ResourceScatterNdAddUseLocking(value bool) ResourceScatterNdAddAttr { // `updates` is `Tensor` of rank `Q-1+P-K` with shape: // // ``` -// [d_0, ..., d_{Q-2}, ref.shape[K], ..., ref.shape[P-1]]. +// [d_0, ..., d_{Q-2}, ref.shape[K], ..., ref.shape[P-1]] // ``` // -// For example, say we want to update 4 scattered elements to a rank-1 tensor to -// 8 elements. In Python, that update would look like this: +// For example, say we want to add 4 scattered elements to a rank-1 tensor to +// 8 elements. In Python, that addition would look like this: // // ```python -// ref = tf.Variable([1, 2, 3, 4, 5, 6, 7, 8], use_resource=True) -// indices = tf.constant([[4], [3], [1] ,[7]]) -// updates = tf.constant([9, 10, 11, 12]) -// update = tf.scatter_nd_add(ref, indices, updates) -// with tf.Session() as sess: -// print sess.run(update) +// ref = tf.Variable([1, 2, 3, 4, 5, 6, 7, 8], use_resource=True) +// indices = tf.constant([[4], [3], [1], [7]]) +// updates = tf.constant([9, 10, 11, 12]) +// add = tf.scatter_nd_add(ref, indices, updates) +// with tf.Session() as sess: +// print sess.run(add) // ``` // // The resulting update to ref would look like this: // -// [1, 12, 3, 14, 14, 6, 7, 20] +// [1, 13, 3, 14, 14, 6, 7, 20] // // See `tf.scatter_nd` for more details about how to make updates to // slices. @@ -13274,6 +13723,91 @@ func StatelessRandomNormal(scope *Scope, shape tf.Output, seed tf.Output, option return op.Output(0) } +// UnicodeDecodeAttr is an optional argument to UnicodeDecode. +type UnicodeDecodeAttr func(optionalAttr) + +// UnicodeDecodeErrors sets the optional errors attribute to value. +// +// value: Error handling policy when there is invalid formatting found in the input. +// The value of 'strict' will cause the operation to produce a InvalidArgument +// error on any invalid input formatting. A value of 'replace' (the default) will +// cause the operation to replace any invalid formatting in the input with the +// `replacement_char` codepoint. A value of 'ignore' will cause the operation to +// skip any invalid formatting in the input and produce no corresponding output +// character. +// If not specified, defaults to "replace" +func UnicodeDecodeErrors(value string) UnicodeDecodeAttr { + return func(m optionalAttr) { + m["errors"] = value + } +} + +// UnicodeDecodeReplacementChar sets the optional replacement_char attribute to value. +// +// value: The replacement character codepoint to be used in place of any invalid +// formatting in the input when `errors='replace'`. Any valid unicode codepoint may +// be used. The default value is the default unicode replacement character is +// 0xFFFD or U+65533.) +// If not specified, defaults to 65533 +func UnicodeDecodeReplacementChar(value int64) UnicodeDecodeAttr { + return func(m optionalAttr) { + m["replacement_char"] = value + } +} + +// UnicodeDecodeReplaceControlCharacters sets the optional replace_control_characters attribute to value. +// +// value: Whether to replace the C0 control characters (00-1F) with the +// `replacement_char`. Default is false. +// If not specified, defaults to false +func UnicodeDecodeReplaceControlCharacters(value bool) UnicodeDecodeAttr { + return func(m optionalAttr) { + m["replace_control_characters"] = value + } +} + +// Decodes each string in `input` into a sequence of Unicode code points. +// +// The character codepoints for all strings are returned using a single vector +// `char_values`, with strings expanded to characters in row-major order. +// +// The `row_splits` tensor indicates where the codepoints for +// each input string begin and end within the `char_values` tensor. +// In particular, the values for the `i`th +// string (in row-major order) are stored in the slice +// `[row_splits[i]:row_splits[i+1]]`. Thus: +// +// * `char_values[row_splits[i]+j]` is the Unicode codepoint for the `j`th +// character in the `i`th string (in row-major order). +// * `row_splits[i+1] - row_splits[i]` is the number of characters in the `i`th +// string (in row-major order). +// +// Arguments: +// input: The text to be decoded. Can have any shape. Note that the output is flattened +// to a vector of char values. +// input_encoding: Text encoding of the input strings. This is any of the encodings supported +// by ICU ucnv algorithmic converters. Examples: `"UTF-16", "US ASCII", "UTF-8"`. +// +// Returns A 1D int32 tensor containing the row splits.A 1D int32 Tensor containing the decoded codepoints. +func UnicodeDecode(scope *Scope, input tf.Output, input_encoding string, optional ...UnicodeDecodeAttr) (row_splits tf.Output, char_values tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"input_encoding": input_encoding} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "UnicodeDecode", + Input: []tf.Input{ + input, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1) +} + // Adds up a SparseTensor and a dense Tensor, using these special rules: // // (1) Broadcasts the dense side to have the same shape as the sparse side, if @@ -13322,6 +13856,84 @@ func Erfc(scope *Scope, x tf.Output) (y tf.Output) { return op.Output(0) } +// UnicodeEncodeAttr is an optional argument to UnicodeEncode. +type UnicodeEncodeAttr func(optionalAttr) + +// UnicodeEncodeErrors sets the optional errors attribute to value. +// +// value: Error handling policy when there is invalid formatting found in the input. +// The value of 'strict' will cause the operation to produce a InvalidArgument +// error on any invalid input formatting. A value of 'replace' (the default) will +// cause the operation to replace any invalid formatting in the input with the +// `replacement_char` codepoint. A value of 'ignore' will cause the operation to +// skip any invalid formatting in the input and produce no corresponding output +// character. +// If not specified, defaults to "replace" +func UnicodeEncodeErrors(value string) UnicodeEncodeAttr { + return func(m optionalAttr) { + m["errors"] = value + } +} + +// UnicodeEncodeReplacementChar sets the optional replacement_char attribute to value. +// +// value: The replacement character codepoint to be used in place of any invalid +// formatting in the input when `errors='replace'`. Any valid unicode codepoint may +// be used. The default value is the default unicode replacement character is +// 0xFFFD (U+65533). +// If not specified, defaults to 65533 +func UnicodeEncodeReplacementChar(value int64) UnicodeEncodeAttr { + return func(m optionalAttr) { + m["replacement_char"] = value + } +} + +// Encode a tensor of ints into unicode strings. +// +// Returns a vector of strings, where `output[i]` is constructed by encoding the +// Unicode codepoints in `input_values[input_splits[i]:input_splits[i+1]]` +// using `output_encoding`. +// +// --- +// +// Example: +// +// ``` +// input_values = [72, 101, 108, 108, 111, 87, 111, 114, 108, 100] +// input_splits = [0, 5, 10] +// output_encoding = 'UTF-8' +// +// output = ['Hello', 'World'] +// ``` +// +// Arguments: +// input_values: A 1D tensor containing the unicode codepoints that should be encoded. +// input_splits: A 1D tensor specifying how the unicode codepoints should be split into strings. +// In particular, `output[i]` is constructed by encoding the codepoints in the +// slice `input_values[input_splits[i]:input_splits[i+1]]`. +// output_encoding: Unicode encoding of the output strings. Valid encodings are: `"UTF-8", +// "UTF-16-BE", and "UTF-32-BE"`. +// +// Returns The 1-D Tensor of strings encoded from the provided unicode codepoints. +func UnicodeEncode(scope *Scope, input_values tf.Output, input_splits tf.Output, output_encoding string, optional ...UnicodeEncodeAttr) (output tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_encoding": output_encoding} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "UnicodeEncode", + Input: []tf.Input{ + input_values, input_splits, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Returns the number of tensors in the input tensor list. // // input_handle: the input list @@ -14644,6 +15256,117 @@ func MultiDeviceIteratorToStringHandle(scope *Scope, multi_device_iterator tf.Ou return op.Output(0) } +// CudnnRNNV3Attr is an optional argument to CudnnRNNV3. +type CudnnRNNV3Attr func(optionalAttr) + +// CudnnRNNV3RnnMode sets the optional rnn_mode attribute to value. +// If not specified, defaults to "lstm" +func CudnnRNNV3RnnMode(value string) CudnnRNNV3Attr { + return func(m optionalAttr) { + m["rnn_mode"] = value + } +} + +// CudnnRNNV3InputMode sets the optional input_mode attribute to value. +// If not specified, defaults to "linear_input" +func CudnnRNNV3InputMode(value string) CudnnRNNV3Attr { + return func(m optionalAttr) { + m["input_mode"] = value + } +} + +// CudnnRNNV3Direction sets the optional direction attribute to value. +// If not specified, defaults to "unidirectional" +func CudnnRNNV3Direction(value string) CudnnRNNV3Attr { + return func(m optionalAttr) { + m["direction"] = value + } +} + +// CudnnRNNV3Dropout sets the optional dropout attribute to value. +// If not specified, defaults to 0 +func CudnnRNNV3Dropout(value float32) CudnnRNNV3Attr { + return func(m optionalAttr) { + m["dropout"] = value + } +} + +// CudnnRNNV3Seed sets the optional seed attribute to value. +// If not specified, defaults to 0 +func CudnnRNNV3Seed(value int64) CudnnRNNV3Attr { + return func(m optionalAttr) { + m["seed"] = value + } +} + +// CudnnRNNV3Seed2 sets the optional seed2 attribute to value. +// If not specified, defaults to 0 +func CudnnRNNV3Seed2(value int64) CudnnRNNV3Attr { + return func(m optionalAttr) { + m["seed2"] = value + } +} + +// CudnnRNNV3IsTraining sets the optional is_training attribute to value. +// If not specified, defaults to true +func CudnnRNNV3IsTraining(value bool) CudnnRNNV3Attr { + return func(m optionalAttr) { + m["is_training"] = value + } +} + +// A RNN backed by cuDNN. +// +// Computes the RNN from the input and initial states, with respect to the params +// buffer. Accepts one extra input "sequence_lengths" than CudnnRNN. +// +// rnn_mode: Indicates the type of the RNN model. +// input_mode: Indicates whether there is a linear projection between the input and +// the actual computation before the first layer. 'skip_input' is only allowed +// when input_size == num_units; 'auto_select' implies 'skip_input' when +// input_size == num_units; otherwise, it implies 'linear_input'. +// direction: Indicates whether a bidirectional model will be used. Should be +// "unidirectional" or "bidirectional". +// dropout: Dropout probability. When set to 0., dropout is disabled. +// seed: The 1st part of a seed to initialize dropout. +// seed2: The 2nd part of a seed to initialize dropout. +// input: A 3-D tensor with the shape of [seq_length, batch_size, input_size]. +// input_h: A 3-D tensor with the shape of [num_layer * dir, batch_size, +// num_units]. +// input_c: For LSTM, a 3-D tensor with the shape of +// [num_layer * dir, batch, num_units]. For other models, it is ignored. +// params: A 1-D tensor that contains the weights and biases in an opaque layout. +// The size must be created through CudnnRNNParamsSize, and initialized +// separately. Note that they might not be compatible across different +// generations. So it is a good idea to save and restore +// sequence_lengths: a vector of lengths of each input sequence. +// output: A 3-D tensor with the shape of [seq_length, batch_size, +// dir * num_units]. +// output_h: The same shape has input_h. +// output_c: The same shape as input_c for LSTM. An empty tensor for other models. +// is_training: Indicates whether this operation is used for inferenece or +// training. +// reserve_space: An opaque tensor that can be used in backprop calculation. It +// is only produced if is_training is true. +func CudnnRNNV3(scope *Scope, input tf.Output, input_h tf.Output, input_c tf.Output, params tf.Output, sequence_lengths tf.Output, optional ...CudnnRNNV3Attr) (output tf.Output, output_h tf.Output, output_c tf.Output, reserve_space tf.Output, host_reserved tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "CudnnRNNV3", + Input: []tf.Input{ + input, input_h, input_c, params, sequence_lengths, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1), op.Output(2), op.Output(3), op.Output(4) +} + // Applies softmax to a batched N-D `SparseTensor`. // // The inputs represent an N-D SparseTensor with logical shape `[..., B, C]` @@ -16873,6 +17596,23 @@ func SparseReduceSum(scope *Scope, input_indices tf.Output, input_values tf.Outp return op.Output(0) } +// Records the latency of producing `input_dataset` elements in a StatsAggregator. +func ExperimentalLatencyStatsDataset(scope *Scope, input_dataset tf.Output, tag tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "ExperimentalLatencyStatsDataset", + Input: []tf.Input{ + input_dataset, tag, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // SparseTensorDenseMatMulAttr is an optional argument to SparseTensorDenseMatMul. type SparseTensorDenseMatMulAttr func(optionalAttr) @@ -17555,6 +18295,69 @@ func Timestamp(scope *Scope) (ts tf.Output) { return op.Output(0) } +// ResourceSparseApplyKerasMomentumAttr is an optional argument to ResourceSparseApplyKerasMomentum. +type ResourceSparseApplyKerasMomentumAttr func(optionalAttr) + +// ResourceSparseApplyKerasMomentumUseLocking sets the optional use_locking attribute to value. +// +// value: If `True`, updating of the var and accum tensors will be protected +// by a lock; otherwise the behavior is undefined, but may exhibit less +// contention. +// If not specified, defaults to false +func ResourceSparseApplyKerasMomentumUseLocking(value bool) ResourceSparseApplyKerasMomentumAttr { + return func(m optionalAttr) { + m["use_locking"] = value + } +} + +// ResourceSparseApplyKerasMomentumUseNesterov sets the optional use_nesterov attribute to value. +// +// value: If `True`, the tensor passed to compute grad will be +// var + momentum * accum, so in the end, the var you get is actually +// var + momentum * accum. +// If not specified, defaults to false +func ResourceSparseApplyKerasMomentumUseNesterov(value bool) ResourceSparseApplyKerasMomentumAttr { + return func(m optionalAttr) { + m["use_nesterov"] = value + } +} + +// Update relevant entries in '*var' and '*accum' according to the momentum scheme. +// +// Set use_nesterov = True if you want to use Nesterov momentum. +// +// That is for rows we have grad for, we update var and accum as follows: +// +// accum = accum * momentum - lr * grad +// var += accum +// +// Arguments: +// var_: Should be from a Variable(). +// accum: Should be from a Variable(). +// lr: Learning rate. Must be a scalar. +// grad: The gradient. +// indices: A vector of indices into the first dimension of var and accum. +// momentum: Momentum. Must be a scalar. +// +// Returns the created operation. +func ResourceSparseApplyKerasMomentum(scope *Scope, var_ tf.Output, accum tf.Output, lr tf.Output, grad tf.Output, indices tf.Output, momentum tf.Output, optional ...ResourceSparseApplyKerasMomentumAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ResourceSparseApplyKerasMomentum", + Input: []tf.Input{ + var_, accum, lr, grad, indices, momentum, + }, + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + // VariableShapeAttr is an optional argument to VariableShape. type VariableShapeAttr func(optionalAttr) @@ -18293,6 +19096,96 @@ func DivNoNan(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { return op.Output(0) } +// UnicodeDecodeWithOffsetsAttr is an optional argument to UnicodeDecodeWithOffsets. +type UnicodeDecodeWithOffsetsAttr func(optionalAttr) + +// UnicodeDecodeWithOffsetsErrors sets the optional errors attribute to value. +// +// value: Error handling policy when there is invalid formatting found in the input. +// The value of 'strict' will cause the operation to produce a InvalidArgument +// error on any invalid input formatting. A value of 'replace' (the default) will +// cause the operation to replace any invalid formatting in the input with the +// `replacement_char` codepoint. A value of 'ignore' will cause the operation to +// skip any invalid formatting in the input and produce no corresponding output +// character. +// If not specified, defaults to "replace" +func UnicodeDecodeWithOffsetsErrors(value string) UnicodeDecodeWithOffsetsAttr { + return func(m optionalAttr) { + m["errors"] = value + } +} + +// UnicodeDecodeWithOffsetsReplacementChar sets the optional replacement_char attribute to value. +// +// value: The replacement character codepoint to be used in place of any invalid +// formatting in the input when `errors='replace'`. Any valid unicode codepoint may +// be used. The default value is the default unicode replacement character is +// 0xFFFD or U+65533.) +// If not specified, defaults to 65533 +func UnicodeDecodeWithOffsetsReplacementChar(value int64) UnicodeDecodeWithOffsetsAttr { + return func(m optionalAttr) { + m["replacement_char"] = value + } +} + +// UnicodeDecodeWithOffsetsReplaceControlCharacters sets the optional replace_control_characters attribute to value. +// +// value: Whether to replace the C0 control characters (00-1F) with the +// `replacement_char`. Default is false. +// If not specified, defaults to false +func UnicodeDecodeWithOffsetsReplaceControlCharacters(value bool) UnicodeDecodeWithOffsetsAttr { + return func(m optionalAttr) { + m["replace_control_characters"] = value + } +} + +// Decodes each string in `input` into a sequence of Unicode code points. +// +// The character codepoints for all strings are returned using a single vector +// `char_values`, with strings expanded to characters in row-major order. +// Similarly, the character start byte offsets are returned using a single vector +// `char_to_byte_starts`, with strings expanded in row-major order. +// +// The `row_splits` tensor indicates where the codepoints and start offsets for +// each input string begin and end within the `char_values` and +// `char_to_byte_starts` tensors. In particular, the values for the `i`th +// string (in row-major order) are stored in the slice +// `[row_splits[i]:row_splits[i+1]]`. Thus: +// +// * `char_values[row_splits[i]+j]` is the Unicode codepoint for the `j`th +// character in the `i`th string (in row-major order). +// * `char_to_bytes_starts[row_splits[i]+j]` is the start byte offset for the `j`th +// character in the `i`th string (in row-major order). +// * `row_splits[i+1] - row_splits[i]` is the number of characters in the `i`th +// string (in row-major order). +// +// Arguments: +// input: The text to be decoded. Can have any shape. Note that the output is flattened +// to a vector of char values. +// input_encoding: Text encoding of the input strings. This is any of the encodings supported +// by ICU ucnv algorithmic converters. Examples: `"UTF-16", "US ASCII", "UTF-8"`. +// +// Returns A 1D int32 tensor containing the row splits.A 1D int32 Tensor containing the decoded codepoints.A 1D int32 Tensor containing the byte index in the input string where each +// character in `char_values` starts. +func UnicodeDecodeWithOffsets(scope *Scope, input tf.Output, input_encoding string, optional ...UnicodeDecodeWithOffsetsAttr) (row_splits tf.Output, char_values tf.Output, char_to_byte_starts tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"input_encoding": input_encoding} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "UnicodeDecodeWithOffsets", + Input: []tf.Input{ + input, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1), op.Output(2) +} + // Returns x - y element-wise. // // *NOTE*: `Subtract` supports broadcasting. More about broadcasting @@ -20606,15 +21499,62 @@ func Zeta(scope *Scope, x tf.Output, q tf.Output) (z tf.Output) { return op.Output(0) } +// Returns the cardinality of `input_dataset`. +// +// Returns the cardinality of `input_dataset`. +// +// Arguments: +// input_dataset: A variant tensor representing the dataset to return cardinality for. +// +// Returns The cardinality of `input_dataset`. Named constants are used to represent +// infinite and unknown cardinality. +func ExperimentalDatasetCardinality(scope *Scope, input_dataset tf.Output) (cardinality tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "ExperimentalDatasetCardinality", + Input: []tf.Input{ + input_dataset, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Creates a dataset that executes a SQL query and emits rows of the result set. +// +// Arguments: +// driver_name: The database type. Currently, the only supported type is 'sqlite'. +// data_source_name: A connection string to connect to the database. +// query: A SQL query to execute. +// +// +func ExperimentalSqlDataset(scope *Scope, driver_name tf.Output, data_source_name tf.Output, query tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "ExperimentalSqlDataset", + Input: []tf.Input{ + driver_name, data_source_name, query, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Inverse fast Fourier transform. // // Computes the inverse 1-dimensional discrete Fourier transform over the // inner-most dimension of `input`. // // Arguments: -// input: A complex64 tensor. +// input: A complex tensor. // -// Returns A complex64 tensor of the same shape as `input`. The inner-most +// Returns A complex tensor of the same shape as `input`. The inner-most // dimension of `input` is replaced with its inverse 1D Fourier transform. // // @compatibility(numpy) @@ -20640,9 +21580,9 @@ func IFFT(scope *Scope, input tf.Output) (output tf.Output) { // 2 dimensions of `input`. // // Arguments: -// input: A complex64 tensor. +// input: A complex tensor. // -// Returns A complex64 tensor of the same shape as `input`. The inner-most 2 +// Returns A complex tensor of the same shape as `input`. The inner-most 2 // dimensions of `input` are replaced with their 2D Fourier transform. // // @compatibility(numpy) @@ -20668,9 +21608,9 @@ func FFT2D(scope *Scope, input tf.Output) (output tf.Output) { // inner-most 2 dimensions of `input`. // // Arguments: -// input: A complex64 tensor. +// input: A complex tensor. // -// Returns A complex64 tensor of the same shape as `input`. The inner-most 2 +// Returns A complex tensor of the same shape as `input`. The inner-most 2 // dimensions of `input` are replaced with their inverse 2D Fourier transform. // // @compatibility(numpy) @@ -21010,6 +21950,44 @@ func ReaderNumRecordsProducedV2(scope *Scope, reader_handle tf.Output) (records_ return op.Output(0) } +// TensorListConcatAttr is an optional argument to TensorListConcat. +type TensorListConcatAttr func(optionalAttr) + +// TensorListConcatElementShape sets the optional element_shape attribute to value. +// If not specified, defaults to +func TensorListConcatElementShape(value tf.Shape) TensorListConcatAttr { + return func(m optionalAttr) { + m["element_shape"] = value + } +} + +// Concats all tensors in the list along the 0th dimension. +// +// Requires that all tensors have the same shape except the first dimension. +// +// input_handle: The input list. +// tensor: The concated result. +// lengths: Output tensor containing sizes of the 0th dimension of tensors in the list, used for computing the gradient. +// +func TensorListConcat(scope *Scope, input_handle tf.Output, element_dtype tf.DataType, optional ...TensorListConcatAttr) (tensor tf.Output, lengths tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"element_dtype": element_dtype} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "TensorListConcat", + Input: []tf.Input{ + input_handle, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1) +} + // Returns the set of files matching one or more glob patterns. // // Note that this routine only supports wildcard characters in the @@ -22051,6 +23029,61 @@ func SparseSparseMinimum(scope *Scope, a_indices tf.Output, a_values tf.Output, return op.Output(0), op.Output(1) } +// ResourceApplyAdamWithAmsgradAttr is an optional argument to ResourceApplyAdamWithAmsgrad. +type ResourceApplyAdamWithAmsgradAttr func(optionalAttr) + +// ResourceApplyAdamWithAmsgradUseLocking sets the optional use_locking attribute to value. +// +// value: If `True`, updating of the var, m, and v tensors will be protected +// by a lock; otherwise the behavior is undefined, but may exhibit less +// contention. +// If not specified, defaults to false +func ResourceApplyAdamWithAmsgradUseLocking(value bool) ResourceApplyAdamWithAmsgradAttr { + return func(m optionalAttr) { + m["use_locking"] = value + } +} + +// Update '*var' according to the Adam algorithm. +// +// $$lr_t := \text{learning\_rate} * \sqrt{1 - beta_2^t} / (1 - beta_1^t)$$ +// $$m_t := beta_1 * m_{t-1} + (1 - beta_1) * g$$ +// $$v_t := beta_2 * v_{t-1} + (1 - beta_2) * g * g$$ +// $$vhat_t := max{vhat_{t-1}, v_t}$$ +// $$variable := variable - lr_t * m_t / (\sqrt{vhat_t} + \epsilon)$$ +// +// Arguments: +// var_: Should be from a Variable(). +// m: Should be from a Variable(). +// v: Should be from a Variable(). +// vhat: Should be from a Variable(). +// beta1_power: Must be a scalar. +// beta2_power: Must be a scalar. +// lr: Scaling factor. Must be a scalar. +// beta1: Momentum factor. Must be a scalar. +// beta2: Momentum factor. Must be a scalar. +// epsilon: Ridge term. Must be a scalar. +// grad: The gradient. +// +// Returns the created operation. +func ResourceApplyAdamWithAmsgrad(scope *Scope, var_ tf.Output, m tf.Output, v tf.Output, vhat tf.Output, beta1_power tf.Output, beta2_power tf.Output, lr tf.Output, beta1 tf.Output, beta2 tf.Output, epsilon tf.Output, grad tf.Output, optional ...ResourceApplyAdamWithAmsgradAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ResourceApplyAdamWithAmsgrad", + Input: []tf.Input{ + var_, m, v, vhat, beta1_power, beta2_power, lr, beta1, beta2, epsilon, grad, + }, + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + // MapUnstageNoKeyAttr is an optional argument to MapUnstageNoKey. type MapUnstageNoKeyAttr func(optionalAttr) @@ -22452,6 +23485,93 @@ func SetSize(scope *Scope, set_indices tf.Output, set_values tf.Output, set_shap return op.Output(0) } +// Adds sparse `updates` to an existing tensor according to `indices`. +// +// This operation creates a new tensor by adding sparse `updates` to the passed +// in `tensor`. +// This operation is very similar to `tf.scatter_nd_add`, except that the updates +// are added onto an existing tensor (as opposed to a variable). If the memory +// for the existing tensor cannot be re-used, a copy is made and updated. +// +// `indices` is an integer tensor containing indices into a new tensor of shape +// `shape`. The last dimension of `indices` can be at most the rank of `shape`: +// +// indices.shape[-1] <= shape.rank +// +// The last dimension of `indices` corresponds to indices into elements +// (if `indices.shape[-1] = shape.rank`) or slices +// (if `indices.shape[-1] < shape.rank`) along dimension `indices.shape[-1]` of +// `shape`. `updates` is a tensor with shape +// +// indices.shape[:-1] + shape[indices.shape[-1]:] +// +// The simplest form of tensor_scatter_add is to add individual elements to a +// tensor by index. For example, say we want to add 4 elements in a rank-1 +// tensor with 8 elements. +// +// In Python, this scatter add operation would look like this: +// +// ```python +// indices = tf.constant([[4], [3], [1], [7]]) +// updates = tf.constant([9, 10, 11, 12]) +// tensor = tf.ones([8], dtype=tf.int32) +// updated = tf.tensor_scatter_add(tensor, indices, updates) +// with tf.Session() as sess: +// print(sess.run(scatter)) +// ``` +// +// The resulting tensor would look like this: +// +// [1, 12, 1, 11, 10, 1, 1, 13] +// +// We can also, insert entire slices of a higher rank tensor all at once. For +// example, if we wanted to insert two slices in the first dimension of a +// rank-3 tensor with two matrices of new values. +// +// In Python, this scatter add operation would look like this: +// +// ```python +// indices = tf.constant([[0], [2]]) +// updates = tf.constant([[[5, 5, 5, 5], [6, 6, 6, 6], +// [7, 7, 7, 7], [8, 8, 8, 8]], +// [[5, 5, 5, 5], [6, 6, 6, 6], +// [7, 7, 7, 7], [8, 8, 8, 8]]]) +// tensor = tf.ones([4, 4, 4]) +// updated = tf.tensor_scatter_add(tensor, indices, updates) +// with tf.Session() as sess: +// print(sess.run(scatter)) +// ``` +// +// The resulting tensor would look like this: +// +// [[[6, 6, 6, 6], [7, 7, 7, 7], [8, 8, 8, 8], [9, 9, 9, 9]], +// [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], +// [[6, 6, 6, 6], [7, 7, 7, 7], [8, 8, 8, 8], [9, 9, 9, 9]], +// [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]] +// +// Note that on CPU, if an out of bound index is found, an error is returned. +// On GPU, if an out of bound index is found, the index is ignored. +// +// Arguments: +// tensor: Tensor to copy/update. +// indices: Index tensor. +// updates: Updates to scatter into output. +// +// Returns A new tensor copied from tensor and updates added according to the indices. +func TensorScatterAdd(scope *Scope, tensor tf.Output, indices tf.Output, updates tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorScatterAdd", + Input: []tf.Input{ + tensor, indices, updates, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Computes the sign and the log of the absolute value of the determinant of // // one or more square matrices. @@ -22676,6 +23796,84 @@ func QuantizedResizeBilinear(scope *Scope, images tf.Output, size tf.Output, min return op.Output(0), op.Output(1), op.Output(2) } +// Creates a dataset that uses a custom thread pool to compute `input_dataset`. +// +// Arguments: +// +// num_threads: Identifies the number of threads to use for the private threadpool. +// +// +func ExperimentalPrivateThreadPoolDataset(scope *Scope, input_dataset tf.Output, num_threads tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "ExperimentalPrivateThreadPoolDataset", + Input: []tf.Input{ + input_dataset, num_threads, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// ExperimentalParseExampleDatasetAttr is an optional argument to ExperimentalParseExampleDataset. +type ExperimentalParseExampleDatasetAttr func(optionalAttr) + +// ExperimentalParseExampleDatasetSloppy sets the optional sloppy attribute to value. +// If not specified, defaults to false +func ExperimentalParseExampleDatasetSloppy(value bool) ExperimentalParseExampleDatasetAttr { + return func(m optionalAttr) { + m["sloppy"] = value + } +} + +// Transforms `input_dataset` containing `Example` protos as vectors of DT_STRING into a dataset of `Tensor` or `SparseTensor` objects representing the parsed features. +// +// Arguments: +// +// +// dense_defaults: A dict mapping string keys to `Tensor`s. +// The keys of the dict must match the dense_keys of the feature. +// sparse_keys: A list of string keys in the examples features. +// The results for these keys will be returned as `SparseTensor` objects. +// dense_keys: A list of Ndense string Tensors (scalars). +// The keys expected in the Examples features associated with dense values. +// sparse_types: A list of `DTypes` of the same length as `sparse_keys`. +// Only `tf.float32` (`FloatList`), `tf.int64` (`Int64List`), +// and `tf.string` (`BytesList`) are supported. +// dense_shapes: List of tuples with the same length as `dense_keys`. +// The shape of the data for each dense feature referenced by `dense_keys`. +// Required for any input tensors identified by `dense_keys`. Must be +// either fully defined, or may contain an unknown first dimension. +// An unknown first dimension means the feature is treated as having +// a variable number of blocks, and the output shape along this dimension +// is considered unknown at graph build time. Padding is applied for +// minibatch elements smaller than the maximum number of blocks for the +// given feature along this dimension. +// output_types: The type list for the return values. +// output_shapes: The list of shapes being produced. +func ExperimentalParseExampleDataset(scope *Scope, input_dataset tf.Output, num_parallel_calls tf.Output, dense_defaults []tf.Output, sparse_keys []string, dense_keys []string, sparse_types []tf.DataType, dense_shapes []tf.Shape, output_types []tf.DataType, output_shapes []tf.Shape, optional ...ExperimentalParseExampleDatasetAttr) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"sparse_keys": sparse_keys, "dense_keys": dense_keys, "sparse_types": sparse_types, "dense_shapes": dense_shapes, "output_types": output_types, "output_shapes": output_shapes} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ExperimentalParseExampleDataset", + Input: []tf.Input{ + input_dataset, num_parallel_calls, tf.OutputList(dense_defaults), + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // SdcaOptimizerAttr is an optional argument to SdcaOptimizer. type SdcaOptimizerAttr func(optionalAttr) @@ -23420,6 +24618,26 @@ func MatMul(scope *Scope, a tf.Output, b tf.Output, optional ...MatMulAttr) (pro return op.Output(0) } +// Serializes the tree handle to a proto +// +// Arguments: +// tree_handle: Handle to the tree resource to be serialized. +// +// Returns Serialied proto string of the tree resource. +func TensorForestTreeSerialize(scope *Scope, tree_handle tf.Output) (tree_config tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorForestTreeSerialize", + Input: []tf.Input{ + tree_handle, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // SparseMatMulAttr is an optional argument to SparseMatMul. type SparseMatMulAttr func(optionalAttr) @@ -26575,6 +27793,28 @@ func Invert(scope *Scope, x tf.Output) (y tf.Output) { return op.Output(0) } +// Deserialize bucket boundaries and ready flag into current QuantileAccumulator. +// +// An op that deserializes bucket boundaries and are boundaries ready flag into current QuantileAccumulator. +// +// Arguments: +// quantile_stream_resource_handle: resource handle referring to a QuantileStreamResource. +// bucket_boundaries: float; List of Rank 1 Tensors each containing the bucket boundaries for a feature. +// +// Returns the created operation. +func BoostedTreesQuantileStreamResourceDeserialize(scope *Scope, quantile_stream_resource_handle tf.Output, bucket_boundaries []tf.Output) (o *tf.Operation) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "BoostedTreesQuantileStreamResourceDeserialize", + Input: []tf.Input{ + quantile_stream_resource_handle, tf.OutputList(bucket_boundaries), + }, + } + return scope.AddOperation(opspec) +} + // Inverse 3D fast Fourier transform. // // Computes the inverse 3-dimensional discrete Fourier transform over the @@ -27102,6 +28342,29 @@ func AudioSummaryV2(scope *Scope, tag tf.Output, tensor tf.Output, sample_rate t return op.Output(0) } +// Splits a tensor into a list. +// +// list[i] corresponds to lengths[i] tensors from the input tensor. +// The tensor must have rank at least 1 and contain exactly sum(lengths) elements. +// +// tensor: The input tensor. +// element_shape: A shape compatible with that of elements in the tensor. +// lengths: Vector of sizes of the 0th dimension of tensors in the list. +// output_handle: The list. +func TensorListSplit(scope *Scope, tensor tf.Output, element_shape tf.Output, lengths tf.Output) (output_handle tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorListSplit", + Input: []tf.Input{ + tensor, element_shape, lengths, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // AvgPoolAttr is an optional argument to AvgPool. type AvgPoolAttr func(optionalAttr) @@ -27222,6 +28485,26 @@ func TensorListGetItem(scope *Scope, input_handle tf.Output, index tf.Output, el return op.Output(0) } +// Resizes the list. +// +// +// input_handle: the input list +// size: size of the output list +// +func TensorListResize(scope *Scope, input_handle tf.Output, size tf.Output) (output_handle tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorListResize", + Input: []tf.Input{ + input_handle, size, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Returns a diagonal tensor with a given diagonal values. // // Given a `diagonal`, this operation returns a tensor with the `diagonal` and @@ -27412,7 +28695,7 @@ func TensorListScatter(scope *Scope, tensor tf.Output, indices tf.Output, elemen // limits: The limits of each range. // deltas: The deltas of each range. // -// Returns The `row_splits` for the returned `RaggedTensor`.The `inner_values` for the returned `RaggedTensor`. +// Returns The `row_splits` for the returned `RaggedTensor`.The `flat_values` for the returned `RaggedTensor`. func RaggedRange(scope *Scope, starts tf.Output, limits tf.Output, deltas tf.Output) (rt_nested_splits tf.Output, rt_dense_values tf.Output) { if scope.Err() != nil { return @@ -27744,6 +29027,66 @@ func AdjustSaturation(scope *Scope, images tf.Output, scale tf.Output) (output t return op.Output(0) } +// ResourceApplyKerasMomentumAttr is an optional argument to ResourceApplyKerasMomentum. +type ResourceApplyKerasMomentumAttr func(optionalAttr) + +// ResourceApplyKerasMomentumUseLocking sets the optional use_locking attribute to value. +// +// value: If `True`, updating of the var and accum tensors will be protected +// by a lock; otherwise the behavior is undefined, but may exhibit less +// contention. +// If not specified, defaults to false +func ResourceApplyKerasMomentumUseLocking(value bool) ResourceApplyKerasMomentumAttr { + return func(m optionalAttr) { + m["use_locking"] = value + } +} + +// ResourceApplyKerasMomentumUseNesterov sets the optional use_nesterov attribute to value. +// +// value: If `True`, the tensor passed to compute grad will be +// var + momentum * accum, so in the end, the var you get is actually +// var + momentum * accum. +// If not specified, defaults to false +func ResourceApplyKerasMomentumUseNesterov(value bool) ResourceApplyKerasMomentumAttr { + return func(m optionalAttr) { + m["use_nesterov"] = value + } +} + +// Update '*var' according to the momentum scheme. Set use_nesterov = True if you +// +// want to use Nesterov momentum. +// +// accum = accum * momentum - lr * grad +// var += accum +// +// Arguments: +// var_: Should be from a Variable(). +// accum: Should be from a Variable(). +// lr: Scaling factor. Must be a scalar. +// grad: The gradient. +// momentum: Momentum. Must be a scalar. +// +// Returns the created operation. +func ResourceApplyKerasMomentum(scope *Scope, var_ tf.Output, accum tf.Output, lr tf.Output, grad tf.Output, momentum tf.Output, optional ...ResourceApplyKerasMomentumAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ResourceApplyKerasMomentum", + Input: []tf.Input{ + var_, accum, lr, grad, momentum, + }, + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + // MatrixSolveAttr is an optional argument to MatrixSolve. type MatrixSolveAttr func(optionalAttr) @@ -27813,6 +29156,70 @@ func DatasetToGraph(scope *Scope, input_dataset tf.Output) (graph tf.Output) { return op.Output(0) } +// LuAttr is an optional argument to Lu. +type LuAttr func(optionalAttr) + +// LuOutputIdxType sets the optional output_idx_type attribute to value. +// If not specified, defaults to DT_INT32 +func LuOutputIdxType(value tf.DataType) LuAttr { + return func(m optionalAttr) { + m["output_idx_type"] = value + } +} + +// Computes the LU decomposition of one or more square matrices. +// +// The input is a tensor of shape `[..., M, M]` whose inner-most 2 dimensions +// form square matrices. +// +// The input has to be invertible. +// +// The output consists of two tensors LU and P containing the LU decomposition +// of all input submatrices `[..., :, :]`. LU encodes the lower triangular and +// upper triangular factors. +// +// For each input submatrix of shape `[M, M]`, L is a lower triangular matrix of +// shape `[M, M]` with unit diagonal whose entries correspond to the strictly lower +// triangular part of LU. U is a upper triangular matrix of shape `[M, M]` whose +// entries correspond to the upper triangular part, including the diagonal, of LU. +// +// P represents a permutation matrix encoded as a list of indices each between `0` +// and `M-1`, inclusive. If P_mat denotes the permutation matrix corresponding to +// P, then the L, U and P satisfies P_mat * input = L * U. +// +// Arguments: +// input: A tensor of shape `[..., M, M]` whose inner-most 2 dimensions form matrices of +// size `[M, M]`. +// +// Returns A tensor of shape `[..., M, M]` whose strictly lower triangular part denotes the +// lower triangular factor `L` with unit diagonal, and whose upper triangular part +// denotes the upper triangular factor `U`.Permutation of the rows encoded as a list of indices in `0..M-1`. Shape is +// `[..., M]`. +// @compatibility(scipy) +// Similar to `scipy.linalg.lu`, except the triangular factors `L` and `U` are +// packed into a single tensor, the permutation is applied to `input` instead of +// the right hand side and the permutation `P` is returned as a list of indices +// instead of a permutation matrix. +// @end_compatibility +func Lu(scope *Scope, input tf.Output, optional ...LuAttr) (lu tf.Output, p tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "Lu", + Input: []tf.Input{ + input, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1) +} + // Computes the matrix square root of one or more square matrices: // // matmul(sqrtm(A), sqrtm(A)) = A @@ -29948,6 +31355,43 @@ func Iterator(scope *Scope, shared_name string, container string, output_types [ return op.Output(0) } +// TensorForestTreeResourceHandleOpAttr is an optional argument to TensorForestTreeResourceHandleOp. +type TensorForestTreeResourceHandleOpAttr func(optionalAttr) + +// TensorForestTreeResourceHandleOpContainer sets the optional container attribute to value. +// If not specified, defaults to "" +func TensorForestTreeResourceHandleOpContainer(value string) TensorForestTreeResourceHandleOpAttr { + return func(m optionalAttr) { + m["container"] = value + } +} + +// TensorForestTreeResourceHandleOpSharedName sets the optional shared_name attribute to value. +// If not specified, defaults to "" +func TensorForestTreeResourceHandleOpSharedName(value string) TensorForestTreeResourceHandleOpAttr { + return func(m optionalAttr) { + m["shared_name"] = value + } +} + +// Creates a handle to a TensorForestTreeResource +func TensorForestTreeResourceHandleOp(scope *Scope, optional ...TensorForestTreeResourceHandleOpAttr) (resource tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "TensorForestTreeResourceHandleOp", + + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // CropAndResizeGradImageAttr is an optional argument to CropAndResizeGradImage. type CropAndResizeGradImageAttr func(optionalAttr) @@ -30312,6 +31756,29 @@ func FakeParam(scope *Scope, dtype tf.DataType, shape tf.Shape) (output tf.Outpu return op.Output(0) } +// Returns the next representable value of `x1` in the direction of `x2`, element-wise. +// +// This operation returns the same result as the C++ std::nextafter function. +// +// It can also return a subnormal number. +// +// @compatibility(cpp) +// Equivalent to C++ std::nextafter function. +// @end_compatibility +func NextAfter(scope *Scope, x1 tf.Output, x2 tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "NextAfter", + Input: []tf.Input{ + x1, x2, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Computes the gradient for the inverse of `x` wrt its input. // // Specifically, `grad = -dy * y*y`, where `y = 1/x`, and `dy` @@ -30461,6 +31928,71 @@ func BoostedTreesQuantileStreamResourceAddSummaries(scope *Scope, quantile_strea return scope.AddOperation(opspec) } +// Creates a Dataset that returns pseudorandom numbers. +// +// Arguments: +// seed: A scalar seed for the random number generator. If either seed or +// seed2 is set to be non-zero, the random number generator is seeded +// by the given seed. Otherwise, a random seed is used. +// seed2: A second scalar seed to avoid seed collision. +// +// +func ExperimentalRandomDataset(scope *Scope, seed tf.Output, seed2 tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "ExperimentalRandomDataset", + Input: []tf.Input{ + seed, seed2, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// A dataset that splits the elements of its input into multiple elements. +func ExperimentalUnbatchDataset(scope *Scope, input_dataset tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "ExperimentalUnbatchDataset", + Input: []tf.Input{ + input_dataset, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Creates a dataset that overrides the maximum intra-op parallelism. +// +// Arguments: +// +// max_intra_op_parallelism: Identifies the maximum intra-op parallelism to use. +// +// +func ExperimentalMaxIntraOpParallelismDataset(scope *Scope, input_dataset tf.Output, max_intra_op_parallelism tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "ExperimentalMaxIntraOpParallelismDataset", + Input: []tf.Input{ + input_dataset, max_intra_op_parallelism, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // StringSplitV2Attr is an optional argument to StringSplitV2. type StringSplitV2Attr func(optionalAttr) @@ -30823,6 +32355,83 @@ func DeserializeIterator(scope *Scope, resource_handle tf.Output, serialized tf. return scope.AddOperation(opspec) } +// ResourceScatterNdSubAttr is an optional argument to ResourceScatterNdSub. +type ResourceScatterNdSubAttr func(optionalAttr) + +// ResourceScatterNdSubUseLocking sets the optional use_locking attribute to value. +// +// value: An optional bool. Defaults to True. If True, the assignment will +// be protected by a lock; otherwise the behavior is undefined, +// but may exhibit less contention. +// If not specified, defaults to true +func ResourceScatterNdSubUseLocking(value bool) ResourceScatterNdSubAttr { + return func(m optionalAttr) { + m["use_locking"] = value + } +} + +// Applies sparse subtraction to individual values or slices in a Variable. +// +// `ref` is a `Tensor` with rank `P` and `indices` is a `Tensor` of rank `Q`. +// +// `indices` must be integer tensor, containing indices into `ref`. +// It must be shape `[d_0, ..., d_{Q-2}, K]` where `0 < K <= P`. +// +// The innermost dimension of `indices` (with length `K`) corresponds to +// indices into elements (if `K = P`) or slices (if `K < P`) along the `K`th +// dimension of `ref`. +// +// `updates` is `Tensor` of rank `Q-1+P-K` with shape: +// +// ``` +// [d_0, ..., d_{Q-2}, ref.shape[K], ..., ref.shape[P-1]] +// ``` +// +// For example, say we want to subtract 4 scattered elements from a rank-1 tensor +// with 8 elements. In Python, that subtraction would look like this: +// +// ```python +// ref = tf.Variable([1, 2, 3, 4, 5, 6, 7, 8], use_resource=True) +// indices = tf.constant([[4], [3], [1], [7]]) +// updates = tf.constant([9, 10, 11, 12]) +// sub = tf.scatter_nd_sub(ref, indices, updates) +// with tf.Session() as sess: +// print sess.run(sub) +// ``` +// +// The resulting update to ref would look like this: +// +// [1, -9, 3, -6, -4, 6, 7, -4] +// +// See `tf.scatter_nd` for more details about how to make updates to +// slices. +// +// Arguments: +// ref: A resource handle. Must be from a VarHandleOp. +// indices: A Tensor. Must be one of the following types: int32, int64. +// A tensor of indices into ref. +// updates: A Tensor. Must have the same type as ref. A tensor of +// values to add to ref. +// +// Returns the created operation. +func ResourceScatterNdSub(scope *Scope, ref tf.Output, indices tf.Output, updates tf.Output, optional ...ResourceScatterNdSubAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ResourceScatterNdSub", + Input: []tf.Input{ + ref, indices, updates, + }, + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + // TensorArrayConcatV2Attr is an optional argument to TensorArrayConcatV2. type TensorArrayConcatV2Attr func(optionalAttr) @@ -31032,6 +32641,43 @@ func TFRecordDataset(scope *Scope, filenames tf.Output, compression_type tf.Outp return op.Output(0) } +// ExperimentalStatsAggregatorHandleAttr is an optional argument to ExperimentalStatsAggregatorHandle. +type ExperimentalStatsAggregatorHandleAttr func(optionalAttr) + +// ExperimentalStatsAggregatorHandleContainer sets the optional container attribute to value. +// If not specified, defaults to "" +func ExperimentalStatsAggregatorHandleContainer(value string) ExperimentalStatsAggregatorHandleAttr { + return func(m optionalAttr) { + m["container"] = value + } +} + +// ExperimentalStatsAggregatorHandleSharedName sets the optional shared_name attribute to value. +// If not specified, defaults to "" +func ExperimentalStatsAggregatorHandleSharedName(value string) ExperimentalStatsAggregatorHandleAttr { + return func(m optionalAttr) { + m["shared_name"] = value + } +} + +// Creates a statistics manager resource. +func ExperimentalStatsAggregatorHandle(scope *Scope, optional ...ExperimentalStatsAggregatorHandleAttr) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ExperimentalStatsAggregatorHandle", + + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // A container for an iterator resource. // // Returns A handle to the iterator that can be passed to a "MakeIterator" or @@ -31157,6 +32803,21 @@ func BatchToSpace(scope *Scope, input tf.Output, crops tf.Output, block_size int return op.Output(0) } +// Produces a summary of any statistics recorded by the given statistics manager. +func ExperimentalStatsAggregatorSummary(scope *Scope, iterator tf.Output) (summary tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "ExperimentalStatsAggregatorSummary", + Input: []tf.Input{ + iterator, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Makes a new iterator from the given `dataset` and stores it in `iterator`. // // This operation may be executed multiple times. Each execution will reset the @@ -31488,6 +33149,26 @@ func FIFOQueueV2(scope *Scope, component_types []tf.DataType, optional ...FIFOQu return op.Output(0) } +// Deserializes a proto into the tree handle +// +// Arguments: +// tree_handle: Handle to the tree resource to be restored. +// tree_config: Serialied proto string of the boosted_trees.Tree proto. +// +// Returns the created operation. +func TensorForestTreeDeserialize(scope *Scope, tree_handle tf.Output, tree_config tf.Output) (o *tf.Operation) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "TensorForestTreeDeserialize", + Input: []tf.Input{ + tree_handle, tree_config, + }, + } + return scope.AddOperation(opspec) +} + // Constructs an Optional variant from a tuple of tensors. func OptionalFromValue(scope *Scope, components []tf.Output) (optional tf.Output) { if scope.Err() != nil { @@ -31705,9 +33386,9 @@ func IteratorGetNextAsOptional(scope *Scope, iterator tf.Output, output_types [] // dimension of `input`. // // Arguments: -// input: A complex64 tensor. +// input: A complex tensor. // -// Returns A complex64 tensor of the same shape as `input`. The inner-most +// Returns A complex tensor of the same shape as `input`. The inner-most // dimension of `input` is replaced with its 1D Fourier transform. // // @compatibility(numpy) @@ -32418,6 +34099,28 @@ func Merge(scope *Scope, inputs []tf.Output) (output tf.Output, value_index tf.O return op.Output(0), op.Output(1) } +// Writes the given dataset to the given file using the TFRecord format. +// +// Arguments: +// input_dataset: A variant tensor representing the dataset to write. +// filename: A scalar string tensor representing the filename to use. +// compression_type: A scalar string tensor containing either (i) the empty string (no +// compression), (ii) "ZLIB", or (iii) "GZIP". +// +// Returns the created operation. +func ExperimentalDatasetToTFRecord(scope *Scope, input_dataset tf.Output, filename tf.Output, compression_type tf.Output) (o *tf.Operation) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "ExperimentalDatasetToTFRecord", + Input: []tf.Input{ + input_dataset, filename, compression_type, + }, + } + return scope.AddOperation(opspec) +} + // QueueCloseV2Attr is an optional argument to QueueCloseV2. type QueueCloseV2Attr func(optionalAttr) @@ -32754,6 +34457,23 @@ func Rpc(scope *Scope, address tf.Output, method tf.Output, request tf.Output, o return op.Output(0) } +// Records the bytes size of each element of `input_dataset` in a StatsAggregator. +func ExperimentalBytesProducedStatsDataset(scope *Scope, input_dataset tf.Output, tag tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "ExperimentalBytesProducedStatsDataset", + Input: []tf.Input{ + input_dataset, tag, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // StackPushV2Attr is an optional argument to StackPushV2. type StackPushV2Attr func(optionalAttr) -- GitLab From 55fda7fb66f1774ac5271e523259faa7d2a44d0c Mon Sep 17 00:00:00 2001 From: Davide Libenzi Date: Fri, 4 Jan 2019 15:31:48 -0800 Subject: [PATCH 0219/2345] Allow XLA input/output buffer aliasing to work via XRT. PiperOrigin-RevId: 227924255 --- .../xla/service/cpu/cpu_executable.cc | 29 +++- .../xla/service/gpu/gpu_executable.cc | 26 +++- tensorflow/compiler/xrt/kernels/BUILD | 1 + .../compiler/xrt/kernels/xrt_execute_op.cc | 17 +++ tensorflow/compiler/xrt/tests/raw_api_test.cc | 127 +++++++++++++++++- tensorflow/compiler/xrt/xrt_state.cc | 43 +++++- tensorflow/compiler/xrt/xrt_state.h | 23 +++- 7 files changed, 249 insertions(+), 17 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/cpu_executable.cc b/tensorflow/compiler/xla/service/cpu/cpu_executable.cc index 412c2715b9..23d0af3423 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_executable.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_executable.cc @@ -213,6 +213,8 @@ StatusOr CpuExecutable::CreateResultShapedBuffer( /*on_host_shape=*/result_shape(), /*on_device_shape=*/result_shape(), run_options->allocator(), stream->parent()->device_ordinal()); + const HloInputOutputAliasConfig& input_output_alias = + module().input_output_alias_config(); // Move OwningDeviceMemory values which contain the array(s) of the result // into the respective location in ScopedShapedBuffer which is returned to the @@ -232,12 +234,31 @@ StatusOr CpuExecutable::CreateResultShapedBuffer( TF_ASSIGN_OR_RETURN( const BufferAllocation::Slice slice, this->assignment_->GetUniqueSlice(src, buffer_source->index())); - CHECK(!slice.allocation()->is_entry_computation_parameter()); - const BufferAllocation::Index buffer_index = slice.index(); OwningDeviceMemory& buffer = buffers[buffer_index]; - CHECK(!buffer.is_null() || buffer.size() == 0); - *device_memory = buffer.Forget(); + if (!slice.allocation()->is_entry_computation_parameter()) { + // If the buffer coming out of the result is from a parameter, the + // owning buffer will be null, and that means the caller aliased some + // parameter buffer to an output one (via the + // HloInputOutputAliasConfig API). If that is the case, the caller + // will receive a partially complete scoped shaped buffer, which they + // will have to fill up on return. Unfortunately the interface to the + // execute APIs are ShapedBuffer pointer based, which assumes caller + // ownership, and hence a buffer coming from there cannot be part of + // the new ScopedShapedBuffer we create for the result (which assumes + // ownership). + *device_memory = buffer.Forget(); + } else { + auto output_alias = input_output_alias.GetAliasedOutput( + slice.allocation()->parameter_number(), + slice.allocation()->param_shape_index()); + CHECK(output_alias) + << "Ouput buffer is coming from parameter " + << slice.allocation()->parameter_number() << " at index " + << slice.allocation()->param_shape_index() + << ", but no alias exists"; + CHECK_EQ(*output_alias, index); + } return Status::OK(); })); return std::move(result_buffer); diff --git a/tensorflow/compiler/xla/service/gpu/gpu_executable.cc b/tensorflow/compiler/xla/service/gpu/gpu_executable.cc index 128cecd765..434060ad89 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_executable.cc +++ b/tensorflow/compiler/xla/service/gpu/gpu_executable.cc @@ -310,12 +310,34 @@ StatusOr GpuExecutable::ExecuteOnStream( TF_ASSIGN_OR_RETURN( const BufferAllocation::Slice slice, this->assignment_->GetUniqueSlice(src_hlo, sources[0]->index())); - CHECK(!slice.allocation()->is_entry_computation_parameter()); se::DeviceMemoryBase src_base = buffer_allocations->GetDeviceAddress(slice.index()); CHECK(!src_base.is_null() || src_base.size() == 0); - *device_memory = src_base; + if (!slice.allocation()->is_entry_computation_parameter()) { + // If the buffer coming out of the result is from a parameter, it + // means the caller aliased some parameter buffer to an output one + // (via the HloInputOutputAliasConfig API). If that is the case, the + // caller will receive a partially complete scoped shaped buffer, + // which they will have to fill up on return. + // Unfortunately the interface to the execute APIs are ShapedBuffer + // pointer based, which assumes caller ownership, and hence a buffer + // coming from there cannot be part of the new ScopedShapedBuffer we + // create for the result (which assumes ownership). + *device_memory = src_base; + } else { + const HloInputOutputAliasConfig& input_output_alias = + module().input_output_alias_config(); + auto output_alias = input_output_alias.GetAliasedOutput( + slice.allocation()->parameter_number(), + slice.allocation()->param_shape_index()); + CHECK(output_alias) + << "Ouput buffer is coming from parameter " + << slice.allocation()->parameter_number() << " at index " + << slice.allocation()->param_shape_index() + << ", but no alias exists"; + CHECK_EQ(*output_alias, index); + } buffers_in_result.insert(src_base); return Status::OK(); })); diff --git a/tensorflow/compiler/xrt/kernels/BUILD b/tensorflow/compiler/xrt/kernels/BUILD index 67f475846e..c44769dfe3 100644 --- a/tensorflow/compiler/xrt/kernels/BUILD +++ b/tensorflow/compiler/xrt/kernels/BUILD @@ -55,6 +55,7 @@ cc_library( "//tensorflow/compiler/xla/client:xla_computation", "//tensorflow/compiler/xla/service:compiler", "//tensorflow/compiler/xla/service:computation_placer", + "//tensorflow/compiler/xla/service:hlo", "//tensorflow/compiler/xrt:xrt_proto", "//tensorflow/compiler/xrt:xrt_utils", "//tensorflow/core:core_cpu_internal", diff --git a/tensorflow/compiler/xrt/kernels/xrt_execute_op.cc b/tensorflow/compiler/xrt/kernels/xrt_execute_op.cc index 7f0ac123a5..7544541ca4 100644 --- a/tensorflow/compiler/xrt/kernels/xrt_execute_op.cc +++ b/tensorflow/compiler/xrt/kernels/xrt_execute_op.cc @@ -19,6 +19,7 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/xla_op_registry.h" #include "tensorflow/compiler/xla/service/computation_placer.h" +#include "tensorflow/compiler/xla/service/hlo_input_output_alias_config.h" #include "tensorflow/compiler/xla/shape_util.h" #include "tensorflow/compiler/xla/status_macros.h" #include "tensorflow/compiler/xla/statusor.h" @@ -228,6 +229,22 @@ Status XRTExecuteOp::DoWork(OpKernelContext* context) { TF_RETURN_IF_ERROR(XRTTupleAllocation::CreateFromBuffer( shaped_buffer, device_ref.backend(), device_ref.device_ordinal(), &output_tuple)); + + // The ScopedShapedBuffer returned by the executable Run() API, in case of + // input/output buffer aliasing, might have holes in it, which need to be + // filled using the proper input tuples buffers which are the source of + // aliasing. + const xla::HloInputOutputAliasConfig& input_output_alias = + executable->executable()->module().input_output_alias_config(); + auto alias_function = [&](const xla::ShapeIndex& output_index, + int64 param_number, + const xla::ShapeIndex& param_index) -> Status { + TF_RET_CHECK(param_number < input_tuples.size()); + return output_tuple->AliasBufferFrom(*input_tuples[param_number], + param_index, output_index); + }; + TF_RETURN_IF_ERROR(input_output_alias.ForEachAliasWithStatus(alias_function)); + if (config_proto.return_exploded_tuple() && output_tuple->on_device_shape().IsTuple()) { int64 tuple_element_count = diff --git a/tensorflow/compiler/xrt/tests/raw_api_test.cc b/tensorflow/compiler/xrt/tests/raw_api_test.cc index c8479cb778..f81faf0e61 100644 --- a/tensorflow/compiler/xrt/tests/raw_api_test.cc +++ b/tensorflow/compiler/xrt/tests/raw_api_test.cc @@ -96,14 +96,21 @@ xla::LiteralProto FloatMatrix( return array.ToProto(); } +xla::Literal ReadOutputLiteral(const std::vector& outputs, size_t idx) { + xla::LiteralProto response; + CHECK(response.ParseFromString(outputs[idx].scalar()())); + return xla::Literal::CreateFromProto(response).ValueOrDie(); +} + bool CompareLiteralProtos(const xla::LiteralProto& a, const xla::LiteralProto& b) { auto l_a = xla::Literal::CreateFromProto(a).ValueOrDie(); auto l_b = xla::Literal::CreateFromProto(b).ValueOrDie(); bool equal = l_a == l_b; if (!equal) { - LOG(INFO) << "LiteralProtos don't match: " << a.DebugString() - << " != " << b.DebugString(); + LOG(INFO) << "LiteralProtos don't match:\n" + << a.DebugString() << "\n!=\n" + << b.DebugString(); } return equal; } @@ -113,8 +120,19 @@ bool CompareLiteralToLiteralProto(const xla::Literal& a, auto l_b = xla::Literal::CreateFromProto(b).ValueOrDie(); bool equal = a == l_b; if (!equal) { - LOG(INFO) << "Literal and LiteralProto don't match " - << a.ToProto().DebugString() << " != " << b.DebugString(); + LOG(INFO) << "Literal and LiteralProto don't match:\n" + << a.ToProto().DebugString() << "\n!=\n" + << b.DebugString(); + } + return equal; +} + +bool CompareLiterals(const xla::Literal& a, const xla::Literal& b) { + bool equal = a == b; + if (!equal) { + LOG(INFO) << "Literals don't match:\n" + << a.ToProto().DebugString() << "\n!=\n" + << b.ToProto().DebugString(); } return equal; } @@ -939,6 +957,107 @@ TEST(RawApiTest, LeakCompilationReference) { TF_EXPECT_OK(session.Run({c_handle.handle}, &outputs)); } +TEST(RawApiTest, CompileAndExecuteWithReusedBuffers) { + xla::Shape element_shape = xla::ShapeUtil::MakeShape(xla::F32, {2}); + xla::Shape shape = + xla::ShapeUtil::MakeTupleShape({element_shape, element_shape}); + xla::Shape return_shape = xla::ShapeUtil::MakeTupleShape( + {element_shape, element_shape, element_shape, element_shape}); + xla::XlaBuilder builder("ReuseBuffer"); + auto param = xla::Parameter(&builder, 0, shape, "param"); + auto p0 = xla::GetTupleElement(param, 0); + auto p1 = xla::GetTupleElement(param, 1); + auto add = xla::Add(p0, p1); + auto sub = xla::Sub(p0, p1); + xla::Tuple(&builder, {add, sub, p0, p1}); + + // Flip the tuple literals in the input handle. + builder.SetUpAlias({1}, 0, {0}); + builder.SetUpAlias({0}, 0, {1}); + + auto computation = builder.Build().ValueOrDie(); + + auto literal0 = xla::LiteralUtil::CreateR1({1.0f, 2.0f}); + auto literal1 = xla::LiteralUtil::CreateR1({5.0f, 9.0f}); + auto literal = xla::LiteralUtil::MakeTuple({&literal0, &literal1}); + + xrt::XLAAllocation param_alloc; + *param_alloc.mutable_value() = literal.ToProto(); + + xrt::XLAComputation c; + auto config = c.mutable_config(); + auto shapes = config->mutable_program_shape(); + *shapes->add_parameters() = shape.ToProto(); + *shapes->mutable_result() = return_shape.ToProto(); + StoreComputationSnapshot(computation, c.mutable_hlo_snapshot()); + + xrt::XRTExecutionConfig e; + e.set_release_input_handles(false); + e.set_release_compilation_handle(true); + + Scope root = Scope::NewRootScope().WithDevice(DeviceFromFlag()); + ClientSession session(root); + auto e_config = + ops::Const(root.WithDevice("/device:CPU:0"), e.SerializeAsString()); + auto c_data = + ops::Const(root.WithDevice("/device:CPU:0"), c.SerializeAsString()); + auto c_handle = ops::XRTCompile(root, c_data); + auto param_value = ops::Const(root.WithDevice("/device:CPU:0"), + param_alloc.SerializeAsString()); + auto param_handle = ops::XRTAllocate(root, param_value); + TF_ASSERT_OK(root.status()); + + std::vector outputs; + TF_EXPECT_OK(session.Run({param_handle}, &outputs)); + + int64 alloc_handle = outputs[0].scalar()(); + + // Note that we release the result handle immediately, but since we aliased + // the output buffers onto the input allocation ones (held in alloc_handle), + // we can fetch the result from there. + auto result = + ops::XRTExecute(root, c_handle.handle, e_config, {Input(alloc_handle)}); + auto read_back = ops::XRTReadLiteral(root, result); + auto release = ops::XRTReleaseAllocationHandle( + root.WithControlDependencies(read_back), result); + TF_ASSERT_OK(root.status()); + + outputs.clear(); + TF_EXPECT_OK(session.Run(tensorflow::ClientSession::FeedType(), {read_back}, + {release}, &outputs)); + + xla::Literal exec_literal = ReadOutputLiteral(outputs, 0); + auto exec_literal_parts = exec_literal.DecomposeTuple(); + ASSERT_EQ(exec_literal_parts.size(), 4); + + EXPECT_TRUE(CompareLiterals(exec_literal_parts[2], literal0)); + EXPECT_TRUE(CompareLiterals(exec_literal_parts[3], literal1)); + + // Now we read back the original input handle values, which at this point + // should contain the result of the XLA computation. + auto read_handle = ops::XRTReadLiteral(root, Input(alloc_handle)); + TF_ASSERT_OK(root.status()); + auto release_handle = ops::XRTReleaseAllocationHandle( + root.WithControlDependencies(read_handle), Input(alloc_handle)); + TF_ASSERT_OK(root.status()); + + outputs.clear(); + TF_EXPECT_OK(session.Run(tensorflow::ClientSession::FeedType(), {read_handle}, + {release_handle}, &outputs)); + + xla::Literal return_literal = ReadOutputLiteral(outputs, 0); + + auto expected_literal0 = xla::LiteralUtil::CreateR1({6.0f, 11.0f}); + auto expected_literal1 = xla::LiteralUtil::CreateR1({-4.0f, -7.0f}); + // The first element of the computation returned tuple would be the add + // (expected_literal0), but since we flipped the buffers, the sub + // (expected_literal1) should come first. + auto expected_literal = + xla::LiteralUtil::MakeTuple({&expected_literal1, &expected_literal0}); + + EXPECT_TRUE(CompareLiterals(return_literal, expected_literal)); +} + TEST(RawApiTest, CompileAndExecuteWithS64Argument) { xrt::XLAAllocation p0; *p0.mutable_value() = xla::LiteralUtil::CreateR0(11031965).ToProto(); diff --git a/tensorflow/compiler/xrt/xrt_state.cc b/tensorflow/compiler/xrt/xrt_state.cc index 343460ff10..13c275aaca 100644 --- a/tensorflow/compiler/xrt/xrt_state.cc +++ b/tensorflow/compiler/xrt/xrt_state.cc @@ -133,7 +133,8 @@ Status AllocateScopedShapedBuffer( XRTBufferAllocation::XRTBufferAllocation(const se::DeviceMemoryBase& allocation, int device_ordinal, xla::DeviceMemoryAllocator* allocator) - : allocation_(allocation), + : size_(allocation.size()), + allocation_(allocation), device_ordinal_(device_ordinal), allocator_(allocator) { if (VLOG_IS_ON(2)) { @@ -223,8 +224,19 @@ Status XRTTupleAllocation::ToLiteral(xla::Backend* backend, int device_ordinal, xla::Literal* literal) { auto transfer_manager = backend->transfer_manager(); TF_ASSIGN_OR_RETURN(auto stream, backend->BorrowStream(device_ordinal)); + + // Validate the allocation buffers as if nulls gets to + // TransferLiteralFromDevice() a CHECK is issued. + xla::ShapedBuffer shaped_buffer = ToShapedBuffer(); + for (auto& index_buffer : shaped_buffer.buffers()) { + if (index_buffer.second.is_null()) { + return errors::InvalidArgument("Literal buffer at index ", + index_buffer.first.ToString(), + " has been released"); + } + } TF_ASSIGN_OR_RETURN(*literal, transfer_manager->TransferLiteralFromDevice( - stream.get(), ToShapedBuffer())); + stream.get(), shaped_buffer)); return Status::OK(); } @@ -505,11 +517,34 @@ xla::ShapedBuffer XRTTupleAllocation::ToShapedBuffer() { return shaped_buffer; } +Status XRTTupleAllocation::AliasBufferFrom(const XRTTupleAllocation& source, + const xla::ShapeIndex& source_index, + const xla::ShapeIndex& dest_index) { + XRTBufferAllocation* source_buffer = source.buffers_.element(source_index); + XRTBufferAllocation* dest_buffer = buffers_.element(dest_index); + // We allow the destination size being zero, because there are cases where we + // are coming in later filling in null/uninitialized device buffers. + // In all other cases, the size of the new buffer must match. + if (source_buffer->size() != dest_buffer->size() && + dest_buffer->size() != 0) { + return errors::InvalidArgument( + "Source buffer at index ", source_index.ToString(), + " does not match the size of destination buffer at index ", + dest_index.ToString(), ": ", source_buffer->size(), " vs ", + dest_buffer->size()); + } + *buffers_.mutable_element(dest_index) = source_buffer; + source_buffer->Ref(); + dest_buffer->Unref(); + return Status::OK(); +} + xla::ShapeTree -XRTTupleAllocation::ToDeviceMemoryTree(bool release) { +XRTTupleAllocation::ToDeviceMemoryTree( + const std::function& release_checker) { xla::ShapeTree shaped_tree(on_device_shape()); for (const auto& buffer : buffers_) { - if (!release) { + if (!release_checker(buffer.first)) { *shaped_tree.mutable_element(buffer.first) = buffer.second->allocation(); } else { *shaped_tree.mutable_element(buffer.first) = xla::OwningDeviceMemory( diff --git a/tensorflow/compiler/xrt/xrt_state.h b/tensorflow/compiler/xrt/xrt_state.h index 3e3d502412..ac4be3a064 100644 --- a/tensorflow/compiler/xrt/xrt_state.h +++ b/tensorflow/compiler/xrt/xrt_state.h @@ -18,6 +18,7 @@ limitations under the License. #ifndef TENSORFLOW_COMPILER_XRT_XRT_STATE_H_ #define TENSORFLOW_COMPILER_XRT_XRT_STATE_H_ +#include #include #include #include @@ -58,7 +59,14 @@ class XRTBufferAllocation : public core::RefCounted { // freed when the reference count drops to zero. void DiscardAllocation(); + // Returns the expected size of the allocation. Since DiscardAllocation() will + // set allocation_ to {null,0}, and since later we might want to replace the + // discarded buffer with a new one, we need to be able to verify the size + // compatibility. + uint64 size() const { return size_; } + private: + uint64 size_ = 0; se::DeviceMemoryBase allocation_; int device_ordinal_; xla::DeviceMemoryAllocator* allocator_; @@ -168,9 +176,18 @@ class XRTTupleAllocation : public ResourceBase { // the same shape as on_host_shape. xla::ShapedBuffer ToShapedBuffer(); - // Returns the device memory tree of this allocation. If 'release' is set, the - // ownership of the device memory is transferred to the result. - xla::ShapeTree ToDeviceMemoryTree(bool release); + // Aliases the source buffer at source_index into the current tuple allocation + // dest_index. + Status AliasBufferFrom(const XRTTupleAllocation& source, + const xla::ShapeIndex& source_index, + const xla::ShapeIndex& dest_index); + + // Returns the device memory tree of this allocation. If the release_checker + // function returns true for a given index, the ownership of the device memory + // at that index is transferred to the result. Every attempt to read the value + // at that index will fail. + xla::ShapeTree ToDeviceMemoryTree( + const std::function& release_checker); string DebugString() override { return "XLA allocation handle"; } -- GitLab From 02bfac424857fa45621efab5908a31ffd4ce799b Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Fri, 4 Jan 2019 15:32:34 -0800 Subject: [PATCH 0220/2345] Add kullback leibler divergence v2 loss and metric. PiperOrigin-RevId: 227924402 --- tensorflow/python/keras/losses.py | 27 +++++++++ tensorflow/python/keras/losses_test.py | 80 +++++++++++++++++++++++++ tensorflow/python/keras/metrics.py | 32 ++++++++++ tensorflow/python/keras/metrics_test.py | 47 +++++++++++++++ 4 files changed, 186 insertions(+) diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index 46e729c197..de1605a09c 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -581,6 +581,33 @@ class Logcosh(Loss): return logcosh(y_true, y_pred) +class KullbackLeiblerDivergence(Loss): + """Computes kullback leibler divergence loss between `y_true` and `y_pred`. + + loss = y_true * log(y_true / y_pred) + + Usage: + + ```python + k = tf.losses.KullbackLeiblerDivergence() + loss = k([.4, .9, .2], [.5, .8, .12]) + print('Loss: ', loss.numpy()) # Loss: -0.043 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile('sgd', loss=tf.losses.KullbackLeiblerDivergence()) + ``` + """ + + def call(self, y_true, y_pred): + y_pred = ops.convert_to_tensor(y_pred) + y_true = math_ops.cast(y_true, y_pred.dtype) + return kullback_leibler_divergence(y_true, y_pred) + + @keras_export('keras.metrics.mean_squared_error', 'keras.metrics.mse', 'keras.metrics.MSE', diff --git a/tensorflow/python/keras/losses_test.py b/tensorflow/python/keras/losses_test.py index f34a6caa18..caa4ff4c2e 100644 --- a/tensorflow/python/keras/losses_test.py +++ b/tensorflow/python/keras/losses_test.py @@ -1256,5 +1256,85 @@ class PoissonTest(test.TestCase): self.assertAlmostEqual(self.evaluate(loss), 0., 3) +@test_util.run_all_in_graph_and_eager_modes +class KullbackLeiblerDivergenceTest(test.TestCase): + + def setup(self): + self.np_y_pred = np.asarray([.4, .9, .12, .36, .3, .4]).reshape((2, 3)) + self.np_y_true = np.asarray([.5, .8, .12, .7, .43, .8]).reshape((2, 3)) + + self.batch_size = 2 + self.expected_losses = np.multiply(self.np_y_true, + np.log(self.np_y_true / self.np_y_pred)) + + self.y_pred = constant_op.constant(self.np_y_pred, dtype=dtypes.float32) + self.y_true = constant_op.constant(self.np_y_true) + + def test_config(self): + k_obj = keras.losses.KullbackLeiblerDivergence( + reduction=losses_impl.ReductionV2.SUM, name='kld') + self.assertEqual(k_obj.name, 'kld') + self.assertEqual(k_obj.reduction, losses_impl.ReductionV2.SUM) + + def test_unweighted(self): + self.setup() + k_obj = keras.losses.KullbackLeiblerDivergence() + + loss = k_obj(self.y_true, self.y_pred) + expected_loss = np.sum(self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_scalar_weighted(self): + self.setup() + k_obj = keras.losses.KullbackLeiblerDivergence() + sample_weight = 2.3 + + loss = k_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + expected_loss = sample_weight * np.sum( + self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + # Verify we get the same output when the same input is given + loss_2 = k_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + self.assertAlmostEqual(self.evaluate(loss), self.evaluate(loss_2), 3) + + def test_sample_weighted(self): + self.setup() + k_obj = keras.losses.KullbackLeiblerDivergence() + sample_weight = constant_op.constant([1.2, 3.4], shape=(2, 1)) + loss = k_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + + expected_loss = np.multiply( + self.expected_losses, + np.asarray([1.2, 1.2, 1.2, 3.4, 3.4, 3.4]).reshape(2, 3)) + expected_loss = np.sum(expected_loss) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_timestep_weighted(self): + self.setup() + k_obj = keras.losses.KullbackLeiblerDivergence() + y_true = self.np_y_true.reshape(2, 3, 1) + y_pred = self.np_y_pred.reshape(2, 3, 1) + sample_weight = np.asarray([3, 6, 5, 0, 4, 2]).reshape(2, 3) + expected_losses = np.sum( + np.multiply(y_true, np.log(y_true / y_pred)), axis=-1) + + y_pred = constant_op.constant(y_pred, dtype=dtypes.float32) + y_true = constant_op.constant(y_true) + loss = k_obj( + y_true, y_pred, sample_weight=constant_op.constant(sample_weight)) + + num_timesteps = 3 + expected_loss = np.sum(expected_losses * sample_weight) / ( + self.batch_size * num_timesteps) + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_zero_weighted(self): + self.setup() + k_obj = keras.losses.KullbackLeiblerDivergence() + loss = k_obj(self.y_true, self.y_pred, sample_weight=0) + self.assertAlmostEqual(self.evaluate(loss), 0., 3) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index 71568e3fc5..9a2e057780 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -1716,6 +1716,38 @@ class Poisson(MeanMetricWrapper): return super(Poisson, cls).from_config(config) +class KullbackLeiblerDivergence(MeanMetricWrapper): + """Computes kullback leibler divergence metric between `y_true` and `y_pred`. + + metric = y_true * log(y_true / y_pred) + + Usage: + + ```python + m = tf.keras.metrics.KullbackLeiblerDivergence() + m.update_state([.4, .9, .2], [.5, .8, .12]) + print('Final result: ', m.result().numpy()) # Final result: -0.043 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile('sgd', metrics=[tf.keras.metrics.KullbackLeiblerDivergence()]) + ``` + """ + + def __init__(self, name='kullback_leibler_divergence', dtype=None): + super(KullbackLeiblerDivergence, self).__init__( + kullback_leibler_divergence, name, dtype=dtype) + + @classmethod + def from_config(cls, config): + if 'fn' in config: + config.pop('fn') + return super(KullbackLeiblerDivergence, cls).from_config(config) + + def accuracy(y_true, y_pred): y_pred.get_shape().assert_is_compatible_with(y_true.get_shape()) if y_true.dtype != y_pred.dtype: diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index 88b763d569..a02b95bb46 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -1556,6 +1556,53 @@ class PoissonTest(test.TestCase): self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) +@test_util.run_all_in_graph_and_eager_modes +class KullbackLeiblerDivergenceTest(test.TestCase): + + def setup(self): + y_pred = np.asarray([.4, .9, .12, .36, .3, .4]).reshape((2, 3)) + y_true = np.asarray([.5, .8, .12, .7, .43, .8]).reshape((2, 3)) + + self.batch_size = 2 + self.expected_results = np.multiply(y_true, np.log(y_true / y_pred)) + + self.y_pred = constant_op.constant(y_pred, dtype=dtypes.float32) + self.y_true = constant_op.constant(y_true) + + def test_config(self): + k_obj = metrics.KullbackLeiblerDivergence(name='kld', dtype=dtypes.int32) + self.assertEqual(k_obj.name, 'kld') + self.assertEqual(k_obj._dtype, dtypes.int32) + + k_obj2 = metrics.KullbackLeiblerDivergence.from_config(k_obj.get_config()) + self.assertEqual(k_obj2.name, 'kld') + self.assertEqual(k_obj2._dtype, dtypes.int32) + + def test_unweighted(self): + self.setup() + k_obj = metrics.KullbackLeiblerDivergence() + self.evaluate(variables.variables_initializer(k_obj.variables)) + + update_op = k_obj.update_state(self.y_true, self.y_pred) + self.evaluate(update_op) + result = k_obj.result() + expected_result = np.sum(self.expected_results) / self.batch_size + self.assertAllClose(result, expected_result, atol=1e-3) + + def test_weighted(self): + self.setup() + k_obj = metrics.KullbackLeiblerDivergence() + self.evaluate(variables.variables_initializer(k_obj.variables)) + + sample_weight = constant_op.constant([1.2, 3.4], shape=(2, 1)) + result = k_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + + sample_weight = np.asarray([1.2, 1.2, 1.2, 3.4, 3.4, 3.4]).reshape((2, 3)) + expected_result = np.multiply(self.expected_results, sample_weight) + expected_result = np.sum(expected_result) / (1.2 + 3.4) + self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) + + def _get_model(compile_metrics): model_layers = [ layers.Dense(3, activation='relu', kernel_initializer='ones'), -- GitLab From 984ace61cdef46145a63081151c7eeb8609c4133 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 15:45:38 -0800 Subject: [PATCH 0221/2345] Report the CUcontext in the cuda_driver log spew, not the CudaContext pointer. CUDA tools (like nvprof, cuda-memcheck) report the CUcontext, so this makes it possible to match them up during debugging. PiperOrigin-RevId: 227926402 --- .../stream_executor/cuda/cuda_driver.cc | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/tensorflow/stream_executor/cuda/cuda_driver.cc b/tensorflow/stream_executor/cuda/cuda_driver.cc index b34d1f722e..ca7a717bdb 100644 --- a/tensorflow/stream_executor/cuda/cuda_driver.cc +++ b/tensorflow/stream_executor/cuda/cuda_driver.cc @@ -431,7 +431,8 @@ bool DeviceOptionsToContextFlags(const DeviceOptions &device_options, *context = CreatedContexts::Add(new_context); CHECK(*context != nullptr) << "success in this call must entail non-null result"; - VLOG(2) << "created or reused context " << context << " for this thread"; + VLOG(2) << "created or reused context " << new_context + << " for this thread"; return port::Status::OK(); } @@ -769,13 +770,13 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { ScopedActivateContext activated{context}; CUresult res = cuStreamCreate(out, 0); if (res != CUDA_SUCCESS) { - LOG(ERROR) << "could not allocate CUDA stream for context " << context - << ": " << ToString(res); + LOG(ERROR) << "could not allocate CUDA stream for context " + << context->context() << ": " << ToString(res); return false; } VLOG(2) << "successfully created stream " << *out << " for context " - << context << " on thread"; + << context->context() << " on thread"; return true; } @@ -788,11 +789,11 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { ScopedActivateContext activated{context}; CUresult res = cuStreamDestroy(*stream); if (res != CUDA_SUCCESS) { - LOG(ERROR) << "failed to destroy CUDA stream for context " << context - << ": " << ToString(res); + LOG(ERROR) << "failed to destroy CUDA stream for context " + << context->context() << ": " << ToString(res); } else { VLOG(2) << "successfully destroyed stream " << *stream << " for context " - << context; + << context->context(); *stream = nullptr; } } @@ -809,8 +810,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { return nullptr; } void *ptr = reinterpret_cast(result); - VLOG(2) << "allocated " << ptr << " for context " << context << " of " - << bytes << " bytes"; + VLOG(2) << "allocated " << ptr << " for context " << context->context() + << " of " << bytes << " bytes"; return ptr; } @@ -823,7 +824,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { LOG(ERROR) << "failed to free device memory at " << location << "; result: " << ToString(res); } else { - VLOG(2) << "deallocated " << location << " for context " << context; + VLOG(2) << "deallocated " << location << " for context " + << context->context(); } } @@ -839,8 +841,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { return nullptr; } void *ptr = reinterpret_cast(result); - VLOG(2) << "allocated " << ptr << " for context " << context << " of " - << bytes << " bytes in unified memory"; + VLOG(2) << "allocated " << ptr << " for context " << context->context() + << " of " << bytes << " bytes in unified memory"; return ptr; } @@ -854,7 +856,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { << "; result: " << ToString(res); } else { VLOG(2) << "deallocated unified memory at " << location << " for context " - << context; + << context->context(); } } -- GitLab From 57d501422c16dd73b391d19bcc1e203942cc6b24 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 15:55:26 -0800 Subject: [PATCH 0222/2345] Add Keras LSTM model to correctness test. PiperOrigin-RevId: 227927675 --- tensorflow/contrib/distribute/python/BUILD | 3 +- .../contrib/distribute/python/combinations.py | 15 ++- .../python/keras_correctness_test.py | 100 +++++++++++++++--- 3 files changed, 99 insertions(+), 19 deletions(-) diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index de6b6f1f84..c620a0448e 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -670,11 +670,12 @@ py_library( cuda_py_test( name = "keras_correctness_test", + size = "medium", srcs = ["keras_correctness_test.py"], additional_deps = [ ":keras_correctness_test_lib", ], - shard_count = 16, + shard_count = 20, tags = [ "multi_and_single_gpu", "no_oss", # TODO(b/117919883): Fix python error. diff --git a/tensorflow/contrib/distribute/python/combinations.py b/tensorflow/contrib/distribute/python/combinations.py index f6c4291659..8db0e9c316 100644 --- a/tensorflow/contrib/distribute/python/combinations.py +++ b/tensorflow/contrib/distribute/python/combinations.py @@ -321,11 +321,12 @@ class NamedDistribution(object): return self._required_tpu -def _get_tpu_strategy_creator(steps_per_run): +def _get_tpu_strategy_creator(steps_per_run, **kwargs): def _create_tpu_strategy(): resolver = cluster_resolver.TPUClusterResolver("") tpu_lib.initialize_tpu_system(resolver) - strategy = tpu_lib.TPUStrategy(resolver, steps_per_run=steps_per_run) + strategy = tpu_lib.TPUStrategy(resolver, + steps_per_run=steps_per_run, **kwargs) return strategy return _create_tpu_strategy @@ -344,7 +345,15 @@ tpu_strategy = NamedDistribution( tpu_strategy_one_step = NamedDistribution( "TPUOneStep", _get_tpu_strategy_creator(steps_per_run=1), required_tpu=True) - +# TODO(b/122327153): Remove below two NamedDistributions. +tpu_strategy_loop_on_device = NamedDistribution( + "TPULoopOnDevice", _get_tpu_strategy_creator( + steps_per_run=2, _disable_training_loop_on_host=True), + required_tpu=True) +tpu_strategy_one_step_loop_on_device = NamedDistribution( + "TPUOneStepLoopOnDevice", _get_tpu_strategy_creator( + steps_per_run=1, _disable_training_loop_on_host=True), + required_tpu=True) mirrored_strategy_with_one_cpu = NamedDistribution( "Mirrored1CPU", lambda: mirrored_lib.MirroredStrategy(["/cpu:0"])) diff --git a/tensorflow/contrib/distribute/python/keras_correctness_test.py b/tensorflow/contrib/distribute/python/keras_correctness_test.py index 3abdee2c0e..1c4519ef71 100644 --- a/tensorflow/contrib/distribute/python/keras_correctness_test.py +++ b/tensorflow/contrib/distribute/python/keras_correctness_test.py @@ -62,24 +62,41 @@ def all_strategy_combinations_with_graph_mode(): return combinations.combine(distribution=all_strategies, mode=['graph']) +def tpu_strategies_with_host_training_loop_disabled(): + strategies = [s for s in all_strategies if not s.required_tpu] + strategies.append(combinations.tpu_strategy_loop_on_device) + strategies.append(combinations.tpu_strategy_one_step_loop_on_device) + return strategies + + def strategy_and_input_combinations(): def cnn_model_with_batch_norm(**kwargs): return _create_cnn_model(with_batch_norm=True, **kwargs) - return ( - combinations.times( - combinations.combine(distribution=all_strategies), - combinations.combine(mode=['graph', 'eager'], - use_numpy=[True, False], - use_validation_data=[True, False]), - combinations.combine(model_with_data=[ - ModelWithData('dnn', _create_dnn_model, _dnn_training_data), - ModelWithData('cnn', _create_cnn_model, _cnn_training_data), - ModelWithData('cnn_batch_norm', - cnn_model_with_batch_norm, - _cnn_training_data, - with_batch_norm=True), - ]))) + combinations_without_embedding_model = combinations.times( + combinations.combine( + distribution=tpu_strategies_with_host_training_loop_disabled()), + combinations.combine(mode=['graph', 'eager'], + use_numpy=[True, False], + use_validation_data=[True, False]), + combinations.combine(model_with_data= + [ModelWithData('lstm', + _create_lstm_model, + _lstm_training_data)])) + + combinations_with_embedding_model = combinations.times( + combinations.combine(distribution=all_strategies), + combinations.combine(mode=['graph', 'eager'], + use_numpy=[True, False], + use_validation_data=[True, False]), + combinations.combine(model_with_data=[ + ModelWithData('dnn', _create_dnn_model, _dnn_training_data), + ModelWithData('cnn', _create_cnn_model, _cnn_training_data), + ModelWithData('cnn_batch_norm', cnn_model_with_batch_norm, + _cnn_training_data, with_batch_norm=True),])) + + return (combinations_with_embedding_model + + combinations_without_embedding_model) class MaybeDistributionScope(object): @@ -191,6 +208,59 @@ def _create_cnn_model(initial_weights=None, distribution=None, return model +def _lstm_training_data(count=_GLOBAL_BATCH_SIZE * _EVAL_STEPS, + min_words=10, + max_words=20, + max_word_id=99, + num_classes=2): + distribution = [] + for _ in range(num_classes): + dist = np.abs(np.random.randn(max_word_id)) + dist /= np.sum(dist) + distribution.append(dist) + + features = [] + labels = [] + for _ in range(count): + label = np.random.randint(0, num_classes, size=1)[0] + num_words = np.random.randint(min_words, max_words, size=1)[0] + word_ids = np.random.choice( + max_word_id, size=num_words, replace=True, p=distribution[label]) + word_ids = word_ids + labels.append(label) + features.append(word_ids) + + features = keras.preprocessing.sequence.pad_sequences( + features, maxlen=max_words) + x_train = np.asarray(features, dtype=np.float32) + y_train = np.asarray(labels, dtype=np.int32).reshape((count, 1)) + x_predict = x_train + return x_train, y_train, x_predict + + +def _create_lstm_model(max_words=20, + initial_weights=None, + distribution=None): + with MaybeDistributionScope(distribution): + word_ids = keras.layers.Input( + shape=(max_words,), dtype=np.int32, name='words') + word_embed = keras.layers.Embedding(input_dim=100, output_dim=10)(word_ids) + lstm_embed = keras.layers.LSTM( + units=8, return_sequences=False)(word_embed) + + preds = keras.layers.Dense(2, activation='softmax')(lstm_embed) + model = keras.Model(inputs=[word_ids], outputs=[preds]) + + if initial_weights: + model.set_weights(initial_weights) + + model.compile( + optimizer=gradient_descent.GradientDescentOptimizer(learning_rate=0.1), + loss='sparse_categorical_crossentropy', + metrics=['sparse_categorical_accuracy']) + return model + + def batch_wrapper(dataset, batch_size, distribution, repeat=None): if repeat: dataset = dataset.repeat(repeat) @@ -243,7 +313,7 @@ def get_correctness_test_inputs(use_numpy, use_validation_data, } else: if len(x_train) < _GLOBAL_BATCH_SIZE * _EVAL_STEPS: - # Currently, we cannot detech the size of a dataset. So, the eval steps is + # Currently, we cannot detect the size of a dataset. So, the eval steps is # hard coded. raise ValueError('x_train must have at least ' '_GLOBAL_BATCH_SIZE * _EVAL_STEPS samples') -- GitLab From 571d0114eda553e2d1b5c9c71f77c2211b5914e3 Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Fri, 4 Jan 2019 16:10:25 -0800 Subject: [PATCH 0223/2345] Internal Cleanup. PiperOrigin-RevId: 227929845 --- .../keras/optimizer_v2/optimizer_v2_test.py | 18 ------------------ tensorflow/python/keras/optimizers.py | 5 +++-- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py b/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py index 290bdcad14..58c8b1f343 100644 --- a/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py +++ b/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py @@ -27,7 +27,6 @@ import numpy as np from tensorflow.python import keras from tensorflow.python.eager import context from tensorflow.python.eager import def_function -from tensorflow.python.eager import function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops @@ -262,23 +261,6 @@ class OptimizerTest(test.TestCase): self.evaluate(sgd.iterations.initializer) self.assertEqual(0, self.evaluate(sgd.iterations)) - @test_util.run_in_graph_and_eager_modes - def testSerializationWithinDefun(self): - with self.cached_session(): - sgd = gradient_descent.SGD(3.0) - var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], - dtype=dtypes.float32) - loss = lambda: 3 * var0 - sgd.minimize(loss, [var0]) - - def serialize(): - config = sgd.get_config() - gradient_descent.SGD.from_config(config) - - compiled_serialize = function.defun(serialize) - with self.assertRaisesRegexp(RuntimeError, 'inside Tensorflow graph'): - compiled_serialize() - @test_util.run_in_graph_and_eager_modes def testConfig(self): with self.cached_session(): diff --git a/tensorflow/python/keras/optimizers.py b/tensorflow/python/keras/optimizers.py index 0ab52fd0a0..12168e5574 100644 --- a/tensorflow/python/keras/optimizers.py +++ b/tensorflow/python/keras/optimizers.py @@ -575,7 +575,7 @@ class Adamax(Optimizer): def get_updates(self, loss, params): grads = self.get_gradients(loss, params) - self.updates = [state_ops.assign_add(self.iterations, 1)] + self.updates = [] lr = self.lr if self.initial_decay > 0: @@ -583,7 +583,8 @@ class Adamax(Optimizer): 1. / (1. + self.decay * math_ops.cast(self.iterations, K.dtype(self.decay)))) - t = math_ops.cast(self.iterations, K.floatx()) + 1 + with ops.control_dependencies([state_ops.assign_add(self.iterations, 1)]): + t = math_ops.cast(self.iterations, K.floatx()) lr_t = lr / (1. - math_ops.pow(self.beta_1, t)) shapes = [K.int_shape(p) for p in params] -- GitLab From abdbe6ee3d81d22d70e0181cea2a3bb261f7c09f Mon Sep 17 00:00:00 2001 From: Gaurav Jain Date: Fri, 4 Jan 2019 16:31:04 -0800 Subject: [PATCH 0224/2345] Add anonymous shared name GUID to avoid kernel cache Fixes #19671 We generate unique shared names for variables in eager mode to avoid unintended sharing. This results in a memory leak due to the kernel cache expanding with each new shared_name encountered. To avoid this we reserve a shared name to send in with the op. When the handle creation code sees this shared name, it generates a unique name internally and returns a fresh handle each time. PiperOrigin-RevId: 227932460 --- tensorflow/core/framework/resource_handle.cc | 3 ++ tensorflow/core/framework/resource_handle.h | 4 +++ tensorflow/core/framework/resource_mgr.cc | 12 ++++++- tensorflow/core/framework/resource_mgr.h | 33 ++++++++++++------- tensorflow/python/eager/context.py | 21 ++++++++++++ tensorflow/python/eager/def_function.py | 9 ++--- tensorflow/python/eager/memory_test.py | 11 +++++++ .../python/ops/resource_variable_ops.py | 6 ++-- 8 files changed, 81 insertions(+), 18 deletions(-) diff --git a/tensorflow/core/framework/resource_handle.cc b/tensorflow/core/framework/resource_handle.cc index fc3a329b3b..a3c48d63d4 100644 --- a/tensorflow/core/framework/resource_handle.cc +++ b/tensorflow/core/framework/resource_handle.cc @@ -19,6 +19,9 @@ limitations under the License. namespace tensorflow { +const char ResourceHandle::ANONYMOUS_NAME[] = + "cd2c89b7-88b7-44c8-ad83-06c2a9158347"; + ResourceHandle::ResourceHandle() {} ResourceHandle::ResourceHandle(const ResourceHandleProto& proto) { diff --git a/tensorflow/core/framework/resource_handle.h b/tensorflow/core/framework/resource_handle.h index db213669a3..c3beffb8ac 100644 --- a/tensorflow/core/framework/resource_handle.h +++ b/tensorflow/core/framework/resource_handle.h @@ -67,6 +67,10 @@ class ResourceHandle { string DebugString() const; + // GUID for anonymous resources. Resources with this shared_name will have + // their shared_name replaced with a GUID at creation time + static const char ANONYMOUS_NAME[]; + public: string device_; string container_; diff --git a/tensorflow/core/framework/resource_mgr.cc b/tensorflow/core/framework/resource_mgr.cc index 9f3204ab96..6a94ff6642 100644 --- a/tensorflow/core/framework/resource_mgr.cc +++ b/tensorflow/core/framework/resource_mgr.cc @@ -13,6 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#include + #include "tensorflow/core/framework/resource_mgr.h" #include "tensorflow/core/framework/device_attributes.pb.h" @@ -26,6 +28,10 @@ limitations under the License. #include "tensorflow/core/platform/demangle.h" namespace tensorflow { + +// Used to generate unique names for anonymous variables +static std::atomic current_id_; + ResourceHandle MakeResourceHandle(OpKernelContext* ctx, const string& container, const string& name, const TypeIndex& type_index) { @@ -38,7 +44,11 @@ ResourceHandle MakeResourceHandle(OpKernelContext* ctx, const string& container, actual_container = ctx->resource_manager()->default_container(); } result.set_container(actual_container); - result.set_name(name); + if (name == ResourceHandle::ANONYMOUS_NAME) { + result.set_name(strings::StrCat("_AnonymousVar", current_id_.fetch_add(1))); + } else { + result.set_name(name); + } result.set_hash_code(type_index.hash_code()); result.set_maybe_type_name(type_index.name()); return result; diff --git a/tensorflow/core/framework/resource_mgr.h b/tensorflow/core/framework/resource_mgr.h index 3195cd2e9d..8a7c25da92 100644 --- a/tensorflow/core/framework/resource_mgr.h +++ b/tensorflow/core/framework/resource_mgr.h @@ -619,20 +619,31 @@ ResourceHandleOp::ResourceHandleOp(OpKernelConstruction* context) template void ResourceHandleOp::Compute(OpKernelContext* ctx) { - if (!initialized_.load()) { - mutex_lock ml(mutex_); - // Checking again to see if another thread has initialized the resource. + if (name_ == ResourceHandle::ANONYMOUS_NAME) { + AllocatorAttributes attr; + attr.set_on_host(true); + Tensor handle; + OP_REQUIRES_OK( + ctx, ctx->allocate_temp(DT_RESOURCE, TensorShape({}), &handle, attr)); + handle.scalar()() = + MakeResourceHandle(ctx, container_, name_); + ctx->set_output(0, handle); + } else { if (!initialized_.load()) { - AllocatorAttributes attr; - attr.set_on_host(true); - OP_REQUIRES_OK(ctx, ctx->allocate_temp(DT_RESOURCE, TensorShape({}), - &resource_, attr)); - resource_.scalar()() = - MakeResourceHandle(ctx, container_, name_); - initialized_.store(true); + mutex_lock ml(mutex_); + // Checking again to see if another thread has initialized the resource. + if (!initialized_.load()) { + AllocatorAttributes attr; + attr.set_on_host(true); + OP_REQUIRES_OK(ctx, ctx->allocate_temp(DT_RESOURCE, TensorShape({}), + &resource_, attr)); + resource_.scalar()() = + MakeResourceHandle(ctx, container_, name_); + initialized_.store(true); + } } + ctx->set_output(0, resource_); } - ctx->set_output(0, resource_); } template diff --git a/tensorflow/python/eager/context.py b/tensorflow/python/eager/context.py index cd43dc7ab2..faaf742c26 100644 --- a/tensorflow/python/eager/context.py +++ b/tensorflow/python/eager/context.py @@ -788,6 +788,27 @@ def in_eager_mode(): return executing_eagerly() +def shared_name(name=None): + """Returns the anonymous shared name GUID if no shared name is specified. + + In eager mode we need to use a unique shared name to avoid spurious sharing + issues. The runtime generates a unique name on our behalf when the reserved + GUID is used as a shared name. + + Args: + name: Optional shared name + + Returns: + Eager compatible shared name. + """ + if name or not executing_eagerly(): + return name + + # Ensure a unique name when eager execution is enabled to avoid spurious + # sharing issues. + return "cd2c89b7-88b7-44c8-ad83-06c2a9158347" + + def graph_mode(): """Context-manager to disable eager execution for the current thread.""" return context()._mode(GRAPH_MODE) # pylint: disable=protected-access diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index ebc47d1566..aa4f20df49 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -130,8 +130,9 @@ class UnliftedInitializerVariable(resource_variable_ops.ResourceVariable): if init_from_fn else [initial_value]) as name: # pylint: disable=protected-access with ops.init_scope(): - shared_name = ops._name_from_scope_name(name) - shared_name = "%s_%d" % (shared_name, ops.uid()) + handle_name = ops._name_from_scope_name(name) + unique_id = "%s_%d" % (handle_name, ops.uid()) + shared_name = context.shared_name(unique_id) with ops.name_scope("Initializer"), ops.device(None): initial_value = ops.convert_to_tensor( initial_value() if init_from_fn else initial_value, @@ -144,8 +145,8 @@ class UnliftedInitializerVariable(resource_variable_ops.ResourceVariable): name=name, graph_mode=self._in_graph_mode) self._shape = initial_value.shape - self._unique_id = shared_name - self._handle_name = shared_name + ":0" + self._unique_id = unique_id + self._handle_name = handle_name + ":0" self._dtype = initial_value.dtype.base_dtype self._constraint = constraint assert initial_value is not None diff --git a/tensorflow/python/eager/memory_test.py b/tensorflow/python/eager/memory_test.py index 5e4516239c..9d29180379 100644 --- a/tensorflow/python/eager/memory_test.py +++ b/tensorflow/python/eager/memory_test.py @@ -33,6 +33,7 @@ from tensorflow.python.eager import context from tensorflow.python.eager import test from tensorflow.python.framework import dtypes from tensorflow.python.ops import array_ops +from tensorflow.python.ops.variables import Variable # memory_profiler might not be available in the OSS version of TensorFlow. try: @@ -81,6 +82,16 @@ class MemoryTest(test.TestCase): "Maximum allowed increase: %f") % (initial, increase, increase_threshold_absolute_mb) + def testMemoryLeakAnonymousVariable(self): + if memory_profiler is None: + self.skipTest("memory_profiler required to run this test") + + def f(): + inputs = Variable(array_ops.zeros([32, 100], dtypes.float32)) + del inputs + + self.assertNotIncreasingMemory(f, num_iters=10000) + def testMemoryLeakInSimpleModelForwardOnly(self): if memory_profiler is None: self.skipTest("memory_profiler required to run this test") diff --git a/tensorflow/python/ops/resource_variable_ops.py b/tensorflow/python/ops/resource_variable_ops.py index d2e5dd9cfe..1d98fb2c89 100644 --- a/tensorflow/python/ops/resource_variable_ops.py +++ b/tensorflow/python/ops/resource_variable_ops.py @@ -403,10 +403,12 @@ class ResourceVariable(variables.VariableV1): handle_name = ops._name_from_scope_name(name) if self._in_graph_mode: shared_name = handle_name + unique_id = shared_name else: # When in eager mode use a uid for the shared_name, to prevent # accidental sharing. - shared_name = "%s_%d" % (handle_name, ops.uid()) + unique_id = "%s_%d" % (handle_name, ops.uid()) + shared_name = context.shared_name() # Use attr_scope and device(None) to simulate the behavior of # colocate_with when the variable we want to colocate with doesn't # yet exist. @@ -434,7 +436,7 @@ class ResourceVariable(variables.VariableV1): "variable inside a loop or conditional, use a lambda as the " "initializer." % name) # pylint: enable=protected-access - self._unique_id = shared_name + self._unique_id = unique_id self._initial_value = initial_value if self._in_graph_mode else None self._handle_name = handle_name + ":0" self._dtype = initial_value.dtype.base_dtype -- GitLab From 84bddea0109598e47fbe76ba1d443fdcb34c9bf4 Mon Sep 17 00:00:00 2001 From: Paul Donnelly Date: Fri, 4 Jan 2019 16:50:13 -0800 Subject: [PATCH 0225/2345] Improve Maxpool op to use cuDNN 7.3 improvements to cudnnPoolingForward() PiperOrigin-RevId: 227934668 --- tensorflow/core/kernels/maxpooling_op.cc | 8 ++ .../core/kernels/maxpooling_op_gpu.cu.cc | 1 - tensorflow/core/kernels/pooling_ops_common.cc | 83 +++++++++++++++---- tensorflow/stream_executor/cuda/cuda_dnn.cc | 25 ++++++ tensorflow/stream_executor/cuda/cuda_dnn.h | 8 ++ tensorflow/stream_executor/dnn.h | 11 +++ tensorflow/stream_executor/stream.cc | 22 +++++ tensorflow/stream_executor/stream.h | 7 ++ 8 files changed, 149 insertions(+), 16 deletions(-) diff --git a/tensorflow/core/kernels/maxpooling_op.cc b/tensorflow/core/kernels/maxpooling_op.cc index 507fc99837..ab235843f7 100644 --- a/tensorflow/core/kernels/maxpooling_op.cc +++ b/tensorflow/core/kernels/maxpooling_op.cc @@ -41,6 +41,7 @@ limitations under the License. #include "tensorflow/core/util/use_cudnn.h" #if GOOGLE_CUDA +#include "cuda/include/cudnn.h" #include "tensorflow/core/kernels/maxpooling_op_gpu.h" #include "tensorflow/core/kernels/pooling_ops_common_gpu.h" #include "tensorflow/core/platform/stream_executor.h" @@ -1134,11 +1135,18 @@ class MaxPoolingNoMaskOp : public OpKernel { errors::InvalidArgument( "qint8 should be used with data_format NCHW_VECT_C.")); +#if CUDNN_VERSION >= 7300 + if (use_dnn_) { + DnnPoolingOp::Compute(context, se::dnn::PoolingMode::kMaximum, ksize_, + stride_, padding_, data_format_, tensor_in, + out_shape, propagate_nans_); +#else // These is_int8x4 checks avoid linker errors for missing qint8 kernels. if (!is_int8x4 && use_dnn_ && data_format_ == FORMAT_NCHW) { DnnPoolingOp::Compute(context, se::dnn::PoolingMode::kMaximum, ksize_, stride_, padding_, data_format_, tensor_in, out_shape, propagate_nans_); +#endif } else { Tensor* output = nullptr; OP_REQUIRES_OK(context, context->allocate_output(0, out_shape, &output)); diff --git a/tensorflow/core/kernels/maxpooling_op_gpu.cu.cc b/tensorflow/core/kernels/maxpooling_op_gpu.cu.cc index 56d0340547..f28811ffa4 100644 --- a/tensorflow/core/kernels/maxpooling_op_gpu.cu.cc +++ b/tensorflow/core/kernels/maxpooling_op_gpu.cu.cc @@ -390,7 +390,6 @@ bool MaxPoolForwardNoMask_NCHW_VECT_C::operator()( 0, d.stream()>>>(output_size, bottom_data, height, width, channels, pooled_height, pooled_width, kernel_h, kernel_w, stride_h, stride_w, pad_t, pad_l, top_data); - d.synchronize(); return d.ok(); } diff --git a/tensorflow/core/kernels/pooling_ops_common.cc b/tensorflow/core/kernels/pooling_ops_common.cc index e583f7feb4..69122f467c 100644 --- a/tensorflow/core/kernels/pooling_ops_common.cc +++ b/tensorflow/core/kernels/pooling_ops_common.cc @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #if GOOGLE_CUDA +#include "cuda/include/cudnn.h" #include "tensorflow/core/kernels/conv_2d.h" #include "tensorflow/core/kernels/pooling_ops_common_gpu.h" #include "tensorflow/core/platform/stream_executor.h" @@ -28,6 +29,20 @@ limitations under the License. namespace tensorflow { +namespace { + +template +struct RawType { + using type = T; +}; + +template <> +struct RawType { + using type = int8; +}; + +} // namespace + PoolParameters::PoolParameters(OpKernelContext* context, const std::vector& ksize, const std::vector& stride, @@ -156,7 +171,10 @@ void DnnPoolingOp::Compute(OpKernelContext* context, return; } - /// For now, cudnn does not support NHWC format, so we need to convert it + int batch_size = params.tensor_in_batch; + int depth = params.depth; +#if CUDNN_VERSION < 7300 + /// Earlier versions do not support NHWC format, so we need to convert it /// to NCHW before calling cudnn. We need to get rid of this once it is done Tensor transformed_input; if (data_format == FORMAT_NHWC) { @@ -181,7 +199,31 @@ void DnnPoolingOp::Compute(OpKernelContext* context, } else { transformed_output = *tensor_out; } - + se::dnn::DataLayout data_layout = se::dnn::DataLayout::kBatchDepthYX; +#else + auto& transformed_input = tensor_in; + auto& transformed_output = *tensor_out; + se::dnn::DataLayout data_layout; + switch (data_format) { + case FORMAT_NHWC: + data_layout = se::dnn::DataLayout::kBatchYXDepth; + break; + case FORMAT_NCHW: + data_layout = se::dnn::DataLayout::kBatchDepthYX; + break; + case FORMAT_NCHW_VECT_C: + // NCHW_VECT_C is not supported by cudnnPoolingForward(), but can be + // emulated via NHWC. + data_layout = se::dnn::DataLayout::kBatchYXDepth; + batch_size *= depth; + depth = 4; + break; + default: + OP_REQUIRES(context, false, + errors::InvalidArgument("Unsupported format: ", + ToString(data_format))); + } +#endif /// Get ready to call cudnn se::dnn::PoolingDescriptor pooling_desc; pooling_desc.set_pooling_mode(pooling_mode) @@ -194,23 +236,27 @@ void DnnPoolingOp::Compute(OpKernelContext* context, .set_propagate_nans(propagate_nans); se::dnn::BatchDescriptor input_desc; - input_desc.set_count(params.tensor_in_batch) + input_desc.set_count(batch_size) .set_height(params.tensor_in_rows) .set_width(params.tensor_in_cols) - .set_feature_map_count(params.depth) - .set_layout(se::dnn::DataLayout::kBatchDepthYX); + .set_feature_map_count(depth) + .set_layout(data_layout); se::dnn::BatchDescriptor output_desc; - output_desc.set_count(params.tensor_in_batch) + output_desc.set_count(batch_size) .set_height(params.out_height) .set_width(params.out_width) - .set_feature_map_count(params.depth) - .set_layout(se::dnn::DataLayout::kBatchDepthYX); + .set_feature_map_count(depth) + .set_layout(data_layout); + + auto input_data = + AsDeviceMemory(reinterpret_cast::type*>( + transformed_input.template flat().data()), + transformed_input.template flat().size()); - auto input_data = AsDeviceMemory(transformed_input.template flat().data(), - transformed_input.template flat().size()); auto output_data = - AsDeviceMemory(transformed_output.template flat().data(), + AsDeviceMemory(reinterpret_cast::type*>( + transformed_output.template flat().data()), transformed_output.template flat().size()); auto* stream = context->op_device_context()->stream(); @@ -222,15 +268,17 @@ void DnnPoolingOp::Compute(OpKernelContext* context, .ok(); OP_REQUIRES(context, status, errors::Internal("cudnn PoolForward launch failed")); - +#if CUDNN_VERSION < 7300 if (data_format == FORMAT_NHWC) { /// Transform the output data from NCHW back to NHWC auto toConstTensor = [](const Tensor& x) -> const Tensor { return x; }; - functor::NCHWToNHWC()( + using RT = typename RawType::type; + functor::NCHWToNHWC()( context->eigen_device(), - toConstTensor(transformed_output).template tensor(), - tensor_out->tensor()); + toConstTensor(transformed_output).template tensor(), + tensor_out->tensor()); } +#endif } template @@ -388,6 +436,11 @@ void DnnPoolingGradOp::Compute( template class DnnPoolingOp; \ template class DnnPoolingGradOp; TF_CALL_GPU_NUMBER_TYPES(DEFINE_DNN_OPS) + +#if CUDNN_VERSION >= 7300 +template class DnnPoolingOp; +#endif + #undef DEFINE_DNN_OPS #endif // GOOGLE_CUDA diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index 01ff248150..a34aa9354d 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -4207,6 +4207,31 @@ bool CudnnSupport::DoPoolForward( return IsStatusOk(status, /*report_error=*/true); } +bool CudnnSupport::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) { + // Alpha is the scaling factor for input. + float alpha = 1.0; + // Beta is the scaling factor for output. + float beta = 0.0; + + CudnnTensorDescriptor src_desc(input_dimensions, CUDNN_DATA_INT8); + CudnnTensorDescriptor dest_desc(output_dimensions, CUDNN_DATA_INT8); + CudnnPoolingDescriptor pooling_desc(pooling_dimensions); + + auto cudnn = cudnn_->GetHandle(parent_, stream); + 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())); + return port::Status::OK(); + }(); + return IsStatusOk(status, /*report_error=*/true); +} + bool CudnnSupport::DoPoolBackward( Stream* stream, const dnn::PoolingDescriptor& pooling_dimensions, const dnn::BatchDescriptor& input_dimensions, diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.h b/tensorflow/stream_executor/cuda/cuda_dnn.h index 044ed54514..4cce3c5626 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.h +++ b/tensorflow/stream_executor/cuda/cuda_dnn.h @@ -540,6 +540,14 @@ class CudnnSupport : public dnn::DnnSupport { DeviceMemory* output_data, ScratchAllocator* workspace_allocator) 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) override; + bool DoPoolBackward(Stream* stream, const dnn::PoolingDescriptor& pooling_dimensions, const dnn::BatchDescriptor& input_dimensions, diff --git a/tensorflow/stream_executor/dnn.h b/tensorflow/stream_executor/dnn.h index 1001824ed5..ff7b02cf6b 100644 --- a/tensorflow/stream_executor/dnn.h +++ b/tensorflow/stream_executor/dnn.h @@ -1617,6 +1617,17 @@ class DnnSupport { return false; } + virtual 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) { + LOG(FATAL) << "DoPoolForward not implemented for int8."; + return false; + } + // Performs differentiation of the pooling operation. virtual bool DoPoolBackward(Stream* stream, const dnn::PoolingDescriptor& pooling_dimensions, diff --git a/tensorflow/stream_executor/stream.cc b/tensorflow/stream_executor/stream.cc index 3edc66cde8..1b14e4c339 100644 --- a/tensorflow/stream_executor/stream.cc +++ b/tensorflow/stream_executor/stream.cc @@ -1490,6 +1490,28 @@ Stream &Stream::ThenPoolForward( return *this; } +Stream &Stream::ThenPoolForward( + 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) { + VLOG_CALL(PARAM(pooling_dimensions), PARAM(input_dimensions), + PARAM(input_data), PARAM(output_dimensions), PARAM(output_data), + PARAM(workspace_allocator)); + + if (ok()) { + if (dnn::DnnSupport *dnn = parent_->AsDnn()) { + CheckError(dnn->DoPoolForward(this, pooling_dimensions, input_dimensions, + input_data, output_dimensions, output_data, + workspace_allocator)); + } else { + SetErrorAndLogNoDnnSupport(); + } + } + return *this; +} + Stream &Stream::ThenPoolBackward( const dnn::PoolingDescriptor &pooling_dimensions, const dnn::BatchDescriptor &input_dimensions, diff --git a/tensorflow/stream_executor/stream.h b/tensorflow/stream_executor/stream.h index 0fc90cf83d..d5e2fdf58d 100644 --- a/tensorflow/stream_executor/stream.h +++ b/tensorflow/stream_executor/stream.h @@ -650,6 +650,13 @@ class Stream { DeviceMemory *output_data, ScratchAllocator *workspace_allocator = nullptr); + Stream &ThenPoolForward(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); + Stream &ThenPoolBackward(const dnn::PoolingDescriptor &pooling_dimensions, const dnn::BatchDescriptor &input_dimensions, const DeviceMemory &input_data, -- GitLab From 75f2e9c266ec68d202a11ea5514898ccd1735d86 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Fri, 4 Jan 2019 16:51:01 -0800 Subject: [PATCH 0226/2345] Automated rollback of commit 7cc1ff9072ce4c2c7f36b7011e24486071ad13ee PiperOrigin-RevId: 227934767 --- tensorflow/python/framework/constant_op.py | 4 ---- tensorflow/python/kernel_tests/constant_op_test.py | 11 ----------- 2 files changed, 15 deletions(-) diff --git a/tensorflow/python/framework/constant_op.py b/tensorflow/python/framework/constant_op.py index 62a25f3fea..ade0797dcd 100644 --- a/tensorflow/python/framework/constant_op.py +++ b/tensorflow/python/framework/constant_op.py @@ -277,10 +277,6 @@ def _constant_impl( (num_t, shape, shape.num_elements())) g = ops.get_default_graph() tensor_value = attr_value_pb2.AttrValue() - if tensor_util.is_tensor(value): - raise ValueError( - ("Cannot create a tf.constant from symbolic Tensor %s. Did you mean " - "tf.convert_to_tensor?") % (value,)) tensor_value.tensor.CopyFrom( tensor_util.make_tensor_proto( value, dtype=dtype, shape=shape, verify_shape=verify_shape, diff --git a/tensorflow/python/kernel_tests/constant_op_test.py b/tensorflow/python/kernel_tests/constant_op_test.py index 2b743ed4c4..583082c2aa 100644 --- a/tensorflow/python/kernel_tests/constant_op_test.py +++ b/tensorflow/python/kernel_tests/constant_op_test.py @@ -24,7 +24,6 @@ from google.protobuf import text_format from tensorflow.core.framework import graph_pb2 from tensorflow.core.framework import tensor_pb2 -from tensorflow.python.eager import def_function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes as dtypes_lib from tensorflow.python.framework import errors_impl @@ -631,16 +630,6 @@ class OnesTest(test.TestCase): self.assertEqual([2, 3], z.get_shape()) self.assertAllEqual(z.eval(), np.ones([2, 3])) - @test_util.run_in_graph_and_eager_modes - def testIteratedConstantSensibleException(self): - - @def_function.function - def constant_on_constant(x): - return constant_op.constant(x) - - with self.assertRaisesRegexp(ValueError, "convert_to_tensor"): - constant_on_constant(constant_op.constant(2)) - class OnesLikeTest(test.TestCase): -- GitLab From fe66882827806db63761b593c70c43340f40750e Mon Sep 17 00:00:00 2001 From: Martin Wicke Date: Fri, 4 Jan 2019 16:51:20 -0800 Subject: [PATCH 0227/2345] Refactor compatibility upgrade tool to use pasta (an AST based refactoring tool) instead of line based edits. This allows for some simplification (e.g., we can now remove arguments with a dict entry) and for much more powerful transformations. This passes all tests with no destructive test changes (though there are cosmetic test changes). Also adds transformations for tf.to_dtype -> tf.cast(..., dtype=...). PiperOrigin-RevId: 227934801 --- tensorflow/tools/compatibility/BUILD | 12 +- tensorflow/tools/compatibility/ast_edits.py | 668 +++++++++--------- .../tools/compatibility/ast_edits_test.py | 27 +- tensorflow/tools/compatibility/tf_upgrade.py | 26 +- .../tools/compatibility/tf_upgrade_test.py | 2 +- .../tools/compatibility/tf_upgrade_v2.py | 288 +++++--- .../tools/compatibility/tf_upgrade_v2_test.py | 105 +-- tensorflow/tools/pip_package/BUILD | 1 + tensorflow/workspace.bzl | 2 + third_party/pasta/BUILD | 1 + third_party/pasta/BUILD.bazel | 29 + third_party/pasta/BUILD.system | 13 + third_party/pasta/workspace.bzl | 16 + 13 files changed, 687 insertions(+), 503 deletions(-) create mode 100644 third_party/pasta/BUILD create mode 100644 third_party/pasta/BUILD.bazel create mode 100644 third_party/pasta/BUILD.system create mode 100644 third_party/pasta/workspace.bzl diff --git a/tensorflow/tools/compatibility/BUILD b/tensorflow/tools/compatibility/BUILD index a9902d77f5..452e6db941 100644 --- a/tensorflow/tools/compatibility/BUILD +++ b/tensorflow/tools/compatibility/BUILD @@ -1,17 +1,21 @@ -licenses(["notice"]) # Apache 2.0 - -package(default_visibility = ["//tensorflow:internal"]) - load( "//tensorflow:tensorflow.bzl", "tf_copts", # @unused "tf_cc_test", # @unused ) +licenses(["notice"]) # Apache 2.0 + +package(default_visibility = ["//tensorflow:internal"]) + py_library( name = "ast_edits", srcs = ["ast_edits.py"], srcs_version = "PY2AND3", + deps = [ + "@pasta", + "@six_archive//:six", + ], ) py_test( diff --git a/tensorflow/tools/compatibility/ast_edits.py b/tensorflow/tools/compatibility/ast_edits.py index 9106ec97c8..0ec6c2ac8c 100644 --- a/tensorflow/tools/compatibility/ast_edits.py +++ b/tensorflow/tools/compatibility/ast_edits.py @@ -19,7 +19,6 @@ from __future__ import division from __future__ import print_function import ast -import collections import os import re import shutil @@ -27,6 +26,9 @@ import sys import tempfile import traceback +import pasta +import six + # Some regular expressions we will need for parsing FIND_OPEN = re.compile(r"^\s*(\[).*$") FIND_STRING_CHARS = re.compile(r"['\"]") @@ -44,264 +46,294 @@ class APIChangeSpec(object): notifications) * `function_reorders`: maps functions whose argument order has changed to the list of arguments in the new order - * `function_handle`: maps function names to custom handlers for the function * `function_warnings`: maps full names of functions to warnings that will be printed out if the function is used. (e.g. tf.nn.convolution()) - * `unrestricted_function_warnings`: maps names of functions to warnings that - will be printed out when the function is used (e.g. foo.convolution()). - * `function_keyword_additions`: maps function names to a map of arg->value - names that should be passed to the function. + * `function_transformers`: maps function names to custom handlers For an example, see `TFAPIChangeSpec`. """ -class _FileEditTuple( - collections.namedtuple("_FileEditTuple", - ["comment", "line", "start", "old", "new"])): - """Each edit that is recorded by a _FileEditRecorder. +class _PastaEditVisitor(ast.NodeVisitor): + """AST Visitor that processes function calls. - Fields: - comment: A description of the edit and why it was made. - line: The line number in the file where the edit occurs (1-indexed). - start: The column number in the file where the edit occurs (0-indexed). - old: text string to remove (this must match what was in file). - new: text string to add in place of `old`. + Updates function calls from old API version to new API version using a given + change spec. """ - __slots__ = () + def __init__(self, api_change_spec): + self._api_change_spec = api_change_spec + self._log = [] # Holds 3-tuples: line, col, msg. + self._errors = [] # Same structure as _log. + self._stack = [] # Allow easy access to parents. + # Overridden to maintain a stack of nodes to allow for parent access + def visit(self, node): + self._stack.append(node) + super(_PastaEditVisitor, self).visit(node) + self._stack.pop() -class _FileEditRecorder(object): - """Record changes that need to be done to the file.""" + @property + def errors(self): + return self._errors - def __init__(self, filename): - # all edits are lists of chars - self._filename = filename + @property + def log(self): + return self._log - self._line_to_edit = collections.defaultdict(list) - self._errors = [] + def _format_log(self, log): + text = "" + for log_entry in log: + text += "Line %d:%d: %s\n" % log_entry + return text - def process(self, text): - """Process a list of strings, each corresponding to the recorded changes. + def log_text(self): + return self._format_log(self.log) - Args: - text: A list of lines of text (assumed to contain newlines) - Returns: - A tuple of the modified text and a textual description of what is done. - Raises: - ValueError: if substitution source location does not have expected text. - """ + def add_log(self, lineno, col, msg): + self._log.append((lineno, col, msg)) + print("Line %d:%d: %s" % (lineno, col, msg)) - change_report = "" - - # Iterate of each line - for line, edits in self._line_to_edit.items(): - offset = 0 - # sort by column so that edits are processed in order in order to make - # indexing adjustments cumulative for changes that change the string - # length - edits.sort(key=lambda x: x.start) - - # Extract each line to a list of characters, because mutable lists - # are editable, unlike immutable strings. - char_array = list(text[line - 1]) - - # Record a description of the change - change_report += "%r Line %d\n" % (self._filename, line) - change_report += "-" * 80 + "\n\n" - for e in edits: - change_report += "%s\n" % e.comment - change_report += "\n Old: %s" % (text[line - 1]) - - # Make underscore buffers for underlining where in the line the edit was - change_list = [" "] * len(text[line - 1]) - change_list_new = [" "] * len(text[line - 1]) - - # Iterate for each edit - for e in edits: - # Create effective start, end by accounting for change in length due - # to previous edits - start_eff = e.start + offset - end_eff = start_eff + len(e.old) - - # Make sure the edit is changing what it should be changing - old_actual = "".join(char_array[start_eff:end_eff]) - if old_actual != e.old: - raise ValueError("Expected text %r but got %r" % - ("".join(e.old), "".join(old_actual))) - # Make the edit - char_array[start_eff:end_eff] = list(e.new) - - # Create the underline highlighting of the before and after - change_list[e.start:e.start + len(e.old)] = "~" * len(e.old) - change_list_new[start_eff:end_eff] = "~" * len(e.new) - - # Keep track of how to generate effective ranges - offset += len(e.new) - len(e.old) - - # Finish the report comment - change_report += " %s\n" % "".join(change_list) - text[line - 1] = "".join(char_array) - change_report += " New: %s" % (text[line - 1]) - change_report += " %s\n\n" % "".join(change_list_new) - return "".join(text), change_report, self._errors - - def add(self, comment, line, start, old, new, error=None): - """Add a new change that is needed. + def add_error(self, lineno, col, msg): + # All errors are also added to the regular log. + self.add_log(lineno, col, msg) + self._errors.append((lineno, col, msg)) - Args: - comment: A description of what was changed - line: Line number (1 indexed) - start: Column offset (0 indexed) - old: old text - new: new text - error: this "edit" is something that cannot be fixed automatically - Returns: - None - """ - - self._line_to_edit[line].append( - _FileEditTuple(comment, line, start, old, new)) - if error: - self._errors.append("%s:%d: %s" % (self._filename, line, error)) - - -class _ASTCallVisitor(ast.NodeVisitor): - """AST Visitor that processes function calls. + def add_logs(self, logs): + """Record a log and print it. - Updates function calls from old API version to new API version using a given - change spec. - """ - - def __init__(self, filename, lines, api_change_spec): - self._filename = filename - self._file_edit = _FileEditRecorder(filename) - self._lines = lines - self._api_change_spec = api_change_spec + The log should be a tuple (lineno, col_offset, msg), which will be printed + and then recorded. It is part of the log available in the self.log property. - def process(self, lines): - return self._file_edit.process(lines) - - def generic_visit(self, node): - ast.NodeVisitor.generic_visit(self, node) - - def _rename_functions(self, node, full_name): - symbol_renames = self._api_change_spec.symbol_renames - try: - new_name = symbol_renames[full_name] - self._file_edit.add("Renamed function %r to %r" % (full_name, new_name), - node.lineno, node.col_offset, full_name, new_name) - except KeyError: - pass - - def _print_warning_for_function(self, node, full_name): - function_warnings = self._api_change_spec.function_warnings - try: - warning_message = function_warnings[full_name] - warning_message = warning_message.replace("", full_name) - self._file_edit.add(warning_message, - node.lineno, node.col_offset, full_name, full_name, - error="%s requires manual check." % full_name) - except KeyError: - pass + Args: + logs: The log to add. Must be a tuple (lineno, col_offset, msg). + """ + self._log.extend(logs) + for log in logs: + print("Line %d:%d: %s" % log) - def _print_warning_for_function_unrestricted(self, node): - """Print a warning when specific functions are called. + def add_errors(self, errors): + """Record an error and print it. - The function _print_warning_for_function matches the full name of the called - function, e.g., tf.foo.bar(). This function matches the function name that - is called, as long as the function is an attribute. For example, - `tf.foo.bar()` and `foo.bar()` are matched, but not `bar()`. + The error must be a tuple (lineno, col_offset, msg), which will be printed + and then recorded as both a log and an error. It is therefore part of the + log available in the self.log as well as the self.errors property. Args: - node: ast.Call object + errors: The log to add. Must be a tuple (lineno, col_offset, msg). """ - function_warnings = getattr( - self._api_change_spec, "unrestricted_function_warnings", {}) - if isinstance(node.func, ast.Attribute): - function_name = node.func.attr - try: - warning_message = function_warnings[function_name] - self._file_edit.add(warning_message, - node.lineno, node.col_offset, "", "", - error="%s requires manual check." % function_name) - except KeyError: - pass - - def _get_attribute_full_path(self, node): - """Traverse an attribute to generate a full name e.g. tf.foo.bar. + self.add_logs(errors) + self._errors.extend(errors) + + def _get_applicable_entries(self, transformer_field, full_name, name): + """Get all list entries indexed by name that apply to full_name or name.""" + # Transformers are indexed to full name, name, or no name + # as a performance optimization. + function_transformers = getattr(self._api_change_spec, + transformer_field, {}) + + glob_name = "*." + name if name else None + transformers = [] + if full_name in function_transformers: + transformers.append(function_transformers[full_name]) + if glob_name in function_transformers: + transformers.append(function_transformers[glob_name]) + if "*" in function_transformers: + transformers.append(function_transformers["*"]) + return transformers + + def _get_applicable_dict(self, transformer_field, full_name, name): + """Get all dict entries indexed by name that apply to full_name or name.""" + # Transformers are indexed to full name, name, or no name + # as a performance optimization. + function_transformers = getattr(self._api_change_spec, + transformer_field, {}) + + glob_name = "*." + name if name else None + transformers = function_transformers.get("*", {}).copy() + transformers.update(function_transformers.get(glob_name, {})) + transformers.update(function_transformers.get(full_name, {})) + return transformers + + def _get_full_name(self, node): + """Traverse an Attribute node to generate a full name, e.g., "tf.foo.bar". + + This is the inverse of _full_name_node. Args: node: A Node of type Attribute. Returns: - a '.'-delimited full-name or None if the tree was not a simple form. + a '.'-delimited full-name or None if node was not Attribute or Name. i.e. `foo()+b).bar` returns None, while `a.b.c` would return "a.b.c". """ curr = node items = [] while not isinstance(curr, ast.Name): if not isinstance(curr, ast.Attribute): - return None, None + return None items.append(curr.attr) curr = curr.value items.append(curr.id) - return ".".join(reversed(items)), items[0] + return ".".join(reversed(items)) - def _find_true_position(self, node): - """Return correct line number and column offset for a given node. + def _full_name_node(self, name, ctx=ast.Load()): + """Make an Attribute or Name node for name. - This is necessary mainly because ListComp's location reporting reports - the next token after the list comprehension list opening. + Translate a qualified name into nested Attribute nodes (and a Name node). + + Args: + name: The name to translate to a node. + ctx: What context this name is used in. Defaults to Load() Returns: - lineno, offset for the given node + A Name or Attribute node. + """ + names = name.split(".") + names.reverse() + node = ast.Name(id=names.pop(), ctx=ast.Load()) + while names: + node = ast.Attribute(value=node, attr=names.pop(), ctx=ast.Load()) + + # Change outermost ctx to the one given to us (inner ones should be Load). + node.ctx = ctx + return node + + def _maybe_add_warning(self, node, full_name): + """Adds an error to be printed about full_name at node.""" + function_warnings = self._api_change_spec.function_warnings + if full_name in function_warnings: + warning_message = function_warnings[full_name] + warning_message = warning_message.replace("", full_name) + self.add_error(node.lineno, node.col_offset, + "%s requires manual check: %s." % (full_name, + warning_message)) + return True + else: + return False + + def _maybe_add_call_warning(self, node, full_name, name): + """Print a warning when specific functions are called. + + The function _print_warning_for_function matches the full name of the called + function, e.g., tf.foo.bar(). This function matches the function name that + is called, as long as the function is an attribute. For example, + `tf.foo.bar()` and `foo.bar()` are matched, but not `bar()`. Args: - node: Node for which we wish to know the lineno and col_offset + node: ast.Call object + full_name: The precomputed full name of the callable, if one exists, None + otherwise. + name: The precomputed name of the callable, if one exists, None otherwise. + + Returns: + Whether an error was recorded. """ - if isinstance(node, ast.ListComp): - # Strangely, ast.ListComp returns the col_offset of the first token - # after the '[' token which appears to be a bug. Workaround by - # explicitly finding the real start of the list comprehension. - line = node.lineno - col = node.col_offset - # loop over lines - while 1: - # Reverse the text to and regular expression search for whitespace - text = self._lines[line - 1] - reversed_preceding_text = text[:col][::-1] - # First find if a [ can be found with only whitespace between it and - # col. - m = FIND_OPEN.match(reversed_preceding_text) - if m: - new_col_offset = col - m.start(1) - 1 - return line, new_col_offset + # Only look for *.-warnings here, the other will be handled by the Attribute + # visitor. Also, do not warn for bare functions, only if the call func is + # an attribute. + warned = False + if isinstance(node.func, ast.Attribute): + warned = self._maybe_add_warning(node, "*." + name) + + # All arg warnings are handled here, since only we have the args + arg_warnings = self._get_applicable_dict("function_arg_warnings", + full_name, name) + + used_args = [kw.arg for kw in node.keywords] + for arg, warning in arg_warnings.items(): + if arg in used_args: + warned = True + warning_message = warning.replace("", full_name or name) + self.add_error(node.lineno, node.col_offset, + "%s called with %s argument requires manual check: %s." % + (full_name or name, arg, warning_message)) + + return warned + + def _maybe_rename(self, parent, node, full_name): + """Replace node (Attribute or Name) with a node representing full_name.""" + new_name = self._api_change_spec.symbol_renames.get(full_name, None) + if new_name: + self.add_log(node.lineno, node.col_offset, + "Renamed %r to %r" % (full_name, new_name)) + new_node = self._full_name_node(new_name, node.ctx) + ast.copy_location(new_node, node) + pasta.ast_utils.replace_child(parent, node, new_node) + return True + else: + return False + + def _maybe_change_to_function_call(self, parent, node, full_name): + """Wraps node (typically, an Attribute or Expr) in a Call.""" + if full_name in self._api_change_spec.change_to_function: + if not isinstance(parent, ast.Call): + # ast.Call's constructor is really picky about how many arguments it + # wants, and also, it changed between Py2 and Py3. + if six.PY2: + new_node = ast.Call(node, [], [], None, None) else: - if (reversed_preceding_text == "" or - reversed_preceding_text.isspace()): - line = line - 1 - prev_line = self._lines[line - 1] - # TODO(aselle): - # this is poor comment detection, but it is good enough for - # cases where the comment does not contain string literal starting/ - # ending characters. If ast gave us start and end locations of the - # ast nodes rather than just start, we could use string literal - # node ranges to filter out spurious #'s that appear in string - # literals. - comment_start = prev_line.find("#") - if comment_start == -1: - col = len(prev_line) - 1 - elif FIND_STRING_CHARS.search(prev_line[comment_start:]) is None: - col = comment_start - else: - return None, None - else: - return None, None - # Most other nodes return proper locations (with notably does not), but - # it is not possible to use that in an argument. - return node.lineno, node.col_offset + new_node = ast.Call(node, [], []) + pasta.ast_utils.replace_child(parent, node, new_node) + ast.copy_location(new_node, node) + self.add_log(node.lineno, node.col_offset, + "Changed %r to a function call" % full_name) + return True + return False + + def _maybe_add_arg_names(self, node, full_name): + """Make args into keyword args if function called full_name requires it.""" + function_reorders = self._api_change_spec.function_reorders + + if full_name in function_reorders: + reordered = function_reorders[full_name] + new_keywords = [] + for idx, arg in enumerate(node.args): + keyword_arg = reordered[idx] + new_keywords.append(ast.keyword(arg=keyword_arg, value=arg)) + + if new_keywords: + self.add_log(node.lineno, node.col_offset, + "Added keywords to args of function %r" % full_name) + node.args = [] + node.keywords = new_keywords + (node.keywords or []) + return True + return False + + def _maybe_modify_args(self, node, full_name, name): + """Rename keyword args if the function called full_name requires it.""" + renamed_keywords = self._get_applicable_dict("function_keyword_renames", + full_name, name) + + if not renamed_keywords: + return False + + modified = False + new_keywords = [] + for keyword in node.keywords: + argkey = keyword.arg + if argkey in renamed_keywords: + modified = True + if renamed_keywords[argkey] is None: + lineno = getattr(keyword, "lineno", node.lineno) + col_offset = getattr(keyword, "col_offset", node.col_offset) + self.add_log(lineno, col_offset, + "Removed argument %s for function %s" % ( + argkey, full_name or name)) + else: + keyword.arg = renamed_keywords[argkey] + lineno = getattr(keyword, "lineno", node.lineno) + col_offset = getattr(keyword, "col_offset", node.col_offset) + self.add_log(lineno, col_offset, + "Renamed keyword argument for %s from %s to %s" % ( + full_name, argkey, renamed_keywords[argkey])) + new_keywords.append(keyword) + else: + new_keywords.append(keyword) + + if modified: + node.keywords = new_keywords + return modified def visit_Call(self, node): # pylint: disable=invalid-name """Handle visiting a call node in the AST. @@ -309,104 +341,74 @@ class _ASTCallVisitor(ast.NodeVisitor): Args: node: Current Node """ - self._print_warning_for_function_unrestricted(node) - - # Find a simple attribute name path e.g. "tf.foo.bar" - full_name, name = self._get_attribute_full_path(node.func) - - # Make sure the func is marked as being part of a call - node.func.is_function_for_call = True + assert self._stack[-1] is node + # Get the name for this call, so we can index stuff with it. + full_name = self._get_full_name(node.func) if full_name: - # Call special handlers - function_handles = self._api_change_spec.function_handle - glob_name = "*.{}".format(name) - if glob_name in function_handles: - function_handles[glob_name](self._file_edit, node, self._lines) - if full_name in function_handles: - function_handles[full_name](self._file_edit, node, self._lines) - - # Examine any non-keyword argument and make it into a keyword argument - # if reordering required. - function_reorders = self._api_change_spec.function_reorders - function_keyword_renames = ( - self._api_change_spec.function_keyword_renames) - - if full_name in function_reorders: - reordered = function_reorders[full_name] - for idx, arg in enumerate(node.args): - lineno, col_offset = self._find_true_position(arg) - if lineno is None or col_offset is None: - self._file_edit.add( - "Failed to add keyword %r to reordered function %r" % - (reordered[idx], full_name), - arg.lineno, - arg.col_offset, - "", - "", - error="A necessary keyword argument failed to be inserted.") - else: - keyword_arg = reordered[idx] - if (full_name in function_keyword_renames and - keyword_arg in function_keyword_renames[full_name]): - keyword_arg = function_keyword_renames[full_name][keyword_arg] - self._file_edit.add("Added keyword %r to reordered function %r" % - (reordered[idx], full_name), lineno, col_offset, - "", keyword_arg + "=") - - # Examine each keyword argument and convert it to the final renamed form - renamed_keywords = ({} if full_name not in function_keyword_renames else - function_keyword_renames[full_name]) - for keyword in node.keywords: - argkey = keyword.arg - argval = keyword.value - - if argkey in renamed_keywords: - argval_lineno, argval_col_offset = self._find_true_position(argval) - if argval_lineno is not None and argval_col_offset is not None: - # TODO(aselle): We should scan backward to find the start of the - # keyword key. Unfortunately ast does not give you the location of - # keyword keys, so we are forced to infer it from the keyword arg - # value. - key_start = argval_col_offset - len(argkey) - 1 - key_end = key_start + len(argkey) + 1 - if (self._lines[argval_lineno - 1][key_start:key_end] == argkey + - "="): - self._file_edit.add("Renamed keyword argument from %r to %r" % - (argkey, - renamed_keywords[argkey]), argval_lineno, - argval_col_offset - len(argkey) - 1, - argkey + "=", renamed_keywords[argkey] + "=") - continue - self._file_edit.add( - "Failed to rename keyword argument from %r to %r" % - (argkey, renamed_keywords[argkey]), - argval.lineno, - argval.col_offset - len(argkey) - 1, - "", - "", - error="Failed to find keyword lexographically. Fix manually.") - - ast.NodeVisitor.generic_visit(self, node) + name = full_name.split(".")[-1] + elif isinstance(node.func, ast.Name): + name = node.func.id + elif isinstance(node.func, ast.Attribute): + name = node.func.attr + else: + name = None + + # Call standard transformers for this node. + # Make sure warnings come first, since args or names triggering warnings + # may be removed by the other transformations. + self._maybe_add_call_warning(node, full_name, name) + # Make all args into kwargs + self._maybe_add_arg_names(node, full_name) + # Argument name changes or deletions + self._maybe_modify_args(node, full_name, name) + + # Call transformers. These have the ability to modify the node, and if they + # do, will return the new node they created (or the same node if they just + # changed it). The are given the parent, but we will take care of + # integrating their changes into the parent if they return a new node. + # + # These are matched on the old name, since renaming is performed by the + # Attribute visitor, which happens later. + transformers = self._get_applicable_entries("function_transformers", + full_name, name) + + parent = self._stack[-2] + + for transformer in transformers: + logs = [] + errors = [] + new_node = transformer(parent, node, full_name, name, logs, errors) + self.add_logs(logs) + self.add_errors(errors) + if new_node: + if new_node is not node: + pasta.ast_utils.replace_child(parent, node, new_node) + node = new_node + self._stack[-1] = node + + self.generic_visit(node) def visit_Attribute(self, node): # pylint: disable=invalid-name - """Handle bare Attributes i.e. [tf.foo, tf.bar]. + """Handle bare Attributes i.e. [tf.foo, tf.bar].""" + assert self._stack[-1] is node - Args: - node: Node that is of type ast.Attribute - """ - full_name, _ = self._get_attribute_full_path(node) + full_name = self._get_full_name(node) if full_name: + parent = self._stack[-2] + # Make sure the warning comes first, otherwise the name may have changed - self._print_warning_for_function(node, full_name) - self._rename_functions(node, full_name) - if full_name in self._api_change_spec.change_to_function: - if not hasattr(node, "is_function_for_call"): - new_text = full_name + "()" - self._file_edit.add("Changed %r to %r" % (full_name, new_text), - node.lineno, node.col_offset, full_name, new_text) + self._maybe_add_warning(node, full_name) - ast.NodeVisitor.generic_visit(self, node) + # Once we did a modification, node is invalid and not worth inspecting + # further. Also, we only perform modifications for simple nodes, so + # There'd be no point in descending further. + if self._maybe_rename(parent, node, full_name): + return + if self._maybe_change_to_function_call(parent, node, full_name): + return + + self.generic_visit(node) class ASTCodeUpgrader(object): @@ -429,16 +431,42 @@ class ASTCodeUpgrader(object): """ # Write to a temporary file, just in case we are doing an implace modify. + # pylint: disable=g-backslash-continuation with open(in_filename, "r") as in_file, \ tempfile.NamedTemporaryFile("w", delete=False) as temp_file: ret = self.process_opened_file(in_filename, in_file, out_filename, temp_file) + # pylint: enable=g-backslash-continuation shutil.move(temp_file.name, out_filename) return ret - # Broad exceptions are required here because ast throws whatever it wants. - # pylint: disable=broad-except + def _format_errors(self, errors, in_filename): + return ["%s:%d:%d: %s" % ((in_filename,) + error) for error in errors] + + def update_string_pasta(self, text, in_filename): + """Updates a file using pasta.""" + try: + t = pasta.parse(text) + except (SyntaxError, ValueError, TypeError): + log = "Failed to parse.\n\n" + traceback.format_exc() + return 0, "", log, [] + + visitor = _PastaEditVisitor(self._api_change_spec) + visitor.visit(t) + + errors = self._format_errors(visitor.errors, in_filename) + return 1, pasta.dump(t), visitor.log_text(), errors + + def _format_log(self, log, in_filename, out_filename): + text = "-" * 80 + "\n" + text += "Processing file %r\n outputting to %r\n" % (in_filename, + out_filename) + text += "-" * 80 + "\n\n" + text += log + text += "-" * 80 + "\n\n" + return text + def process_opened_file(self, in_filename, in_file, out_filename, out_file): """Process the given python file for incompatible changes. @@ -453,30 +481,16 @@ class ASTCodeUpgrader(object): Returns: A tuple representing number of files processed, log of actions, errors """ - process_errors = [] - text = "-" * 80 + "\n" - text += "Processing file %r\n outputting to %r\n" % (in_filename, - out_filename) - text += "-" * 80 + "\n\n" - - parsed_ast = None lines = in_file.readlines() - try: - parsed_ast = ast.parse("".join(lines)) - except Exception: - text += "Failed to parse %r\n\n" % in_filename - text += traceback.format_exc() - if parsed_ast: - visitor = _ASTCallVisitor(in_filename, lines, self._api_change_spec) - visitor.visit(parsed_ast) - out_text, new_text, process_errors = visitor.process(lines) - text += new_text - if out_file: - out_file.write(out_text) - text += "\n" - return 1, text, process_errors - - # pylint: enable=broad-except + processed_file, new_file_content, log, process_errors = ( + self.update_string_pasta("".join(lines), in_filename)) + + if out_file and processed_file: + out_file.write(new_file_content) + + return (processed_file, + self._format_log(log, in_filename, out_filename), + process_errors) def process_tree(self, root_directory, output_root_directory, copy_other_files): diff --git a/tensorflow/tools/compatibility/ast_edits_test.py b/tensorflow/tools/compatibility/ast_edits_test.py index 99f20a026f..b2e9f67638 100644 --- a/tensorflow/tools/compatibility/ast_edits_test.py +++ b/tensorflow/tools/compatibility/ast_edits_test.py @@ -39,6 +39,8 @@ following new APIs: from __future__ import absolute_import from __future__ import division from __future__ import print_function +import ast +import pasta import six from tensorflow.python.framework import test_util from tensorflow.python.platform import test as test_lib @@ -54,7 +56,6 @@ class NoUpdateSpec(ast_edits.APIChangeSpec): self.function_keyword_renames = {} self.symbol_renames = {} self.function_warnings = {} - self.unrestricted_function_warnings = {} self.change_to_function = {} @@ -401,7 +402,8 @@ class TestAstEdits(test_util.TensorFlowTestCase): def __init__(self): NoUpdateSpec.__init__(self) - self.unrestricted_function_warnings = {"foo": "not good"} + self.function_warnings = {"*.foo": "not good"} + texts = ["object.foo()", "get_object().foo()", "get_object().foo()", "object.foo().bar()"] for text in texts: @@ -416,5 +418,26 @@ class TestAstEdits(test_util.TensorFlowTestCase): self.assertNotIn("not good", report) +class ManualEditsTest(test_util.TensorFlowTestCase): + + def disabled_test_arg_order(self): + """Tests that generated arg order is sane.""" + text = "f(a)" + t = pasta.parse(text) + node = pasta.ast_utils.find_nodes_by_type(t, (ast.Call,))[0] + arg = ast.keyword(arg="b", value=ast.Num(n=0)) + node.keywords.append(arg) + + # This is only needed in Python3, and I think it's a bug (but maybe in ast). + arg.value.lineno = 0 + arg.value.col_offset = 0 + + # pasta.dump should never put kwargs before args, even if the col_offset is + # messed up. + # This fails if run with python3, but works find for python2. + # In python3, the dump yields "f(b=0, a)". + self.assertEqual(pasta.dump(t), "f(a, b=0)") + + if __name__ == "__main__": test_lib.main() diff --git a/tensorflow/tools/compatibility/tf_upgrade.py b/tensorflow/tools/compatibility/tf_upgrade.py index 68d2c02570..241b08510f 100644 --- a/tensorflow/tools/compatibility/tf_upgrade.py +++ b/tensorflow/tools/compatibility/tf_upgrade.py @@ -175,27 +175,13 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.op_scope": ["values", "name", "default_name"], } - # Specially handled functions. - self.function_handle = {"tf.reverse": self._reverse_handler} - # Warnings that should be printed if corresponding functions are used. - self.function_warnings = {} - - @staticmethod - def _reverse_handler(file_edit_recorder, node, lines): - del lines - # TODO(aselle): Could check for a literal list of bools and try to convert - # them to indices. - comment = ("ERROR: tf.reverse has had its argument semantics changed " - "significantly the converter cannot detect this reliably, so " - "you need to inspect this usage manually.\n") - file_edit_recorder.add( - comment, - node.lineno, - node.col_offset, - "tf.reverse", - "tf.reverse", - error="tf.reverse requires manual check.") + self.function_warnings = { + "tf.reverse": + "ERROR: tf.reverse has had its argument semantics changed " + "significantly. The converter cannot detect this reliably, so " + "you need to inspect this usage manually.\n", + } if __name__ == "__main__": diff --git a/tensorflow/tools/compatibility/tf_upgrade_test.py b/tensorflow/tools/compatibility/tf_upgrade_test.py index 66325ea2ad..cf05575a9d 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_test.py @@ -112,7 +112,7 @@ class TestUpgrade(test_util.TensorFlowTestCase): text = "tf.reverse(a, b)\n" _, unused_report, errors, new_text = self._upgrade(text) self.assertEqual(new_text, new_text) - self.assertEqual(errors, ["test.py:1: tf.reverse requires manual check."]) + self.assertIn("tf.reverse requires manual check", errors[0]) def testListComprehension(self): def _test(input, output): # pylint: disable=redefined-builtin diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2.py b/tensorflow/tools/compatibility/tf_upgrade_v2.py index 82839afc3d..9d9c5878f7 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2.py @@ -18,7 +18,9 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import re +import ast + +import pasta from tensorflow.tools.compatibility import ast_edits from tensorflow.tools.compatibility import renames_v2 @@ -31,7 +33,22 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): def __init__(self): # Maps from a function name to a dictionary that describes how to # map from an old argument keyword to the new argument keyword. + # If the new argument is None, it will be removed. + # Only keyword args are handled, so make sure to also put any function in + # function_reorders to ensure that all args are made into keywords first. self.function_keyword_renames = { + "tf.gradients": { + "colocate_gradients_with_ops": None, + }, + "tf.hessians": { + "colocate_gradients_with_ops": None, + }, + "*.minimize": { + "colocate_gradients_with_ops": None, + }, + "*.compute_gradients": { + "colocate_gradients_with_ops": None, + }, "tf.argmin": { "dimension": "axis", }, @@ -672,14 +689,31 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): # positional arguments yourself, this could do the wrong thing. self.function_reorders = reorders_v2.reorders - # Specially handled functions. - self.function_handle = { - "tf.batch_gather": self._batch_gather_handler, - "tf.nn.dropout": self._dropout_handler, - "tf.gradients": self._colocate_handler("tf.gradients"), - "*.minimize": self._colocate_handler("Optimizer.minimize"), - "*.compute_gradients": - self._colocate_handler("Optimizer.compute_gradients"), + # Specially handled functions (pasta version) + # Each transformer is a callable which will be called with the arguments + # transformer(parent, node, full_name, name, logs, errors) + # Where logs and errors are lists to which (line, col, msg) tuples can be + # appended, full_name is the FQN of the function called (or None if that is + # unknown), name is the name of the function called (or None is that is + # unknown). node is an ast.Call node representing this function call, and + # parent is its parent in the AST. + # The function may modify node (but not parent), and must return + # - none, if nothing was modified + # - node, if node was modified in place (make sure to use + # pasta.ast_utils.replace_child to swap out children, otherwise formatting + # may get messy) + # - a replacement for node, if the whole call node was replaced. The caller + # will take care of changing parent. + self.function_transformers = { + "tf.nn.dropout": self._dropout_transformer, + "tf.batch_gather": self._batch_gather_transformer, + "tf.to_bfloat16": self._cast_transformer, + "tf.to_complex128": self._cast_transformer, + "tf.to_complex64": self._cast_transformer, + "tf.to_double": self._cast_transformer, + "tf.to_float": self._cast_transformer, + "tf.to_int32": self._cast_transformer, + "tf.to_int64": self._cast_transformer, } decay_function_comment = ( @@ -748,9 +782,45 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "compat.v1 for backward compatibility. Please update these calls to " "the TF 2.0 versions.") + export_saved_model_renamed = ( + "(Manual edit required) Please rename the method export_savedmodel() " + "to export_saved_model(). Two things to note:\n\t(1) The argument " + "strip_default_attributes has been removed. The function will always " + "strip the default attributes from ops. If this breaks your code, " + "please switch to tf.compat.v1.estimator.Estimator.\n\t(2) This change " + "only effects core estimator. If you are using " + "tf.contrib.learn.Estimator, please switch to using core estimator.") + + make_initializable_iterator_deprecation = ( + "(Manual edit required) The " + "`tf.data.Dataset.make_initializable_iterator()` method has been " + "removed. If you are using the Estimator API, you can return a dataset " + "directly from your input functions without creating an iterator. " + "As a last resort, please replace calls to that method on `dataset` " + "with a call to " + "`tf.compat.v1.data.make_initializable_iterator(dataset)`.") + + make_one_shot_iterator_deprecation = ( + "(Manual edit required) The " + "`tf.data.Dataset.make_one_shot_iterator()` method has been " + "removed. If you are using eager execution, you can iterate over " + "`dataset` using a Python `for` loop. If you are using the Estimator " + "API, you can return a dataset directly from your input functions " + "without creating an iterator. As a last resort, please replace calls " + "to that method on `dataset` with a call to " + "`tf.compat.v1.data.make_one_shot_iterator(dataset)`.") + # Function warnings. placeholder inside warnings will be # replaced by function name. + # You can use *. to add items which do not check the FQN, and apply to e.g., + # methods. self.function_warnings = { + "*.export_savedmodel": + export_saved_model_renamed, + "*.make_initializable_iterator": + make_initializable_iterator_deprecation, + "*.make_one_shot_iterator": + make_one_shot_iterator_deprecation, "tf.assert_greater": assert_return_type_comment, "tf.assert_equal": @@ -834,11 +904,6 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): default_loss_reduction_changed, "tf.estimator.BaselineRegressor": default_loss_reduction_changed, - "tf.hessians": - "tf.hessians no longer takes " - "'colocate_gradients_with_ops' argument. Also, " - "arguments have been reordered so that 'name' is the " - "last argument.", "tf.nn.conv1d": "WARNING: use_cudnn_on_gpu argument has been removed and \"value\"" " was renamed to \"input\"", @@ -1064,111 +1129,118 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): metrics_comment, } + # Warnings that are emitted only if a specific arg is found. + self.function_arg_warnings = { + "tf.gradients": { + "colocate_gradients_with_ops": + "tf.gradients no longer takes " + "'colocate_gradients_with_ops' argument, it behaves as if it " + "was set to True.", + }, + "*.minimize": { + "colocate_gradients_with_ops": + "Optimizer.minimize no longer takes " + "'colocate_gradients_with_ops' argument, it behaves as if it " + "was set to True.", + }, + "*.compute_gradients": { + "colocate_gradients_with_ops": + "Optimizer.compute_gradients no " + "longer takes 'colocate_gradients_with_ops' argument, it " + "behaves as if it was set to True.", + }, + } + self.symbol_renames = { name: new_name for name, new_name in self.symbol_renames.items() } - export_saved_model_renamed = ( - "(Manual edit required) Please rename the method export_savedmodel() " - "to export_saved_model(). Two things to note:\n\t(1) The argument " - "strip_default_attributes has been removed. The function will always " - "strip the default attributes from ops. If this breaks your code, " - "please switch to tf.compat.v1.estimator.Estimator.\n\t(2) This change " - "only effects core estimator. If you are using " - "tf.contrib.learn.Estimator, please switch to using core estimator.") - - make_initializable_iterator_deprecation = ( - "(Manual edit required) The " - "`tf.data.Dataset.make_initializable_iterator()` method has been " - "removed. If you are using the Estimator API, you can return a dataset " - "directly from your input functions without creating an iterator. " - "As a last resort, please replace calls to that method on `dataset` " - "with a call to " - "`tf.compat.v1.data.make_initializable_iterator(dataset)`.") - - make_one_shot_iterator_deprecation = ( - "(Manual edit required) The " - "`tf.data.Dataset.make_one_shot_iterator()` method has been " - "removed. If you are using eager execution, you can iterate over " - "`dataset` using a Python `for` loop. If you are using the Estimator " - "API, you can return a dataset directly from your input functions " - "without creating an iterator. As a last resort, please replace calls " - "to that method on `dataset` with a call to " - "`tf.compat.v1.data.make_one_shot_iterator(dataset)`.") + @staticmethod + def _dropout_transformer(parent, node, full_name, name, logs, errors): + def _replace_keep_prob_node(parent, old_value): + """Replaces old_value with 1-(old_value).""" + one = ast.Num(n=1) + one.lineno = 0 + one.col_offset = 0 + new_value = ast.BinOp(left=one, op=ast.Sub(), + right=old_value) + # This copies the prefix and suffix on old_value to new_value. + pasta.ast_utils.replace_child(parent, old_value, new_value) + ast.copy_location(new_value, old_value) + # Put parentheses around keep_prob.value (and remove the old prefix/ + # suffix, they should only be around new_value). + pasta.base.formatting.set(old_value, "prefix", "(") + pasta.base.formatting.set(old_value, "suffix", ")") - # Specify warnings for functions that aren't restricted to the tf.x.y.z - # format. This should only be used for methods with unique names, e.g. - # export_savedmodel, which is only defined in Estimator objects. - self.unrestricted_function_warnings = { - "export_savedmodel": export_saved_model_renamed, - "make_initializable_iterator": make_initializable_iterator_deprecation, - "make_one_shot_iterator": make_one_shot_iterator_deprecation, - } + # Check if we have a keep_prob keyword arg + for keep_prob in node.keywords: + if keep_prob.arg == "keep_prob": + logs.append((node.lineno, node.col_offset, + "Changing keep_prob arg of tf.nn.dropout to rate, and " + "recomputing value. Please check this transformation.\n")) + keep_prob.arg = "rate" + _replace_keep_prob_node(keep_prob, keep_prob.value) + return node - @staticmethod - def _dropout_handler(file_edit_recorder, node, lines): - del lines + # Maybe it was a positional arg if len(node.args) < 2: - comment = ("ERROR: tf.nn.dropout did not take arguments, so automatic " - "transformation was disabled. tf.nn.dropout has changed " - "the semantics of the second argument.") - file_edit_recorder.add( - comment, - node.lineno, - node.col_offset, - "tf.nn.dropout", - "tf.nn.dropout", - error="tf.nn.dropout requires manual check.") + errors.append((node.lineno, node.col_offset, + "ERROR: tf.nn.dropout called without arguments, so " + "automatic fix was disabled. tf.nn.dropout has changed " + "the semantics of the second argument.")) else: - comment = ("WARNING: tf.nn.dropout has changed the semantics of the " - "second argument. Please check the transformation.\n") - file_edit_recorder.add( - comment, - node.args[1].lineno, - node.args[1].col_offset, - "", - "1 - ") + _replace_keep_prob_node(node, node.args[1]) + logs.append((node.lineno, node.col_offset, + "Changing keep_prob arg of tf.nn.dropout to rate, and " + "recomputing value.\n")) + errors.append((node.lineno, node.col_offset, + "WARNING: tf.nn.dropout has changed the semantics of the " + "second argument. Please check the applied transformation." + )) + return node @staticmethod - def _colocate_handler(name): - def _helper(file_edit_recorder, node, lines): - """Handler for updating colocate arguments.""" - del lines - for keyword in node.keywords: - if keyword.arg == "colocate_gradients_with_ops": - # TODO(jhseu): Since ast_edit.py does string replacement, there's no - # straightforward way to remove the argument. Try to fix before 2.0 is - # final. - comment = ("For tf.gradients and tf.Optimizer.minimize, " - "colocate_gradients_with_op has been removed and now " - "defaults to True.") - file_edit_recorder.add( - comment, - node.lineno, - node.col_offset, - "", - "", - error="{} requires manual check.".format(name)) - return _helper + def _cast_transformer(parent, node, full_name, name, logs, errors): + """Transforms to_int and to_float to cast(..., dtype=...).""" - @staticmethod - def _batch_gather_handler(file_edit_recorder, node, lines): - lineno = node.lineno - column = node.col_offset + # Find out the dtype to cast to from the function name + dtype_str = name[3:] + new_arg = ast.keyword(arg="dtype", + value=ast.Attribute(value=ast.Name(id="tf", + ctx=ast.Load()), + attr=dtype_str, ctx=ast.Load())) + + # Python3 ast requires the args for the Attribute, but codegen will mess up + # the arg order if we just set them to 0. + new_arg.value.lineno = node.lineno + new_arg.value.col_offset = node.col_offset+100 - # Find the position to add the batch_dims argument. We add it as the - # first argument, since that's easiest. This is safe because we included - # batch_gather in self.reordered_function_names, so it will have all - # of its arguments changed to keyword arguments. - m = re.match(r"tf\s*\.\s*batch_gather\s*\(", lines[lineno - 1][column:]) - if m is not None: - file_edit_recorder.add( - "Added keyword argument 'batch_dims=-1' to 'tf.batch_gather'", - lineno, column + m.end(), "", "batch_dims=-1, ") + node.keywords.append(new_arg) + if isinstance(node.func, ast.Attribute): + node.func.attr = "cast" else: - file_edit_recorder.add( - "Unable to add keyword argument 'batch_dims=-1' to 'tf.batch_gather'", - lineno, column, "", "", - error="Unable to add keyword argument batch_dims=-1 to " - "tf.batch_gather; please add it manually.") + assert isinstance(node.func, ast.Name) + node.func.id = "cast" + + logs.append((node.lineno, node.col_offset, + "Changed %s call to tf.cast(..., dtype=tf.%s)." % (full_name, + dtype_str))) + return node + + @staticmethod + def _batch_gather_transformer(parent, node, full_name, name, logs, errors): + # Check if the call already has a batch_dims argument + if any([kw.arg == "batch_dims" for kw in node.keywords]): + logs.append((node.lineno, node.col_offset, "tf.batch_gather already has " + "batch_dims argument. Neat.")) + return None + + minus_one = ast.Num(n=-1) + minus_one.lineno = 0 + minus_one.col_offset = 0 + new_arg = ast.keyword("batch_dims", minus_one) + node.keywords.append(new_arg) + logs.append((node.lineno, node.col_offset, + "Added keyword argument batch_dims=-1 to tf.batch_gather.")) + return node diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py index 54120cfdd7..d8bc0ba75a 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py @@ -239,8 +239,8 @@ class TestUpgrade(test_util.TensorFlowTestCase): } function_warnings = ( tf_upgrade_v2.TFAPIChangeSpec().function_warnings) - function_handles = ( - tf_upgrade_v2.TFAPIChangeSpec().function_handle) + function_transformers = ( + tf_upgrade_v2.TFAPIChangeSpec().function_transformers) keyword_renames = ( tf_upgrade_v2.TFAPIChangeSpec().function_keyword_renames) @@ -255,7 +255,7 @@ class TestUpgrade(test_util.TensorFlowTestCase): for name in names_v1: tf_name = "tf.%s" % name - if tf_name in function_warnings or tf_name in function_handles: + if tf_name in function_warnings or tf_name in function_transformers: continue # These require manual change if tf_name in v1_name_exceptions: continue @@ -362,15 +362,14 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map text = "%s(a, b)\n" % decay _, report, errors, _ = self._upgrade(text) - self.assertEqual(errors, ["test.py:1: %s requires manual check." % decay]) + self.assertIn("%s requires manual check" % decay, errors[0]) self.assertIn("%s has been changed" % decay, report) def testPiecewiseDecay(self): text = "tf.train.piecewise_constant_decay(a, b)\n" _, report, errors, _ = self._upgrade(text) - self.assertEqual( - errors, - ["test.py:1: tf.train.piecewise_constant_decay requires manual check."]) + self.assertIn("tf.train.piecewise_constant_decay requires manual check", + errors[0]) self.assertIn("tf.train.piecewise_constant_decay has been changed", report) def testMetrics(self): @@ -414,7 +413,7 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map text = ns + "(a, b)" _, report, errors, new_text = self._upgrade(text) self.assertEqual("tf.compat.v1.metrics." + m + "(a, b)", new_text) - self.assertEqual(errors, ["test.py:1: %s requires manual check." % ns]) + self.assertIn("test.py:1:0: %s requires manual check" % ns, errors[0]) self.assertIn( "WARNING: tf.metrics have been converted to object oriented" " versions in TF 2.0 and after. The metric function calls have been " @@ -445,7 +444,7 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map text = ns + "(a, b)" _, report, errors, new_text = self._upgrade(text) self.assertEqual("tf.compat.v1.losses." + l + "(a, b)", new_text) - self.assertEqual(errors, ["test.py:1: %s requires manual check." % ns]) + self.assertIn("test.py:1:0: %s requires manual check" % ns, errors[0]) self.assertIn( "WARNING: tf.losses have been converted to object oriented" " versions in TF 2.0 and after. The loss function calls have been " @@ -463,7 +462,7 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map text = ns + "(a, b)" _, report, errors, new_text = self._upgrade(text) self.assertEqual(text, new_text) - self.assertEqual(errors, ["test.py:1: %s requires manual check." % ns]) + self.assertIn("%s requires manual check" % ns, errors[0]) self.assertIn("loss_reduction has been changed", report) def testDropout(self): @@ -471,15 +470,40 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map _, unused_report, unused_errors, new_text = self._upgrade(text) self.assertEqual( new_text, - "tf.nn.dropout(x, 1 - keep_prob, name=\"foo\")\n", + "tf.nn.dropout(x, 1 - (keep_prob), name=\"foo\")\n", + ) + + text = "tf.nn.dropout(x, keep_prob=.4, name=\"foo\")\n" + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual( + new_text, + "tf.nn.dropout(x, rate=1 - (.4), name=\"foo\")\n", + ) + + text = ( + "tf.nn.dropout(x, # Stuff before\n" + " keep_prob=.4, # Stuff after\n" + " name=\"foo\")\n" + ) + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual( + new_text, + "tf.nn.dropout(x, # Stuff before\n" + " rate=1 - (.4), # Stuff after\n" + " name=\"foo\")\n", ) text = "tf.nn.dropout(x)\n" _, unused_report, errors, new_text = self._upgrade(text) self.assertEqual(new_text, text) + self.assertIn("tf.nn.dropout called without arguments", errors[0]) + + def testDropoutExpr(self): + text = "tf.nn.dropout(x, 1 - func(3 + 4.), name=\"foo\")\n" + _, unused_report, unused_errors, new_text = self._upgrade(text) self.assertEqual( - errors, - ["test.py:1: tf.nn.dropout requires manual check."] + new_text, + "tf.nn.dropout(x, 1 - (1 - func(3 + 4.)), name=\"foo\")\n", ) def testCountNonZeroChanges(self): @@ -543,9 +567,11 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map text = "tf.gradients(a, colocate_gradients_with_ops=False)\n" _, unused_report, errors, new_text = self._upgrade(text) - self.assertEqual(text, new_text) - self.assertEqual(errors, ["test.py:1: tf.gradients requires manual check."]) + self.assertEqual("tf.gradients(a)\n", new_text) + self.assertIn("tf.gradients", errors[0]) + self.assertIn("requires manual check", errors[0]) + def testColocateGradientsWithOpsMinimize(self): text = "optimizer.minimize(a, foo=False)\n" _, unused_report, errors, new_text = self._upgrade(text) self.assertEqual(text, new_text) @@ -553,10 +579,11 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map text = "optimizer.minimize(a, colocate_gradients_with_ops=False)\n" _, unused_report, errors, new_text = self._upgrade(text) - self.assertEqual(text, new_text) - self.assertEqual(errors, - ["test.py:1: Optimizer.minimize requires manual check."]) + self.assertEqual("optimizer.minimize(a)\n", new_text) + self.assertIn("requires manual check", errors[0]) + self.assertIn("minimize", errors[0]) + def testColocateGradientsWithOpsComputeGradients(self): text = "optimizer.compute_gradients(a, foo=False)\n" _, unused_report, errors, new_text = self._upgrade(text) self.assertEqual(text, new_text) @@ -564,10 +591,9 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map text = "optimizer.compute_gradients(a, colocate_gradients_with_ops=False)\n" _, unused_report, errors, new_text = self._upgrade(text) - self.assertEqual(text, new_text) - self.assertEqual(errors, - ["test.py:1: Optimizer.compute_gradients " - "requires manual check."]) + self.assertEqual("optimizer.compute_gradients(a)\n", new_text) + self.assertIn("requires manual check", errors[0]) + self.assertIn("compute_gradients", errors[0]) def testExportSavedModelRename(self): text = "self.est.export_savedmodel(path)" @@ -673,7 +699,7 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map _, report, errors, new_text = self._upgrade(text) self.assertEqual(new_text, expected_text) self.assertIn( - "tf.nn.softmax_cross_entropy_with_logits requires manual check.", + "tf.nn.softmax_cross_entropy_with_logits requires manual check", errors[0]) self.assertIn( "tf.nn.softmax_cross_entropy_with_logits behavior has changed. ", @@ -817,27 +843,24 @@ tf.print('abc') def testBatchGather(self): text = "tf.batch_gather(foo, bar)" - expected_text = "tf.gather(batch_dims=-1, params=foo, indices=bar)" + expected_text1 = "tf.gather(params=foo, indices=bar, batch_dims=-1)" + expected_text2 = "tf.gather(batch_dims=-1, params=foo, indices=bar)" _, unused_report, unused_errors, new_text = self._upgrade(text) - self.assertEqual(new_text, expected_text) + self.assertIn(new_text, [expected_text1, expected_text2]) text = "tf.batch_gather(params=foo, indices=bar)" - expected_text = "tf.gather(batch_dims=-1, params=foo, indices=bar)" - _, unused_report, unused_errors, new_text = self._upgrade(text) - self.assertEqual(new_text, expected_text) - - text = "tf.batch_gather ( foo, bar)" - expected_text = "tf.gather (batch_dims=-1, params=foo, indices=bar)" - _, unused_report, unused_errors, new_text = self._upgrade(text) - self.assertEqual(new_text, expected_text) - - text = "(tf.batch_gather\n(foo, bar))" - expected_text = "(tf.gather\n(params=foo, indices=bar))" - expected_errors = ["test.py:1: Unable to add keyword argument batch_dims=-1" - " to tf.batch_gather; please add it manually."] - _, unused_report, errors, new_text = self._upgrade(text) - self.assertEqual(errors, expected_errors) - self.assertEqual(new_text, expected_text) + expected_text1 = "tf.gather(params=foo, indices=bar, batch_dims=-1)" + expected_text2 = "tf.gather(batch_dims=-1, params=foo, indices=bar)" + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertIn(new_text, [expected_text1, expected_text2]) + + def testCast(self): + for dtype in ["int32", "int64", "float", "double", + "complex64", "complex128", "bfloat16"]: + text = "tf.to_%s(x, name='test')" % dtype + expected_text = "tf.cast(x, name='test', dtype=tf.%s)" % dtype + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) class TestUpgradeFiles(test_util.TensorFlowTestCase): diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index 93a85763f5..c51b45a49c 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -169,6 +169,7 @@ filegroup( "@local_config_sycl//sycl:LICENSE.text", "@nasm//:LICENSE", "@nsync//:LICENSE", + "@pasta//:LICENSE", "@pcre//:LICENCE", "@png_archive//:LICENSE", "@protobuf_archive//:LICENSE", diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 729eb8069f..b43312abb2 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -29,6 +29,7 @@ load("//third_party/jpeg:workspace.bzl", jpeg = "repo") load("//third_party/nasm:workspace.bzl", nasm = "repo") load("//third_party/kissfft:workspace.bzl", kissfft = "repo") load("//third_party/keras_applications_archive:workspace.bzl", keras_applications = "repo") +load("//third_party/pasta:workspace.bzl", pasta = "repo") def initialize_third_party(): """ Load third party repositories. See above load() statements. """ @@ -41,6 +42,7 @@ def initialize_third_party(): kissfft() jpeg() nasm() + pasta() # Sanitize a dependency so that it works correctly from code that includes # TensorFlow as a submodule. diff --git a/third_party/pasta/BUILD b/third_party/pasta/BUILD new file mode 100644 index 0000000000..9bd256a579 --- /dev/null +++ b/third_party/pasta/BUILD @@ -0,0 +1 @@ +# Empty BUILD file to force build system to see this directory at all. diff --git a/third_party/pasta/BUILD.bazel b/third_party/pasta/BUILD.bazel new file mode 100644 index 0000000000..6be33511e4 --- /dev/null +++ b/third_party/pasta/BUILD.bazel @@ -0,0 +1,29 @@ +# Description: +# AST-based python refactoring. + +licenses(["notice"]) # Apache2 + +exports_files(["LICENSE"]) + +py_library( + name = "pasta", + srcs = [ + "__init__.py", + "augment/__init__.py", + "augment/errors.py", + "augment/import_utils.py", + "augment/inline.py", + "augment/rename.py", + "base/__init__.py", + "base/annotate.py", + "base/ast_constants.py", + "base/ast_utils.py", + "base/codegen.py", + "base/formatting.py", + "base/scope.py", + "base/test_utils.py", + "base/token_generator.py", + ], + srcs_version = "PY2AND3", + visibility = ["//visibility:public"], +) diff --git a/third_party/pasta/BUILD.system b/third_party/pasta/BUILD.system new file mode 100644 index 0000000000..6adc953c5a --- /dev/null +++ b/third_party/pasta/BUILD.system @@ -0,0 +1,13 @@ +# Description: Pasta, AST based python refactoring. + +licenses(["notice"]) # Apache2 + +filegroup( + name = "LICENSE", + visibility = ["//visibility:public"], +) + +py_library( + name = "pasta", + visibility = ["//visibility:public"], +) diff --git a/third_party/pasta/workspace.bzl b/third_party/pasta/workspace.bzl new file mode 100644 index 0000000000..063bdb7b98 --- /dev/null +++ b/third_party/pasta/workspace.bzl @@ -0,0 +1,16 @@ +"""Loads pasta python package.""" + +load("//third_party:repo.bzl", "third_party_http_archive") + +def repo(): + third_party_http_archive( + name = "pasta", + urls = [ + "https://mirror.bazel.build/github.com/google/pasta/archive/c3d72cdee6fc806251949e912510444d58d7413c.tar.gz", + "https://github.com/google/pasta/archive/c3d72cdee6fc806251949e912510444d58d7413c.tar.gz", + ], + strip_prefix = "pasta-c3d72cdee6fc806251949e912510444d58d7413c/pasta", + sha256 = "b5905f9cecc4b28363c563f3c4cb0545288bd35f7cc72c55066e97e53befc084", + build_file = "//third_party/pasta:BUILD.bazel", + system_build_file = "//third_party/pasta:BUILD.system", + ) -- GitLab From 4b90409c6b9e11d0bf0517a8fc49f9826167cb0a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 17:04:27 -0800 Subject: [PATCH 0228/2345] Several fixes/improvements to wrap_function: 1. Makes gradient tape work on captures post-pruning 2. Allows users to prune with wrapped_func.inputs even when the func includes captures 3. Makes sure to write variables to collections for better legacy compatibility in compat.v1 (currently if the outer init_scope is eager, variables won't be written to the global_variables / trainable_variables collections) PiperOrigin-RevId: 227936352 --- tensorflow/python/eager/wrap_function.py | 23 +++++++++- tensorflow/python/eager/wrap_function_test.py | 44 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/eager/wrap_function.py b/tensorflow/python/eager/wrap_function.py index 0930b6116d..2c65e97871 100644 --- a/tensorflow/python/eager/wrap_function.py +++ b/tensorflow/python/eager/wrap_function.py @@ -38,8 +38,20 @@ class VariableHolder(object): self._variables = [] def variable_creator_scope(self, next_creator, **kwargs): + """Creates variables & adds them to collections to match legacy code.""" v = next_creator(**kwargs) self._variables.append(v) + + collections = kwargs.get("collections") + trainable = v.trainable + + if collections is None: + collections = [ops.GraphKeys.GLOBAL_VARIABLES] + if trainable and ops.GraphKeys.TRAINABLE_VARIABLES not in collections: + collections = list(collections) + [ops.GraphKeys.TRAINABLE_VARIABLES] + + ops.add_to_collections(collections, v) + return v def __call__(self, *args, **kwargs): @@ -61,6 +73,13 @@ class WrappedFunction(function.Function): for f in flat_feeds: if not isinstance(f, ops.Tensor): raise ValueError("Feeds must be tensors.") + + # Ignoring all feeds that are captures allows prune to be called + # using wrapped_func.inputs even when it uses variables + internal_captures = self.graph.internal_captures + flat_feeds = [f for f in flat_feeds + if f not in internal_captures] + tensor_fetches = [] operation_fetches = [] for f in flat_fetches: @@ -87,7 +106,7 @@ class WrappedFunction(function.Function): sink_tensor = control_flow_ops.no_op() lift_map = lift_to_graph.lift_to_graph( sink_tensor, pruned_graph, - sources=flat_feeds + self.graph.internal_captures) + sources=flat_feeds + internal_captures) for original_fetch, identity_fetch in zip( tensor_fetches, identity_fetches): lift_map[original_fetch] = lift_map[identity_fetch] @@ -98,6 +117,8 @@ class WrappedFunction(function.Function): pruned_graph.inputs.extend(lift_map[x] for x in flat_feeds) pruned_graph.inputs.extend(pruned_graph.captures.values()) + pruned_graph.variables = self.graph.variables + def _structured_output_mapping(fetched): lifted = lift_map[fetched] if isinstance(lifted, ops.Operation): diff --git a/tensorflow/python/eager/wrap_function_test.py b/tensorflow/python/eager/wrap_function_test.py index 65dd73aafc..17e204b4d8 100644 --- a/tensorflow/python/eager/wrap_function_test.py +++ b/tensorflow/python/eager/wrap_function_test.py @@ -18,6 +18,7 @@ from __future__ import division from __future__ import print_function +from tensorflow.python.eager import backprop from tensorflow.python.eager import wrap_function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes @@ -90,11 +91,54 @@ class WrapFunctionTest(test.TestCase): f_wrapped = wrap_function.wrap_function(f, []) self.assertAllEqual(6.0, f_wrapped()) + + # Test pruning directly on the inputs + pruned = f_wrapped.prune( + feeds=f_wrapped.inputs, + fetches=f_wrapped.graph.get_tensor_by_name('fetch:0')) + self.assertAllEqual(6.0, pruned()) + + # Test pruning with no inputs pruned = f_wrapped.prune( feeds=(), fetches=f_wrapped.graph.get_tensor_by_name('fetch:0')) self.assertAllEqual(6.0, pruned()) + def testGradientsOfPrune(self): + + v1 = variables.Variable(2.) + v2_holder = [] + + def f(z): + v2 = variables.Variable(3.) + v2_holder.append(v2) + return array_ops.identity(v1 * v2 * z, 'fetch') + + f_wrapped = wrap_function.wrap_function( + f, [tensor_spec.TensorSpec((), dtype=dtypes.float32)]) + + x = constant_op.constant(1.) + with backprop.GradientTape() as tape: + tape.watch(x) + out = f_wrapped(x) + grads = tape.gradient(out, [x, v1, v2_holder[0]]) + + self.assertAllEqual(6.0, out) + self.assertAllEqual([6.0, 3.0, 2.0], grads) + + pruned = f_wrapped.prune( + feeds=f_wrapped.inputs, + fetches=f_wrapped.graph.get_tensor_by_name('fetch:0')) + + x = constant_op.constant(1.) + with backprop.GradientTape() as tape: + tape.watch(x) + out = pruned(x) + grads = tape.gradient(out, [x, v1, v2_holder[0]]) + + self.assertAllEqual(6.0, out) + self.assertAllEqual([6.0, 3.0, 2.0], grads) + def testPruneOperations(self): v = variables.Variable(0) -- GitLab From 542e30bab143034047ee18a0b060648be78df58c Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Fri, 4 Jan 2019 17:11:01 -0800 Subject: [PATCH 0229/2345] Enable nnapi_delegate_test in oss builds PiperOrigin-RevId: 227937061 --- tensorflow/lite/delegates/nnapi/BUILD | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/delegates/nnapi/BUILD b/tensorflow/lite/delegates/nnapi/BUILD index fd954ba222..dda3c02567 100644 --- a/tensorflow/lite/delegates/nnapi/BUILD +++ b/tensorflow/lite/delegates/nnapi/BUILD @@ -3,6 +3,7 @@ package(default_visibility = [ ]) load("//tensorflow:tensorflow.bzl", "tf_cc_test") +load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite") licenses(["notice"]) # Apache 2.0 @@ -23,7 +24,7 @@ tf_cc_test( name = "nnapi_delegate_test", size = "small", srcs = ["nnapi_delegate_test.cc"], - tags = ["no_oss"], + tags = ["tflite_not_portable_ios"], deps = [ ":nnapi_delegate", "//tensorflow/lite:framework", @@ -32,3 +33,5 @@ tf_cc_test( "@com_google_googletest//:gtest", ], ) + +tflite_portable_test_suite() -- GitLab From 774ede5790c6cf11da05ca1082e9870db7b843f9 Mon Sep 17 00:00:00 2001 From: Karmel Allison Date: Fri, 4 Jan 2019 17:23:06 -0800 Subject: [PATCH 0230/2345] Consolidated duplicate tests for model cloning and added Keras test decorators where appropriate. Updated models_tests to run in v1/v2. This required several other changes: * Added a testing utility to get v2 optimizers * Ensured that subclassed models cleared key compilation attrs upon clone_reset (_is_compiled; optimizer) * Modified train/eval loops to check self._is_compiled rather than the existence of other attrs created during compiling, as these other attrs would not be reset during cloning. PiperOrigin-RevId: 227938218 --- tensorflow/python/keras/BUILD | 7 +- tensorflow/python/keras/engine/training.py | 4 +- tensorflow/python/keras/models.py | 44 +- tensorflow/python/keras/models_test.py | 734 ++++++++------------- tensorflow/python/keras/testing_utils.py | 56 +- 5 files changed, 375 insertions(+), 470 deletions(-) diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 914568f652..3ffff61c2b 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -1,5 +1,7 @@ # Description: # Contains the Keras API (internal TensorFlow version). +load("//tensorflow:tensorflow.bzl", "tf_py_test") +load("//tensorflow:tensorflow.bzl", "cuda_py_test") licenses(["notice"]) # Apache 2.0 @@ -7,9 +9,6 @@ package(default_visibility = ["//visibility:public"]) exports_files(["LICENSE"]) -load("//tensorflow:tensorflow.bzl", "tf_py_test") -load("//tensorflow:tensorflow.bzl", "cuda_py_test") - config_setting( name = "empty_condition", values = {"define": "UNUSED=unused"}, @@ -1022,7 +1021,7 @@ tf_py_test( "//tensorflow/python:client_testlib", "//tensorflow/python:training", ], - shard_count = 2, + shard_count = 8, tags = ["notsan"], # b/67509773 ) diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index 8263467c37..bd06751428 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -1991,7 +1991,7 @@ class Model(Network): ' without calling `model.compile` after ?', 1) def _make_train_function_helper(self, fn_name, outputs, metric_updates=None): - if not hasattr(self, fn_name): + if not self._is_compiled: raise RuntimeError('You must compile your model before using it.') self._check_trainable_weights_consistency() if getattr(self, fn_name) is None: @@ -2040,7 +2040,7 @@ class Model(Network): '_fit_function', [self.total_loss] + metrics_tensors) def _make_test_function_helper(self, fn_name, outputs, metric_updates=None): - if not hasattr(self, fn_name): + if not self._is_compiled: raise RuntimeError('You must compile your model before using it.') if getattr(self, fn_name) is None: inputs = (self._feed_inputs + diff --git a/tensorflow/python/keras/models.py b/tensorflow/python/keras/models.py index f36c89041c..851de6e71f 100644 --- a/tensorflow/python/keras/models.py +++ b/tensorflow/python/keras/models.py @@ -281,8 +281,6 @@ def clone_model(model, input_tensors=None): # "Clone" a subclassed model by reseting all of the attributes. - - def _in_place_subclassed_model_reset(model): """Substitute for model cloning that works for subclassed models. @@ -382,11 +380,30 @@ def _in_place_subclassed_model_reset(model): for name in attributes_to_cache: attributes_cache[name] = getattr(model, name) model._original_attributes_cache = attributes_cache - # Reset built state + _reset_build_compile_trackers(model) + model._setattr_tracking = setattr_tracking + + +def _reset_build_compile_trackers(model): + """Reset state trackers for model. + + Note that we do not actually zero out attributes such as optimizer, + but instead rely on the expectation that all of the attrs will be + over-written on calling build/compile/etc. This is somewhat fragile, + insofar as we check elsewhere for the presence of these attributes as + evidence of having been built/compiled/etc. Pending a better way to do this, + we reset key attributes here to allow building and compiling. + + Args: + model: the model that is being reset + """ + # Reset build state model.built = False model.inputs = None model.outputs = None - model._setattr_tracking = setattr_tracking + # Reset compile state + model._is_compiled = False # pylint:disable=protected-access + model.optimizer = None def in_place_subclassed_model_state_restoration(model): @@ -418,9 +435,7 @@ def in_place_subclassed_model_state_restoration(model): model._setattr_tracking = setattr_tracking else: # Restore to the state of a never-called model. - model.built = False - model.inputs = None - model.outputs = None + _reset_build_compile_trackers(model) def clone_and_build_model( @@ -462,7 +477,10 @@ def clone_and_build_model( - cloning a subclassed model with `in_place_reset` set to False. - compiling the clone when the original model has not been compiled. """ - if compile_clone and not model.optimizer: + # Grab optimizer now, as we reset-in-place for subclassed models, but + # want to maintain access to the original optimizer. + orig_optimizer = model.optimizer + if compile_clone and not orig_optimizer: raise ValueError( 'Error when cloning model: compile_clone was set to True, but the ' 'original model has not been compiled.') @@ -498,14 +516,14 @@ def clone_and_build_model( input_tensors = input_tensors[0] clone._set_inputs(input_tensors) - if compile_clone and model.optimizer: - if isinstance(model.optimizer, optimizers.TFOptimizer): + if compile_clone: + if isinstance(orig_optimizer, optimizers.TFOptimizer): optimizer = optimizers.TFOptimizer( - model.optimizer.optimizer, optimizer_iterations) + orig_optimizer.optimizer, optimizer_iterations) K.track_tf_optimizer(optimizer) else: - optimizer_config = model.optimizer.get_config() - optimizer = model.optimizer.__class__.from_config(optimizer_config) + optimizer_config = orig_optimizer.get_config() + optimizer = orig_optimizer.__class__.from_config(optimizer_config) if optimizer_iterations is not None: optimizer.iterations = optimizer_iterations diff --git a/tensorflow/python/keras/models_test.py b/tensorflow/python/keras/models_test.py index 0a5f9a7bea..3eab10f624 100644 --- a/tensorflow/python/keras/models_test.py +++ b/tensorflow/python/keras/models_test.py @@ -19,18 +19,21 @@ from __future__ import division from __future__ import print_function import copy +import functools import os +from absl.testing import parameterized import numpy as np from tensorflow.python import keras from tensorflow.python.eager import context from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops -from tensorflow.python.framework import test_util from tensorflow.python.keras import backend as K +from tensorflow.python.keras import keras_parameterized from tensorflow.python.keras import metrics from tensorflow.python.keras import models +from tensorflow.python.keras import testing_utils from tensorflow.python.ops import array_ops from tensorflow.python.ops import random_ops from tensorflow.python.ops import resource_variable_ops @@ -52,158 +55,181 @@ class TestModel(keras.Model): return self.layer1(x) -def sequential_model(add_input_layer, include_input_shape=True): - model = keras.models.Sequential() +def _get_layers(input_shape=(4,), add_input_layer=False): if add_input_layer: - model.add(keras.layers.InputLayer(input_shape=(4,))) - model.add(keras.layers.Dense(4)) - elif include_input_shape: - model.add(keras.layers.Dense(4, input_shape=(4,))) + model_layers = [keras.layers.InputLayer(input_shape=input_shape), + keras.layers.Dense(4)] + elif input_shape: + model_layers = [keras.layers.Dense(4, input_shape=input_shape)] else: - model.add(keras.layers.Dense(4)) - model.add(keras.layers.BatchNormalization()) - model.add(keras.layers.Dropout(0.5)) - model.add(keras.layers.Dense(4)) - return model - - -class TestModelCloning(test.TestCase): - - @test_util.run_v1_only('b/120545219') - def test_clone_sequential_model(self): - with self.cached_session(): - val_a = np.random.random((10, 4)) - val_out = np.random.random((10, 4)) - - model = sequential_model(False) - - # Everything should work in a new session. - keras.backend.clear_session() - - with self.cached_session(): - # With placeholder creation - new_model = keras.models.clone_model(model) + model_layers = [keras.layers.Dense(4)] + + model_layers += [ + keras.layers.BatchNormalization(), + keras.layers.Dropout(0.5), + keras.layers.Dense(4)] + + return model_layers + + +def _get_model(input_shape=(4,)): + model_layers = _get_layers(input_shape=None, add_input_layer=False) + return testing_utils.get_model_from_layers( + model_layers, input_shape=input_shape) + + +class TestModelCloning(keras_parameterized.TestCase): + + @keras_parameterized.run_all_keras_modes + @parameterized.named_parameters([ + {'testcase_name': 'has_input_layer', + 'input_shape': (4,), + 'add_input_layer': True, + 'share_weights': False}, + {'testcase_name': 'no_input_layer', + 'input_shape': None, + 'add_input_layer': False, + 'share_weights': False}, + {'testcase_name': 'has_input_layer_share_weights', + 'input_shape': (4,), + 'add_input_layer': True, + 'share_weights': True}, + {'testcase_name': 'no_input_layer_share_weights', + 'input_shape': None, + 'add_input_layer': False, + 'share_weights': True}, + ]) + def test_clone_sequential_model( + self, input_shape, add_input_layer, share_weights): + + if share_weights: + clone_fn = functools.partial( + keras.models._clone_sequential_model, share_weights=True) + else: + clone_fn = keras.models.clone_model + + val_a = np.random.random((10, 4)) + model = models.Sequential(_get_layers(input_shape, add_input_layer)) + # Sanity check + self.assertEqual( + isinstance(model._layers[0], keras.layers.InputLayer), + add_input_layer) + self.assertEqual(model._is_graph_network, add_input_layer) + + # With placeholder creation -- clone model should have an InputLayer + # if the original model has one. + new_model = clone_fn(model) + self.assertEqual( + isinstance(new_model._layers[0], keras.layers.InputLayer), + add_input_layer) + self.assertEqual(new_model._is_graph_network, model._is_graph_network) + if input_shape: # update ops from batch norm needs to be included self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch(val_a, val_out) - - # On top of new tensor - input_a = keras.Input(shape=(4,)) - new_model = keras.models.clone_model(model, input_tensors=input_a) - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch(val_a, val_out) - # On top of new, non-Keras tensor + # On top of new tensor -- clone model should always have an InputLayer. + input_a = keras.Input(shape=(4,)) + new_model = clone_fn(model, input_tensors=input_a) + self.assertIsInstance(new_model._layers[0], keras.layers.InputLayer) + self.assertTrue(new_model._is_graph_network) + + # On top of new, non-Keras tensor -- clone model should always have an + # InputLayer. + if not context.executing_eagerly(): + # TODO(b/121277734):Skip Eager contexts, as Input() layers raise an error + # saying they should not be used with EagerTensors input_a = keras.backend.variable(val_a) - new_model = keras.models.clone_model(model, input_tensors=input_a) - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch(None, val_out) - - @test_util.run_v1_only('b/120545219') - def test_clone_sequential_model_input_layer(self): - - def test_input_layer(include_inputs): - with self.cached_session(): - val_a = np.random.random((10, 4)) - model = sequential_model(include_inputs, include_inputs) - # Sanity check - self.assertEqual( - isinstance(model._layers[0], keras.layers.InputLayer), - include_inputs) - self.assertEqual(model._is_graph_network, include_inputs) - - keras.backend.clear_session() - with self.cached_session(): - # With placeholder creation -- clone model should have an InputLayer - # if the original model has one. - new_model = keras.models.clone_model(model) - self.assertEqual( - isinstance(new_model._layers[0], keras.layers.InputLayer), - include_inputs) - self.assertEqual(new_model._is_graph_network, model._is_graph_network) - - # On top of new tensor -- clone model should always have an InputLayer. - input_a = keras.Input(shape=(4,)) - new_model = keras.models.clone_model(model, input_tensors=input_a) - self.assertIsInstance(new_model._layers[0], keras.layers.InputLayer) - self.assertTrue(new_model._is_graph_network) - - # On top of new, non-Keras tensor -- clone model should always have an - # InputLayer. - input_a = keras.backend.variable(val_a) - new_model = keras.models.clone_model(model, input_tensors=input_a) - self.assertIsInstance(new_model._layers[0], keras.layers.InputLayer) - self.assertTrue(new_model._is_graph_network) - - test_input_layer(True) - test_input_layer(False) - - @test_util.run_v1_only('b/120545219') - def test_clone_functional_model(self): - with self.cached_session(): - val_a = np.random.random((10, 4)) - val_b = np.random.random((10, 4)) - val_out = np.random.random((10, 4)) - - input_a = keras.Input(shape=(4,)) - input_b = keras.Input(shape=(4,)) - dense_1 = keras.layers.Dense(4,) - dense_2 = keras.layers.Dense(4,) - - x_a = dense_1(input_a) - x_a = keras.layers.Dropout(0.5)(x_a) - x_a = keras.layers.BatchNormalization()(x_a) - x_b = dense_1(input_b) - x_a = dense_2(x_a) - outputs = keras.layers.add([x_a, x_b]) - model = keras.models.Model([input_a, input_b], outputs) - - # Everything should work in a new session. - keras.backend.clear_session() - - with self.cached_session(): - # With placeholder creation - new_model = keras.models.clone_model(model) - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch([val_a, val_b], val_out) - - # On top of new tensors - input_a = keras.Input(shape=(4,), name='a') - input_b = keras.Input(shape=(4,), name='b') - new_model = keras.models.clone_model( - model, input_tensors=[input_a, input_b]) - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch([val_a, val_b], val_out) - - # On top of new, non-Keras tensors + new_model = clone_fn(model, input_tensors=input_a) + self.assertIsInstance(new_model._layers[0], keras.layers.InputLayer) + self.assertTrue(new_model._is_graph_network) + + @keras_parameterized.run_all_keras_modes + @parameterized.named_parameters([ + {'testcase_name': 'clone_weights', 'share_weights': False}, + {'testcase_name': 'share_weights', 'share_weights': True}, + ]) + def test_clone_functional_model(self, share_weights): + if share_weights: + clone_fn = functools.partial( + keras.models._clone_functional_model, share_weights=True) + else: + clone_fn = keras.models.clone_model + + val_a = np.random.random((10, 4)) + val_b = np.random.random((10, 4)) + val_out = np.random.random((10, 4)) + + input_a = keras.Input(shape=(4,)) + input_b = keras.Input(shape=(4,)) + dense_1 = keras.layers.Dense(4,) + dense_2 = keras.layers.Dense(4,) + + x_a = dense_1(input_a) + x_a = keras.layers.Dropout(0.5)(x_a) + x_a = keras.layers.BatchNormalization()(x_a) + x_b = dense_1(input_b) + x_a = dense_2(x_a) + outputs = keras.layers.add([x_a, x_b]) + model = keras.models.Model([input_a, input_b], outputs) + + # With placeholder creation + new_model = clone_fn(model) + self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) + new_model.compile( + testing_utils.get_v2_optimizer('rmsprop'), 'mse', + run_eagerly=testing_utils.should_run_eagerly()) + new_model.train_on_batch([val_a, val_b], val_out) + + # On top of new tensors + input_a = keras.Input(shape=(4,), name='a') + input_b = keras.Input(shape=(4,), name='b') + new_model = keras.models.clone_model( + model, input_tensors=[input_a, input_b]) + self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) + new_model.compile( + testing_utils.get_v2_optimizer('rmsprop'), 'mse', + run_eagerly=testing_utils.should_run_eagerly()) + new_model.train_on_batch([val_a, val_b], val_out) + + # On top of new, non-Keras tensors + if not context.executing_eagerly(): + # TODO(b/121277734):Skip Eager contexts, as Input() layers raise an error + # saying they should not be used with EagerTensors input_a = keras.backend.variable(val_a) input_b = keras.backend.variable(val_b) - new_model = keras.models.clone_model( - model, input_tensors=[input_a, input_b]) + new_model = clone_fn(model, input_tensors=[input_a, input_b]) self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') + new_model.compile( + testing_utils.get_v2_optimizer('rmsprop'), 'mse', + run_eagerly=testing_utils.should_run_eagerly()) new_model.train_on_batch(None, val_out) - @test_util.run_in_graph_and_eager_modes - def test_clone_functional_model_with_masking(self): - with self.cached_session(): - x = np.array([[[1], [1]], [[0], [0]]]) - inputs = keras.Input((2, 1)) - outputs = keras.layers.Masking(mask_value=0)(inputs) - outputs = keras.layers.TimeDistributed( - keras.layers.Dense(1, kernel_initializer='one'))(outputs) - model = keras.Model(inputs, outputs) - - model = keras.models.clone_model(model) - model.compile(loss='mse', optimizer=adam.AdamOptimizer(0.01)) - y = np.array([[[1], [1]], [[1], [1]]]) - loss = model.train_on_batch(x, y) - self.assertEqual(float(loss), 0.) + @keras_parameterized.run_all_keras_modes + @parameterized.named_parameters([ + {'testcase_name': 'clone_weights', 'share_weights': False}, + {'testcase_name': 'share_weights', 'share_weights': True}, + ]) + def test_clone_functional_with_masking(self, share_weights): + if share_weights: + clone_fn = functools.partial( + keras.models._clone_functional_model, share_weights=True) + else: + clone_fn = keras.models.clone_model + + x = np.array([[[1.], [1.]], [[0.], [0.]]]) + inputs = keras.Input((2, 1)) + outputs = keras.layers.Masking(mask_value=0)(inputs) + outputs = keras.layers.TimeDistributed( + keras.layers.Dense(1, kernel_initializer='one'))(outputs) + model = keras.Model(inputs, outputs) + + model = clone_fn(model) + model.compile( + loss='mse', optimizer=testing_utils.get_v2_optimizer('adam'), + run_eagerly=testing_utils.should_run_eagerly()) + y = np.array([[[1], [1]], [[1], [1]]]) + loss = model.train_on_batch(x, y) + self.assertEqual(float(loss), 0.) def test_model_cloning_invalid_use_cases(self): seq_model = keras.models.Sequential() @@ -249,168 +275,23 @@ class TestModelCloning(test.TestCase): self.assertFalse(has_placeholder) -class TestModelCloningLayerPreserveWeights(test.TestCase): - - @test_util.run_deprecated_v1 - def test_clone_sequential_model(self): - with self.cached_session(): - val_a = np.random.random((10, 4)) - val_out = np.random.random((10, 4)) - - model = sequential_model(False) - - # Everything should work in a new session. - keras.backend.clear_session() - - with self.cached_session(): - # With placeholder creation - new_model = keras.models._clone_sequential_model( - model, share_weights=True) - # update ops from batch norm needs to be included - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch(val_a, val_out) - - # On top of new tensor - input_a = keras.Input(shape=(4,)) - new_model = keras.models._clone_sequential_model( - model, input_tensors=input_a, share_weights=True) - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch(val_a, val_out) - - # On top of new, non-Keras tensor - input_a = keras.backend.variable(val_a) - new_model = keras.models._clone_sequential_model( - model, input_tensors=input_a, share_weights=True) - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch(None, val_out) - - @test_util.run_deprecated_v1 - def test_clone_sequential_model_input_layer(self): - - @test_util.run_deprecated_v1 - def test_input_layer(include_inputs): - with self.cached_session(): - val_a = np.random.random((10, 4)) - model = sequential_model(include_inputs, include_inputs) - # Sanity check - self.assertEqual( - isinstance(model._layers[0], keras.layers.InputLayer), - include_inputs) - self.assertEqual(model._is_graph_network, include_inputs) - - keras.backend.clear_session() - with self.cached_session(): - # With placeholder creation -- clone model should have an InputLayer - # if the original model has one. - new_model = keras.models._clone_sequential_model( - model, share_weights=True) - self.assertEqual( - isinstance(new_model._layers[0], keras.layers.InputLayer), - include_inputs) - self.assertEqual(new_model._is_graph_network, model._is_graph_network) - - # On top of new tensor -- clone model should always have an InputLayer. - input_a = keras.Input(shape=(4,)) - new_model = keras.models._clone_sequential_model( - model, input_tensors=input_a, share_weights=True) - self.assertIsInstance(new_model._layers[0], keras.layers.InputLayer) - self.assertTrue(new_model._is_graph_network) - - # On top of new, non-Keras tensor -- clone model should always have an - # InputLayer. - input_a = keras.backend.variable(val_a) - new_model = keras.models._clone_sequential_model( - model, input_tensors=input_a, share_weights=True) - self.assertIsInstance(new_model._layers[0], keras.layers.InputLayer) - self.assertTrue(new_model._is_graph_network) - - test_input_layer(True) - test_input_layer(False) - - @test_util.run_deprecated_v1 - def test_clone_functional_model(self): - with self.cached_session(): - val_a = np.random.random((10, 4)) - val_b = np.random.random((10, 4)) - val_out = np.random.random((10, 4)) - - input_a = keras.Input(shape=(4,)) - input_b = keras.Input(shape=(4,)) - dense_1 = keras.layers.Dense(4,) - dense_2 = keras.layers.Dense(4,) - - x_a = dense_1(input_a) - x_a = keras.layers.Dropout(0.5)(x_a) - x_a = keras.layers.BatchNormalization()(x_a) - x_b = dense_1(input_b) - x_a = dense_2(x_a) - outputs = keras.layers.add([x_a, x_b]) - model = keras.models.Model([input_a, input_b], outputs) - - # Everything should work in a new session. - keras.backend.clear_session() - - with self.cached_session(): - # With placeholder creation - new_model = keras.models._clone_functional_model( - model, share_weights=True) - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch([val_a, val_b], val_out) - - # On top of new tensors - input_a = keras.Input(shape=(4,), name='a') - input_b = keras.Input(shape=(4,), name='b') - new_model = keras.models._clone_functional_model( - model, input_tensors=[input_a, input_b], share_weights=True) - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch([val_a, val_b], val_out) - - # On top of new, non-Keras tensors - input_a = keras.backend.variable(val_a) - input_b = keras.backend.variable(val_b) - new_model = keras.models._clone_functional_model( - model, input_tensors=[input_a, input_b], share_weights=True) - self.assertEqual(len(new_model.get_updates_for(new_model.inputs)), 2) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch(None, val_out) - - @test_util.run_in_graph_and_eager_modes - def test_clone_functional_model_with_masking(self): - with self.cached_session(): - x = np.array([[[1], [1]], [[0], [0]]]) - inputs = keras.Input((2, 1)) - outputs = keras.layers.Masking(mask_value=0)(inputs) - outputs = keras.layers.TimeDistributed( - keras.layers.Dense(1, kernel_initializer='one'))(outputs) - model = keras.Model(inputs, outputs) - - model = keras.models._clone_functional_model( - model, share_weights=True) - model.compile(loss='mse', optimizer=adam.AdamOptimizer(0.01)) - y = np.array([[[1], [1]], [[1], [1]]]) - loss = model.train_on_batch(x, y) - self.assertEqual(float(loss), 0.) - - def _has_placeholder(graph): ops_types = [op.type for op in graph.get_operations()] return any('Placeholder' in s for s in ops_types) -class CheckpointingTests(test.TestCase): +@keras_parameterized.run_with_all_model_types +@keras_parameterized.run_all_keras_modes +class CheckpointingTests(keras_parameterized.TestCase): - @test_util.run_in_graph_and_eager_modes def test_optimizer_dependency(self): - model = keras.models.Sequential() - model.add(keras.layers.Dense(1, input_shape=(4,))) - opt = adam.AdamOptimizer(0.01) - model.compile(optimizer=opt, loss='mse') - model.fit(x=np.array([[1., 2., 3., 4.]]), y=[1.], epochs=2) + model = _get_model() + opt = adam.AdamOptimizer(.01) + model.compile( + optimizer=opt, loss='mse', + run_eagerly=testing_utils.should_run_eagerly()) + + model.fit(x=np.array([[1., 2., 3., 4.]]), y=np.array([1.]), epochs=2) save_prefix = os.path.join(self.get_temp_dir(), 'ckpt') beta1_power, _ = opt._get_beta_accumulators() self.evaluate(beta1_power.assign(12.)) @@ -420,7 +301,8 @@ class CheckpointingTests(test.TestCase): self.assertEqual(12., self.evaluate(beta1_power)) -class TestModelBackend(test.TestCase): +@keras_parameterized.run_all_keras_modes +class TestModelBackend(keras_parameterized.TestCase): def test_model_backend_float64_use_cases(self): # Test case for GitHub issue 19318 @@ -430,7 +312,9 @@ class TestModelBackend(test.TestCase): x = keras.Input((5,)) y = keras.layers.Dense(1)(x) model = keras.models.Model(x, y) - model.compile('rmsprop', 'mse') + model.compile( + testing_utils.get_v2_optimizer('rmsprop'), 'mse', + run_eagerly=testing_utils.should_run_eagerly()) keras.backend.set_floatx(floatx) @@ -465,48 +349,46 @@ class TestModelDeepCopy(test.TestCase): model_copy.get_weights()[0])) -@test_util.run_v1_only('b/120545219') -class TestCloneAndBuildModel(test.TestCase): +@keras_parameterized.run_all_keras_modes +class TestCloneAndBuildModel(keras_parameterized.TestCase): + @keras_parameterized.run_with_all_model_types def test_clone_and_build_non_compiled_model(self): - with self.cached_session(): - inp = np.random.random((10, 4)) - out = np.random.random((10, 4)) + inp = np.random.random((10, 4)) + out = np.random.random((10, 4)) - model = keras.models.Sequential() - model.add(keras.layers.Dense(4, input_shape=(4,))) - model.add(keras.layers.BatchNormalization()) - model.add(keras.layers.Dropout(0.5)) - model.add(keras.layers.Dense(4)) - - # Everything should work in a new session. - keras.backend.clear_session() - - with self.cached_session(): - with self.assertRaisesRegexp(ValueError, 'has not been compiled'): - models.clone_and_build_model(model, compile_clone=True) - - # With placeholder creation - new_model = models.clone_and_build_model(model, compile_clone=False) - with self.assertRaisesRegexp(RuntimeError, 'must compile'): - new_model.evaluate(inp, out) - with self.assertRaisesRegexp(RuntimeError, 'must compile'): - new_model.train_on_batch(inp, out) - new_model.compile('rmsprop', 'mse') - new_model.train_on_batch(inp, out) + model = _get_model() + + with self.assertRaisesRegexp(ValueError, 'has not been compiled'): + models.clone_and_build_model(model, compile_clone=True) - # Create new tensors for inputs and targets - input_a = keras.Input(shape=(4,)) - target_a = keras.Input(shape=(4,)) - new_model = models.clone_and_build_model(model, input_tensors=input_a, - target_tensors=[target_a], - compile_clone=False) - with self.assertRaisesRegexp(RuntimeError, 'must compile'): - new_model.evaluate(inp, out) - with self.assertRaisesRegexp(RuntimeError, 'must compile'): - new_model.train_on_batch(inp, out) - new_model.compile('rmsprop', 'mse') + is_subclassed = (testing_utils.get_model_type() == 'subclass') + # With placeholder creation + new_model = models.clone_and_build_model( + model, compile_clone=False, in_place_reset=is_subclassed) + with self.assertRaisesRegexp(RuntimeError, 'must compile'): + new_model.evaluate(inp, out) + with self.assertRaisesRegexp(RuntimeError, 'must compile'): + new_model.train_on_batch(inp, out) + new_model.compile( + testing_utils.get_v2_optimizer('rmsprop'), 'mse', + run_eagerly=testing_utils.should_run_eagerly()) + new_model.train_on_batch(inp, out) + + # Create new tensors for inputs and targets + input_a = keras.Input(shape=(4,)) + target_a = keras.Input(shape=(4,)) + new_model = models.clone_and_build_model( + model, input_tensors=input_a, target_tensors=[target_a], + compile_clone=False, in_place_reset=is_subclassed) + with self.assertRaisesRegexp(RuntimeError, 'must compile'): + new_model.evaluate(inp, out) + with self.assertRaisesRegexp(RuntimeError, 'must compile'): new_model.train_on_batch(inp, out) + new_model.compile( + testing_utils.get_v2_optimizer('rmsprop'), 'mse', + run_eagerly=testing_utils.should_run_eagerly()) + new_model.train_on_batch(inp, out) def _assert_same_compile_params(self, model): """Assert that two models have the same compile parameters.""" @@ -519,134 +401,88 @@ class TestCloneAndBuildModel(test.TestCase): self.assertEqual(['acc', metrics.categorical_accuracy], model._compile_metrics) - def _clone_and_build_test_helper(self, model, is_subclassed=False): + def _clone_and_build_test_helper(self, model, model_type): inp = np.random.random((10, 4)) out = np.random.random((10, 4)) - # Everything should work in a new session. - keras.backend.clear_session() - - with self.cached_session(): - # With placeholder creation - new_model = models.clone_and_build_model( - model, compile_clone=True, in_place_reset=is_subclassed) + is_subclassed = (model_type == 'subclass') + + # With placeholder creation + new_model = models.clone_and_build_model( + model, compile_clone=True, in_place_reset=is_subclassed) + + self._assert_same_compile_params(new_model) + new_model.train_on_batch(inp, out) + new_model.evaluate(inp, out) + + # Create new tensors for inputs and targets + input_a = keras.Input(shape=(4,), name='a') + new_model = models.clone_and_build_model( + model, input_tensors=input_a, compile_clone=True, + in_place_reset=is_subclassed) + self._assert_same_compile_params(new_model) + new_model.train_on_batch(inp, out) + new_model.evaluate(inp, out) + + target_a = keras.Input(shape=(4,), name='b') + new_model = models.clone_and_build_model( + model, input_tensors=input_a, target_tensors=[target_a], + compile_clone=True, in_place_reset=is_subclassed) + self._assert_same_compile_params(new_model) + new_model.train_on_batch(inp, out) + new_model.evaluate(inp, out) + + @keras_parameterized.run_with_all_model_types + def test_clone_and_build_compiled(self): + model = _get_model() + model.compile( + testing_utils.get_v2_optimizer('rmsprop'), 'mse', + metrics=['acc', metrics.categorical_accuracy], + run_eagerly=testing_utils.should_run_eagerly()) + + self._clone_and_build_test_helper(model, testing_utils.get_model_type()) + + def test_clone_and_build_sequential_without_inputs_defined(self): + model = models.Sequential(_get_layers(input_shape=None)) + model.compile( + testing_utils.get_v2_optimizer('rmsprop'), + 'mse', metrics=['acc', metrics.categorical_accuracy], + run_eagerly=testing_utils.should_run_eagerly()) + self._clone_and_build_test_helper(model, 'sequential') - self._assert_same_compile_params(new_model) - new_model.train_on_batch(inp, out) - new_model.evaluate(inp, out) - - # Create new tensors for inputs and targets - input_a = keras.Input(shape=(4,), name='a') - new_model = models.clone_and_build_model( - model, input_tensors=input_a, compile_clone=True, - in_place_reset=is_subclassed) - self._assert_same_compile_params(new_model) - new_model.train_on_batch(inp, out) - new_model.evaluate(inp, out) - - target_a = keras.Input(shape=(4,), name='b') - new_model = models.clone_and_build_model( - model, input_tensors=input_a, target_tensors=[target_a], - compile_clone=True, in_place_reset=is_subclassed) - self._assert_same_compile_params(new_model) - new_model.train_on_batch(inp, out) - new_model.evaluate(inp, out) - - def test_clone_and_build_compiled_sequential_model(self): - with self.cached_session(): - model = keras.models.Sequential() - model.add(keras.layers.Dense(4, input_shape=(4,))) - model.add(keras.layers.BatchNormalization()) - model.add(keras.layers.Dropout(0.5)) - model.add(keras.layers.Dense(4)) - model.compile('rmsprop', 'mse', - metrics=['acc', metrics.categorical_accuracy]) - - self._clone_and_build_test_helper(model) - - def test_clone_and_build_functional_model(self): - with self.cached_session(): - input_a = keras.Input(shape=(4,)) - dense_1 = keras.layers.Dense(4,) - dense_2 = keras.layers.Dense(4,) - - x_a = dense_1(input_a) - x_a = keras.layers.Dropout(0.5)(x_a) - x_a = keras.layers.BatchNormalization()(x_a) - x_a = dense_2(x_a) - model = keras.models.Model(input_a, x_a) - model.compile('rmsprop', 'mse', - metrics=['acc', metrics.categorical_accuracy]) - - self._clone_and_build_test_helper(model) - - def test_clone_and_build_subclassed_model(self): - class SubclassedModel(keras.Model): - - def __init__(self): - super(SubclassedModel, self).__init__() - self.layer1 = keras.layers.Dense(4) - self.layer2 = keras.layers.Dense(4) - - def call(self, inp): - out = self.layer1(inp) - out = keras.layers.BatchNormalization()(out) - out = keras.layers.Dropout(0.5)(out) - out = self.layer2(out) - return out - - with self.cached_session(): - model = SubclassedModel() - model.compile('rmsprop', 'mse', - metrics=['acc', metrics.categorical_accuracy]) - self._clone_and_build_test_helper(model, True) + inp = np.random.random((10, 4)) + out = np.random.random((10, 4)) + model.train_on_batch(inp, out) + self._clone_and_build_test_helper(model, 'sequential') def assert_optimizer_iterations_increases(self, optimizer): - with self.cached_session(): - input_a = keras.Input(shape=(4,)) - dense_1 = keras.layers.Dense(4,) - dense_2 = keras.layers.Dense(4,) - - x_a = dense_1(input_a) - x_a = keras.layers.Dropout(0.5)(x_a) - x_a = keras.layers.BatchNormalization()(x_a) - x_a = dense_2(x_a) - model = keras.models.Model(input_a, x_a) - model.compile(optimizer, 'mse', - metrics=['acc', metrics.categorical_accuracy]) + model = _get_model() + model.compile( + optimizer, 'mse', metrics=['acc', metrics.categorical_accuracy], + run_eagerly=testing_utils.should_run_eagerly()) - global_step = keras.backend.variable(123, dtype=dtypes.int64) - clone_model = models.clone_and_build_model( - model, compile_clone=True, optimizer_iterations=global_step) + global_step = keras.backend.variable(123, dtype=dtypes.int64) + clone_model = models.clone_and_build_model( + model, compile_clone=True, optimizer_iterations=global_step, + in_place_reset=(testing_utils.get_model_type() == 'subclass')) - inp = np.random.random((10, 4)) - out = np.random.random((10, 4)) - clone_model.train_on_batch(inp, out) + inp = np.random.random((10, 4)) + out = np.random.random((10, 4)) + clone_model.train_on_batch(inp, out) - self.assertEqual(K.eval(global_step), 124) + self.assertEqual(K.eval(global_step), 124) + @keras_parameterized.run_with_all_model_types def test_replace_tf_optimizer_iterations_variable(self): self.assert_optimizer_iterations_increases(adam.AdamOptimizer(0.01)) + @keras_parameterized.run_with_all_model_types def test_replace_keras_optimizer_iterations_variable(self): - self.assert_optimizer_iterations_increases('adam') + if testing_utils.should_run_eagerly(): + # This needs to be updated to run with v2 optimizers. + self.skipTest('b/120991591') - def test_replace_keras_optimizer_v2_iterations_variable(self): - self.assert_optimizer_iterations_increases( - keras.optimizer_v2.adam.Adam(0.01)) - - def test_clone_and_build_sequential_model_without_inputs_defined(self): - with self.cached_session(): - model = sequential_model(False, False) - model.compile('rmsprop', 'mse', - metrics=['acc', metrics.categorical_accuracy]) - self._clone_and_build_test_helper(model, False) - - with self.cached_session(): - inp = np.random.random((10, 4)) - out = np.random.random((10, 4)) - model.train_on_batch(inp, out) - self._clone_and_build_test_helper(model, False) + self.assert_optimizer_iterations_increases('adam') if __name__ == '__main__': diff --git a/tensorflow/python/keras/testing_utils.py b/tensorflow/python/keras/testing_utils.py index fd062b0ab3..6448f87d25 100644 --- a/tensorflow/python/keras/testing_utils.py +++ b/tensorflow/python/keras/testing_utils.py @@ -25,6 +25,13 @@ import numpy as np from tensorflow.python import keras from tensorflow.python.eager import context from tensorflow.python.framework import tensor_shape +from tensorflow.python.keras.optimizer_v2 import adadelta as adadelta_v2 +from tensorflow.python.keras.optimizer_v2 import adagrad as adagrad_v2 +from tensorflow.python.keras.optimizer_v2 import adam as adam_v2 +from tensorflow.python.keras.optimizer_v2 import adamax as adamax_v2 +from tensorflow.python.keras.optimizer_v2 import gradient_descent as gradient_descent_v2 +from tensorflow.python.keras.optimizer_v2 import nadam as nadam_v2 +from tensorflow.python.keras.optimizer_v2 import rmsprop as rmsprop_v2 from tensorflow.python.training.rmsprop import RMSPropOptimizer from tensorflow.python.util import tf_contextlib from tensorflow.python.util import tf_inspect @@ -355,11 +362,20 @@ class _SubclassModel(keras.Model): def __init__(self, layers): super(_SubclassModel, self).__init__() - self.all_layers = layers + # Note that clone and build doesn't support lists of layers in subclassed + # models. Adding each layer directly here. + for i, layer in enumerate(layers): + setattr(self, self._layer_name_for_i(i), layer) + + self.num_layers = len(layers) + + def _layer_name_for_i(self, i): + return 'layer{}'.format(i) def call(self, inputs, **kwargs): x = inputs - for layer in self.all_layers: + for i in range(self.num_layers): + layer = getattr(self, self._layer_name_for_i(i)) x = layer(x) return x @@ -626,3 +642,39 @@ def get_multi_io_model( return keras.Model(inputs, outputs) raise ValueError('Unknown model type {}'.format(model_type)) + + +_V2_OPTIMIZER_MAP = { + 'adadelta': adadelta_v2.Adadelta, + 'adagrad': adagrad_v2.Adagrad, + 'adam': adam_v2.Adam, + 'adamax': adamax_v2.Adamax, + 'nadam': nadam_v2.Nadam, + 'rmsprop': rmsprop_v2.RMSprop, + 'sgd': gradient_descent_v2.SGD +} + + +def get_v2_optimizer(name, **kwargs): + """Get the v2 optimizer requested. + + This is only necessary until v2 are the default, as we are testing in Eager, + and Eager + v1 optimizers fail tests. When we are in v2, the strings alone + should be sufficient, and this mapping can theoretically be removed. + + Args: + name: string name of Keras v2 optimizer. + **kwargs: any kwargs to pass to the optimizer constructor. + + Returns: + Initialized Keras v2 optimizer. + + Raises: + ValueError: if an unknown name was passed. + """ + try: + return _V2_OPTIMIZER_MAP[name](**kwargs) + except KeyError: + raise ValueError( + 'Could not find requested v2 optimizer: {}\nValid choices: {}'.format( + name, list(_V2_OPTIMIZER_MAP.keys()))) -- GitLab From 17bf2b33a90017c8536ef4a242bb60636bfc5e8a Mon Sep 17 00:00:00 2001 From: Russell Power Date: Fri, 4 Jan 2019 17:45:29 -0800 Subject: [PATCH 0231/2345] Graph construction: skip testing for duplicate edges during source/sink setup. When fixing up source/sink edges, we are only adding missing edges. Skip the test for duplicate edges in this case, which avoids a linear traversal over sink edges. PiperOrigin-RevId: 227940303 --- tensorflow/core/graph/algorithm.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/graph/algorithm.cc b/tensorflow/core/graph/algorithm.cc index 9b4200e0b4..25bd3516a1 100644 --- a/tensorflow/core/graph/algorithm.cc +++ b/tensorflow/core/graph/algorithm.cc @@ -222,11 +222,12 @@ bool FixupSourceAndSinkEdges(Graph* g) { bool changed = false; for (Node* n : g->nodes()) { if (!n->IsSource() && n->in_edges().empty()) { - g->AddControlEdge(g->source_node(), n); + g->AddControlEdge(g->source_node(), n, + true /* skip test for duplicates */); changed = true; } if (!n->IsSink() && n->out_edges().empty()) { - g->AddControlEdge(n, g->sink_node()); + g->AddControlEdge(n, g->sink_node(), true /* skip test for duplicates */); changed = true; } } -- GitLab From 1814e22d3cf78fd6e5b172c6de1a44cd6abc25f3 Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Fri, 4 Jan 2019 17:45:29 -0800 Subject: [PATCH 0232/2345] Fix some empty struct build warnings in TFLite PiperOrigin-RevId: 227940304 --- tensorflow/lite/c/builtin_op_data.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tensorflow/lite/c/builtin_op_data.h b/tensorflow/lite/c/builtin_op_data.h index 46ac656ecf..332c2db145 100644 --- a/tensorflow/lite/c/builtin_op_data.h +++ b/tensorflow/lite/c/builtin_op_data.h @@ -25,6 +25,11 @@ extern "C" { // TODO(aselle): Consider using "if this then that" for testing. +// Useful placeholder to put in otherwise empty structs to avoid size warnings. +typedef struct { + char dummy; +} EmptyStructPlaceholder; + // IMPORTANT: All new members of structs must be added at the end to ensure // backwards compatibility. @@ -152,9 +157,11 @@ typedef struct { } TfLiteAddParams; typedef struct { + EmptyStructPlaceholder placeholder; } TfLiteSpaceToBatchNDParams; typedef struct { + EmptyStructPlaceholder placeholder; } TfLiteBatchToSpaceNDParams; typedef struct { @@ -230,9 +237,11 @@ typedef struct { } TfLiteResizeNearestNeighborParams; typedef struct { + EmptyStructPlaceholder placeholder; } TfLitePadParams; typedef struct { + EmptyStructPlaceholder placeholder; } TfLitePadV2Params; typedef struct { @@ -272,6 +281,7 @@ typedef struct { } TfLiteGatherParams; typedef struct { + EmptyStructPlaceholder placeholder; } TfLiteTransposeParams; typedef struct { -- GitLab From 447189935cbc1ab3aeb2ac67f81f03d1a188e548 Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Fri, 4 Jan 2019 18:40:27 -0800 Subject: [PATCH 0233/2345] Make TRT tests respect RewriterConfig set by individual test case everywhere. PiperOrigin-RevId: 227944638 --- .../test/tf_trt_integration_test_base.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py index 495a9391a1..671abba6a6 100644 --- a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py +++ b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py @@ -239,8 +239,8 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): def _GetConfigProto(self, run_params, graph_state): """Get config proto based on specific settings.""" + conversion_params = self.GetConversionParams(run_params) if graph_state != GraphState.ORIGINAL and run_params.use_optimizer: - conversion_params = self.GetConversionParams(run_params) rewriter_cfg = trt_convert.get_tensorrt_rewriter_config( conversion_params.rewriter_config, conversion_params.max_batch_size, conversion_params.max_workspace_size_bytes, @@ -254,6 +254,9 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): graph_options = config_pb2.GraphOptions(rewrite_options=rewriter_cfg) else: graph_options = config_pb2.GraphOptions() + if conversion_params.rewriter_config is not None: + graph_options.rewrite_options.CopyFrom( + conversion_params.rewriter_config) config = config_pb2.ConfigProto( gpu_options=self._GetGPUOptions(), graph_options=graph_options) @@ -321,16 +324,13 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): return self._RunGraph( run_params, gdef, input_data, config, GraphState.CALIBRATE, num_runs=5) - def _GetTrtGraphDef(self, run_params, gdef): + def _GetTrtGraphDef(self, run_params, graph_state, gdef): """Return trt converted graphdef.""" params = self._GetParamsCached() conversion_params = self.GetConversionParams(run_params) logging.info(conversion_params) - config_for_trt = config_pb2.ConfigProto(gpu_options=self._GetGPUOptions()) - if conversion_params.rewriter_config is not None: - config_for_trt.graph_options.rewrite_options.CopyFrom( - conversion_params.rewriter_config) + config_for_trt = self._GetConfigProto(run_params, graph_state) return trt_convert.create_inference_graph( input_graph_def=gdef, outputs=params.input_names + params.output_names, @@ -506,7 +506,8 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): result = self._RunCalibration(run_params, input_gdef, input_data, calib_config) else: - calib_gdef = self._GetTrtGraphDef(run_params, input_gdef) + calib_gdef = self._GetTrtGraphDef(run_params, GraphState.CALIBRATE, + input_gdef) self._VerifyGraphDef(run_params, calib_gdef, GraphState.CALIBRATE) result = self._RunCalibration(run_params, calib_gdef, input_data, calib_config) @@ -527,7 +528,8 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): logging.info("Running final inference graph, config:\n%s", str(infer_config)) if not run_params.use_optimizer: - infer_gdef = self._GetTrtGraphDef(run_params, infer_gdef) + infer_gdef = self._GetTrtGraphDef(run_params, GraphState.INFERENCE, + infer_gdef) self._VerifyGraphDef(run_params, infer_gdef, GraphState.INFERENCE) result = self._RunGraph(run_params, infer_gdef, input_data, infer_config, -- GitLab From d93409b9dfc3bfaacfa452fab603a089931b3335 Mon Sep 17 00:00:00 2001 From: Davide Libenzi Date: Fri, 4 Jan 2019 19:00:39 -0800 Subject: [PATCH 0234/2345] Automated rollback of commit 55fda7fb66f1774ac5271e523259faa7d2a44d0c PiperOrigin-RevId: 227945898 --- .../xla/service/cpu/cpu_executable.cc | 29 +--- .../xla/service/gpu/gpu_executable.cc | 26 +--- tensorflow/compiler/xrt/kernels/BUILD | 1 - .../compiler/xrt/kernels/xrt_execute_op.cc | 17 --- tensorflow/compiler/xrt/tests/raw_api_test.cc | 127 +----------------- tensorflow/compiler/xrt/xrt_state.cc | 43 +----- tensorflow/compiler/xrt/xrt_state.h | 23 +--- 7 files changed, 17 insertions(+), 249 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/cpu_executable.cc b/tensorflow/compiler/xla/service/cpu/cpu_executable.cc index 23d0af3423..412c2715b9 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_executable.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_executable.cc @@ -213,8 +213,6 @@ StatusOr CpuExecutable::CreateResultShapedBuffer( /*on_host_shape=*/result_shape(), /*on_device_shape=*/result_shape(), run_options->allocator(), stream->parent()->device_ordinal()); - const HloInputOutputAliasConfig& input_output_alias = - module().input_output_alias_config(); // Move OwningDeviceMemory values which contain the array(s) of the result // into the respective location in ScopedShapedBuffer which is returned to the @@ -234,31 +232,12 @@ StatusOr CpuExecutable::CreateResultShapedBuffer( TF_ASSIGN_OR_RETURN( const BufferAllocation::Slice slice, this->assignment_->GetUniqueSlice(src, buffer_source->index())); + CHECK(!slice.allocation()->is_entry_computation_parameter()); + const BufferAllocation::Index buffer_index = slice.index(); OwningDeviceMemory& buffer = buffers[buffer_index]; - if (!slice.allocation()->is_entry_computation_parameter()) { - // If the buffer coming out of the result is from a parameter, the - // owning buffer will be null, and that means the caller aliased some - // parameter buffer to an output one (via the - // HloInputOutputAliasConfig API). If that is the case, the caller - // will receive a partially complete scoped shaped buffer, which they - // will have to fill up on return. Unfortunately the interface to the - // execute APIs are ShapedBuffer pointer based, which assumes caller - // ownership, and hence a buffer coming from there cannot be part of - // the new ScopedShapedBuffer we create for the result (which assumes - // ownership). - *device_memory = buffer.Forget(); - } else { - auto output_alias = input_output_alias.GetAliasedOutput( - slice.allocation()->parameter_number(), - slice.allocation()->param_shape_index()); - CHECK(output_alias) - << "Ouput buffer is coming from parameter " - << slice.allocation()->parameter_number() << " at index " - << slice.allocation()->param_shape_index() - << ", but no alias exists"; - CHECK_EQ(*output_alias, index); - } + CHECK(!buffer.is_null() || buffer.size() == 0); + *device_memory = buffer.Forget(); return Status::OK(); })); return std::move(result_buffer); diff --git a/tensorflow/compiler/xla/service/gpu/gpu_executable.cc b/tensorflow/compiler/xla/service/gpu/gpu_executable.cc index 434060ad89..128cecd765 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_executable.cc +++ b/tensorflow/compiler/xla/service/gpu/gpu_executable.cc @@ -310,34 +310,12 @@ StatusOr GpuExecutable::ExecuteOnStream( TF_ASSIGN_OR_RETURN( const BufferAllocation::Slice slice, this->assignment_->GetUniqueSlice(src_hlo, sources[0]->index())); + CHECK(!slice.allocation()->is_entry_computation_parameter()); se::DeviceMemoryBase src_base = buffer_allocations->GetDeviceAddress(slice.index()); CHECK(!src_base.is_null() || src_base.size() == 0); - if (!slice.allocation()->is_entry_computation_parameter()) { - // If the buffer coming out of the result is from a parameter, it - // means the caller aliased some parameter buffer to an output one - // (via the HloInputOutputAliasConfig API). If that is the case, the - // caller will receive a partially complete scoped shaped buffer, - // which they will have to fill up on return. - // Unfortunately the interface to the execute APIs are ShapedBuffer - // pointer based, which assumes caller ownership, and hence a buffer - // coming from there cannot be part of the new ScopedShapedBuffer we - // create for the result (which assumes ownership). - *device_memory = src_base; - } else { - const HloInputOutputAliasConfig& input_output_alias = - module().input_output_alias_config(); - auto output_alias = input_output_alias.GetAliasedOutput( - slice.allocation()->parameter_number(), - slice.allocation()->param_shape_index()); - CHECK(output_alias) - << "Ouput buffer is coming from parameter " - << slice.allocation()->parameter_number() << " at index " - << slice.allocation()->param_shape_index() - << ", but no alias exists"; - CHECK_EQ(*output_alias, index); - } + *device_memory = src_base; buffers_in_result.insert(src_base); return Status::OK(); })); diff --git a/tensorflow/compiler/xrt/kernels/BUILD b/tensorflow/compiler/xrt/kernels/BUILD index c44769dfe3..67f475846e 100644 --- a/tensorflow/compiler/xrt/kernels/BUILD +++ b/tensorflow/compiler/xrt/kernels/BUILD @@ -55,7 +55,6 @@ cc_library( "//tensorflow/compiler/xla/client:xla_computation", "//tensorflow/compiler/xla/service:compiler", "//tensorflow/compiler/xla/service:computation_placer", - "//tensorflow/compiler/xla/service:hlo", "//tensorflow/compiler/xrt:xrt_proto", "//tensorflow/compiler/xrt:xrt_utils", "//tensorflow/core:core_cpu_internal", diff --git a/tensorflow/compiler/xrt/kernels/xrt_execute_op.cc b/tensorflow/compiler/xrt/kernels/xrt_execute_op.cc index 7544541ca4..7f0ac123a5 100644 --- a/tensorflow/compiler/xrt/kernels/xrt_execute_op.cc +++ b/tensorflow/compiler/xrt/kernels/xrt_execute_op.cc @@ -19,7 +19,6 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/xla_op_registry.h" #include "tensorflow/compiler/xla/service/computation_placer.h" -#include "tensorflow/compiler/xla/service/hlo_input_output_alias_config.h" #include "tensorflow/compiler/xla/shape_util.h" #include "tensorflow/compiler/xla/status_macros.h" #include "tensorflow/compiler/xla/statusor.h" @@ -229,22 +228,6 @@ Status XRTExecuteOp::DoWork(OpKernelContext* context) { TF_RETURN_IF_ERROR(XRTTupleAllocation::CreateFromBuffer( shaped_buffer, device_ref.backend(), device_ref.device_ordinal(), &output_tuple)); - - // The ScopedShapedBuffer returned by the executable Run() API, in case of - // input/output buffer aliasing, might have holes in it, which need to be - // filled using the proper input tuples buffers which are the source of - // aliasing. - const xla::HloInputOutputAliasConfig& input_output_alias = - executable->executable()->module().input_output_alias_config(); - auto alias_function = [&](const xla::ShapeIndex& output_index, - int64 param_number, - const xla::ShapeIndex& param_index) -> Status { - TF_RET_CHECK(param_number < input_tuples.size()); - return output_tuple->AliasBufferFrom(*input_tuples[param_number], - param_index, output_index); - }; - TF_RETURN_IF_ERROR(input_output_alias.ForEachAliasWithStatus(alias_function)); - if (config_proto.return_exploded_tuple() && output_tuple->on_device_shape().IsTuple()) { int64 tuple_element_count = diff --git a/tensorflow/compiler/xrt/tests/raw_api_test.cc b/tensorflow/compiler/xrt/tests/raw_api_test.cc index f81faf0e61..c8479cb778 100644 --- a/tensorflow/compiler/xrt/tests/raw_api_test.cc +++ b/tensorflow/compiler/xrt/tests/raw_api_test.cc @@ -96,21 +96,14 @@ xla::LiteralProto FloatMatrix( return array.ToProto(); } -xla::Literal ReadOutputLiteral(const std::vector& outputs, size_t idx) { - xla::LiteralProto response; - CHECK(response.ParseFromString(outputs[idx].scalar()())); - return xla::Literal::CreateFromProto(response).ValueOrDie(); -} - bool CompareLiteralProtos(const xla::LiteralProto& a, const xla::LiteralProto& b) { auto l_a = xla::Literal::CreateFromProto(a).ValueOrDie(); auto l_b = xla::Literal::CreateFromProto(b).ValueOrDie(); bool equal = l_a == l_b; if (!equal) { - LOG(INFO) << "LiteralProtos don't match:\n" - << a.DebugString() << "\n!=\n" - << b.DebugString(); + LOG(INFO) << "LiteralProtos don't match: " << a.DebugString() + << " != " << b.DebugString(); } return equal; } @@ -120,19 +113,8 @@ bool CompareLiteralToLiteralProto(const xla::Literal& a, auto l_b = xla::Literal::CreateFromProto(b).ValueOrDie(); bool equal = a == l_b; if (!equal) { - LOG(INFO) << "Literal and LiteralProto don't match:\n" - << a.ToProto().DebugString() << "\n!=\n" - << b.DebugString(); - } - return equal; -} - -bool CompareLiterals(const xla::Literal& a, const xla::Literal& b) { - bool equal = a == b; - if (!equal) { - LOG(INFO) << "Literals don't match:\n" - << a.ToProto().DebugString() << "\n!=\n" - << b.ToProto().DebugString(); + LOG(INFO) << "Literal and LiteralProto don't match " + << a.ToProto().DebugString() << " != " << b.DebugString(); } return equal; } @@ -957,107 +939,6 @@ TEST(RawApiTest, LeakCompilationReference) { TF_EXPECT_OK(session.Run({c_handle.handle}, &outputs)); } -TEST(RawApiTest, CompileAndExecuteWithReusedBuffers) { - xla::Shape element_shape = xla::ShapeUtil::MakeShape(xla::F32, {2}); - xla::Shape shape = - xla::ShapeUtil::MakeTupleShape({element_shape, element_shape}); - xla::Shape return_shape = xla::ShapeUtil::MakeTupleShape( - {element_shape, element_shape, element_shape, element_shape}); - xla::XlaBuilder builder("ReuseBuffer"); - auto param = xla::Parameter(&builder, 0, shape, "param"); - auto p0 = xla::GetTupleElement(param, 0); - auto p1 = xla::GetTupleElement(param, 1); - auto add = xla::Add(p0, p1); - auto sub = xla::Sub(p0, p1); - xla::Tuple(&builder, {add, sub, p0, p1}); - - // Flip the tuple literals in the input handle. - builder.SetUpAlias({1}, 0, {0}); - builder.SetUpAlias({0}, 0, {1}); - - auto computation = builder.Build().ValueOrDie(); - - auto literal0 = xla::LiteralUtil::CreateR1({1.0f, 2.0f}); - auto literal1 = xla::LiteralUtil::CreateR1({5.0f, 9.0f}); - auto literal = xla::LiteralUtil::MakeTuple({&literal0, &literal1}); - - xrt::XLAAllocation param_alloc; - *param_alloc.mutable_value() = literal.ToProto(); - - xrt::XLAComputation c; - auto config = c.mutable_config(); - auto shapes = config->mutable_program_shape(); - *shapes->add_parameters() = shape.ToProto(); - *shapes->mutable_result() = return_shape.ToProto(); - StoreComputationSnapshot(computation, c.mutable_hlo_snapshot()); - - xrt::XRTExecutionConfig e; - e.set_release_input_handles(false); - e.set_release_compilation_handle(true); - - Scope root = Scope::NewRootScope().WithDevice(DeviceFromFlag()); - ClientSession session(root); - auto e_config = - ops::Const(root.WithDevice("/device:CPU:0"), e.SerializeAsString()); - auto c_data = - ops::Const(root.WithDevice("/device:CPU:0"), c.SerializeAsString()); - auto c_handle = ops::XRTCompile(root, c_data); - auto param_value = ops::Const(root.WithDevice("/device:CPU:0"), - param_alloc.SerializeAsString()); - auto param_handle = ops::XRTAllocate(root, param_value); - TF_ASSERT_OK(root.status()); - - std::vector outputs; - TF_EXPECT_OK(session.Run({param_handle}, &outputs)); - - int64 alloc_handle = outputs[0].scalar()(); - - // Note that we release the result handle immediately, but since we aliased - // the output buffers onto the input allocation ones (held in alloc_handle), - // we can fetch the result from there. - auto result = - ops::XRTExecute(root, c_handle.handle, e_config, {Input(alloc_handle)}); - auto read_back = ops::XRTReadLiteral(root, result); - auto release = ops::XRTReleaseAllocationHandle( - root.WithControlDependencies(read_back), result); - TF_ASSERT_OK(root.status()); - - outputs.clear(); - TF_EXPECT_OK(session.Run(tensorflow::ClientSession::FeedType(), {read_back}, - {release}, &outputs)); - - xla::Literal exec_literal = ReadOutputLiteral(outputs, 0); - auto exec_literal_parts = exec_literal.DecomposeTuple(); - ASSERT_EQ(exec_literal_parts.size(), 4); - - EXPECT_TRUE(CompareLiterals(exec_literal_parts[2], literal0)); - EXPECT_TRUE(CompareLiterals(exec_literal_parts[3], literal1)); - - // Now we read back the original input handle values, which at this point - // should contain the result of the XLA computation. - auto read_handle = ops::XRTReadLiteral(root, Input(alloc_handle)); - TF_ASSERT_OK(root.status()); - auto release_handle = ops::XRTReleaseAllocationHandle( - root.WithControlDependencies(read_handle), Input(alloc_handle)); - TF_ASSERT_OK(root.status()); - - outputs.clear(); - TF_EXPECT_OK(session.Run(tensorflow::ClientSession::FeedType(), {read_handle}, - {release_handle}, &outputs)); - - xla::Literal return_literal = ReadOutputLiteral(outputs, 0); - - auto expected_literal0 = xla::LiteralUtil::CreateR1({6.0f, 11.0f}); - auto expected_literal1 = xla::LiteralUtil::CreateR1({-4.0f, -7.0f}); - // The first element of the computation returned tuple would be the add - // (expected_literal0), but since we flipped the buffers, the sub - // (expected_literal1) should come first. - auto expected_literal = - xla::LiteralUtil::MakeTuple({&expected_literal1, &expected_literal0}); - - EXPECT_TRUE(CompareLiterals(return_literal, expected_literal)); -} - TEST(RawApiTest, CompileAndExecuteWithS64Argument) { xrt::XLAAllocation p0; *p0.mutable_value() = xla::LiteralUtil::CreateR0(11031965).ToProto(); diff --git a/tensorflow/compiler/xrt/xrt_state.cc b/tensorflow/compiler/xrt/xrt_state.cc index 13c275aaca..343460ff10 100644 --- a/tensorflow/compiler/xrt/xrt_state.cc +++ b/tensorflow/compiler/xrt/xrt_state.cc @@ -133,8 +133,7 @@ Status AllocateScopedShapedBuffer( XRTBufferAllocation::XRTBufferAllocation(const se::DeviceMemoryBase& allocation, int device_ordinal, xla::DeviceMemoryAllocator* allocator) - : size_(allocation.size()), - allocation_(allocation), + : allocation_(allocation), device_ordinal_(device_ordinal), allocator_(allocator) { if (VLOG_IS_ON(2)) { @@ -224,19 +223,8 @@ Status XRTTupleAllocation::ToLiteral(xla::Backend* backend, int device_ordinal, xla::Literal* literal) { auto transfer_manager = backend->transfer_manager(); TF_ASSIGN_OR_RETURN(auto stream, backend->BorrowStream(device_ordinal)); - - // Validate the allocation buffers as if nulls gets to - // TransferLiteralFromDevice() a CHECK is issued. - xla::ShapedBuffer shaped_buffer = ToShapedBuffer(); - for (auto& index_buffer : shaped_buffer.buffers()) { - if (index_buffer.second.is_null()) { - return errors::InvalidArgument("Literal buffer at index ", - index_buffer.first.ToString(), - " has been released"); - } - } TF_ASSIGN_OR_RETURN(*literal, transfer_manager->TransferLiteralFromDevice( - stream.get(), shaped_buffer)); + stream.get(), ToShapedBuffer())); return Status::OK(); } @@ -517,34 +505,11 @@ xla::ShapedBuffer XRTTupleAllocation::ToShapedBuffer() { return shaped_buffer; } -Status XRTTupleAllocation::AliasBufferFrom(const XRTTupleAllocation& source, - const xla::ShapeIndex& source_index, - const xla::ShapeIndex& dest_index) { - XRTBufferAllocation* source_buffer = source.buffers_.element(source_index); - XRTBufferAllocation* dest_buffer = buffers_.element(dest_index); - // We allow the destination size being zero, because there are cases where we - // are coming in later filling in null/uninitialized device buffers. - // In all other cases, the size of the new buffer must match. - if (source_buffer->size() != dest_buffer->size() && - dest_buffer->size() != 0) { - return errors::InvalidArgument( - "Source buffer at index ", source_index.ToString(), - " does not match the size of destination buffer at index ", - dest_index.ToString(), ": ", source_buffer->size(), " vs ", - dest_buffer->size()); - } - *buffers_.mutable_element(dest_index) = source_buffer; - source_buffer->Ref(); - dest_buffer->Unref(); - return Status::OK(); -} - xla::ShapeTree -XRTTupleAllocation::ToDeviceMemoryTree( - const std::function& release_checker) { +XRTTupleAllocation::ToDeviceMemoryTree(bool release) { xla::ShapeTree shaped_tree(on_device_shape()); for (const auto& buffer : buffers_) { - if (!release_checker(buffer.first)) { + if (!release) { *shaped_tree.mutable_element(buffer.first) = buffer.second->allocation(); } else { *shaped_tree.mutable_element(buffer.first) = xla::OwningDeviceMemory( diff --git a/tensorflow/compiler/xrt/xrt_state.h b/tensorflow/compiler/xrt/xrt_state.h index ac4be3a064..3e3d502412 100644 --- a/tensorflow/compiler/xrt/xrt_state.h +++ b/tensorflow/compiler/xrt/xrt_state.h @@ -18,7 +18,6 @@ limitations under the License. #ifndef TENSORFLOW_COMPILER_XRT_XRT_STATE_H_ #define TENSORFLOW_COMPILER_XRT_XRT_STATE_H_ -#include #include #include #include @@ -59,14 +58,7 @@ class XRTBufferAllocation : public core::RefCounted { // freed when the reference count drops to zero. void DiscardAllocation(); - // Returns the expected size of the allocation. Since DiscardAllocation() will - // set allocation_ to {null,0}, and since later we might want to replace the - // discarded buffer with a new one, we need to be able to verify the size - // compatibility. - uint64 size() const { return size_; } - private: - uint64 size_ = 0; se::DeviceMemoryBase allocation_; int device_ordinal_; xla::DeviceMemoryAllocator* allocator_; @@ -176,18 +168,9 @@ class XRTTupleAllocation : public ResourceBase { // the same shape as on_host_shape. xla::ShapedBuffer ToShapedBuffer(); - // Aliases the source buffer at source_index into the current tuple allocation - // dest_index. - Status AliasBufferFrom(const XRTTupleAllocation& source, - const xla::ShapeIndex& source_index, - const xla::ShapeIndex& dest_index); - - // Returns the device memory tree of this allocation. If the release_checker - // function returns true for a given index, the ownership of the device memory - // at that index is transferred to the result. Every attempt to read the value - // at that index will fail. - xla::ShapeTree ToDeviceMemoryTree( - const std::function& release_checker); + // Returns the device memory tree of this allocation. If 'release' is set, the + // ownership of the device memory is transferred to the result. + xla::ShapeTree ToDeviceMemoryTree(bool release); string DebugString() override { return "XLA allocation handle"; } -- GitLab From 47c68e37dee827d9c3fe95f39e5fb49d07a573a5 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 19:47:17 -0800 Subject: [PATCH 0235/2345] Automated rollback of commit 57d501422c16dd73b391d19bcc1e203942cc6b24 PiperOrigin-RevId: 227949022 --- tensorflow/contrib/distribute/python/BUILD | 3 +- .../contrib/distribute/python/combinations.py | 15 +-- .../python/keras_correctness_test.py | 100 +++--------------- 3 files changed, 19 insertions(+), 99 deletions(-) diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index c620a0448e..de6b6f1f84 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -670,12 +670,11 @@ py_library( cuda_py_test( name = "keras_correctness_test", - size = "medium", srcs = ["keras_correctness_test.py"], additional_deps = [ ":keras_correctness_test_lib", ], - shard_count = 20, + shard_count = 16, tags = [ "multi_and_single_gpu", "no_oss", # TODO(b/117919883): Fix python error. diff --git a/tensorflow/contrib/distribute/python/combinations.py b/tensorflow/contrib/distribute/python/combinations.py index 8db0e9c316..f6c4291659 100644 --- a/tensorflow/contrib/distribute/python/combinations.py +++ b/tensorflow/contrib/distribute/python/combinations.py @@ -321,12 +321,11 @@ class NamedDistribution(object): return self._required_tpu -def _get_tpu_strategy_creator(steps_per_run, **kwargs): +def _get_tpu_strategy_creator(steps_per_run): def _create_tpu_strategy(): resolver = cluster_resolver.TPUClusterResolver("") tpu_lib.initialize_tpu_system(resolver) - strategy = tpu_lib.TPUStrategy(resolver, - steps_per_run=steps_per_run, **kwargs) + strategy = tpu_lib.TPUStrategy(resolver, steps_per_run=steps_per_run) return strategy return _create_tpu_strategy @@ -345,15 +344,7 @@ tpu_strategy = NamedDistribution( tpu_strategy_one_step = NamedDistribution( "TPUOneStep", _get_tpu_strategy_creator(steps_per_run=1), required_tpu=True) -# TODO(b/122327153): Remove below two NamedDistributions. -tpu_strategy_loop_on_device = NamedDistribution( - "TPULoopOnDevice", _get_tpu_strategy_creator( - steps_per_run=2, _disable_training_loop_on_host=True), - required_tpu=True) -tpu_strategy_one_step_loop_on_device = NamedDistribution( - "TPUOneStepLoopOnDevice", _get_tpu_strategy_creator( - steps_per_run=1, _disable_training_loop_on_host=True), - required_tpu=True) + mirrored_strategy_with_one_cpu = NamedDistribution( "Mirrored1CPU", lambda: mirrored_lib.MirroredStrategy(["/cpu:0"])) diff --git a/tensorflow/contrib/distribute/python/keras_correctness_test.py b/tensorflow/contrib/distribute/python/keras_correctness_test.py index 1c4519ef71..3abdee2c0e 100644 --- a/tensorflow/contrib/distribute/python/keras_correctness_test.py +++ b/tensorflow/contrib/distribute/python/keras_correctness_test.py @@ -62,41 +62,24 @@ def all_strategy_combinations_with_graph_mode(): return combinations.combine(distribution=all_strategies, mode=['graph']) -def tpu_strategies_with_host_training_loop_disabled(): - strategies = [s for s in all_strategies if not s.required_tpu] - strategies.append(combinations.tpu_strategy_loop_on_device) - strategies.append(combinations.tpu_strategy_one_step_loop_on_device) - return strategies - - def strategy_and_input_combinations(): def cnn_model_with_batch_norm(**kwargs): return _create_cnn_model(with_batch_norm=True, **kwargs) - combinations_without_embedding_model = combinations.times( - combinations.combine( - distribution=tpu_strategies_with_host_training_loop_disabled()), - combinations.combine(mode=['graph', 'eager'], - use_numpy=[True, False], - use_validation_data=[True, False]), - combinations.combine(model_with_data= - [ModelWithData('lstm', - _create_lstm_model, - _lstm_training_data)])) - - combinations_with_embedding_model = combinations.times( - combinations.combine(distribution=all_strategies), - combinations.combine(mode=['graph', 'eager'], - use_numpy=[True, False], - use_validation_data=[True, False]), - combinations.combine(model_with_data=[ - ModelWithData('dnn', _create_dnn_model, _dnn_training_data), - ModelWithData('cnn', _create_cnn_model, _cnn_training_data), - ModelWithData('cnn_batch_norm', cnn_model_with_batch_norm, - _cnn_training_data, with_batch_norm=True),])) - - return (combinations_with_embedding_model + - combinations_without_embedding_model) + return ( + combinations.times( + combinations.combine(distribution=all_strategies), + combinations.combine(mode=['graph', 'eager'], + use_numpy=[True, False], + use_validation_data=[True, False]), + combinations.combine(model_with_data=[ + ModelWithData('dnn', _create_dnn_model, _dnn_training_data), + ModelWithData('cnn', _create_cnn_model, _cnn_training_data), + ModelWithData('cnn_batch_norm', + cnn_model_with_batch_norm, + _cnn_training_data, + with_batch_norm=True), + ]))) class MaybeDistributionScope(object): @@ -208,59 +191,6 @@ def _create_cnn_model(initial_weights=None, distribution=None, return model -def _lstm_training_data(count=_GLOBAL_BATCH_SIZE * _EVAL_STEPS, - min_words=10, - max_words=20, - max_word_id=99, - num_classes=2): - distribution = [] - for _ in range(num_classes): - dist = np.abs(np.random.randn(max_word_id)) - dist /= np.sum(dist) - distribution.append(dist) - - features = [] - labels = [] - for _ in range(count): - label = np.random.randint(0, num_classes, size=1)[0] - num_words = np.random.randint(min_words, max_words, size=1)[0] - word_ids = np.random.choice( - max_word_id, size=num_words, replace=True, p=distribution[label]) - word_ids = word_ids - labels.append(label) - features.append(word_ids) - - features = keras.preprocessing.sequence.pad_sequences( - features, maxlen=max_words) - x_train = np.asarray(features, dtype=np.float32) - y_train = np.asarray(labels, dtype=np.int32).reshape((count, 1)) - x_predict = x_train - return x_train, y_train, x_predict - - -def _create_lstm_model(max_words=20, - initial_weights=None, - distribution=None): - with MaybeDistributionScope(distribution): - word_ids = keras.layers.Input( - shape=(max_words,), dtype=np.int32, name='words') - word_embed = keras.layers.Embedding(input_dim=100, output_dim=10)(word_ids) - lstm_embed = keras.layers.LSTM( - units=8, return_sequences=False)(word_embed) - - preds = keras.layers.Dense(2, activation='softmax')(lstm_embed) - model = keras.Model(inputs=[word_ids], outputs=[preds]) - - if initial_weights: - model.set_weights(initial_weights) - - model.compile( - optimizer=gradient_descent.GradientDescentOptimizer(learning_rate=0.1), - loss='sparse_categorical_crossentropy', - metrics=['sparse_categorical_accuracy']) - return model - - def batch_wrapper(dataset, batch_size, distribution, repeat=None): if repeat: dataset = dataset.repeat(repeat) @@ -313,7 +243,7 @@ def get_correctness_test_inputs(use_numpy, use_validation_data, } else: if len(x_train) < _GLOBAL_BATCH_SIZE * _EVAL_STEPS: - # Currently, we cannot detect the size of a dataset. So, the eval steps is + # Currently, we cannot detech the size of a dataset. So, the eval steps is # hard coded. raise ValueError('x_train must have at least ' '_GLOBAL_BATCH_SIZE * _EVAL_STEPS samples') -- GitLab From 5633ba3fc829a31c297a412929f5953e8c7a1f2f Mon Sep 17 00:00:00 2001 From: Jason Zaman Date: Wed, 2 Jan 2019 18:29:06 +0800 Subject: [PATCH 0236/2345] cloud oauth_client: update for OpenSSL 1.1.0 compatibility EVP_MD_CTX_cleanup was removed in OpenSSL 1.1.0. There is a call to EVP_MD_CTX_destroy right after and _destroy will call _cleanup if required. EVP_MD_CTX_destroy exists in OpenSSL 1.0, 1.1 and BoringSSL so removing the call to _cleanup works everywhere. Signed-off-by: Jason Zaman --- tensorflow/core/platform/cloud/oauth_client.cc | 6 +++++- tensorflow/core/platform/cloud/oauth_client_test.cc | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/platform/cloud/oauth_client.cc b/tensorflow/core/platform/cloud/oauth_client.cc index 9b85cae9b9..a8657359a3 100644 --- a/tensorflow/core/platform/cloud/oauth_client.cc +++ b/tensorflow/core/platform/cloud/oauth_client.cc @@ -95,6 +95,11 @@ Status CreateSignature(RSA* private_key, StringPiece to_sign, if (!md) { return errors::Internal("Could not get a sha256 encryptor."); } + + // EVP_MD_CTX_destroy is renamed to EVP_MD_CTX_free in OpenSSL 1.1.0 but + // the old name is still retained as a compatibility macro. + // Keep this around until support is dropped for OpenSSL 1.0 + // https://www.openssl.org/news/cl110.txt std::unique_ptr> md_ctx( EVP_MD_CTX_create(), [](EVP_MD_CTX* ptr) { EVP_MD_CTX_destroy(ptr); }); if (!md_ctx) { @@ -119,7 +124,6 @@ Status CreateSignature(RSA* private_key, StringPiece to_sign, if (EVP_DigestSignFinal(md_ctx.get(), sig.get(), &sig_len) != 1) { return errors::Internal("DigestFinal (signature compute) failed."); } - EVP_MD_CTX_cleanup(md_ctx.get()); return Base64Encode(StringPiece(reinterpret_cast(sig.get()), sig_len), signature); } diff --git a/tensorflow/core/platform/cloud/oauth_client_test.cc b/tensorflow/core/platform/cloud/oauth_client_test.cc index 1cd0641cd3..ce3b9d79c8 100644 --- a/tensorflow/core/platform/cloud/oauth_client_test.cc +++ b/tensorflow/core/platform/cloud/oauth_client_test.cc @@ -166,7 +166,6 @@ TEST(OAuthClientTest, GetTokenFromServiceAccountJson) { const_cast( reinterpret_cast(signature.data())), signature.size())); - EVP_MD_CTX_cleanup(md_ctx); // Free all the crypto-related resources. EVP_PKEY_free(key); -- GitLab From 13fc6cfefb8204d5144f4c94b4b8cb50f69e8e8c Mon Sep 17 00:00:00 2001 From: Russell Power Date: Fri, 4 Jan 2019 21:25:17 -0800 Subject: [PATCH 0237/2345] Grappler micro-optimizations for virtual scheduler. * Only collect additional memory information when VLOG(1) is active. * Use const reference to avoid copy. PiperOrigin-RevId: 227954880 --- .../core/grappler/costs/virtual_scheduler.cc | 26 ++++++++++++------- .../core/grappler/costs/virtual_scheduler.h | 3 +++ .../grappler/costs/virtual_scheduler_test.cc | 4 ++- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/tensorflow/core/grappler/costs/virtual_scheduler.cc b/tensorflow/core/grappler/costs/virtual_scheduler.cc index ae5200b359..7071c679dc 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler.cc +++ b/tensorflow/core/grappler/costs/virtual_scheduler.cc @@ -319,6 +319,7 @@ VirtualScheduler::VirtualScheduler(const GrapplerItem* grappler_item, placer_(cluster) { graph_costs_.num_ops_total = 0; initialized_ = false; + track_mem_usage_snapshot_ = VLOG_IS_ON(1); } VirtualScheduler::VirtualScheduler(const bool use_static_shapes, @@ -331,6 +332,7 @@ VirtualScheduler::VirtualScheduler(const bool use_static_shapes, placer_(cluster) { graph_costs_.num_ops_total = 0; initialized_ = false; + track_mem_usage_snapshot_ = VLOG_IS_ON(1); } Status VirtualScheduler::Init(const GrapplerItem* item) { @@ -562,7 +564,7 @@ void VirtualScheduler::MaybeUpdateInputOutput(const NodeDef* node) { inputs.push_back(control_message); outputs.push_back(control_message); } else { - auto output_properties = + const auto& output_properties = graph_properties_->GetOutputProperties(NodeName(input_source_name)); // Like with HasInputProperties, if a node does not have output // properties, it's likely it was pruned during the shape inference run. @@ -778,13 +780,16 @@ bool VirtualScheduler::MarkCurrNodeExecuted(const Costs& node_costs) { auto& op_cost = FindOrCreateZero(op_name, &op_to_cost_); op_cost = CombineCosts(op_cost, node_costs); - // Also keep track of op counts and costs per op (with their shapes). - OpContext op_context = GetCurrNode(); - string node_description = GetOpDescription(op_context.op_info); - op_counts_[node_description] += 1; - op_costs_[node_description] = - std::make_pair(node_costs.execution_time.asMicroSeconds().count(), - !node_costs.inaccurate); + if (VLOG_IS_ON(2)) { + // Also keep track of op counts and costs per op (with their shapes). + OpContext op_context = GetCurrNode(); + + string node_description = GetOpDescription(op_context.op_info); + op_counts_[node_description] += 1; + op_costs_[node_description] = + std::make_pair(node_costs.execution_time.asMicroSeconds().count(), + !node_costs.inaccurate); + } // Update node and device states. auto& node_state = node_map_[node]; @@ -868,7 +873,10 @@ bool VirtualScheduler::MarkCurrNodeExecuted(const Costs& node_costs) { // check max memory usage. if (device.memory_usage > device.max_memory_usage) { device.max_memory_usage = device.memory_usage; - device.mem_usage_snapshot_at_peak = device.nodes_in_memory; + + if (track_mem_usage_snapshot_) { + device.mem_usage_snapshot_at_peak = device.nodes_in_memory; + } } } diff --git a/tensorflow/core/grappler/costs/virtual_scheduler.h b/tensorflow/core/grappler/costs/virtual_scheduler.h index 6a835f32d1..f500e4ab51 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler.h +++ b/tensorflow/core/grappler/costs/virtual_scheduler.h @@ -305,6 +305,8 @@ class VirtualScheduler { return &node_map_; } + void enable_mem_usage_tracking() { track_mem_usage_snapshot_ = true; } + private: // Constants. const string kAttrInputSrc = "input_source_"; @@ -356,6 +358,7 @@ class VirtualScheduler { const GrapplerItem* grappler_item_; // Not owned. bool use_static_shapes_; bool initialized_; + bool track_mem_usage_snapshot_; VirtualPlacer placer_; // owned. }; diff --git a/tensorflow/core/grappler/costs/virtual_scheduler_test.cc b/tensorflow/core/grappler/costs/virtual_scheduler_test.cc index 0a695458e1..c5651715f4 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler_test.cc +++ b/tensorflow/core/grappler/costs/virtual_scheduler_test.cc @@ -31,7 +31,9 @@ namespace grappler { class TestVirtualScheduler : public VirtualScheduler { public: TestVirtualScheduler(const bool use_static_shapes, Cluster* cluster) - : VirtualScheduler(use_static_shapes, cluster, &ready_node_manager_) {} + : VirtualScheduler(use_static_shapes, cluster, &ready_node_manager_) { + enable_mem_usage_tracking(); + } FRIEND_TEST(VirtualSchedulerTest, MemoryUsage); FRIEND_TEST(VirtualSchedulerTest, ControlDependency); -- GitLab From 20dc97f0f11534adca40d66e5268eb74e012e254 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 21:37:11 -0800 Subject: [PATCH 0238/2345] export find_all_hinted_output_nodes for Ophint. PiperOrigin-RevId: 227955652 --- tensorflow/lite/python/convert_test.py | 23 ++++++++++++++++++ tensorflow/lite/python/op_hint.py | 32 +++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/python/convert_test.py b/tensorflow/lite/python/convert_test.py index cf49ee2b47..a29d431322 100644 --- a/tensorflow/lite/python/convert_test.py +++ b/tensorflow/lite/python/convert_test.py @@ -29,6 +29,7 @@ from tensorflow.python.framework import dtypes from tensorflow.python.framework import test_util from tensorflow.python.framework.graph_util_impl import _bfs_for_reachable_nodes from tensorflow.python.framework.graph_util_impl import _extract_graph_summary +from tensorflow.python.framework.graph_util_impl import _node_name from tensorflow.python.ops import array_ops from tensorflow.python.ops import math_ops from tensorflow.python.platform import test @@ -389,6 +390,28 @@ class ConvertTestOpHint(test_util.TensorFlowTestCase): with self.assertRaises(ValueError): convert.convert_dtype_to_tflite_type(dtypes.bool) + def testFindHintedOutputNodes(self): + """Test if all hinted output nodes are correctly found.""" + + def _build_ophinted_op(name, input1, input2): + custom_op = op_hint.OpHint(name) + input1 = custom_op.add_input(input1) + input2 = custom_op.add_input(input2) + output = math_ops.mul(input1, input2) + return custom_op.add_output(output) + + output_1 = _build_ophinted_op("custom_op_1", array_ops.constant([1.]), + array_ops.constant([2.])) + output_2 = _build_ophinted_op("custom_op_2", array_ops.constant([3.]), + array_ops.constant([4.])) + with self.cached_session() as sess: + hinted_outputs_nodes = op_hint.find_all_hinted_output_nodes(sess) + expected_hinted_output_nodes = [ + _node_name(output_1.name), + _node_name(output_2.name) + ] + self.assertCountEqual(hinted_outputs_nodes, expected_hinted_output_nodes) + if __name__ == "__main__": test.main() diff --git a/tensorflow/lite/python/op_hint.py b/tensorflow/lite/python/op_hint.py index 8d7f9316bf..6ec050171f 100644 --- a/tensorflow/lite/python/op_hint.py +++ b/tensorflow/lite/python/op_hint.py @@ -964,6 +964,35 @@ def _convert_op_hints_to_stubs_helper( return curr_graph_def +def find_all_hinted_output_nodes(session=None, graph_def=None): + """Find all Ophints output nodes in the graph. + + This is used to get all the output nodes those are ophinted, it is important + for operation like convert_variables_to_constants keep all ophints structure. + Note: only one of session or graph_def should be used, not both. + + Args: + session: A TensorFlow session that contains the graph to convert. + graph_def: A graph def that we should convert. + + Returns: + A list of OpHints output nodes. + Raises: + ValueError: If both session and graph_def are provided. + """ + if session is not None and graph_def is not None: + raise ValueError("Provide only one of session and graph_def.") + hinted_outputs_nodes = [] + if session is not None: + hints = _find_all_hints_in_graph_def(session.graph_def) + elif graph_def is not None: + hints = _find_all_hints_in_graph_def(graph_def) + for hint in _six.itervalues(hints): + _, ouput_nodes = hint.flattened_inputs_and_outputs() + hinted_outputs_nodes.extend(ouput_nodes) + return hinted_outputs_nodes + + def convert_op_hints_to_stubs(session=None, graph_def=None, write_callback=lambda graph_def, comments: None): @@ -996,6 +1025,7 @@ def convert_op_hints_to_stubs(session=None, _allowed_symbols = [ - "OpHint", "convert_op_hints_to_stubs", "convert_op_hints_to_stubs_new" + "OpHint", "convert_op_hints_to_stubs", "convert_op_hints_to_stubs_new", + "find_all_hinted_output_nodes" ] remove_undocumented(__name__, _allowed_symbols) -- GitLab From 058bb7c3512455d2d83d2094a6f6e60b0ca6ee05 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 4 Jan 2019 22:49:14 -0800 Subject: [PATCH 0239/2345] Rollback flaky test PiperOrigin-RevId: 227959516 --- tensorflow/core/BUILD | 1 - .../core/util/presized_cuckoo_map_test.cc | 48 ++----------------- 2 files changed, 5 insertions(+), 44 deletions(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 460353df41..7c9decbd09 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -3813,7 +3813,6 @@ tf_cc_tests( "//tensorflow/core/kernels:ops_util", "//third_party/eigen3", "@com_google_absl//absl/base", - "@com_google_absl//absl/time", ], ) diff --git a/tensorflow/core/util/presized_cuckoo_map_test.cc b/tensorflow/core/util/presized_cuckoo_map_test.cc index 8dbb2ec065..f2c7904b00 100644 --- a/tensorflow/core/util/presized_cuckoo_map_test.cc +++ b/tensorflow/core/util/presized_cuckoo_map_test.cc @@ -15,7 +15,6 @@ limitations under the License. #include -#include "absl/time/clock.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/fingerprint.h" #include "tensorflow/core/platform/test.h" @@ -53,48 +52,11 @@ TEST(PresizedCuckooMapTest, Basic) { } TEST(PresizedCuckooMapTest, Prefetch) { - { - PresizedCuckooMap pscm(2); - EXPECT_TRUE(pscm.InsertUnique(1, 2)); - // Works for both present and absent keys. - pscm.PrefetchKey(1); - pscm.PrefetchKey(2); - } - - // Do not run in debug mode, when prefetch is not implemented, or when - // sanitizers are enabled. -#if defined(NDEBUG) && defined(__GNUC__) && !defined(ADDRESS_SANITIZER) && \ - !defined(MEMORY_SANITIZER) && !defined(THREAD_SANITIZER) && \ - !defined(UNDEFINED_BEHAVIOR_SANITIZER) - const auto now = [] { return absl::Now(); }; - - // Make size enough to not fit in L2 cache (16.7 Mb) - static constexpr int size = 1 << 22; - PresizedCuckooMap pscm(size); - for (int i = 0; i < size; ++i) { - pscm.InsertUnique(i, i); - } - - absl::Duration no_prefetch, prefetch; - int64 out; - for (int iter = 0; iter < 10; ++iter) { - auto time = now(); - for (int i = 0; i < size; ++i) { - testing::DoNotOptimize(pscm.Find(i, &out)); - } - no_prefetch += now() - time; - - time = now(); - for (int i = 0; i < size; ++i) { - pscm.PrefetchKey(i + 20); - testing::DoNotOptimize(pscm.Find(i, &out)); - } - prefetch += now() - time; - } - - // no_prefetch is at least 30% slower. - EXPECT_GE(1.0 * no_prefetch / prefetch, 1.3); -#endif + PresizedCuckooMap pscm(2); + EXPECT_TRUE(pscm.InsertUnique(1, 2)); + // Works for both present and absent keys. + pscm.PrefetchKey(1); + pscm.PrefetchKey(2); } TEST(PresizedCuckooMapTest, TooManyItems) { -- GitLab From 9e8962b78b3c514cb1163c01fa7ab767592f952f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sat, 5 Jan 2019 01:03:08 -0800 Subject: [PATCH 0240/2345] compat: Update forward compatibility horizon to 2019-01-05 PiperOrigin-RevId: 227969226 --- 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 66c4fa4893..3b808d71a5 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, 1, 4) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 5) @tf_export("compat.forward_compatible") -- GitLab From 186f573d7067b1df32e3bf2ffb7d34cffcdb4c64 Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Sat, 5 Jan 2019 17:31:21 +0530 Subject: [PATCH 0241/2345] Minor bug fixes `take_while` ShortCircuit tests are parameterized Fix errors in `BUILD` files using `buildifier` LoopIteratorPredicate takes `vector&` --- tensorflow/core/kernels/data/experimental/BUILD | 2 +- .../data/experimental/take_while_dataset_op.cc | 8 +++++--- .../python/data/experimental/kernel_tests/BUILD | 4 ++-- .../kernel_tests/take_while_test.py | 17 +++++++++++------ tensorflow/python/data/experimental/ops/BUILD | 4 ++-- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tensorflow/core/kernels/data/experimental/BUILD b/tensorflow/core/kernels/data/experimental/BUILD index 53d1f02a3a..d71b473b19 100644 --- a/tensorflow/core/kernels/data/experimental/BUILD +++ b/tensorflow/core/kernels/data/experimental/BUILD @@ -303,7 +303,7 @@ tf_kernel_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core/kernels/data:captured_function", - "//tensorflow/core/kernels/data:dataset_utils" + "//tensorflow/core/kernels/data:dataset_utils", ], ) diff --git a/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc index b670e4efaa..3a6f70e504 100644 --- a/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/take_while_dataset_op.cc @@ -21,6 +21,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/util/ptr_util.h" namespace tensorflow { namespace data { @@ -33,7 +34,7 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { public: using LoopIteratorPredicate = std::function, bool*)>; + std::vector&, bool*)>; explicit TakeWhileDatasetOp(OpKernelConstruction* ctx) : UnaryDatasetOpKernel(ctx) { @@ -106,8 +107,9 @@ class TakeWhileDatasetOp : public UnaryDatasetOpKernel { std::unique_ptr MakeIteratorInternal( const string& prefix) const override { - return std::unique_ptr(new Iterator( - {this, strings::StrCat(prefix, "::TakeWhile")}, loop_pred_)); + return MakeUnique( + Iterator::Params{this, strings::StrCat(prefix, "::TakeWhile")}, + loop_pred_); } const DataTypeVector& output_dtypes() const override { diff --git a/tensorflow/python/data/experimental/kernel_tests/BUILD b/tensorflow/python/data/experimental/kernel_tests/BUILD index b4196685e2..e832231912 100644 --- a/tensorflow/python/data/experimental/kernel_tests/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/BUILD @@ -650,13 +650,13 @@ py_test( "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python:framework_test_lib", - "//tensorflow/python:script_ops", - "//tensorflow/python:sparse_tensor", + "//tensorflow/python:math_ops", "//tensorflow/python/data/experimental/ops:take_while_ops", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/eager:context", "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) diff --git a/tensorflow/python/data/experimental/kernel_tests/take_while_test.py b/tensorflow/python/data/experimental/kernel_tests/take_while_test.py index 3faee0639f..7392f5420a 100644 --- a/tensorflow/python/data/experimental/kernel_tests/take_while_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/take_while_test.py @@ -83,19 +83,24 @@ class TakeWhileTest(test_base.DatasetTestBase, parameterized.TestCase): with self.assertRaises(errors.OutOfRangeError): self.assertEqual(b"test", self.evaluate(next_element())) - def testTakewhileDatasetShortCircuit(self): + @parameterized.parameters( + (5, 3), + (10, 0), + (100, 5), + (8, 7)) + def testTakewhileDatasetShortCircuit(self, size, index): def _predicate_func(data_elem): return data_elem - boolean_array = [True, True, True, True, False] + boolean_array = [True]*size + boolean_array[index] = False dataset = dataset_ops.Dataset.from_tensor_slices(boolean_array).apply( take_while_ops.take_while(_predicate_func)) next_element = self.getNext(dataset) - self.assertEqual(True, self.evaluate(next_element())) - self.assertEqual(True, self.evaluate(next_element())) - self.assertEqual(True, self.evaluate(next_element())) - self.assertEqual(True, self.evaluate(next_element())) + + for _ in range(index): + self.assertTrue(self.evaluate(next_element())) with self.assertRaises(errors.OutOfRangeError): self.evaluate(next_element()) diff --git a/tensorflow/python/data/experimental/ops/BUILD b/tensorflow/python/data/experimental/ops/BUILD index 95e9509678..56bf59344f 100644 --- a/tensorflow/python/data/experimental/ops/BUILD +++ b/tensorflow/python/data/experimental/ops/BUILD @@ -359,9 +359,9 @@ py_library( srcs = ["take_while_ops.py"], srcs_version = "PY2AND3", deps = [ + "//tensorflow/python:dtypes", "//tensorflow/python:experimental_dataset_ops_gen", "//tensorflow/python:framework_ops", - "//tensorflow/python:dtypes", "//tensorflow/python:function", "//tensorflow/python/data/ops:dataset_ops", ], @@ -467,7 +467,7 @@ py_library( ":shuffle_ops", ":sleep", ":stats_ops", - "take_while_ops", + ":take_while_ops", ":threadpool", ":unique", ":writers", -- GitLab From f69eca8cef2e8536364bb5a6e5f966a5e071812f Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Sat, 5 Jan 2019 13:29:23 -0800 Subject: [PATCH 0242/2345] Enable test that works with control flow v2 now. PiperOrigin-RevId: 228006449 --- tensorflow/python/kernel_tests/tensor_array_ops_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/python/kernel_tests/tensor_array_ops_test.py b/tensorflow/python/kernel_tests/tensor_array_ops_test.py index 303e9a006a..3f1bd60d1a 100644 --- a/tensorflow/python/kernel_tests/tensor_array_ops_test.py +++ b/tensorflow/python/kernel_tests/tensor_array_ops_test.py @@ -1005,7 +1005,6 @@ class TensorArrayTest(test.TestCase): self._testWhileLoopWritePackGradients( dynamic_size=True, dtype=dtypes.float32) - @test_util.disable_control_flow_v2("b/119323158") def testGradSerialTwoLoops(self): with self.session(use_gpu=True): def loop(x): -- GitLab From 3e85e82fd5cd6b3100967cae899c2229636c2e95 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 6 Jan 2019 06:56:02 +0000 Subject: [PATCH 0243/2345] Fix incorrectly returned error type during RaggedTensor iteration This fix tries to address the issue raised in 24679 where RaggedTensor iteration does not stop correctly (eager mode): ``` import tensorflow as tf r = tf.ragged.constant([[1., 2.], [3., 4., 5.], [6.]]) for elem in r: print(elem) ``` The reason was that `__getitem__()` should have thrown IndexError (translated to StopIteration in next/__next__()) in order for python to correctly process iteration. This fix fixes the issue. This fix fixes 24679. Signed-off-by: Yong Tang --- .../python/ops/ragged/ragged_getitem.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tensorflow/python/ops/ragged/ragged_getitem.py b/tensorflow/python/ops/ragged/ragged_getitem.py index 001a400596..f5ac3e9a2a 100644 --- a/tensorflow/python/ops/ragged/ragged_getitem.py +++ b/tensorflow/python/ops/ragged/ragged_getitem.py @@ -18,6 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from tensorflow.python.eager import context from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.ops import array_ops @@ -150,6 +151,27 @@ def _ragged_getitem(rt_input, key_list): else: starts = rt_input.row_splits[:-1] limits = rt_input.row_splits[1:] + if context.executing_eagerly(): + # In python, __getitem__ should throw IndexError for out of bound + # indices. This will allow iteration run correctly as python will + # translate IndexError into StopIteration for next()/__next__(). + # Below is an example: + # import tensorflow as tf + # r = tf.ragged.constant([[1., 2.], [3., 4., 5.], [6.]]) + # for elem in r: + # print(elem) + # In non eager mode, the exception is thrown when session runs + # so we don't know if out of bound happens before. + # In eager mode, however, it is possible to find out when to + # throw out of bound IndexError. + # In the following row_key >= len(starts) is checked. In case of + # TypeError which happens when row_key is not an integer, the exception + # will simply be ignored as it will be processed later anyway. + try: + if row_key >= len(starts): + raise IndexError('row key {} out of bounds'.format(row_key)) + except TypeError: + pass row = rt_input.values[starts[row_key]:limits[row_key]] return row.__getitem__(inner_keys) -- GitLab From 1467dea7ee9550fed8e580cb627c05c62d126def Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 6 Jan 2019 06:59:28 +0000 Subject: [PATCH 0244/2345] Fix failed test cases due to returned IndexError change Signed-off-by: Yong Tang --- .../python/ops/ragged/ragged_tensor_test.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tensorflow/python/ops/ragged/ragged_tensor_test.py b/tensorflow/python/ops/ragged/ragged_tensor_test.py index 89691b015d..ae2dda76bd 100644 --- a/tensorflow/python/ops/ragged/ragged_tensor_test.py +++ b/tensorflow/python/ops/ragged/ragged_tensor_test.py @@ -829,13 +829,13 @@ class RaggedTensorTest(ragged_test_util.RaggedTensorTestCase, @parameterized.parameters( # Tests for out-of-bound errors (SLICE_BUILDER[5], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), (SLICE_BUILDER[-6], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), (SLICE_BUILDER[0, 2], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), (SLICE_BUILDER[3, 0], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), # Indexing into an inner ragged dimension (SLICE_BUILDER[:, 3], ValueError, @@ -954,13 +954,13 @@ class RaggedTensorTest(ragged_test_util.RaggedTensorTestCase, # Test for out-of-bounds errors. (SLICE_BUILDER[1, 0], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), (SLICE_BUILDER[0, 0, 3], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), (SLICE_BUILDER[5], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), (SLICE_BUILDER[0, 5], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), ) def testRaggedTensorGetItemErrorsWithRaggedRank2(self, slice_spec, expected, message): @@ -983,9 +983,9 @@ class RaggedTensorTest(ragged_test_util.RaggedTensorTestCase, @parameterized.parameters( (SLICE_BUILDER[0], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), (SLICE_BUILDER[-1], - (ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), ) def testRaggedTensorGetItemErrorsWithEmptyTensor(self, slice_spec, expected, message): -- GitLab From 453ebe01a3a1cc6382ec90539fc98e4554dece87 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 6 Jan 2019 07:00:16 +0000 Subject: [PATCH 0245/2345] Add additional test case for GitHub issue 24679 Signed-off-by: Yong Tang --- tensorflow/python/ops/ragged/ragged_tensor_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tensorflow/python/ops/ragged/ragged_tensor_test.py b/tensorflow/python/ops/ragged/ragged_tensor_test.py index ae2dda76bd..706b6eccf2 100644 --- a/tensorflow/python/ops/ragged/ragged_tensor_test.py +++ b/tensorflow/python/ops/ragged/ragged_tensor_test.py @@ -1207,5 +1207,18 @@ class RaggedTensorTest(ragged_test_util.RaggedTensorTestCase, res2 = session.partial_run(handle, r2, feed_dict={c: c_val}) self.assertAllEqual(res2, [15, 7]) + # Test case for GitHub issue 24679. + def testEagerForLoop(self): + if not context.executing_eagerly(): + return + + values = [[1., 2.], [3., 4., 5.], [6.]] + r = ragged_factory_ops.constant(values) + i = 0 + for elem in r: + value = values[i] + i += 1 + self.assertAllEqual(elem, value) + if __name__ == '__main__': googletest.main() -- GitLab From 929a9173c55597fb612d4aca75769bd9f8ac41a1 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 6 Jan 2019 07:04:00 +0000 Subject: [PATCH 0246/2345] Fix line too long issue Signed-off-by: Yong Tang --- .../python/ops/ragged/ragged_tensor_test.py | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tensorflow/python/ops/ragged/ragged_tensor_test.py b/tensorflow/python/ops/ragged/ragged_tensor_test.py index 706b6eccf2..0c9506c731 100644 --- a/tensorflow/python/ops/ragged/ragged_tensor_test.py +++ b/tensorflow/python/ops/ragged/ragged_tensor_test.py @@ -829,13 +829,17 @@ class RaggedTensorTest(ragged_test_util.RaggedTensorTestCase, @parameterized.parameters( # Tests for out-of-bound errors (SLICE_BUILDER[5], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), (SLICE_BUILDER[-6], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), (SLICE_BUILDER[0, 2], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), (SLICE_BUILDER[3, 0], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), # Indexing into an inner ragged dimension (SLICE_BUILDER[:, 3], ValueError, @@ -954,13 +958,17 @@ class RaggedTensorTest(ragged_test_util.RaggedTensorTestCase, # Test for out-of-bounds errors. (SLICE_BUILDER[1, 0], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), (SLICE_BUILDER[0, 0, 3], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), (SLICE_BUILDER[5], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), (SLICE_BUILDER[0, 5], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), ) def testRaggedTensorGetItemErrorsWithRaggedRank2(self, slice_spec, expected, message): @@ -983,9 +991,11 @@ class RaggedTensorTest(ragged_test_util.RaggedTensorTestCase, @parameterized.parameters( (SLICE_BUILDER[0], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), (SLICE_BUILDER[-1], - (IndexError, ValueError, errors.InvalidArgumentError), '.*out of bounds.*'), + (IndexError, ValueError, errors.InvalidArgumentError), + '.*out of bounds.*'), ) def testRaggedTensorGetItemErrorsWithEmptyTensor(self, slice_spec, expected, message): -- GitLab From 933109e06a36d1e85daa59214ee37f0e3d170cc8 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 6 Jan 2019 01:03:37 -0800 Subject: [PATCH 0247/2345] compat: Update forward compatibility horizon to 2019-01-06 PiperOrigin-RevId: 228038143 --- 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 3b808d71a5..b779c80aa7 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, 1, 5) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 6) @tf_export("compat.forward_compatible") -- GitLab From a0804de99207eb89b38a315b7baa0eb2976931c7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 6 Jan 2019 03:06:52 -0800 Subject: [PATCH 0248/2345] Internal change PiperOrigin-RevId: 228045421 --- tensorflow/contrib/BUILD | 1 - tensorflow/contrib/__init__.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tensorflow/contrib/BUILD b/tensorflow/contrib/BUILD index 832db0f4ab..307bb6eca3 100644 --- a/tensorflow/contrib/BUILD +++ b/tensorflow/contrib/BUILD @@ -63,7 +63,6 @@ py_library( "//tensorflow/contrib/libsvm", "//tensorflow/contrib/linear_optimizer:sdca_estimator_py", "//tensorflow/contrib/linear_optimizer:sdca_ops_py", - "//tensorflow/contrib/lite/python:lite", "//tensorflow/contrib/lookup:lookup_py", "//tensorflow/contrib/losses:losses_py", "//tensorflow/contrib/losses:metric_learning_py", diff --git a/tensorflow/contrib/__init__.py b/tensorflow/contrib/__init__.py index 4f1a2a5693..af59686120 100644 --- a/tensorflow/contrib/__init__.py +++ b/tensorflow/contrib/__init__.py @@ -91,7 +91,6 @@ from tensorflow.contrib import tpu from tensorflow.contrib import training from tensorflow.contrib import util from tensorflow.contrib.eager.python import tfe as eager -from tensorflow.contrib.lite.python import lite from tensorflow.contrib.optimizer_v2 import optimizer_v2_symbols as optimizer_v2 from tensorflow.contrib.receptive_field import receptive_field_api as receptive_field from tensorflow.contrib.recurrent.python import recurrent_api as recurrent -- GitLab From c67e5c39503c4ca2f1b383acfe55cd4fd8fba37d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 6 Jan 2019 03:15:34 -0800 Subject: [PATCH 0249/2345] Extend config generation to include a default CPU configuration and provide a platform definition for this configuration. We create a new platform instead of changing the old one so we can transition without the need to change all users atomically. PiperOrigin-RevId: 228045771 --- third_party/toolchains/BUILD | 28 ++++++++-- .../toolchains/preconfig/generate/BUILD | 6 ++ .../preconfig/generate/containers.bzl | 1 + .../preconfig/generate/generate.bzl | 55 ++++++++++++------- .../toolchains/preconfig/generate/generate.sh | 31 ++++++++--- .../preconfig/generate/workspace.bzl | 7 +++ 6 files changed, 96 insertions(+), 32 deletions(-) diff --git a/third_party/toolchains/BUILD b/third_party/toolchains/BUILD index f2248341c4..6ed6e5c367 100644 --- a/third_party/toolchains/BUILD +++ b/third_party/toolchains/BUILD @@ -4,10 +4,9 @@ package(default_visibility = ["//visibility:public"]) load("//third_party/toolchains/preconfig/generate:containers.bzl", "container_digests") -# Platform for use with remote execution with -# custom container based off RBE Ubuntu16_04 -# http://gcr.io/cloud-marketplace/google/rbe-ubuntu16-04 -# Built with //tensorflow/tools/ci_build/Dockerfile.rbe.cpu +# TODO(b/122347293): This is the RBE config based on the CPU configuration / image provided +# in the asci-toolchain setup. Delete this once we switched CPU remote builds to the +# new platform below. platform( name = "rbe_ubuntu16_04-tf", constraint_values = [ @@ -23,6 +22,26 @@ platform( }""", ) +# Remote build platforms. +# Each of the platform rules here provide a platform definition that is bound to a docker image. +# The result of the skylark configuration is checked into +# //tensorflow/third_party/toolchains/preconfig. + +# Built with //tensorflow/tools/ci_build/Dockerfile.rbe.cpu. +platform( + name = "rbe_ubuntu16.04", + constraint_values = [ + "@bazel_tools//platforms:x86_64", + "@bazel_tools//platforms:linux", + ], + remote_execution_properties = """ + properties: { + name: "container-image" + value:"docker://gcr.io/tensorflow-testing/nosla-ubuntu16.04@%s" + }""" % container_digests["ubuntu16.04"], +) + +# Built with //tensorflow/tools/ci_build/Dockerfile.rbe.cuda9.0-cudnn7-ubuntu14.04. platform( name = "rbe_cuda9.0-cudnn7-ubuntu14.04", constraint_values = [ @@ -36,6 +55,7 @@ platform( }""" % container_digests["cuda9.0-cudnn7-ubuntu14.04"], ) +# Built with //tensorflow/tools/ci_build/Dockerfile.rbe.cuda10.0-cudnn7-ubuntu14.04. platform( name = "rbe_cuda10.0-cudnn7-ubuntu14.04", constraint_values = [ diff --git a/third_party/toolchains/preconfig/generate/BUILD b/third_party/toolchains/preconfig/generate/BUILD index b4c98dc94d..ad79255251 100644 --- a/third_party/toolchains/preconfig/generate/BUILD +++ b/third_party/toolchains/preconfig/generate/BUILD @@ -2,6 +2,12 @@ licenses(["restricted"]) load(":generate.bzl", "tensorflow_rbe_config") +tensorflow_rbe_config( + name = "ubuntu16.04-py3-clang", + compiler = "clang", + python_version = "3", +) + tensorflow_rbe_config( name = "ubuntu14.04-py3-gcc-cuda9.0-cudnn7-tensorrt5", compiler = "gcc", diff --git a/third_party/toolchains/preconfig/generate/containers.bzl b/third_party/toolchains/preconfig/generate/containers.bzl index 67e43485ed..428208523b 100644 --- a/third_party/toolchains/preconfig/generate/containers.bzl +++ b/third_party/toolchains/preconfig/generate/containers.bzl @@ -1,4 +1,5 @@ container_digests = { + "ubuntu16.04": "sha256:d0d98c53111c3ec071aa81632a2b0d6f210e5c2411c5172e31f99002125ec4de", "cuda9.0-cudnn7-ubuntu14.04": "sha256:006a76ee1838122ff7f21ebac85f24c1ef350d4dd79b3ceff0e4fe649ed90d33", "cuda10.0-cudnn7-ubuntu14.04": "sha256:e36f05f1ff39e39ddf07122e37f2b1895948bb6f7acc3db37a3c496be5e66228", } diff --git a/third_party/toolchains/preconfig/generate/generate.bzl b/third_party/toolchains/preconfig/generate/generate.bzl index 75deea41b8..fc485d43d2 100644 --- a/third_party/toolchains/preconfig/generate/generate.bzl +++ b/third_party/toolchains/preconfig/generate/generate.bzl @@ -3,30 +3,38 @@ load( "docker_toolchain_autoconfig", ) -def _tensorflow_rbe_config(name, cuda_version, cudnn_version, python_version, compiler, tensorrt_version): - docker_toolchain_autoconfig( - name = name, - base = "@cuda%s-cudnn%s-ubuntu14.04//image" % (cuda_version, cudnn_version), - bazel_version = "0.19.2", +def _tensorflow_rbe_config(name, compiler, python_version, cuda_version = None, cudnn_version = None, tensorrt_version = None): + base = "@ubuntu16.04//image" + config_repos = [ + "local_config_python", + "local_config_cc", + ] + env = { + "ABI_VERSION": "gcc", + "ABI_LIBC_VERSION": "glibc_2.19", + "BAZEL_COMPILER": compiler, + "BAZEL_HOST_SYSTEM": "i686-unknown-linux-gnu", + "BAZEL_TARGET_LIBC": "glibc_2.19", + "BAZEL_TARGET_CPU": "k8", + "BAZEL_TARGET_SYSTEM": "x86_64-unknown-linux-gnu", + "CC_TOOLCHAIN_NAME": "linux_gnu_x86", + "CC": compiler, + "PYTHON_BIN_PATH": "/usr/bin/python%s" % python_version, + "CLEAR_CACHE": "1", + } + + if cuda_version != None: + base = "@cuda%s-cudnn%s-ubuntu14.04//image" % (cuda_version, cudnn_version) + # The cuda toolchain currently contains its own C++ toolchain definition, + # so we do not fetch local_config_cc. config_repos = [ - "local_config_cuda", "local_config_python", + "local_config_cuda", "local_config_tensorrt", - ], - env = { - "ABI_VERSION": "gcc", - "ABI_LIBC_VERSION": "glibc_2.19", - "BAZEL_COMPILER": compiler, - "BAZEL_HOST_SYSTEM": "i686-unknown-linux-gnu", - "BAZEL_TARGET_LIBC": "glibc_2.19", - "BAZEL_TARGET_CPU": "k8", - "BAZEL_TARGET_SYSTEM": "x86_64-unknown-linux-gnu", - "CC_TOOLCHAIN_NAME": "linux_gnu_x86", - "CC": compiler, - "PYTHON_BIN_PATH": "/usr/bin/python%s" % python_version, + ] + env.update({ "TF_NEED_CUDA": "1", "TF_CUDA_CLANG": "1" if compiler == "clang" else "0", - "CLEAR_CACHE": "1", "TF_CUDA_COMPUTE_CAPABILITIES": "3.0", "TF_ENABLE_XLA": "1", "TF_CUDNN_VERSION": cudnn_version, @@ -35,7 +43,14 @@ def _tensorflow_rbe_config(name, cuda_version, cudnn_version, python_version, co "TF_NEED_TENSORRT" : "1", "TF_TENSORRT_VERSION": tensorrt_version, "TENSORRT_INSTALL_PATH": "/usr/lib/x86_64-linux-gnu", - }, + }) + + docker_toolchain_autoconfig( + name = name, + base = base, + bazel_version = "0.21.0", + config_repos = config_repos, + env = env, mount_project = "$(mount_project)", tags = ["manual"], incompatible_changes_off = True, diff --git a/third_party/toolchains/preconfig/generate/generate.sh b/third_party/toolchains/preconfig/generate/generate.sh index 8e3a1e6ada..c05a4de6fb 100755 --- a/third_party/toolchains/preconfig/generate/generate.sh +++ b/third_party/toolchains/preconfig/generate/generate.sh @@ -37,8 +37,16 @@ TENSORRT_VERSION="${PLATFORM[5]}" # TODO(klimek): Put this into the name. -if [[ "${COMPILER}" == "gcc" ]]; then - COMPILER="gcc-nvcc-${CUDA_VERSION}" +if [[ -n "${CUDA_VERSION}" ]]; then + if [[ "${COMPILER}" == "gcc" ]]; then + COMPILER="gcc-nvcc-${CUDA_VERSION}" + fi + # Currently we create a special toolchain for clang when compiling with + # cuda enabled. We can get rid of this once the default toolchain bazel + # provides supports cuda. + if [[ "${COMPILER}" == "clang" ]]; then + COMPILER="cuda-clang" + fi fi echo "OS: ${OS}" @@ -52,6 +60,8 @@ bazel build --define=mount_project="${PWD}" "${PKG}/generate:${TARGET}" cd "${TEMPDIR}" tar xvf "${ROOT}/bazel-bin/${PKG}/generate/${TARGET}_outputs.tar" +# TODO(klimek): The skylark config rules should copy the files instead of +# creating aliases. # Other than in @local_config_tensorrt, the header files in the remote config # repo are not relative to the repository root. Add a dummy include_prefix to # make them available as virtual includes. @@ -74,14 +84,19 @@ mkdir "${OS}" # Python: mv local_config_python "${OS}/${PY_VERSION}" -# Compiler: -mv local_config_cuda/crosstool "${OS}/${COMPILER}" +if [[ -n "${CUDA_VERSION}" ]]; then + # Compiler: + mv local_config_cuda/crosstool "${OS}/${COMPILER}" -# CUDA: -mv local_config_cuda "${OS}/${CUDA_VERSION}-${CUDNN_VERSION}" + # CUDA: + mv local_config_cuda "${OS}/${CUDA_VERSION}-${CUDNN_VERSION}" -# TensorRT: -mv local_config_tensorrt "${OS}/${TENSORRT_VERSION}" + # TensorRT: + mv local_config_tensorrt "${OS}/${TENSORRT_VERSION}" +else + # Compiler: + mv local_config_cc "${OS}/${COMPILER}" +fi # Cleanup for copybara. find "${OS}" -name 'BUILD' -o -name '*.bzl' |xargs buildifier diff --git a/third_party/toolchains/preconfig/generate/workspace.bzl b/third_party/toolchains/preconfig/generate/workspace.bzl index eb74022c24..0495173786 100644 --- a/third_party/toolchains/preconfig/generate/workspace.bzl +++ b/third_party/toolchains/preconfig/generate/workspace.bzl @@ -8,6 +8,13 @@ load(":containers.bzl", "container_digests") def _remote_config_workspace(): container_repositories() + container_pull( + name = "ubuntu16.04", + registry = "gcr.io", + repository = "tensorflow-testing/nosla-ubuntu16.04", + digest = container_digests["ubuntu16.04"], + ) + container_pull( name = "cuda9.0-cudnn7-ubuntu14.04", registry = "gcr.io", -- GitLab From 92985e67564c119203f0aaca94b5a334d87c35e4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 6 Jan 2019 04:03:21 -0800 Subject: [PATCH 0250/2345] Add clang and python3 configurations for ubuntu 16.04. PiperOrigin-RevId: 228048061 --- tensorflow/opensource_only.files | 3 + .../preconfig/ubuntu16.04/clang/BUILD | 111 ++ .../preconfig/ubuntu16.04/clang/CROSSTOOL | 1209 +++++++++++++++++ .../preconfig/ubuntu16.04/clang/WORKSPACE | 2 + .../preconfig/ubuntu16.04/clang/cc_wrapper.sh | 25 + .../ubuntu16.04/clang/dummy_toolchain.bzl | 23 + .../ubuntu16.04/clang/tools/cpp/empty.cc | 1 + .../preconfig/ubuntu16.04/py3/BUILD | 205 +++ .../preconfig/ubuntu16.04/py3/WORKSPACE | 2 + 9 files changed, 1581 insertions(+) create mode 100755 third_party/toolchains/preconfig/ubuntu16.04/clang/BUILD create mode 100755 third_party/toolchains/preconfig/ubuntu16.04/clang/CROSSTOOL create mode 100644 third_party/toolchains/preconfig/ubuntu16.04/clang/WORKSPACE create mode 100755 third_party/toolchains/preconfig/ubuntu16.04/clang/cc_wrapper.sh create mode 100755 third_party/toolchains/preconfig/ubuntu16.04/clang/dummy_toolchain.bzl create mode 100755 third_party/toolchains/preconfig/ubuntu16.04/clang/tools/cpp/empty.cc create mode 100755 third_party/toolchains/preconfig/ubuntu16.04/py3/BUILD create mode 100644 third_party/toolchains/preconfig/ubuntu16.04/py3/WORKSPACE diff --git a/tensorflow/opensource_only.files b/tensorflow/opensource_only.files index 1054c285d6..e00d063cfe 100644 --- a/tensorflow/opensource_only.files +++ b/tensorflow/opensource_only.files @@ -53,6 +53,9 @@ tensorflow/third_party/toolchains/preconfig/generate/containers.bzl tensorflow/third_party/toolchains/preconfig/generate/generate.bzl tensorflow/third_party/toolchains/preconfig/generate/archives.bzl tensorflow/third_party/toolchains/preconfig/generate/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/dummy_toolchain.bzl +tensorflow/third_party/toolchains/preconfig/ubuntu16.04/py3/BUILD 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 diff --git a/third_party/toolchains/preconfig/ubuntu16.04/clang/BUILD b/third_party/toolchains/preconfig/ubuntu16.04/clang/BUILD new file mode 100755 index 0000000000..5a0c52f66a --- /dev/null +++ b/third_party/toolchains/preconfig/ubuntu16.04/clang/BUILD @@ -0,0 +1,111 @@ +# Copyright 2016 The Bazel 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 becomes the BUILD file for @local_config_cc// under non-FreeBSD unixes. + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +cc_library( + name = "malloc", +) + +cc_library( + name = "stl", +) + +filegroup( + name = "empty", + srcs = [], +) + +filegroup( + name = "cc_wrapper", + srcs = ["cc_wrapper.sh"], +) + +filegroup( + name = "compiler_deps", + srcs = glob(["extra_tools/**"]) + [":empty"], +) + +# This is the entry point for --crosstool_top. Toolchains are found +# by lopping off the name of --crosstool_top and searching for +# the "${CPU}" entry in the toolchains attribute. +cc_toolchain_suite( + name = "toolchain", + toolchains = { + "k8|clang": ":cc-compiler-k8", + "k8": ":cc-compiler-k8", + "armeabi-v7a|compiler": ":cc-compiler-armeabi-v7a", + "armeabi-v7a": ":cc-compiler-armeabi-v7a", + }, +) + +cc_toolchain( + name = "cc-compiler-k8", + all_files = ":compiler_deps", + compiler_files = ":compiler_deps", + cpu = "k8", + dwp_files = ":empty", + dynamic_runtime_libs = [":empty"], + linker_files = ":compiler_deps", + objcopy_files = ":empty", + static_runtime_libs = [":empty"], + strip_files = ":empty", + supports_param_files = 1, + toolchain_identifier = "linux_gnu_x86", +) + +toolchain( + name = "cc-toolchain-k8", + exec_compatible_with = [ + # TODO(katre): add autodiscovered constraints for host CPU and OS. + ], + target_compatible_with = [ + # TODO(katre): add autodiscovered constraints for host CPU and OS. + ], + toolchain = ":cc-compiler-k8", + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", +) + +# Android tooling requires a default toolchain for the armeabi-v7a cpu. +cc_toolchain( + name = "cc-compiler-armeabi-v7a", + 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", + supports_param_files = 1, + toolchain_identifier = "stub_armeabi-v7a", +) + +toolchain( + name = "cc-toolchain-armeabi-v7a", + exec_compatible_with = [ + # TODO(katre): add autodiscovered constraints for host CPU and OS. + ], + target_compatible_with = [ + "@bazel_tools//platforms:arm", + "@bazel_tools//platforms:android", + ], + toolchain = ":cc-compiler-armabi-v7a", + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", +) diff --git a/third_party/toolchains/preconfig/ubuntu16.04/clang/CROSSTOOL b/third_party/toolchains/preconfig/ubuntu16.04/clang/CROSSTOOL new file mode 100755 index 0000000000..48f82eb35d --- /dev/null +++ b/third_party/toolchains/preconfig/ubuntu16.04/clang/CROSSTOOL @@ -0,0 +1,1209 @@ +# Copyright 2016 The Bazel 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. + +major_version: "local" +minor_version: "" + +# Android tooling requires a default toolchain for the armeabi-v7a cpu. +toolchain { + abi_version: "armeabi-v7a" + abi_libc_version: "armeabi-v7a" + builtin_sysroot: "" + compiler: "compiler" + host_system_name: "armeabi-v7a" + needsPic: true + supports_gold_linker: false + supports_incremental_linker: false + supports_fission: false + supports_interface_shared_objects: false + supports_normalizing_ar: false + supports_start_end_lib: false + target_libc: "armeabi-v7a" + target_cpu: "armeabi-v7a" + target_system_name: "armeabi-v7a" + toolchain_identifier: "stub_armeabi-v7a" + + tool_path { name: "ar" path: "/bin/false" } + tool_path { name: "compat-ld" path: "/bin/false" } + tool_path { name: "cpp" path: "/bin/false" } + tool_path { name: "dwp" path: "/bin/false" } + tool_path { name: "gcc" path: "/bin/false" } + tool_path { name: "gcov" path: "/bin/false" } + tool_path { name: "ld" path: "/bin/false" } + + tool_path { name: "nm" path: "/bin/false" } + tool_path { name: "objcopy" path: "/bin/false" } + tool_path { name: "objdump" path: "/bin/false" } + tool_path { name: "strip" path: "/bin/false" } + linking_mode_flags { mode: DYNAMIC } +} + +toolchain { + toolchain_identifier: "linux_gnu_x86" + abi_version: "gcc" + abi_libc_version: "glibc_2.19" + builtin_sysroot: "" + compiler: "clang" + host_system_name: "i686-unknown-linux-gnu" + needsPic: true + supports_gold_linker: true + supports_incremental_linker: false + supports_fission: false + supports_interface_shared_objects: false + supports_normalizing_ar: false + supports_start_end_lib: true + target_libc: "glibc_2.19" + target_cpu: "k8" + target_system_name: "x86_64-unknown-linux-gnu" + cxx_flag: "-std=c++0x" + linker_flag: "-fuse-ld=gold" + linker_flag: "-Wl,-no-as-needed" + linker_flag: "-Wl,-z,relro,-z,now" + linker_flag: "-B/usr/local/bin" + linker_flag: "-lstdc++" + linker_flag: "-lm" + 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" + cxx_builtin_include_directory: "/usr/include/c++/4.9" + cxx_builtin_include_directory: "/usr/include/x86_64-linux-gnu/c++/4.9" + cxx_builtin_include_directory: "/usr/include/c++/4.9/backward" + objcopy_embed_flag: "-I" + objcopy_embed_flag: "binary" + unfiltered_cxx_flag: "-no-canonical-prefixes" + unfiltered_cxx_flag: "-Wno-builtin-macro-redefined" + unfiltered_cxx_flag: "-D__DATE__=\"redacted\"" + unfiltered_cxx_flag: "-D__TIMESTAMP__=\"redacted\"" + unfiltered_cxx_flag: "-D__TIME__=\"redacted\"" + compiler_flag: "-U_FORTIFY_SOURCE" + compiler_flag: "-fstack-protector" + compiler_flag: "-Wall" + compiler_flag: "-Wthread-safety" + compiler_flag: "-Wself-assign" + compiler_flag: "-fcolor-diagnostics" + compiler_flag: "-fno-omit-frame-pointer" + tool_path {name: "ar" path: "/usr/bin/ar" } + tool_path {name: "ld" path: "/usr/bin/ld" } + tool_path {name: "cpp" path: "/usr/bin/cpp" } + tool_path {name: "gcc" path: "/usr/local/bin/clang" } + tool_path {name: "dwp" path: "/usr/bin/dwp" } + tool_path {name: "gcov" path: "None" } + 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" } + + compilation_mode_flags { + mode: DBG + compiler_flag: "-g" + } + compilation_mode_flags { + mode: OPT + compiler_flag: "-g0" + compiler_flag: "-O2" + compiler_flag: "-D_FORTIFY_SOURCE=1" + compiler_flag: "-DNDEBUG" + compiler_flag: "-ffunction-sections" + compiler_flag: "-fdata-sections" + linker_flag: "-Wl,--gc-sections" + } + linking_mode_flags { mode: DYNAMIC } + + + feature { + name: 'coverage' + provides: 'profile' + flag_set { + action: 'preprocess-assemble' + action: 'c-compile' + action: 'c++-compile' + action: 'c++-header-parsing' + action: 'c++-module-compile' + flag_group { + flag: '--coverage' + } + } + flag_set { + action: 'c++-link-dynamic-library' + action: 'c++-link-nodeps-dynamic-library' + action: 'c++-link-executable' + flag_group { + flag: '--coverage' + } + } + } + + + feature { + name: 'fdo_optimize' + provides: 'profile' + flag_set { + action: 'c-compile' + action: 'c++-compile' + expand_if_all_available: 'fdo_profile_path' + flag_group { + flag: '-fprofile-use=%{fdo_profile_path}' + flag: '-fprofile-correction', + } + } + } +} + +toolchain { + toolchain_identifier: "msys_x64_mingw" + abi_version: "local" + abi_libc_version: "local" + builtin_sysroot: "" + compiler: "mingw-gcc" + host_system_name: "local" + needsPic: false + target_libc: "mingw" + target_cpu: "x64_windows" + target_system_name: "local" + + artifact_name_pattern { + category_name: 'executable' + prefix: '' + extension: '.exe' + } + + + + linking_mode_flags { mode: DYNAMIC } +} + +toolchain { + toolchain_identifier: "msvc_x64" + 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" + default_python_version: "python2.7" + + + + 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_gold_linker: false + supports_start_end_lib: false + supports_interface_shared_objects: true + supports_incremental_linker: false + supports_normalizing_ar: true + needsPic: false + + # 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=0x0601" + # Turn off warning messages. + compiler_flag: "/D_CRT_SECURE_NO_DEPRECATE" + compiler_flag: "/D_CRT_SECURE_NO_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" + # 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" + } + + artifact_name_pattern { + category_name: 'object_file' + prefix: '' + extension: '.obj' + } + + artifact_name_pattern { + category_name: 'static_library' + prefix: '' + extension: '.lib' + } + + artifact_name_pattern { + category_name: 'alwayslink_static_library' + prefix: '' + extension: '.lo.lib' + } + + artifact_name_pattern { + category_name: 'executable' + prefix: '' + extension: '.exe' + } + + artifact_name_pattern { + category_name: 'dynamic_library' + prefix: '' + extension: '.dll' + } + + artifact_name_pattern { + category_name: 'interface_library' + prefix: '' + extension: '.if.lib' + } + + # 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: "TMP" + value: "" + } + env_entry { + key: "TEMP" + value: "" + } + } + implies: 'msvc_compile_env' + implies: 'msvc_link_env' + } + + feature { + name: "msvc_compile_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" + env_entry { + key: "INCLUDE" + value: "" + } + } + } + + feature { + name: "msvc_link_env" + env_set { + action: "c++-link-executable" + action: "c++-link-dynamic-library" + action: "c++-link-nodeps-dynamic-library" + action: "c++-link-static-library" + env_entry { + key: "LIB" + 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_set { + action: 'c++-link-executable' + action: 'c++-link-dynamic-library' + action: "c++-link-nodeps-dynamic-library" + flag_group { + flag: "" + 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_set { + action: 'c++-link-executable' + action: 'c++-link-dynamic-library' + action: "c++-link-nodeps-dynamic-library" + flag_group { + flag: "" + flag: "/INCREMENTAL:NO" + } + } + implies: 'generate_pdb_file' + } + + feature { + name: 'opt' + flag_set { + action: 'c-compile' + action: 'c++-compile' + flag_group { + flag: "/O2" # Implies /Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy + } + } + implies: 'frame_pointer' + } + + # Keep stack frames for debugging, even in opt mode. + # Must come after /O1, /O2 and /Ox. + feature { + name: "frame_pointer" + flag_set { + action: "c-compile" + action: "c++-compile" + flag_group { + flag: "/Oy-" + } + } + } + + # Remove assert/DCHECKs in opt mode. + # You can have them back with --features=-disable_assertions. + feature { + name: 'disable_assertions' + enabled: true + flag_set { + action: 'c-compile' + action: 'c++-compile' + with_feature: { + feature: 'opt' + } + flag_group { + flag: "/DNDEBUG" + } + } + } + + feature { + name: "determinism" + enabled: true + flag_set { + action: "c-compile" + action: "c++-compile" + flag_group { + # Make C++ compilation deterministic. Use linkstamping instead of these + # compiler symbols. + # TODO: detect clang on Windows and use "-Wno-builtin-macro-redefined" + flag: "/wd4117" # Trying to define or undefine a predefined macro + flag: "-D__DATE__=\"redacted\"" + flag: "-D__TIMESTAMP__=\"redacted\"" + flag: "-D__TIME__=\"redacted\"" + } + } + } + + feature { + name: 'treat_warnings_as_errors' + flag_set { + action: 'c-compile' + action: 'c++-compile' + flag_group { + flag: "/WX" + } + } + } + + # Trade slower build time for smaller binary + feature { + name: 'smaller_binary' + enabled: true + flag_set { + action: 'c-compile' + action: 'c++-compile' + with_feature: { + feature: 'opt' + } + flag_group { + flag: "/Gy" # Enable function-level linking (-ffunction-sections) + flag: "/Gw" # Optimize global data (-fdata-sections) + } + } + flag_set { + action: 'c++-link-executable' + action: 'c++-link-dynamic-library', + action: 'c++-link-nodeps-dynamic-library' + with_feature: { + feature: 'opt' + } + flag_group { + flag: '/OPT:ICF' # Fold identical functions + flag: '/OPT:REF' # Eliminate unreferenced functions and data + } + } + } + + # Suppress warnings that most users do not care + feature { + name: 'ignore_noisy_warnings' + enabled: true + flag_set { + action: 'c++-link-static-library' + flag_group { + # Suppress 'object file does not define any public symbols' warning + flag: '/ignore:4221' + } + } + } + + 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 } +} diff --git a/third_party/toolchains/preconfig/ubuntu16.04/clang/WORKSPACE b/third_party/toolchains/preconfig/ubuntu16.04/clang/WORKSPACE new file mode 100644 index 0000000000..bc05b4c36f --- /dev/null +++ b/third_party/toolchains/preconfig/ubuntu16.04/clang/WORKSPACE @@ -0,0 +1,2 @@ +# DO NOT EDIT: automatically generated WORKSPACE file for cc_autoconf rule +workspace(name = "local_config_cc") diff --git a/third_party/toolchains/preconfig/ubuntu16.04/clang/cc_wrapper.sh b/third_party/toolchains/preconfig/ubuntu16.04/clang/cc_wrapper.sh new file mode 100755 index 0000000000..42a751dccf --- /dev/null +++ b/third_party/toolchains/preconfig/ubuntu16.04/clang/cc_wrapper.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Copyright 2015 The Bazel 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. +# +# Ship the environment to the C++ action +# +set -eu + +# Set-up the environment + + +# Call the C++ compiler +/usr/local/bin/clang "$@" diff --git a/third_party/toolchains/preconfig/ubuntu16.04/clang/dummy_toolchain.bzl b/third_party/toolchains/preconfig/ubuntu16.04/clang/dummy_toolchain.bzl new file mode 100755 index 0000000000..45c0285d23 --- /dev/null +++ b/third_party/toolchains/preconfig/ubuntu16.04/clang/dummy_toolchain.bzl @@ -0,0 +1,23 @@ +# pylint: disable=g-bad-file-header +# Copyright 2017 The Bazel 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. + +"""Skylark rule that stubs a toolchain.""" + +def _dummy_toolchain_impl(ctx): + ctx = ctx # unused argument + toolchain = platform_common.ToolchainInfo() + return [toolchain] + +dummy_toolchain = rule(_dummy_toolchain_impl, attrs = {}) diff --git a/third_party/toolchains/preconfig/ubuntu16.04/clang/tools/cpp/empty.cc b/third_party/toolchains/preconfig/ubuntu16.04/clang/tools/cpp/empty.cc new file mode 100755 index 0000000000..c272dabaeb --- /dev/null +++ b/third_party/toolchains/preconfig/ubuntu16.04/clang/tools/cpp/empty.cc @@ -0,0 +1 @@ +int main() {} \ No newline at end of file diff --git a/third_party/toolchains/preconfig/ubuntu16.04/py3/BUILD b/third_party/toolchains/preconfig/ubuntu16.04/py3/BUILD new file mode 100755 index 0000000000..77eaa4d512 --- /dev/null +++ b/third_party/toolchains/preconfig/ubuntu16.04/py3/BUILD @@ -0,0 +1,205 @@ +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/accu.h", + "python_include/asdl.h", + "python_include/ast.h", + "python_include/bitset.h", + "python_include/bltinmodule.h", + "python_include/boolobject.h", + "python_include/bytearrayobject.h", + "python_include/bytes_methods.h", + "python_include/bytesobject.h", + "python_include/cellobject.h", + "python_include/ceval.h", + "python_include/classobject.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/dynamic_annotations.h", + "python_include/enumobject.h", + "python_include/errcode.h", + "python_include/eval.h", + "python_include/fileobject.h", + "python_include/fileutils.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/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/namespaceobject.h", + "python_include/node.h", + "python_include/numpy/__multiarray_api.h", + "python_include/numpy/__ufunc_api.h", + "python_include/numpy/_neighborhood_iterator_imp.h", + "python_include/numpy/_numpyconfig.h", + "python_include/numpy/arrayobject.h", + "python_include/numpy/arrayscalars.h", + "python_include/numpy/halffloat.h", + "python_include/numpy/multiarray_api.txt", + "python_include/numpy/ndarrayobject.h", + "python_include/numpy/ndarraytypes.h", + "python_include/numpy/noprefix.h", + "python_include/numpy/npy_1_7_deprecated_api.h", + "python_include/numpy/npy_3kcompat.h", + "python_include/numpy/npy_common.h", + "python_include/numpy/npy_cpu.h", + "python_include/numpy/npy_endian.h", + "python_include/numpy/npy_interrupt.h", + "python_include/numpy/npy_math.h", + "python_include/numpy/npy_no_deprecated_api.h", + "python_include/numpy/npy_os.h", + "python_include/numpy/numpyconfig.h", + "python_include/numpy/old_defines.h", + "python_include/numpy/oldnumeric.h", + "python_include/numpy/ufunc_api.txt", + "python_include/numpy/ufuncobject.h", + "python_include/numpy/utils.h", + "python_include/object.h", + "python_include/objimpl.h", + "python_include/odictobject.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/pyatomic.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/pyhash.h", + "python_include/pylifecycle.h", + "python_include/pymacconfig.h", + "python_include/pymacro.h", + "python_include/pymath.h", + "python_include/pymem.h", + "python_include/pyport.h", + "python_include/pystate.h", + "python_include/pystrcmp.h", + "python_include/pystrhex.h", + "python_include/pystrtod.h", + "python_include/pythonrun.h", + "python_include/pythread.h", + "python_include/pytime.h", + "python_include/rangeobject.h", + "python_include/setobject.h", + "python_include/sliceobject.h", + "python_include/structmember.h", + "python_include/structseq.h", + "python_include/symtable.h", + "python_include/sysmodule.h", + "python_include/token.h", + "python_include/traceback.h", + "python_include/tupleobject.h", + "python_include/typeslots.h", + "python_include/ucnhash.h", + "python_include/unicodeobject.h", + "python_include/warnings.h", + "python_include/weakrefobject.h", + ], + cmd = """ +cp -f "/usr/include/python3.5m/Python-ast.h" "$(@D)/python_include/Python-ast.h" && cp -f "/usr/include/python3.5m/Python.h" "$(@D)/python_include/Python.h" && cp -f "/usr/include/python3.5m/abstract.h" "$(@D)/python_include/abstract.h" && cp -f "/usr/include/python3.5m/accu.h" "$(@D)/python_include/accu.h" && cp -f "/usr/include/python3.5m/asdl.h" "$(@D)/python_include/asdl.h" && cp -f "/usr/include/python3.5m/ast.h" "$(@D)/python_include/ast.h" && cp -f "/usr/include/python3.5m/bitset.h" "$(@D)/python_include/bitset.h" && cp -f "/usr/include/python3.5m/bltinmodule.h" "$(@D)/python_include/bltinmodule.h" && cp -f "/usr/include/python3.5m/boolobject.h" "$(@D)/python_include/boolobject.h" && cp -f "/usr/include/python3.5m/bytearrayobject.h" "$(@D)/python_include/bytearrayobject.h" && cp -f "/usr/include/python3.5m/bytes_methods.h" "$(@D)/python_include/bytes_methods.h" && cp -f "/usr/include/python3.5m/bytesobject.h" "$(@D)/python_include/bytesobject.h" && cp -f "/usr/include/python3.5m/cellobject.h" "$(@D)/python_include/cellobject.h" && cp -f "/usr/include/python3.5m/ceval.h" "$(@D)/python_include/ceval.h" && cp -f "/usr/include/python3.5m/classobject.h" "$(@D)/python_include/classobject.h" && cp -f "/usr/include/python3.5m/code.h" "$(@D)/python_include/code.h" && cp -f "/usr/include/python3.5m/codecs.h" "$(@D)/python_include/codecs.h" && cp -f "/usr/include/python3.5m/compile.h" "$(@D)/python_include/compile.h" && cp -f "/usr/include/python3.5m/complexobject.h" "$(@D)/python_include/complexobject.h" && cp -f "/usr/include/python3.5m/datetime.h" "$(@D)/python_include/datetime.h" && cp -f "/usr/include/python3.5m/descrobject.h" "$(@D)/python_include/descrobject.h" && cp -f "/usr/include/python3.5m/dictobject.h" "$(@D)/python_include/dictobject.h" && cp -f "/usr/include/python3.5m/dtoa.h" "$(@D)/python_include/dtoa.h" && cp -f "/usr/include/python3.5m/dynamic_annotations.h" "$(@D)/python_include/dynamic_annotations.h" && cp -f "/usr/include/python3.5m/enumobject.h" "$(@D)/python_include/enumobject.h" && cp -f "/usr/include/python3.5m/errcode.h" "$(@D)/python_include/errcode.h" && cp -f "/usr/include/python3.5m/eval.h" "$(@D)/python_include/eval.h" && cp -f "/usr/include/python3.5m/fileobject.h" "$(@D)/python_include/fileobject.h" && cp -f "/usr/include/python3.5m/fileutils.h" "$(@D)/python_include/fileutils.h" && cp -f "/usr/include/python3.5m/floatobject.h" "$(@D)/python_include/floatobject.h" && cp -f "/usr/include/python3.5m/frameobject.h" "$(@D)/python_include/frameobject.h" && cp -f "/usr/include/python3.5m/funcobject.h" "$(@D)/python_include/funcobject.h" && cp -f "/usr/include/python3.5m/genobject.h" "$(@D)/python_include/genobject.h" && cp -f "/usr/include/python3.5m/graminit.h" "$(@D)/python_include/graminit.h" && cp -f "/usr/include/python3.5m/grammar.h" "$(@D)/python_include/grammar.h" && cp -f "/usr/include/python3.5m/import.h" "$(@D)/python_include/import.h" && cp -f "/usr/include/python3.5m/intrcheck.h" "$(@D)/python_include/intrcheck.h" && cp -f "/usr/include/python3.5m/iterobject.h" "$(@D)/python_include/iterobject.h" && cp -f "/usr/include/python3.5m/listobject.h" "$(@D)/python_include/listobject.h" && cp -f "/usr/include/python3.5m/longintrepr.h" "$(@D)/python_include/longintrepr.h" && cp -f "/usr/include/python3.5m/longobject.h" "$(@D)/python_include/longobject.h" && cp -f "/usr/include/python3.5m/marshal.h" "$(@D)/python_include/marshal.h" && cp -f "/usr/include/python3.5m/memoryobject.h" "$(@D)/python_include/memoryobject.h" && cp -f "/usr/include/python3.5m/metagrammar.h" "$(@D)/python_include/metagrammar.h" && cp -f "/usr/include/python3.5m/methodobject.h" "$(@D)/python_include/methodobject.h" && cp -f "/usr/include/python3.5m/modsupport.h" "$(@D)/python_include/modsupport.h" && cp -f "/usr/include/python3.5m/moduleobject.h" "$(@D)/python_include/moduleobject.h" && cp -f "/usr/include/python3.5m/namespaceobject.h" "$(@D)/python_include/namespaceobject.h" && cp -f "/usr/include/python3.5m/node.h" "$(@D)/python_include/node.h" && cp -f "/usr/include/python3.5m/numpy/__multiarray_api.h" "$(@D)/python_include/numpy/__multiarray_api.h" && cp -f "/usr/include/python3.5m/numpy/__ufunc_api.h" "$(@D)/python_include/numpy/__ufunc_api.h" && cp -f "/usr/include/python3.5m/numpy/_neighborhood_iterator_imp.h" "$(@D)/python_include/numpy/_neighborhood_iterator_imp.h" && cp -f "/usr/include/python3.5m/numpy/_numpyconfig.h" "$(@D)/python_include/numpy/_numpyconfig.h" && cp -f "/usr/include/python3.5m/numpy/arrayobject.h" "$(@D)/python_include/numpy/arrayobject.h" && cp -f "/usr/include/python3.5m/numpy/arrayscalars.h" "$(@D)/python_include/numpy/arrayscalars.h" && cp -f "/usr/include/python3.5m/numpy/halffloat.h" "$(@D)/python_include/numpy/halffloat.h" && cp -f "/usr/include/python3.5m/numpy/multiarray_api.txt" "$(@D)/python_include/numpy/multiarray_api.txt" && cp -f "/usr/include/python3.5m/numpy/ndarrayobject.h" "$(@D)/python_include/numpy/ndarrayobject.h" && cp -f "/usr/include/python3.5m/numpy/ndarraytypes.h" "$(@D)/python_include/numpy/ndarraytypes.h" && cp -f "/usr/include/python3.5m/numpy/noprefix.h" "$(@D)/python_include/numpy/noprefix.h" && cp -f "/usr/include/python3.5m/numpy/npy_1_7_deprecated_api.h" "$(@D)/python_include/numpy/npy_1_7_deprecated_api.h" && cp -f "/usr/include/python3.5m/numpy/npy_3kcompat.h" "$(@D)/python_include/numpy/npy_3kcompat.h" && cp -f "/usr/include/python3.5m/numpy/npy_common.h" "$(@D)/python_include/numpy/npy_common.h" && cp -f "/usr/include/python3.5m/numpy/npy_cpu.h" "$(@D)/python_include/numpy/npy_cpu.h" && cp -f "/usr/include/python3.5m/numpy/npy_endian.h" "$(@D)/python_include/numpy/npy_endian.h" && cp -f "/usr/include/python3.5m/numpy/npy_interrupt.h" "$(@D)/python_include/numpy/npy_interrupt.h" && cp -f "/usr/include/python3.5m/numpy/npy_math.h" "$(@D)/python_include/numpy/npy_math.h" && cp -f "/usr/include/python3.5m/numpy/npy_no_deprecated_api.h" "$(@D)/python_include/numpy/npy_no_deprecated_api.h" && cp -f "/usr/include/python3.5m/numpy/npy_os.h" "$(@D)/python_include/numpy/npy_os.h" && cp -f "/usr/include/python3.5m/numpy/numpyconfig.h" "$(@D)/python_include/numpy/numpyconfig.h" && cp -f "/usr/include/python3.5m/numpy/old_defines.h" "$(@D)/python_include/numpy/old_defines.h" && cp -f "/usr/include/python3.5m/numpy/oldnumeric.h" "$(@D)/python_include/numpy/oldnumeric.h" && cp -f "/usr/include/python3.5m/numpy/ufunc_api.txt" "$(@D)/python_include/numpy/ufunc_api.txt" && cp -f "/usr/include/python3.5m/numpy/ufuncobject.h" "$(@D)/python_include/numpy/ufuncobject.h" && cp -f "/usr/include/python3.5m/numpy/utils.h" "$(@D)/python_include/numpy/utils.h" && cp -f "/usr/include/python3.5m/object.h" "$(@D)/python_include/object.h" && cp -f "/usr/include/python3.5m/objimpl.h" "$(@D)/python_include/objimpl.h" && cp -f "/usr/include/python3.5m/odictobject.h" "$(@D)/python_include/odictobject.h" && cp -f "/usr/include/python3.5m/opcode.h" "$(@D)/python_include/opcode.h" && cp -f "/usr/include/python3.5m/osdefs.h" "$(@D)/python_include/osdefs.h" && cp -f "/usr/include/python3.5m/parsetok.h" "$(@D)/python_include/parsetok.h" && cp -f "/usr/include/python3.5m/patchlevel.h" "$(@D)/python_include/patchlevel.h" && cp -f "/usr/include/python3.5m/pgen.h" "$(@D)/python_include/pgen.h" && cp -f "/usr/include/python3.5m/pgenheaders.h" "$(@D)/python_include/pgenheaders.h" && cp -f "/usr/include/python3.5m/py_curses.h" "$(@D)/python_include/py_curses.h" && cp -f "/usr/include/python3.5m/pyarena.h" "$(@D)/python_include/pyarena.h" && cp -f "/usr/include/python3.5m/pyatomic.h" "$(@D)/python_include/pyatomic.h" && cp -f "/usr/include/python3.5m/pycapsule.h" "$(@D)/python_include/pycapsule.h" && cp -f "/usr/include/python3.5m/pyconfig.h" "$(@D)/python_include/pyconfig.h" && cp -f "/usr/include/python3.5m/pyctype.h" "$(@D)/python_include/pyctype.h" && cp -f "/usr/include/python3.5m/pydebug.h" "$(@D)/python_include/pydebug.h" && cp -f "/usr/include/python3.5m/pyerrors.h" "$(@D)/python_include/pyerrors.h" && cp -f "/usr/include/python3.5m/pyexpat.h" "$(@D)/python_include/pyexpat.h" && cp -f "/usr/include/python3.5m/pyfpe.h" "$(@D)/python_include/pyfpe.h" && cp -f "/usr/include/python3.5m/pygetopt.h" "$(@D)/python_include/pygetopt.h" && cp -f "/usr/include/python3.5m/pyhash.h" "$(@D)/python_include/pyhash.h" && cp -f "/usr/include/python3.5m/pylifecycle.h" "$(@D)/python_include/pylifecycle.h" && cp -f "/usr/include/python3.5m/pymacconfig.h" "$(@D)/python_include/pymacconfig.h" && cp -f "/usr/include/python3.5m/pymacro.h" "$(@D)/python_include/pymacro.h" && cp -f "/usr/include/python3.5m/pymath.h" "$(@D)/python_include/pymath.h" && cp -f "/usr/include/python3.5m/pymem.h" "$(@D)/python_include/pymem.h" && cp -f "/usr/include/python3.5m/pyport.h" "$(@D)/python_include/pyport.h" && cp -f "/usr/include/python3.5m/pystate.h" "$(@D)/python_include/pystate.h" && cp -f "/usr/include/python3.5m/pystrcmp.h" "$(@D)/python_include/pystrcmp.h" && cp -f "/usr/include/python3.5m/pystrhex.h" "$(@D)/python_include/pystrhex.h" && cp -f "/usr/include/python3.5m/pystrtod.h" "$(@D)/python_include/pystrtod.h" && cp -f "/usr/include/python3.5m/pythonrun.h" "$(@D)/python_include/pythonrun.h" && cp -f "/usr/include/python3.5m/pythread.h" "$(@D)/python_include/pythread.h" && cp -f "/usr/include/python3.5m/pytime.h" "$(@D)/python_include/pytime.h" && cp -f "/usr/include/python3.5m/rangeobject.h" "$(@D)/python_include/rangeobject.h" && cp -f "/usr/include/python3.5m/setobject.h" "$(@D)/python_include/setobject.h" && cp -f "/usr/include/python3.5m/sliceobject.h" "$(@D)/python_include/sliceobject.h" && cp -f "/usr/include/python3.5m/structmember.h" "$(@D)/python_include/structmember.h" && cp -f "/usr/include/python3.5m/structseq.h" "$(@D)/python_include/structseq.h" && cp -f "/usr/include/python3.5m/symtable.h" "$(@D)/python_include/symtable.h" && cp -f "/usr/include/python3.5m/sysmodule.h" "$(@D)/python_include/sysmodule.h" && cp -f "/usr/include/python3.5m/token.h" "$(@D)/python_include/token.h" && cp -f "/usr/include/python3.5m/traceback.h" "$(@D)/python_include/traceback.h" && cp -f "/usr/include/python3.5m/tupleobject.h" "$(@D)/python_include/tupleobject.h" && cp -f "/usr/include/python3.5m/typeslots.h" "$(@D)/python_include/typeslots.h" && cp -f "/usr/include/python3.5m/ucnhash.h" "$(@D)/python_include/ucnhash.h" && cp -f "/usr/include/python3.5m/unicodeobject.h" "$(@D)/python_include/unicodeobject.h" && cp -f "/usr/include/python3.5m/warnings.h" "$(@D)/python_include/warnings.h" && cp -f "/usr/include/python3.5m/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 -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/__multiarray_api.h" "$(@D)/numpy_include/numpy/__multiarray_api.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/__ufunc_api.h" "$(@D)/numpy_include/numpy/__ufunc_api.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/_neighborhood_iterator_imp.h" "$(@D)/numpy_include/numpy/_neighborhood_iterator_imp.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/_numpyconfig.h" "$(@D)/numpy_include/numpy/_numpyconfig.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/arrayobject.h" "$(@D)/numpy_include/numpy/arrayobject.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/arrayscalars.h" "$(@D)/numpy_include/numpy/arrayscalars.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/halffloat.h" "$(@D)/numpy_include/numpy/halffloat.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/multiarray_api.txt" "$(@D)/numpy_include/numpy/multiarray_api.txt" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/ndarrayobject.h" "$(@D)/numpy_include/numpy/ndarrayobject.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/ndarraytypes.h" "$(@D)/numpy_include/numpy/ndarraytypes.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/noprefix.h" "$(@D)/numpy_include/numpy/noprefix.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h" "$(@D)/numpy_include/numpy/npy_1_7_deprecated_api.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/npy_3kcompat.h" "$(@D)/numpy_include/numpy/npy_3kcompat.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/npy_common.h" "$(@D)/numpy_include/numpy/npy_common.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/npy_cpu.h" "$(@D)/numpy_include/numpy/npy_cpu.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/npy_endian.h" "$(@D)/numpy_include/numpy/npy_endian.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/npy_interrupt.h" "$(@D)/numpy_include/numpy/npy_interrupt.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/npy_math.h" "$(@D)/numpy_include/numpy/npy_math.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/npy_no_deprecated_api.h" "$(@D)/numpy_include/numpy/npy_no_deprecated_api.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/npy_os.h" "$(@D)/numpy_include/numpy/npy_os.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/numpyconfig.h" "$(@D)/numpy_include/numpy/numpyconfig.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/old_defines.h" "$(@D)/numpy_include/numpy/old_defines.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/oldnumeric.h" "$(@D)/numpy_include/numpy/oldnumeric.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/ufunc_api.txt" "$(@D)/numpy_include/numpy/ufunc_api.txt" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/ufuncobject.h" "$(@D)/numpy_include/numpy/ufuncobject.h" && cp -f "/usr/lib/python3/dist-packages/numpy/core/include/numpy/utils.h" "$(@D)/numpy_include/numpy/utils.h" + """, +) diff --git a/third_party/toolchains/preconfig/ubuntu16.04/py3/WORKSPACE b/third_party/toolchains/preconfig/ubuntu16.04/py3/WORKSPACE new file mode 100644 index 0000000000..1d298fefa3 --- /dev/null +++ b/third_party/toolchains/preconfig/ubuntu16.04/py3/WORKSPACE @@ -0,0 +1,2 @@ +# DO NOT EDIT: automatically generated WORKSPACE file for python_configure rule +workspace(name = "local_config_python") -- GitLab From 1c7de3473c6b9eb6c5b3bd63ee75c203f7a5296c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 6 Jan 2019 13:52:15 -0800 Subject: [PATCH 0251/2345] Fix typo; remove extra space. PiperOrigin-RevId: 228076497 --- tensorflow/contrib/training/python/training/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/training/python/training/training.py b/tensorflow/contrib/training/python/training/training.py index c272a2ac14..fc6e38ab4a 100644 --- a/tensorflow/contrib/training/python/training/training.py +++ b/tensorflow/contrib/training/python/training/training.py @@ -419,7 +419,7 @@ def create_train_op(total_loss, update_ops = set(update_ops) if not global_update_ops.issubset(update_ops): logging.warning('update_ops in create_train_op does not contain all the ' - ' update_ops in GraphKeys.UPDATE_OPS') + 'update_ops in GraphKeys.UPDATE_OPS') # Make sure update_ops are computed before total_loss. if update_ops: -- GitLab From ebd5477f13c8d9585788b9721f821095fca7352e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 6 Jan 2019 16:21:00 -0800 Subject: [PATCH 0252/2345] Make TF_DeleteServer check for __ANDROID__, too. This should avoid a Wdelete-incomplete warning. PiperOrigin-RevId: 228084360 --- tensorflow/c/c_api.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tensorflow/c/c_api.cc b/tensorflow/c/c_api.cc index 9580215a31..9f2f83920c 100644 --- a/tensorflow/c/c_api.cc +++ b/tensorflow/c/c_api.cc @@ -2881,6 +2881,9 @@ const char* TF_ServerTarget(TF_Server* server) { #endif } -void TF_DeleteServer(TF_Server* server) { delete server; } - +void TF_DeleteServer(TF_Server* server) { +#ifndef __ANDROID__ + delete server; +#endif +} } // end extern "C" -- GitLab From 38c91321421694432117b077294df43aa31d1193 Mon Sep 17 00:00:00 2001 From: Bixia Zheng Date: Sun, 6 Jan 2019 16:30:18 -0800 Subject: [PATCH 0253/2345] [XLA:GPU] Selectively exclude 0-2-1 transposed input parameters from the shared memory transpose implementation. Previously, we either use the shared memory tranpose implementation for all the 0-2-1 tranposed input parameters in a fusion or not to use the shared memory tranpose implement for any input parameters, if the kernel contains instructions such as kReverse and kGather which can make the shared memory tranpose implemetation unsafe. There are two problems in such an approach. First, the set of instructions that can make the shared memory tranpose implementation unsafe is more than the aforementioned two instructions. Second, even though a fusion contains such instructions, it may still be safe to use shared memory tranpose to implement some, if not all, 0-2-1 tranposed input parameters that are not involved in the computation of such instructions. This change adds an analysis to inspect the transitive users of each 0-2-1 tranposed input parameter to decide whether it is safe to use the shared memory tranpose implementation for the parameter. Add two test cases. PiperOrigin-RevId: 228084822 --- .../xla/service/gpu/ir_emitter_unnested.cc | 118 +++++++++++++++--- .../gpu/tests/gpu_kernel_tiling_test.cc | 78 ++++++++++-- 2 files changed, 170 insertions(+), 26 deletions(-) diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc index 923b3d5945..f190af921e 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc @@ -2810,7 +2810,8 @@ void IrEmitterUnnested::EmitBlock(const TileGenerator& emit_one_tile, // unnested_hlo: The unnested hlo instruction for which the kernel is generated. // Currently, these hlo instructions are supported: kLoop fusion, kCopy. // tiled_param_ids: The IDs for the parameters that are 0-2-1 transpose of -// other tensors with the same dimensions and need to be tiled and tranposed. +// other tensors with the same dimensions and are safe to be tranposed via +// the shared memory tranpose implementation. // mapping_scheme: The tiling scheme to use. // kernel_generator: Contains function objects for code generation, such as // element generator, block prologue and epilogue generators. @@ -2989,7 +2990,10 @@ LaunchDimensions IrEmitterUnnested::EmitKernel( // Emits a kernel for the given hlo instruction using a tiled 0-2-1 transpose // algorithm to improve the memory access patterns for the input parameters -// with a shape that is a 0-2-1 transpose of the output tensor shape. +// with a shape that is a 0-2-1 transpose of the output tensor shape. The caller +// is responsible for making sure that it is safe to apply the shared memory +// tranpose on the input parameters. +// // // For the purpose of tiling, the output tensors have a logical shape of three // components 0-2-1 while the relevant input parameters have a logical shape @@ -3040,26 +3044,99 @@ LaunchDimensions IrEmitterUnnested::EmitHlo021Tile( } namespace { -// Returns true to indicate it is safe to use the tile based shared memory -// transpose implementation to implement the kernel for the instruction. +// A recursive function to inspect the users of a parameter to determine +// whether it's safe for a parameter to participate in a shared-memory +// transpose. // -// An instruction is not safe for such an implementation if it can change the -// element order of a tensor without changing the dimension of the tensor, and -// the instruction has a corresponding elemental_ir_emitter. -bool IsInstructionSafeForTileBasedTranspose(const HloInstruction* hlo) { - auto is_safe_for_tile_based_transpose = [&](const HloInstruction* instr) { - HloOpcode opcode = instr->opcode(); - CHECK_NE(opcode, HloOpcode::kFusion); - return (opcode != HloOpcode::kReverse && opcode != HloOpcode::kGather); - }; +// Consider a fusion parameter P for which we might want to use a shmem +// transpose. If we do, we use a GPU thread block to preload a tile of P with +// indices [z, y..y+31, x..x+31] to compute an output tile with the same indices +// cooperatively, where z, y, x are the indices for the normalized input/output +// tensor (see the document for FindTranspose021 for the definition of +// normalized tensor for 0-2-1 transpose). This shmem transpose implementation +// requires that the computation of the output tile only read elements within +// the preload tile. If this is not true, we can't use a shmem transpose for P. +// +// If the computation of output element [z, y, x] only requires the element of +// P with the same indices, the shmem tranpose implementation can be applied +// to P safely. This is a sufficient but not necessary condition. We check all +// the transitive users of P to see if we can find a user that may cause an +// exception to the situation. If such a user is not found, we conclude that P +// is safe for shmem transpose. +// +// This is trivially true for elementwise operations and some "data-movement" +// ops like kTuple. However, it's not true for operations that can change the +// dimensions of the inputs (e.g. pad, slice) and bitcast operation. +// For example: +// +// fused_computation { +// param_0 = f32[64,64]{1,0} parameter(0) +// ROOT bitcast = f32[64,64]{0,1} bitcast(param_0) +// } +// The output element at logical address [0, 63] depends on the input element +// at logical address [63, 0], which would not be within the shared-memory +// block. +// +// TODO(bixia): In order to extend this for kInput fusion, that is reduction +// with tranpose, we only need to end the use-chain checking with the input of +// a reduce operations. In this case, the above description on "output" apply +// to the result of such a use-chain, which provides the input to the reduce +// operation. +bool IsInstructionSafeForShmemTranspose(const HloInstruction* hlo) { + if (hlo->IsElementwise()) { + return absl::c_all_of(hlo->users(), [&](const HloInstruction* user) { + return IsInstructionSafeForShmemTranspose(user); + }); + } - if (hlo->opcode() == HloOpcode::kFusion) { - return absl::c_all_of(hlo->fused_instructions_computation()->instructions(), - is_safe_for_tile_based_transpose); + switch (hlo->opcode()) { + // Non-elementwise instructions that don't cause the shmem transpose + // to be unsafe, including the instructions that don't currently fuse. + case HloOpcode::kGetDimensionSize: + // The result of the operation doesn't rely on the content of the + // tensor. As such, there is no need to further inspect its users. + return true; + case HloOpcode::kGetTupleElement: + case HloOpcode::kMap: + case HloOpcode::kParameter: + case HloOpcode::kTuple: + case HloOpcode::kTupleSelect: + return absl::c_all_of(hlo->users(), [&](const HloInstruction* user) { + return IsInstructionSafeForShmemTranspose(user); + }); + + default: + return false; } +} - return is_safe_for_tile_based_transpose(hlo); +// Given a group of input parameters that are 0-2-1 tranpose of the outputs of +// a fusion kernel, returns the input parameters that are safe for the shared +// memory tranpose implementation. +// +// When a tile based shared memory transpose is used to implement an input with +// 0-2-1 transpose, we preload a tile of the input elements +// [z, y..y+31, x..x+31] to compute the output tile elements of the same +// indices. Preloading the input tile this way is only safe when the computation +// of the output tile elements do not need any input element outside the +// preloaded tile. We inspect all the transitive users of the input parameter +// up to the fusion root instruction to see if we can find any instruction +// that can make preloading the input tile unsafe. +std::vector FilterInputsForShmemTranspose(const HloInstruction* fusion, + std::vector input_ids) { + std::vector filtered_input_ids; + for (int64 i = 0; i < input_ids.size(); ++i) { + const HloInstruction* input = fusion->fused_parameter(input_ids[i]); + if (IsInstructionSafeForShmemTranspose(input)) { + filtered_input_ids.push_back(input_ids[i]); + } else { + VLOG(10) << "Input not safe for shmem transpose " << input->ToString() + << "\n"; + } + } + return filtered_input_ids; } + } // namespace bool IrEmitterUnnested::CheckAndEmitHloWithTile021(HloInstruction* hlo) { @@ -3106,8 +3183,11 @@ bool IrEmitterUnnested::CheckAndEmitHloWithTile021(HloInstruction* hlo) { return false; } - if (!IsInstructionSafeForTileBasedTranspose(hlo)) { - return false; + if (opcode == HloOpcode::kFusion) { + params_012 = FilterInputsForShmemTranspose(hlo, params_012); + if (params_012.empty()) { + return false; + } } // Each of our shared memory tiles has 32*33 elements (so ~4kb, if the diff --git a/tensorflow/compiler/xla/service/gpu/tests/gpu_kernel_tiling_test.cc b/tensorflow/compiler/xla/service/gpu/tests/gpu_kernel_tiling_test.cc index a302b582ed..8004ebdc8e 100644 --- a/tensorflow/compiler/xla/service/gpu/tests/gpu_kernel_tiling_test.cc +++ b/tensorflow/compiler/xla/service/gpu/tests/gpu_kernel_tiling_test.cc @@ -65,7 +65,7 @@ TEST_F(GpuKernelTilingTest, UnnestedTransposeWithProperDimensionsTiled) { CompileAndVerifyIr(std::move(hlo_module), R"( ; CHECK-LABEL: define void @copy -; CHECK: tail call void @llvm.nvvm.barrier0() +; CHECK: call void @llvm.nvvm.barrier0() ; CHECK: } )", /*match_optimized_ir=*/true); @@ -91,7 +91,7 @@ TEST_F(GpuKernelTilingTest, UnnestedTransposeWithSmallDimensionsNotTiled) { CompileAndVerifyIr(std::move(hlo_module), R"( ; CHECK-LABEL: define void @copy -; CHECK-NOT: tail call void @llvm.nvvm.barrier0() +; CHECK-NOT: call void @llvm.nvvm.barrier0() ; CHECK: } )", /*match_optimized_ir=*/true); @@ -118,7 +118,7 @@ TEST_F(GpuKernelTilingTest, SimpleFusionWithTransposeTiled) { CompileAndVerifyIr(std::move(hlo_module), R"( ; CHECK-LABEL: define void @fusion -; CHECK: tail call void @llvm.nvvm.barrier0() +; CHECK: call void @llvm.nvvm.barrier0() ; CHECK: } )", /*match_optimized_ir=*/true); @@ -152,7 +152,7 @@ TEST_F(GpuKernelTilingTest, MultipleOutputFusionWithOnePossibleTransposeTiled) { CompileAndVerifyIr(std::move(hlo_module), R"( ; CHECK-LABEL: define void @fusion -; CHECK: tail call void @llvm.nvvm.barrier0() +; CHECK: call void @llvm.nvvm.barrier0() ; CHECK: } )", /*match_optimized_ir=*/true); @@ -187,13 +187,13 @@ TEST_F(GpuKernelTilingTest, CompileAndVerifyIr(std::move(hlo_module), R"( ; CHECK-LABEL: define void @fusion -; CHECK-NOT: tail call void @llvm.nvvm.barrier0() +; CHECK-NOT: call void @llvm.nvvm.barrier0() ; CHECK: } )", /*match_optimized_ir=*/true); } -TEST_F(GpuKernelTilingTest, FusionTransposeWithReverseNotTiled) { +TEST_F(GpuKernelTilingTest, TransposedInputWithUserReverseNotTiled) { const char *const kHloString = R"( HloModule FusionTransposeWithReverseNotTiled fused_computation.1 { @@ -214,12 +214,76 @@ TEST_F(GpuKernelTilingTest, FusionTransposeWithReverseNotTiled) { CompileAndVerifyIr(std::move(hlo_module), R"( ; CHECK-LABEL: define void @fusion -; CHECK-NOT: tail call void @llvm.nvvm.barrier0() +; CHECK-NOT: call void @llvm.nvvm.barrier0() ; CHECK: } )", /*match_optimized_ir=*/true); } +TEST_F(GpuKernelTilingTest, TransposedInputWithUserBitcastNotTiled) { + const char *const kHloString = R"( + HloModule TransposedInputWithUserBitcast + + fused_computation { + param_0 = f32[20,20]{1,0} parameter(0) + ROOT bitcast = f32[20,20]{0,1} bitcast(param_0) + } + + ENTRY kernel_entry { + parameter.0 = f32[20,20]{1,0} parameter(0) + ROOT fusion = f32[20,20]{0,1} fusion(parameter.0), + kind=kLoop, calls=fused_computation + })"; + + // Check that a call to llvm.nvvm.barrier0 is not generated. + auto hlo_module = + ParseHloString(kHloString, ConfigWithoutLayoutAssignment()).ValueOrDie(); + CompileAndVerifyIr(std::move(hlo_module), + R"( +; CHECK-LABEL: define void @fusion +; CHECK-NOT: call void @llvm.nvvm.barrier0() +; CHECK: } +)", + /*match_optimized_ir=*/true); + + // Check that the kernel runs correctly. + EXPECT_TRUE(RunAndCompareNoHloPasses(kHloString, ErrorSpec{0.0})); +} + +TEST_F(GpuKernelTilingTest, TransposedInputWithoutUnsafeUseTiled) { + const char *const kHloString = R"( + HloModule TwoTransposedInputs + + fused_computation { + param_0 = f32[64,64]{1,0} parameter(0) + param_1 = f32[64,64]{1,0} parameter(1) + bitcast = f32[64,64]{0,1} bitcast(param_0) + copy = f32[64,64]{0,1} copy(param_1) + ROOT tuple = (f32[64,64]{0,1}, f32[64,64]{0,1}) tuple(bitcast, copy) + } + + ENTRY kernel_entry { + parameter.0 = f32[64,64]{1,0} parameter(0) + parameter.1 = f32[64,64]{1,0} parameter(1) + ROOT fusion = (f32[64,64]{0,1}, f32[64,64]{0,1}) + fusion(parameter.0, parameter.1), + kind=kLoop, calls=fused_computation + })"; + + // Check that a call to llvm.nvvm.barrier0 is not generated. + auto hlo_module = + ParseHloString(kHloString, ConfigWithoutLayoutAssignment()).ValueOrDie(); + CompileAndVerifyIr(std::move(hlo_module), + R"( +; CHECK-LABEL: define void @fusion +; CHECK: call void @llvm.nvvm.barrier0() +; CHECK: } +)", + /*match_optimized_ir=*/true); + // Check that the kernel runs correctly. + EXPECT_TRUE(RunAndCompareNoHloPasses(kHloString, ErrorSpec{0.0})); +} + } // namespace } // namespace gpu } // namespace xla -- GitLab From 187a8cba72aae56ed8dfbe5c2d5779664b7683f2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 6 Jan 2019 19:22:26 -0800 Subject: [PATCH 0254/2345] Change TensorFlow's resize_uninitialized wrapper to call libc++'s __resize_default_init extension when available. PiperOrigin-RevId: 228095086 --- tensorflow/core/BUILD | 1 + tensorflow/core/lib/gtl/stl_util.h | 37 +++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 7c9decbd09..5e6c8d4dd8 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -2193,6 +2193,7 @@ cc_library( ], }), deps = tf_additional_lib_deps() + [ + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", "//third_party/eigen3", "@com_google_absl//absl/base:core_headers", diff --git a/tensorflow/core/lib/gtl/stl_util.h b/tensorflow/core/lib/gtl/stl_util.h index ffeca4e88a..853a290bf6 100644 --- a/tensorflow/core/lib/gtl/stl_util.h +++ b/tensorflow/core/lib/gtl/stl_util.h @@ -23,9 +23,12 @@ limitations under the License. #include #include #include +#include #include #include +#include "absl/meta/type_traits.h" + namespace tensorflow { namespace gtl { @@ -48,16 +51,38 @@ inline const T* vector_as_array(const std::vector* v) { return v->data(); } +namespace gtl_internal { + +// HasMember is true_type or false_type, depending on whether or not +// T has a __resize_default_init member. Resize will call the +// __resize_default_init member if it exists, and will call the resize +// member otherwise. +template +struct ResizeUninitializedTraits { + using HasMember = std::false_type; + static void Resize(string_type* s, size_t new_size) { s->resize(new_size); } +}; + +// __resize_default_init is provided by libc++ >= 8.0 and by Google's internal +// ::string implementation. +template +struct ResizeUninitializedTraits< + string_type, absl::void_t() + .__resize_default_init(237))> > { + using HasMember = std::true_type; + static void Resize(string_type* s, size_t new_size) { + s->__resize_default_init(new_size); + } +}; + +} // namespace gtl_internal + // Like str->resize(new_size), except any new characters added to "*str" as a // result of resizing may be left uninitialized, rather than being filled with // '0' bytes. Typically used when code is then going to overwrite the backing -// store of the string with known data. Uses a Google extension to ::string. +// store of the string with known data. inline void STLStringResizeUninitialized(string* s, size_t new_size) { -#if __google_stl_resize_uninitialized_string - s->resize_uninitialized(new_size); -#else - s->resize(new_size); -#endif + gtl_internal::ResizeUninitializedTraits::Resize(s, new_size); } // Calls delete (non-array version) on the SECOND item (pointer) in each pair in -- GitLab From b8f2207ead5c6ce6e7e98479a88a08e4977aa83d Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Sun, 6 Jan 2019 23:46:22 -0800 Subject: [PATCH 0255/2345] Increase length of stack trace for original variable definition. PiperOrigin-RevId: 228112246 --- tensorflow/python/ops/variable_scope.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/variable_scope.py b/tensorflow/python/ops/variable_scope.py index 64cdde3d85..35c00778ae 100644 --- a/tensorflow/python/ops/variable_scope.py +++ b/tensorflow/python/ops/variable_scope.py @@ -842,8 +842,11 @@ class _VariableStore(object): if isinstance(var, resource_variable_ops.ResourceVariable): raise ValueError(err_msg) tb = var.op.traceback[::-1] - # Throw away internal tf entries and only take a few lines. - tb = [x for x in tb if "tensorflow/python" not in x[0]][:3] + # Throw away internal tf entries and only take a few lines. In some + # cases the traceback can be longer (e.g. if someone uses factory + # functions to create variables) so we take more than needed in the + # default case. + tb = [x for x in tb if "tensorflow/python" not in x[0]][:5] raise ValueError("%s Originally defined at:\n\n%s" % (err_msg, "".join( traceback.format_list(tb)))) found_var = self._vars[name] -- GitLab From 57d450732007e33db361ad8f16a1e8655a55dc39 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 00:42:55 -0800 Subject: [PATCH 0256/2345] Internal change. PiperOrigin-RevId: 228117862 --- tensorflow/contrib/gan/BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/contrib/gan/BUILD b/tensorflow/contrib/gan/BUILD index 08df38ad27..db0868fb2c 100644 --- a/tensorflow/contrib/gan/BUILD +++ b/tensorflow/contrib/gan/BUILD @@ -2,7 +2,6 @@ load("//tensorflow:tensorflow.bzl", "py_test") package(default_visibility = [ - "//learning/brain/contrib/tfgan:__subpackages__", "//tensorflow:__subpackages__", ]) -- GitLab From 134b9e580f639005f8e5986c53884183d6d9bde0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 01:03:09 -0800 Subject: [PATCH 0257/2345] compat: Update forward compatibility horizon to 2019-01-07 PiperOrigin-RevId: 228120298 --- 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 b779c80aa7..dbad494e40 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, 1, 6) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 7) @tf_export("compat.forward_compatible") -- GitLab From 56ab6a2a7b224cb4a30e545035639ab84d82c58f Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Mon, 7 Jan 2019 01:31:17 -0800 Subject: [PATCH 0258/2345] Fix Template.variables for templates with empty name. PiperOrigin-RevId: 228123554 --- .../python/kernel_tests/template_test.py | 15 ++++++++++ tensorflow/python/ops/template.py | 30 ++++--------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/tensorflow/python/kernel_tests/template_test.py b/tensorflow/python/kernel_tests/template_test.py index 3b2a56bd1f..f587a7ec43 100644 --- a/tensorflow/python/kernel_tests/template_test.py +++ b/tensorflow/python/kernel_tests/template_test.py @@ -160,6 +160,21 @@ class TemplateTest(test.TestCase): self.assertEqual(1, len(result)) self.assertNotEqual(len(first), len(result)) + @test_util.run_in_graph_and_eager_modes + def test_template_with_empty_name(self): + tpl = template.make_template("", variable_scoped_function) + with variable_scope.variable_scope("outer"): + x = variable_scope.get_variable("x", []) + v = tpl() + self.assertEqual("outer/", tpl.variable_scope_name) + self.assertEqual("outer//dummy:0", v.name) + if context.executing_eagerly(): + # In eager mode `x` is not visible to the template since the template does + # not rely on global collections. + self.assertEqual([v], tpl.variables) + else: + self.assertEqual([x, v], tpl.variables) + @test_util.run_in_graph_and_eager_modes def test_template_with_name(self): tmpl1 = template.make_template("s1", variable_scoped_function) diff --git a/tensorflow/python/ops/template.py b/tensorflow/python/ops/template.py index 7c2d3be338..4eaa16a22c 100644 --- a/tensorflow/python/ops/template.py +++ b/tensorflow/python/ops/template.py @@ -387,8 +387,11 @@ class Template(checkpointable.CheckpointableBase): """Returns the variable scope name created by this Template.""" if self._variable_scope: name = self._variable_scope.name - # To prevent partial matches on the scope_name, we add '/' at the end. - return name if name[-1] == "/" else name + "/" + if not name or name[-1] == "/": + return name + else: + # To prevent partial matches on the scope_name, we add '/' at the end. + return name + "/" @property def variables(self): @@ -646,29 +649,6 @@ class EagerTemplate(Template): with self._template_store.as_default(): return self._call_func(args, kwargs) - @property - def name(self): - """Returns the name given to this Template.""" - return self._name - - @property - def func(self): - """Returns the func given to this Template.""" - return self._func - - @property - def variable_scope(self): - """Returns the variable scope object created by this Template.""" - return self._variable_scope - - @property - def variable_scope_name(self): - """Returns the variable scope name created by this Template.""" - if self._variable_scope: - name = self._variable_scope.name - # To prevent partial matches on the scope_name, we add '/' at the end. - return name if name[-1] == "/" else name + "/" - @property def variables(self): """Returns the list of variables created by the Template.""" -- GitLab From 48d0e4d813b914fc480c51de5c48903b9bcc4cc7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 01:46:24 -0800 Subject: [PATCH 0259/2345] Fixing pasta by not stripping the subdirectory. PiperOrigin-RevId: 228125275 --- third_party/pasta/BUILD.bazel | 5 +++-- third_party/pasta/build_defs.bzl | 12 ++++++++++++ third_party/pasta/workspace.bzl | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 third_party/pasta/build_defs.bzl diff --git a/third_party/pasta/BUILD.bazel b/third_party/pasta/BUILD.bazel index 6be33511e4..ade681b606 100644 --- a/third_party/pasta/BUILD.bazel +++ b/third_party/pasta/BUILD.bazel @@ -1,5 +1,6 @@ # Description: # AST-based python refactoring. +load("@//third_party/pasta:build_defs.bzl", "copy_srcs") licenses(["notice"]) # Apache2 @@ -7,7 +8,7 @@ exports_files(["LICENSE"]) py_library( name = "pasta", - srcs = [ + srcs = copy_srcs([ "__init__.py", "augment/__init__.py", "augment/errors.py", @@ -23,7 +24,7 @@ py_library( "base/scope.py", "base/test_utils.py", "base/token_generator.py", - ], + ]), srcs_version = "PY2AND3", visibility = ["//visibility:public"], ) diff --git a/third_party/pasta/build_defs.bzl b/third_party/pasta/build_defs.bzl new file mode 100644 index 0000000000..0a5316de40 --- /dev/null +++ b/third_party/pasta/build_defs.bzl @@ -0,0 +1,12 @@ +"""Skylark makros for building pasta.""" + +def copy_srcs(srcs): + """Copies srcs from 'pasta' to parent directory.""" + for src in srcs: + native.genrule( + name = src.replace(".", "_"), + srcs = ["pasta/" + src], + outs = [src], + cmd = "mkdir -p $$(dirname $@); cp $< $@", + ) + return srcs diff --git a/third_party/pasta/workspace.bzl b/third_party/pasta/workspace.bzl index 063bdb7b98..e46cc4a45e 100644 --- a/third_party/pasta/workspace.bzl +++ b/third_party/pasta/workspace.bzl @@ -9,7 +9,7 @@ def repo(): "https://mirror.bazel.build/github.com/google/pasta/archive/c3d72cdee6fc806251949e912510444d58d7413c.tar.gz", "https://github.com/google/pasta/archive/c3d72cdee6fc806251949e912510444d58d7413c.tar.gz", ], - strip_prefix = "pasta-c3d72cdee6fc806251949e912510444d58d7413c/pasta", + strip_prefix = "pasta-c3d72cdee6fc806251949e912510444d58d7413c", sha256 = "b5905f9cecc4b28363c563f3c4cb0545288bd35f7cc72c55066e97e53befc084", build_file = "//third_party/pasta:BUILD.bazel", system_build_file = "//third_party/pasta:BUILD.system", -- GitLab From f64f7f787d3596cb7c9228f131f06c159a0ec188 Mon Sep 17 00:00:00 2001 From: Tamara Norman Date: Mon, 7 Jan 2019 02:38:36 -0800 Subject: [PATCH 0260/2345] Added option in conversion script to convert an entire directory in place when --inplace=True given as an argument PiperOrigin-RevId: 228130782 --- tensorflow/tools/compatibility/ast_edits.py | 34 ++++++++++++++++++- .../tools/compatibility/tf_upgrade_v2_main.py | 10 +++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/compatibility/ast_edits.py b/tensorflow/tools/compatibility/ast_edits.py index 0ec6c2ac8c..859fd95314 100644 --- a/tensorflow/tools/compatibility/ast_edits.py +++ b/tensorflow/tools/compatibility/ast_edits.py @@ -493,7 +493,7 @@ class ASTCodeUpgrader(object): process_errors) def process_tree(self, root_directory, output_root_directory, - copy_other_files): + copy_other_files, in_place): """Processes upgrades on an entire tree of python files in place. Note that only Python files. If you have custom code in other languages, @@ -503,11 +503,20 @@ 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 ofr all files, and errors """ + 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) + # make sure output directory doesn't exist if output_root_directory and os.path.exists(output_root_directory): print("Output directory %r must not already exist." % @@ -564,3 +573,26 @@ class ASTCodeUpgrader(object): os.makedirs(output_directory) shutil.copy(input_path, output_path) return file_count, report, tree_errors + + def process_tree_inplace(self, root_directory): + """Process a directory of python files in place.""" + files_to_process = [] + for dir_name, _, file_list in os.walk(root_directory): + py_files = [os.path.join(dir_name, + f) for f in file_list if f.endswith(".py")] + files_to_process += py_files + + file_count = 0 + tree_errors = [] + report = "" + report += ("=" * 80) + "\n" + report += "Input tree: %r\n" % root_directory + report += ("=" * 80) + "\n" + + for path in files_to_process: + file_count += 1 + _, l_report, l_errors = self.process_file(path, path) + tree_errors += l_errors + report += l_report + + return file_count, report, tree_errors diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py index 543d078642..870bc6f216 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py @@ -59,6 +59,14 @@ Simple usage: "copy the other files."), type=bool, default=True) + parser.add_argument( + "--inplace", + dest="in_place", + help=("If converting a whole tree of files, whether to " + "allow the conversion to be performed on the " + "files in the input tree."), + type=bool, + default=False) parser.add_argument( "--reportfile", dest="report_filename", @@ -86,7 +94,7 @@ Simple usage: "--outtree= argument is required when converting a " "file tree.") files_processed, report_text, errors = upgrade.process_tree( - args.input_tree, args.output_tree, args.copy_other_files) + args.input_tree, args.output_tree, args.copy_other_files, args.in_place) else: parser.print_help() if report_text: -- GitLab From 6612e1994c26ee4e40fab02b270bd79cdc7b0a74 Mon Sep 17 00:00:00 2001 From: Dayananda V Date: Mon, 7 Jan 2019 17:20:25 +0530 Subject: [PATCH 0261/2345] TF Android Example first time compile and run FileNotFoundException bug fix java.lang.RuntimeException: java.io.FileNotFoundException: mobilenet_v1_1.0_224_quant.tflite this error reported after application compile and run first time using Studio, this fix solves the exist problem. --- .../lite/examples/android/app/build.gradle | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tensorflow/lite/examples/android/app/build.gradle b/tensorflow/lite/examples/android/app/build.gradle index e5f5c7efd1..fe8fa4daf9 100644 --- a/tensorflow/lite/examples/android/app/build.gradle +++ b/tensorflow/lite/examples/android/app/build.gradle @@ -1,5 +1,13 @@ apply plugin: 'com.android.application' +// import DownloadModels task +project.ext.ASSET_DIR = projectDir.toString() + '/src/main/assets' +project.ext.TMP_DIR = project.buildDir.toString() + '/downloads' + +// 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" + android { compileSdkVersion 26 buildToolsVersion '26.0.2' @@ -36,14 +44,6 @@ repositories { } } -// import DownloadModels task -project.ext.ASSET_DIR = projectDir.toString() + '/src/main/assets' -project.ext.TMP_DIR = project.buildDir.toString() + '/downloads' - -// 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 { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'org.tensorflow:tensorflow-lite:0.0.0-nightly' -- GitLab From 2703d8e84aced78a05ec7ad1ea0e5f4d0fe70f9f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 07:03:10 -0800 Subject: [PATCH 0262/2345] Change //tensorflow/compiler/tf2xla/python:xla to a tf_custom_op_py_library Previously it was a simple py_library what didn't linked in the op registration code causing errors when no other dependency pulled in the actual implementations. PiperOrigin-RevId: 228158673 --- tensorflow/compiler/tf2xla/ops/BUILD | 17 ++++++++++++++++- tensorflow/compiler/tf2xla/python/BUILD | 7 ++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/tf2xla/ops/BUILD b/tensorflow/compiler/tf2xla/ops/BUILD index 4dce0a2102..7140b6a122 100644 --- a/tensorflow/compiler/tf2xla/ops/BUILD +++ b/tensorflow/compiler/tf2xla/ops/BUILD @@ -4,7 +4,11 @@ package( licenses(["notice"]) # Apache 2.0 -load("//tensorflow:tensorflow.bzl", "tf_gen_op_wrapper_py") +load( + "//tensorflow:tensorflow.bzl", + "tf_custom_op_library", + "tf_gen_op_wrapper_py", +) cc_library( name = "xla_ops", @@ -24,3 +28,14 @@ tf_gen_op_wrapper_py( ":xla_ops", ], ) + +tf_custom_op_library( + name = "_xla_ops.so", + srcs = [ + "xla_ops.cc", + ], + deps = [ + "//tensorflow/core:lib", + "@com_google_absl//absl/algorithm:container", + ], +) diff --git a/tensorflow/compiler/tf2xla/python/BUILD b/tensorflow/compiler/tf2xla/python/BUILD index fef97b98c3..9abdb04d77 100644 --- a/tensorflow/compiler/tf2xla/python/BUILD +++ b/tensorflow/compiler/tf2xla/python/BUILD @@ -15,6 +15,7 @@ load( "//tensorflow/core:platform/default/build_config.bzl", "tf_py_clif_cc", ) +load("//tensorflow:tensorflow.bzl", "tf_custom_op_py_library") tf_py_clif_cc( name = "xla_op_registry", @@ -27,9 +28,13 @@ tf_py_clif_cc( ], ) -py_library( +tf_custom_op_py_library( name = "xla", srcs = ["xla.py"], + dso = ["//tensorflow/compiler/tf2xla/ops:_xla_ops.so"], + kernels = [ + "//tensorflow/compiler/tf2xla/ops:xla_ops", + ], deps = [ "//tensorflow/compiler/tf2xla/ops:gen_xla_ops", "//tensorflow/compiler/xla:xla_data_proto_py", -- GitLab From e55ec99c9ef627d84750853f9ae83196723852d5 Mon Sep 17 00:00:00 2001 From: Adam Weiss Date: Thu, 13 Dec 2018 15:22:36 -0500 Subject: [PATCH 0263/2345] [XLA] Wrap LocalComputationBuilder::GetOrCreateLocalClient with StatusOr Prevents crashes when GPUs present, but below minimum capability level --- .../xla/python/local_computation_builder.cc | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tensorflow/compiler/xla/python/local_computation_builder.cc b/tensorflow/compiler/xla/python/local_computation_builder.cc index 5d191f5a18..6611c87673 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.cc +++ b/tensorflow/compiler/xla/python/local_computation_builder.cc @@ -101,7 +101,7 @@ int GetReplicaCount() { return g_replica_count; } -LocalClient* GetOrCreateLocalClient() { +StatusOr GetOrCreateLocalClient() { string* platform_name = GetPlatformNameString(); tensorflow::mutex_lock lock(g_local_client_mutex); if (g_local_client != nullptr) { @@ -110,7 +110,8 @@ LocalClient* GetOrCreateLocalClient() { LocalClientOptions options; options.set_platform(PlatformUtil::GetPlatform(*platform_name).ValueOrDie()); options.set_number_of_replicas(g_replica_count); - g_local_client = ClientLibrary::GetOrCreateLocalClient(options).ValueOrDie(); + TF_ASSIGN_OR_RETURN(g_local_client, + ClientLibrary::GetOrCreateLocalClient(options)); CHECK(g_local_client != nullptr); return g_local_client; } @@ -118,7 +119,7 @@ LocalClient* GetOrCreateLocalClient() { Status TransferToInfeedLocal(const Literal& literal) { VLOG(1) << "Infeeding literal without replica number; shape: " << literal.shape(); - LocalClient* client = GetOrCreateLocalClient(); + TF_ASSIGN_OR_RETURN(LocalClient * client, GetOrCreateLocalClient()); return client->TransferToInfeedLocal(literal, /*device_ordinal=*/0); } @@ -126,7 +127,7 @@ Status TransferToInfeedLocalReplica(const Literal& literal, int replica_number) { VLOG(1) << "Infeeding shape " << literal.shape() << " to replica number: " << replica_number; - LocalClient* client = GetOrCreateLocalClient(); + TF_ASSIGN_OR_RETURN(LocalClient * client, GetOrCreateLocalClient()); TF_ASSIGN_OR_RETURN(int device_ordinal, client->ReplicaNumberToDeviceOrdinal(replica_number)); return client->TransferToInfeedLocal(literal, device_ordinal); @@ -136,7 +137,7 @@ StatusOr TransferFromOutfeedLocalReplica(const Shape& shape, int replica_number) { VLOG(1) << "Outfeeding literal from replica number: " << replica_number << " shape: " << shape; - LocalClient* client = GetOrCreateLocalClient(); + TF_ASSIGN_OR_RETURN(LocalClient * client, GetOrCreateLocalClient()); TF_ASSIGN_OR_RETURN(int device_ordinal, client->ReplicaNumberToDeviceOrdinal(replica_number)); return client->TransferFromOutfeedLocal(shape, device_ordinal); @@ -153,7 +154,7 @@ static StatusOr ToBuffer(LocalClient* client, StatusOr LocalShapedBuffer::FromLiteral( const Literal& argument, const absl::optional& shape_with_layout, int replica_number) { - LocalClient* client = GetOrCreateLocalClient(); + TF_ASSIGN_OR_RETURN(LocalClient * client, GetOrCreateLocalClient()); TF_ASSIGN_OR_RETURN(int device_ordinal, client->ReplicaNumberToDeviceOrdinal(replica_number)); VLOG(1) << "Creating shaped buffer from literal on replica/ordinal: " @@ -183,7 +184,7 @@ const Shape& LocalShapedBuffer::shape() const { } StatusOr LocalShapedBuffer::ToLiteral() const { - LocalClient* client = GetOrCreateLocalClient(); + TF_ASSIGN_OR_RETURN(LocalClient * client, GetOrCreateLocalClient()); return client->ShapedBufferToLiteral(*shaped_buffer()); } @@ -319,7 +320,7 @@ CompiledLocalComputation::CompiledLocalComputation( StatusOr CompiledLocalComputation::Execute( absl::Span argument_handles) { - LocalClient* client = GetOrCreateLocalClient(); + TF_ASSIGN_OR_RETURN(LocalClient * client, GetOrCreateLocalClient()); StatusOr device_ordinal_status = client->ReplicaNumberToDeviceOrdinal(0); StatusOr result_buffer_status; if (!device_ordinal_status.ok()) { @@ -362,7 +363,7 @@ StatusOr CompiledLocalComputation::Execute( StatusOr CompiledLocalComputation::ExecutePerReplica( absl::Span> argument_handles) { - LocalClient* client = GetOrCreateLocalClient(); + TF_ASSIGN_OR_RETURN(LocalClient * client, GetOrCreateLocalClient()); const int num_replicas = GetReplicaCount(); if (argument_handles.size() != num_replicas) { @@ -535,7 +536,7 @@ StatusOr LocalComputation::Compile( argument_shape_pointers.push_back(&argument_shape); } - LocalClient* client = GetOrCreateLocalClient(); + TF_ASSIGN_OR_RETURN(LocalClient * client, GetOrCreateLocalClient()); ExecutableBuildOptions options; if (build_options != nullptr) { options = *build_options; -- GitLab From 36c3466356546f09198ce01982e6049dbf1a1afe Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 10:31:04 -0800 Subject: [PATCH 0264/2345] Merged commit includes the following changes: 228190834 by A. Unique TensorFlower: Internal change -- 228187607 by A. Unique TensorFlower: Split depthwise conv GPU code into multiple files This file was a bottleneck during compilation, often taking many minutes to compile. In local testing this change reduces the wall-clock build time for the depthwise conv GPU kernels from 163s to 105s. -- 228181904 by A. Unique TensorFlower: Fix py2 and py3 of gen_git_version.py to fix 1.13 release. Byteify function to handle py2 and py3. -- 228161227 by A. Unique TensorFlower: Create Tensorflow version of LLVM's opt. This is useful for iterating on individual passes in the OptimizationPassRegistry. This is run with a command of the form optimization_pass_runner --input_file_path=/tmp/input.pbtxt --output_file_path=/tmp/output.pbtxt --optimization_pass=NameOfGraphOptimizationPass -- PiperOrigin-RevId: 228190834 --- .../common_runtime/optimization_registry.h | 4 + tensorflow/core/kernels/BUILD | 10 +- ...v_op_gpu.cu.cc => depthwise_conv_op_gpu.h} | 25 +- .../depthwise_conv_op_gpu_double.cu.cc | 30 ++ .../kernels/depthwise_conv_op_gpu_float.cu.cc | 30 ++ .../kernels/depthwise_conv_op_gpu_half.cu.cc | 30 ++ tensorflow/opensource_only.files | 416 +++++++++--------- tensorflow/tools/git/gen_git_source.py | 4 +- tensorflow/tools/optimization/BUILD | 52 +++ .../gpu_optimization_pass_runner_main.cc | 60 +++ .../optimization/optimization_pass_runner.cc | 167 +++++++ .../optimization/optimization_pass_runner.h | 61 +++ 12 files changed, 661 insertions(+), 228 deletions(-) rename tensorflow/core/kernels/{depthwise_conv_op_gpu.cu.cc => depthwise_conv_op_gpu.h} (98%) create mode 100644 tensorflow/core/kernels/depthwise_conv_op_gpu_double.cu.cc create mode 100644 tensorflow/core/kernels/depthwise_conv_op_gpu_float.cu.cc create mode 100644 tensorflow/core/kernels/depthwise_conv_op_gpu_half.cu.cc create mode 100644 tensorflow/tools/optimization/BUILD create mode 100644 tensorflow/tools/optimization/gpu_optimization_pass_runner_main.cc create mode 100644 tensorflow/tools/optimization/optimization_pass_runner.cc create mode 100644 tensorflow/tools/optimization/optimization_pass_runner.h diff --git a/tensorflow/core/common_runtime/optimization_registry.h b/tensorflow/core/common_runtime/optimization_registry.h index 19a76529bb..0e31f389aa 100644 --- a/tensorflow/core/common_runtime/optimization_registry.h +++ b/tensorflow/core/common_runtime/optimization_registry.h @@ -95,6 +95,10 @@ class OptimizationPassRegistry { void Register(Grouping grouping, int phase, std::unique_ptr pass); + const std::map& groups() { + return groups_; + } + // Run all passes in grouping, ordered by phase, with the same // options. Status RunGrouping(Grouping grouping, diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index c72d04d3af..3891caf5d3 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -3695,7 +3695,15 @@ tf_kernel_library( tf_kernel_library( name = "depthwise_conv_op", - prefix = "depthwise_conv_op", + srcs = ["depthwise_conv_op.cc"], + hdrs = ["depthwise_conv_op.h"], + gpu_srcs = [ + "depthwise_conv_op.h", + "depthwise_conv_op_gpu.h", + "depthwise_conv_op_gpu_double.cu.cc", + "depthwise_conv_op_gpu_float.cu.cc", + "depthwise_conv_op_gpu_half.cu.cc", + ], deps = [ ":bounds_check", ":conv_ops", diff --git a/tensorflow/core/kernels/depthwise_conv_op_gpu.cu.cc b/tensorflow/core/kernels/depthwise_conv_op_gpu.h similarity index 98% rename from tensorflow/core/kernels/depthwise_conv_op_gpu.cu.cc rename to tensorflow/core/kernels/depthwise_conv_op_gpu.h index e811968d27..098853e684 100644 --- a/tensorflow/core/kernels/depthwise_conv_op_gpu.cu.cc +++ b/tensorflow/core/kernels/depthwise_conv_op_gpu.h @@ -13,6 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#ifndef TENSORFLOW_CORE_KERNELS_DEPTHWISE_CONV_OP_GPU_H_ +#define TENSORFLOW_CORE_KERNELS_DEPTHWISE_CONV_OP_GPU_H_ + #if GOOGLE_CUDA #define EIGEN_USE_GPU @@ -38,7 +41,7 @@ using Eigen::GpuDevice; // Returns whether depthwise convolution forward or backward input pass can be // performed using the faster ('Small') variant of the kernel. -EIGEN_DEVICE_FUNC bool CanLaunchDepthwiseConv2dGPUSmall( +inline EIGEN_DEVICE_FUNC bool CanLaunchDepthwiseConv2dGPUSmall( const DepthwiseArgs& args) { return args.depth_multiplier == 1 && args.stride == 1 && args.in_rows <= 32 && args.in_cols <= 32 && args.in_rows == args.out_rows && @@ -51,7 +54,7 @@ EIGEN_DEVICE_FUNC bool CanLaunchDepthwiseConv2dGPUSmall( // Returns whether depthwise convolution backward filter pass can be performed // using the faster ('Small') variant of the kernel. -EIGEN_DEVICE_FUNC bool CanLaunchDepthwiseConv2dBackpropFilterGPUSmall( +inline EIGEN_DEVICE_FUNC bool CanLaunchDepthwiseConv2dBackpropFilterGPUSmall( const DepthwiseArgs& args, const int block_height) { return args.depth_multiplier == 1 && args.stride == 1 && args.in_rows <= 32 && args.in_cols <= 32 && args.in_rows == args.out_rows && @@ -652,13 +655,12 @@ struct PseudoHalfType { }; } // namespace detail -namespace { // Maps to float if T is __half, and to T otherwise. template using PseudoHalfType = typename detail::PseudoHalfType::Type; // Returns whether the context's GPU supports efficient fp16 math. -bool HasFastHalfMath(OpKernelContext* ctx) { +inline bool HasFastHalfMath(OpKernelContext* ctx) { int major, minor; ctx->op_device_context() ->stream() @@ -669,7 +671,6 @@ bool HasFastHalfMath(OpKernelContext* ctx) { // GPUs before sm_53 don't support fp16 math, and sm_61's fp16 math is slow. return cuda_arch >= 530 && cuda_arch != 610; } -} // namespace template ::operator()(OpKernelContext* ctx, } } -template struct LaunchDepthwiseConvOp; -template struct LaunchDepthwiseConvOp; -template struct LaunchDepthwiseConvOp; - // A Cuda kernel to compute the depthwise convolution backprop w.r.t. input. template @@ -1030,10 +1027,6 @@ void LaunchDepthwiseConvBackpropInputOp::operator()( } } -template struct LaunchDepthwiseConvBackpropInputOp; -template struct LaunchDepthwiseConvBackpropInputOp; -template struct LaunchDepthwiseConvBackpropInputOp; - // A Cuda kernel to compute the depthwise convolution backprop w.r.t. filter. template @@ -1803,9 +1796,7 @@ void LaunchDepthwiseConvBackpropFilterOp::operator()( ctx, args, out_backprop, input, filter_backprop, data_format)); } } - -template struct LaunchDepthwiseConvBackpropFilterOp; -template struct LaunchDepthwiseConvBackpropFilterOp; -template struct LaunchDepthwiseConvBackpropFilterOp; } // namespace tensorflow #endif // GOOGLE_CUDA + +#endif // TENSORFLOW_CORE_KERNELS_DEPTHWISE_CONV_OP_GPU_H_ diff --git a/tensorflow/core/kernels/depthwise_conv_op_gpu_double.cu.cc b/tensorflow/core/kernels/depthwise_conv_op_gpu_double.cu.cc new file mode 100644 index 0000000000..073e7cf269 --- /dev/null +++ b/tensorflow/core/kernels/depthwise_conv_op_gpu_double.cu.cc @@ -0,0 +1,30 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/depthwise_conv_op.h" +#include "tensorflow/core/kernels/depthwise_conv_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct LaunchDepthwiseConvOp; +template struct LaunchDepthwiseConvBackpropInputOp; +template struct LaunchDepthwiseConvBackpropFilterOp; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/depthwise_conv_op_gpu_float.cu.cc b/tensorflow/core/kernels/depthwise_conv_op_gpu_float.cu.cc new file mode 100644 index 0000000000..4b0e15e476 --- /dev/null +++ b/tensorflow/core/kernels/depthwise_conv_op_gpu_float.cu.cc @@ -0,0 +1,30 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/depthwise_conv_op.h" +#include "tensorflow/core/kernels/depthwise_conv_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct LaunchDepthwiseConvOp; +template struct LaunchDepthwiseConvBackpropInputOp; +template struct LaunchDepthwiseConvBackpropFilterOp; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/depthwise_conv_op_gpu_half.cu.cc b/tensorflow/core/kernels/depthwise_conv_op_gpu_half.cu.cc new file mode 100644 index 0000000000..2db9fa4dff --- /dev/null +++ b/tensorflow/core/kernels/depthwise_conv_op_gpu_half.cu.cc @@ -0,0 +1,30 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/depthwise_conv_op.h" +#include "tensorflow/core/kernels/depthwise_conv_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct LaunchDepthwiseConvOp; +template struct LaunchDepthwiseConvBackpropInputOp; +template struct LaunchDepthwiseConvBackpropFilterOp; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/opensource_only.files b/tensorflow/opensource_only.files index e00d063cfe..40f87d6c36 100644 --- a/tensorflow/opensource_only.files +++ b/tensorflow/opensource_only.files @@ -1,248 +1,248 @@ -tensorflow/contrib/tpu/profiler/pip_package/BUILD -tensorflow/contrib/tpu/profiler/pip_package/setup.py -tensorflow/contrib/tpu/profiler/pip_package/README -tensorflow/contrib/tpu/profiler/pip_package/build_pip_package.sh -tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/main.py -tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/__init__.py -tensorflow/contrib/mpi/BUILD -tensorflow/tools/ci_build/remote/BUILD -tensorflow/tools/pip_package/README -tensorflow/tools/pip_package/MANIFEST.in -tensorflow/tools/pip_package/simple_console.py -tensorflow/tools/pip_package/build_pip_package.sh -tensorflow/tools/pip_package/check_load_py_test.py -tensorflow/tools/pip_package/pip_smoke_test.py -tensorflow/tools/pip_package/simple_console_for_windows.py -tensorflow/tools/pip_package/setup.py -tensorflow/tools/pip_package/BUILD -tensorflow/tools/lib_package/concat_licenses.sh -tensorflow/tools/lib_package/libtensorflow_test.c -tensorflow/tools/lib_package/LibTensorFlowTest.java -tensorflow/tools/lib_package/BUILD -tensorflow/tools/lib_package/libtensorflow_test.sh -tensorflow/tools/lib_package/README.md -tensorflow/tools/lib_package/libtensorflow_java_test.sh -tensorflow/tools/def_file_filter/def_file_filter_configure.bzl -tensorflow/tools/def_file_filter/BUILD -tensorflow/tools/def_file_filter/BUILD.tpl -tensorflow/tools/def_file_filter/def_file_filter.py.tpl -tensorflow/third_party/mkl/MKL_LICENSE -tensorflow/third_party/mkl/LICENSE -tensorflow/third_party/mkl/BUILD -tensorflow/third_party/mkl/mkl.BUILD -tensorflow/third_party/mkl/build_defs.bzl -tensorflow/third_party/backports_weakref.BUILD -tensorflow/third_party/toolchains/clang6/BUILD +tensorflow/api_template_v1.__init__.py +tensorflow/compat_template_v1.__init__.py +tensorflow/stream_executor/BUILD +tensorflow/third_party/curl.BUILD +tensorflow/third_party/snappy.BUILD +tensorflow/third_party/tflite_mobilenet_float.BUILD +tensorflow/third_party/libxsmm.BUILD +tensorflow/third_party/cub.BUILD +tensorflow/third_party/repo.bzl +tensorflow/third_party/systemlibs/curl.BUILD +tensorflow/third_party/systemlibs/snappy.BUILD +tensorflow/third_party/systemlibs/protobuf.BUILD +tensorflow/third_party/systemlibs/absl_py.BUILD +tensorflow/third_party/systemlibs/google_cloud_cpp.google.cloud.bigtable.BUILD +tensorflow/third_party/systemlibs/astor.BUILD +tensorflow/third_party/systemlibs/absl_py.absl.flags.BUILD +tensorflow/third_party/systemlibs/png.BUILD +tensorflow/third_party/systemlibs/BUILD.tpl +tensorflow/third_party/systemlibs/boringssl.BUILD +tensorflow/third_party/systemlibs/absl_py.absl.testing.BUILD +tensorflow/third_party/systemlibs/swig.BUILD +tensorflow/third_party/systemlibs/double_conversion.BUILD +tensorflow/third_party/systemlibs/gif.BUILD +tensorflow/third_party/systemlibs/cython.BUILD +tensorflow/third_party/systemlibs/google_cloud_cpp.BUILD +tensorflow/third_party/systemlibs/zlib.BUILD +tensorflow/third_party/systemlibs/grpc.BUILD +tensorflow/third_party/systemlibs/re2.BUILD +tensorflow/third_party/systemlibs/build_defs.bzl.tpl +tensorflow/third_party/systemlibs/protobuf.bzl +tensorflow/third_party/systemlibs/gast.BUILD +tensorflow/third_party/systemlibs/syslibs_configure.bzl +tensorflow/third_party/systemlibs/nsync.BUILD +tensorflow/third_party/systemlibs/jsoncpp.BUILD +tensorflow/third_party/systemlibs/six.BUILD +tensorflow/third_party/systemlibs/lmdb.BUILD +tensorflow/third_party/systemlibs/BUILD +tensorflow/third_party/systemlibs/googleapis.BUILD +tensorflow/third_party/systemlibs/sqlite.BUILD +tensorflow/third_party/systemlibs/termcolor.BUILD +tensorflow/third_party/systemlibs/pcre.BUILD +tensorflow/third_party/tflite_mobilenet_quant.BUILD +tensorflow/third_party/common.bzl +tensorflow/third_party/boringssl/BUILD +tensorflow/third_party/kafka/config.patch +tensorflow/third_party/kafka/BUILD +tensorflow/third_party/android/android_configure.bzl +tensorflow/third_party/android/android_configure.BUILD.tpl +tensorflow/third_party/android/android.bzl.tpl +tensorflow/third_party/android/BUILD +tensorflow/third_party/astor.BUILD +tensorflow/third_party/icu/udata.patch +tensorflow/third_party/tflite_mobilenet.BUILD +tensorflow/third_party/png.BUILD +tensorflow/third_party/mpi/.gitignore +tensorflow/third_party/mpi/BUILD +tensorflow/third_party/nanopb.BUILD +tensorflow/third_party/arm_neon_2_x86_sse.BUILD +tensorflow/third_party/linenoise.BUILD +tensorflow/third_party/llvm/llvm.autogenerated.BUILD +tensorflow/third_party/llvm/llvm.bzl +tensorflow/third_party/llvm/BUILD +tensorflow/third_party/llvm/expand_cmake_vars.py +tensorflow/third_party/swig.BUILD +tensorflow/third_party/png_fix_rpi.patch +tensorflow/third_party/double_conversion.BUILD +tensorflow/third_party/gif.BUILD +tensorflow/third_party/grpc/BUILD +tensorflow/third_party/cython.BUILD +tensorflow/third_party/eigen.BUILD +tensorflow/third_party/codegen.BUILD +tensorflow/third_party/zlib.BUILD +tensorflow/third_party/tensorrt/BUILD.tpl +tensorflow/third_party/tensorrt/tensorrt_configure.bzl +tensorflow/third_party/tensorrt/build_defs.bzl.tpl +tensorflow/third_party/tensorrt/remote.BUILD.tpl +tensorflow/third_party/tensorrt/LICENSE +tensorflow/third_party/tensorrt/BUILD +tensorflow/third_party/tflite_ovic_testdata.BUILD +tensorflow/third_party/fft2d/fft.h +tensorflow/third_party/fft2d/fft2d.BUILD +tensorflow/third_party/fft2d/LICENSE +tensorflow/third_party/fft2d/BUILD tensorflow/third_party/toolchains/clang6/README.md tensorflow/third_party/toolchains/clang6/repo.bzl -tensorflow/third_party/toolchains/clang6/CROSSTOOL.tpl tensorflow/third_party/toolchains/clang6/clang.BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu14.04/tensorrt5/BUILD +tensorflow/third_party/toolchains/clang6/CROSSTOOL.tpl +tensorflow/third_party/toolchains/clang6/BUILD +tensorflow/third_party/toolchains/preconfig/generate/archives.bzl +tensorflow/third_party/toolchains/preconfig/generate/workspace.bzl +tensorflow/third_party/toolchains/preconfig/generate/generate.bzl +tensorflow/third_party/toolchains/preconfig/generate/containers.bzl +tensorflow/third_party/toolchains/preconfig/generate/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/tensorrt5/build_defs.bzl +tensorflow/third_party/toolchains/preconfig/ubuntu14.04/tensorrt5/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/py3/BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda9.0/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda9.0-cudnn7/cuda/build_defs.bzl +tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda9.0-cudnn7/cuda/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda10.0-cudnn7/cuda/build_defs.bzl tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda10.0-cudnn7/cuda/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda9.0/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda10.0/BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda9.0-cudnn7/cuda/build_defs.bzl -tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda9.0-cudnn7/cuda/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/nccl2/BUILD -tensorflow/third_party/toolchains/preconfig/generate/workspace.bzl -tensorflow/third_party/toolchains/preconfig/generate/containers.bzl -tensorflow/third_party/toolchains/preconfig/generate/generate.bzl -tensorflow/third_party/toolchains/preconfig/generate/archives.bzl -tensorflow/third_party/toolchains/preconfig/generate/BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/dummy_toolchain.bzl -tensorflow/third_party/toolchains/preconfig/ubuntu16.04/py3/BUILD -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/preconfig/win_1803/bazel_018/dummy_toolchain.bzl +tensorflow/third_party/toolchains/preconfig/win_1803/bazel_018/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu16.04/py3/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/dummy_toolchain.bzl +tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/BUILD +tensorflow/third_party/toolchains/cpus/py3/BUILD tensorflow/third_party/toolchains/cpus/arm/CROSSTOOL.tpl tensorflow/third_party/toolchains/cpus/arm/BUILD -tensorflow/third_party/toolchains/cpus/py3/BUILD +tensorflow/third_party/toolchains/cpus/arm/arm_compiler_configure.bzl tensorflow/third_party/toolchains/cpus/py/BUILD +tensorflow/third_party/toolchains/gpus/crosstool/CROSSTOOL +tensorflow/third_party/toolchains/gpus/crosstool/BUILD +tensorflow/third_party/toolchains/gpus/cuda/build_defs.bzl +tensorflow/third_party/toolchains/gpus/cuda/cuda/cuda_config.h +tensorflow/third_party/toolchains/gpus/cuda/BUILD +tensorflow/third_party/toolchains/gpus/py/BUILD tensorflow/third_party/toolchains/BUILD -tensorflow/third_party/gpus/BUILD +tensorflow/third_party/nccl/archive.BUILD +tensorflow/third_party/nccl/nccl_configure.bzl +tensorflow/third_party/nccl/system.BUILD.tpl +tensorflow/third_party/nccl/build_defs.bzl.tpl +tensorflow/third_party/nccl/LICENSE +tensorflow/third_party/nccl/BUILD +tensorflow/third_party/farmhash.BUILD +tensorflow/third_party/sycl/crosstool/BUILD +tensorflow/third_party/backports_weakref.BUILD +tensorflow/third_party/gast.BUILD +tensorflow/third_party/clang_toolchain/cc_configure_clang.bzl +tensorflow/third_party/clang_toolchain/download_clang.bzl +tensorflow/third_party/clang_toolchain/BUILD +tensorflow/third_party/mpi_collectives/BUILD +tensorflow/third_party/jsoncpp.BUILD +tensorflow/third_party/mkl_dnn/mkldnn.BUILD +tensorflow/third_party/mkl_dnn/LICENSE +tensorflow/third_party/python_runtime/BUILD +tensorflow/third_party/ngraph/ngraph.BUILD +tensorflow/third_party/ngraph/nlohmann_json.BUILD +tensorflow/third_party/ngraph/build_defs.bzl +tensorflow/third_party/ngraph/ngraph_tf.BUILD +tensorflow/third_party/ngraph/NGRAPH_LICENSE +tensorflow/third_party/ngraph/tbb.BUILD +tensorflow/third_party/ngraph/LICENSE +tensorflow/third_party/ngraph/BUILD +tensorflow/third_party/gpus/crosstool/windows/msvc_wrapper_for_nvcc.py.tpl +tensorflow/third_party/gpus/crosstool/CROSSTOOL.tpl +tensorflow/third_party/gpus/crosstool/BUILD.tpl +tensorflow/third_party/gpus/crosstool/remote.BUILD.tpl tensorflow/third_party/gpus/crosstool/clang/bin/crosstool_wrapper_driver_rocm.tpl tensorflow/third_party/gpus/crosstool/clang/bin/crosstool_wrapper_driver_is_not_gcc.tpl -tensorflow/third_party/gpus/crosstool/CROSSTOOL.tpl tensorflow/third_party/gpus/crosstool/CROSSTOOL_hipcc.tpl tensorflow/third_party/gpus/crosstool/LICENSE -tensorflow/third_party/gpus/crosstool/remote.BUILD.tpl -tensorflow/third_party/gpus/crosstool/windows/msvc_wrapper_for_nvcc.py.tpl -tensorflow/third_party/gpus/crosstool/BUILD.tpl tensorflow/third_party/gpus/crosstool/BUILD -tensorflow/third_party/gpus/cuda/LICENSE +tensorflow/third_party/gpus/rocm/BUILD.tpl +tensorflow/third_party/gpus/rocm/rocm_config.h.tpl +tensorflow/third_party/gpus/rocm/build_defs.bzl.tpl +tensorflow/third_party/gpus/rocm/BUILD tensorflow/third_party/gpus/cuda/BUILD.tpl +tensorflow/third_party/gpus/cuda/build_defs.bzl.tpl +tensorflow/third_party/gpus/cuda/remote.BUILD.tpl tensorflow/third_party/gpus/cuda/BUILD.windows.tpl tensorflow/third_party/gpus/cuda/cuda_config.h.tpl -tensorflow/third_party/gpus/cuda/remote.BUILD.tpl +tensorflow/third_party/gpus/cuda/LICENSE tensorflow/third_party/gpus/cuda/BUILD -tensorflow/third_party/gpus/cuda/build_defs.bzl.tpl -tensorflow/third_party/gpus/rocm/rocm_config.h.tpl -tensorflow/third_party/gpus/rocm/BUILD -tensorflow/third_party/gpus/rocm/BUILD.tpl -tensorflow/third_party/gpus/rocm/build_defs.bzl.tpl tensorflow/third_party/gpus/cuda_configure.bzl tensorflow/third_party/gpus/rocm_configure.bzl -tensorflow/third_party/snappy.BUILD -tensorflow/third_party/cython.BUILD -tensorflow/third_party/farmhash.BUILD -tensorflow/third_party/eigen3/Eigen/Cholesky -tensorflow/third_party/eigen3/Eigen/QR -tensorflow/third_party/eigen3/Eigen/LU +tensorflow/third_party/gpus/BUILD +tensorflow/third_party/py/python_configure.bzl +tensorflow/third_party/py/BUILD.tpl +tensorflow/third_party/py/numpy/BUILD +tensorflow/third_party/py/remote.BUILD.tpl +tensorflow/third_party/py/BUILD +tensorflow/third_party/mkl/build_defs.bzl +tensorflow/third_party/mkl/mkl.BUILD +tensorflow/third_party/mkl/MKL_LICENSE +tensorflow/third_party/mkl/LICENSE +tensorflow/third_party/mkl/BUILD +tensorflow/third_party/com_google_absl.BUILD +tensorflow/third_party/six.BUILD +tensorflow/third_party/lmdb.BUILD +tensorflow/third_party/BUILD +tensorflow/third_party/googleapis.BUILD +tensorflow/third_party/sqlite.BUILD +tensorflow/third_party/termcolor.BUILD +tensorflow/third_party/protobuf/BUILD +tensorflow/third_party/pcre.BUILD +tensorflow/third_party/git/BUILD.tpl +tensorflow/third_party/git/git_configure.bzl +tensorflow/third_party/git/BUILD +tensorflow/third_party/pprof.BUILD +tensorflow/third_party/tflite_smartreply.BUILD tensorflow/third_party/eigen3/Eigen/Core -tensorflow/third_party/eigen3/Eigen/SVD +tensorflow/third_party/eigen3/Eigen/Cholesky tensorflow/third_party/eigen3/Eigen/Eigenvalues -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/FixedPoint +tensorflow/third_party/eigen3/Eigen/SVD +tensorflow/third_party/eigen3/Eigen/LU +tensorflow/third_party/eigen3/Eigen/QR +tensorflow/third_party/eigen3/unsupported/Eigen/MatrixFunctions tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/Tensor -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/PacketMathAVX512.h +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/ThreadPool tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/TypeCastingAVX512.h -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/PacketMathAVX2.h tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatMatProduct.h -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/FixedPointTypes.h -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatVecProduct.h tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatMatProductNEON.h +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/PacketMathAVX512.h +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatVecProduct.h tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/TypeCastingAVX2.h +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/FixedPointTypes.h tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatMatProductAVX2.h -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/ThreadPool +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/PacketMathAVX2.h +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/FixedPoint tensorflow/third_party/eigen3/unsupported/Eigen/SpecialFunctions -tensorflow/third_party/eigen3/unsupported/Eigen/MatrixFunctions tensorflow/third_party/eigen3/LICENSE tensorflow/third_party/eigen3/BUILD -tensorflow/third_party/systemlibs/build_defs.bzl.tpl -tensorflow/third_party/systemlibs/absl_py.BUILD -tensorflow/third_party/systemlibs/curl.BUILD -tensorflow/third_party/systemlibs/termcolor.BUILD -tensorflow/third_party/systemlibs/absl_py.absl.flags.BUILD -tensorflow/third_party/systemlibs/grpc.BUILD -tensorflow/third_party/systemlibs/swig.BUILD -tensorflow/third_party/systemlibs/protobuf.bzl -tensorflow/third_party/systemlibs/protobuf.BUILD -tensorflow/third_party/systemlibs/BUILD -tensorflow/third_party/systemlibs/google_cloud_cpp.BUILD -tensorflow/third_party/systemlibs/astor.BUILD -tensorflow/third_party/systemlibs/six.BUILD -tensorflow/third_party/systemlibs/absl_py.absl.testing.BUILD -tensorflow/third_party/systemlibs/boringssl.BUILD -tensorflow/third_party/systemlibs/nsync.BUILD -tensorflow/third_party/systemlibs/google_cloud_cpp.google.cloud.bigtable.BUILD -tensorflow/third_party/systemlibs/gif.BUILD -tensorflow/third_party/systemlibs/pcre.BUILD -tensorflow/third_party/systemlibs/BUILD.tpl -tensorflow/third_party/systemlibs/snappy.BUILD -tensorflow/third_party/systemlibs/gast.BUILD -tensorflow/third_party/systemlibs/cython.BUILD -tensorflow/third_party/systemlibs/double_conversion.BUILD -tensorflow/third_party/systemlibs/zlib.BUILD -tensorflow/third_party/systemlibs/jsoncpp.BUILD -tensorflow/third_party/systemlibs/re2.BUILD -tensorflow/third_party/systemlibs/lmdb.BUILD -tensorflow/third_party/systemlibs/googleapis.BUILD -tensorflow/third_party/systemlibs/png.BUILD -tensorflow/third_party/systemlibs/syslibs_configure.bzl -tensorflow/third_party/systemlibs/sqlite.BUILD -tensorflow/third_party/python_runtime/BUILD -tensorflow/third_party/sycl/crosstool/BUILD -tensorflow/third_party/ngraph/LICENSE -tensorflow/third_party/ngraph/tbb.BUILD -tensorflow/third_party/ngraph/BUILD -tensorflow/third_party/ngraph/ngraph.BUILD -tensorflow/third_party/ngraph/build_defs.bzl -tensorflow/third_party/ngraph/NGRAPH_LICENSE -tensorflow/third_party/ngraph/ngraph_tf.BUILD -tensorflow/third_party/ngraph/nlohmann_json.BUILD -tensorflow/third_party/clang_toolchain/download_clang.bzl -tensorflow/third_party/clang_toolchain/BUILD -tensorflow/third_party/clang_toolchain/cc_configure_clang.bzl -tensorflow/third_party/gast.BUILD -tensorflow/third_party/llvm/BUILD -tensorflow/third_party/llvm/expand_cmake_vars.py -tensorflow/third_party/llvm/llvm.autogenerated.BUILD -tensorflow/third_party/llvm/llvm.bzl -tensorflow/third_party/icu/udata.patch -tensorflow/third_party/nccl/archive.BUILD -tensorflow/third_party/nccl/LICENSE -tensorflow/third_party/nccl/system.BUILD.tpl -tensorflow/third_party/nccl/nccl_configure.bzl -tensorflow/third_party/nccl/build_defs.bzl.tpl -tensorflow/third_party/nccl/BUILD -tensorflow/third_party/fft2d/BUILD -tensorflow/third_party/fft2d/fft.h -tensorflow/third_party/fft2d/LICENSE -tensorflow/third_party/fft2d/fft2d.BUILD -tensorflow/third_party/boringssl/BUILD -tensorflow/third_party/mpi/.gitignore -tensorflow/third_party/mpi/BUILD -tensorflow/third_party/tensorrt/LICENSE -tensorflow/third_party/tensorrt/BUILD -tensorflow/third_party/tensorrt/build_defs.bzl.tpl -tensorflow/third_party/tensorrt/BUILD.tpl -tensorflow/third_party/tensorrt/tensorrt_configure.bzl -tensorflow/third_party/tensorrt/remote.BUILD.tpl -tensorflow/third_party/kafka/config.patch -tensorflow/third_party/kafka/BUILD -tensorflow/third_party/android/BUILD -tensorflow/third_party/android/android.bzl.tpl -tensorflow/third_party/android/android_configure.bzl -tensorflow/third_party/android/android_configure.BUILD.tpl -tensorflow/third_party/tflite_smartreply.BUILD -tensorflow/third_party/mkl_dnn/LICENSE -tensorflow/third_party/mkl_dnn/mkldnn.BUILD -tensorflow/third_party/pcre.BUILD -tensorflow/third_party/linenoise.BUILD -tensorflow/third_party/sqlite.BUILD -tensorflow/third_party/common.bzl -tensorflow/third_party/com_google_absl.BUILD -tensorflow/third_party/pprof.BUILD -tensorflow/third_party/BUILD -tensorflow/third_party/tflite_mobilenet_quant.BUILD -tensorflow/third_party/lmdb.BUILD -tensorflow/third_party/git/BUILD.tpl -tensorflow/third_party/git/BUILD -tensorflow/third_party/git/git_configure.bzl -tensorflow/third_party/protobuf/BUILD -tensorflow/third_party/tflite_mobilenet.BUILD -tensorflow/third_party/py/BUILD -tensorflow/third_party/py/BUILD.tpl -tensorflow/third_party/py/remote.BUILD.tpl -tensorflow/third_party/py/numpy/BUILD -tensorflow/third_party/py/python_configure.bzl -tensorflow/third_party/termcolor.BUILD -tensorflow/third_party/png_fix_rpi.patch -tensorflow/third_party/swig.BUILD -tensorflow/third_party/astor.BUILD -tensorflow/third_party/grpc/BUILD -tensorflow/third_party/curl.BUILD -tensorflow/third_party/arm_neon_2_x86_sse.BUILD -tensorflow/third_party/png.BUILD -tensorflow/third_party/googleapis.BUILD -tensorflow/third_party/mpi_collectives/BUILD -tensorflow/third_party/nanopb.BUILD -tensorflow/third_party/gif.BUILD -tensorflow/third_party/double_conversion.BUILD -tensorflow/third_party/six.BUILD -tensorflow/third_party/tflite_mobilenet_float.BUILD -tensorflow/third_party/repo.bzl -tensorflow/third_party/codegen.BUILD -tensorflow/third_party/cub.BUILD -tensorflow/third_party/jsoncpp.BUILD -tensorflow/third_party/tflite_ovic_testdata.BUILD -tensorflow/third_party/libxsmm.BUILD -tensorflow/third_party/zlib.BUILD -tensorflow/third_party/eigen.BUILD -tensorflow/stream_executor/BUILD -tensorflow/api_template_v1.__init__.py -tensorflow/compat_template_v1.__init__.py +tensorflow/tools/pip_package/simple_console.py +tensorflow/tools/pip_package/setup.py +tensorflow/tools/pip_package/MANIFEST.in +tensorflow/tools/pip_package/README +tensorflow/tools/pip_package/check_load_py_test.py +tensorflow/tools/pip_package/pip_smoke_test.py +tensorflow/tools/pip_package/BUILD +tensorflow/tools/pip_package/build_pip_package.sh +tensorflow/tools/pip_package/simple_console_for_windows.py +tensorflow/tools/lib_package/LibTensorFlowTest.java +tensorflow/tools/lib_package/README.md +tensorflow/tools/lib_package/libtensorflow_test.sh +tensorflow/tools/lib_package/libtensorflow_java_test.sh +tensorflow/tools/lib_package/libtensorflow_test.c +tensorflow/tools/lib_package/concat_licenses.sh +tensorflow/tools/lib_package/BUILD +tensorflow/tools/ci_build/remote/BUILD +tensorflow/tools/def_file_filter/BUILD.tpl +tensorflow/tools/def_file_filter/def_file_filter_configure.bzl +tensorflow/tools/def_file_filter/def_file_filter.py.tpl +tensorflow/tools/def_file_filter/BUILD +tensorflow/contrib/mpi/BUILD +tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/main.py +tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/__init__.py +tensorflow/contrib/tpu/profiler/pip_package/setup.py +tensorflow/contrib/tpu/profiler/pip_package/README +tensorflow/contrib/tpu/profiler/pip_package/BUILD +tensorflow/contrib/tpu/profiler/pip_package/build_pip_package.sh tensorflow/api_template.__init__.py tensorflow/__init__.py \ No newline at end of file diff --git a/tensorflow/tools/git/gen_git_source.py b/tensorflow/tools/git/gen_git_source.py index 645d817d9f..0c37508650 100755 --- a/tensorflow/tools/git/gen_git_source.py +++ b/tensorflow/tools/git/gen_git_source.py @@ -174,8 +174,8 @@ def get_git_version(git_base_path, git_tag_override): # There might be "-" in the tag name. But we can be sure that the final # two "-" are those inserted by the git describe command. abbrev_commit = split_val[-1] - val = bytes( - version_separator.join([git_tag_override, "0", abbrev_commit])) + val = version_separator.join( + [bytes(git_tag_override), b"0", abbrev_commit]) return val if val else unknown_label except (subprocess.CalledProcessError, OSError): return unknown_label diff --git a/tensorflow/tools/optimization/BUILD b/tensorflow/tools/optimization/BUILD new file mode 100644 index 0000000000..aa6c850b0b --- /dev/null +++ b/tensorflow/tools/optimization/BUILD @@ -0,0 +1,52 @@ +# Description: +# Utilities that perform useful transformations on graphs + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +load( + "//tensorflow:tensorflow.bzl", + "tf_cc_binary", + "tf_cuda_library", +) + +exports_files(["LICENSE"]) + +tf_cuda_library( + name = "optimization_pass_runner_lib", + srcs = ["optimization_pass_runner.cc"], + hdrs = ["optimization_pass_runner.h"], + deps = [ + "//tensorflow/contrib:contrib_ops_op_lib", + "//tensorflow/core:core_cpu", + "//tensorflow/core:core_cpu_base", + "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", + "//tensorflow/core:framework_lite", + "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:tensorflow", + ], +) + +tf_cc_binary( + name = "gpu_optimization_pass_runner", + srcs = ["gpu_optimization_pass_runner_main.cc"], + deps = [ + ":optimization_pass_runner_lib", + "//tensorflow/compiler/jit:xla_cpu_jit", + "//tensorflow/compiler/jit:xla_gpu_jit", + "//tensorflow/compiler/tf2xla:xla_compiler", + "//tensorflow/contrib:contrib_ops_op_lib", + "//tensorflow/core:core_cpu", + "//tensorflow/core:core_cpu_base", + "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", + "//tensorflow/core:framework_lite", + "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:tensorflow", + "@com_google_absl//absl/strings", + ], +) diff --git a/tensorflow/tools/optimization/gpu_optimization_pass_runner_main.cc b/tensorflow/tools/optimization/gpu_optimization_pass_runner_main.cc new file mode 100644 index 0000000000..0d9f26cd5a --- /dev/null +++ b/tensorflow/tools/optimization/gpu_optimization_pass_runner_main.cc @@ -0,0 +1,60 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +// This file creates a binary that can run any registered optimization pass. +// ./xla_gpu_opt --input_file_path=/tmp/input.pbtxt +// --output_file_path=/tmp/output.pbtxt +// --optimization_pass=NameOfGraphOptimizationPass + +#include "absl/strings/str_cat.h" +#include "tensorflow/compiler/tf2xla/xla_op_registry.h" +#include "tensorflow/core/framework/types.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/protobuf/config.pb.h" +#include "tensorflow/tools/optimization/optimization_pass_runner.h" + +int main(int argc, char** argv) { + tensorflow::OptimizationPassRunner runner; + // Add fake devices for CPU, GPU, and XLA to ensure we have all devices we + // need. + // Most machines in our servers currently use 8 gpus. There is nothing special + // about this number and it can be decreased or increased to test other + // configurations. + int num_gpus_per_machine = 8; + for (int i = 0; i < num_gpus_per_machine; i++) { + TF_CHECK_OK(runner.AddDevice( + absl::StrCat("/job:localhost/replica:0/task:0/device:CPU:", i), + tensorflow::DEVICE_CPU)); + TF_CHECK_OK(runner.AddDevice( + absl::StrCat("/job:localhost/replica:0/task:0/device:GPU:", i), + tensorflow::DEVICE_GPU)); + TF_CHECK_OK(runner.AddDevice( + absl::StrCat("/job:localhost/replica:0/task:0/device:XLA_CPU:", i), + tensorflow::DEVICE_XLA_CPU)); + TF_CHECK_OK(runner.AddDevice( + absl::StrCat("/job:localhost/replica:0/task:0/device:XLA_GPU:", i), + tensorflow::DEVICE_XLA_GPU)); + TF_CHECK_OK(runner.AddDevice( + absl::StrCat("/job:localhost/replica:0/task:0/device:CPU_XLA_JIT:", i), + tensorflow::DEVICE_CPU_XLA_JIT)); + TF_CHECK_OK(runner.AddDevice( + absl::StrCat("/job:localhost/replica:0/task:0/device:GPU_XLA_JIT:", i), + tensorflow::DEVICE_GPU_XLA_JIT)); + } + // This binary is used to test TF:XLA behavior, so turn on auto_jit. + TF_CHECK_OK(runner.SetJitLevel(tensorflow::OptimizerOptions::GlobalJitLevel:: + OptimizerOptions_GlobalJitLevel_ON_2)); + // Run the actual "main" function. + TF_CHECK_OK(runner.RunMain(argc, argv)); +} diff --git a/tensorflow/tools/optimization/optimization_pass_runner.cc b/tensorflow/tools/optimization/optimization_pass_runner.cc new file mode 100644 index 0000000000..231ff08381 --- /dev/null +++ b/tensorflow/tools/optimization/optimization_pass_runner.cc @@ -0,0 +1,167 @@ +/* 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. +==============================================================================*/ +// This file creates a library that can run any registered optimization pass. +// The binary that uses this will be run in a form similar to: +// ./optimization_pass_runner --input_file_path=/tmp/input.pbtxt +// --output_file_path=/tmp/output.pbtxt +// --optimization_pass=NameOfGraphOptimizationPass +#include "tensorflow/tools/optimization/optimization_pass_runner.h" + +#include +#include +#include + +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/device_set.h" +#include "tensorflow/core/common_runtime/optimization_registry.h" +#include "tensorflow/core/framework/allocator.h" +#include "tensorflow/core/framework/device_attributes.pb.h" +#include "tensorflow/core/framework/function.h" +#include "tensorflow/core/framework/function.pb.h" +#include "tensorflow/core/framework/graph.pb.h" +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/types.h" +#include "tensorflow/core/graph/graph.h" +#include "tensorflow/core/graph/graph_constructor.h" +#include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/platform/env.h" +#include "tensorflow/core/platform/init_main.h" +#include "tensorflow/core/protobuf/config.pb.h" +#include "tensorflow/core/public/session_options.h" +#include "tensorflow/core/util/command_line_flags.h" + +namespace tensorflow { + +namespace { +// A fake device used to populate a DeviceSet. +class FakeDevice : public Device { + private: + explicit FakeDevice(const DeviceAttributes& device_attributes) + : Device(nullptr, device_attributes) {} + + public: + Status Sync() override; + static std::unique_ptr Make(const string& name, const string& type); +}; + +Status FakeDevice::Sync() { + return errors::Unimplemented("FakeDevice::Sync()"); +} + +std::unique_ptr FakeDevice::Make(const string& name, + const string& type) { + DeviceAttributes device_attributes; + device_attributes.set_name(name); + device_attributes.set_device_type(DeviceType(type).type()); + return std::unique_ptr(new FakeDevice(device_attributes)); +} +} // namespace + +Status OptimizationPassRunner::RunMain(int argc, char** argv) { + string input_file_path; + string output_file_path; + string optimization_pass; + + const std::vector flag_list = { + Flag("input_file_path", &input_file_path, "Location of the input graph."), + Flag("output_file_path", &output_file_path, + "Location to write the resulting graph."), + // For now only a single optimization pass can be run. + Flag("optimization_pass", &optimization_pass, + "Which optimization pass to run."), + }; + if (!Flags::Parse(&argc, argv, flag_list)) { + return errors::FailedPrecondition("Invalid flags passed"); + } + port::InitMain(argv[0], &argc, &argv); + + if (input_file_path.empty()) { + return errors::FailedPrecondition("input_file_path is a required flag."); + } + if (output_file_path.empty()) { + return errors::FailedPrecondition("output_file_path is a required flag."); + } + if (optimization_pass.empty()) { + return errors::FailedPrecondition("optimization_pass is a required flag."); + } + + // Turn on XLA Auto-Jit. + auto session_options = absl::make_unique(); + session_options->config.mutable_graph_options() + ->mutable_optimizer_options() + ->set_global_jit_level(jit_level_); + FunctionDefLibrary flib; + std::unique_ptr graph = absl::make_unique(OpRegistry::Global()); + + GraphOptimizationPassOptions options; + options.session_options = session_options.release(); + options.graph = &graph; + options.flib_def = + new FunctionLibraryDefinition((*options.graph)->op_registry(), flib); + + // Grab the data + GraphDef graphdef; + GraphConstructorOptions graph_opts; + graph_opts.expect_device_spec = true; + graph_opts.allow_internal_ops = true; + TF_RETURN_IF_ERROR(ReadTextProto(Env::Default(), input_file_path, &graphdef)); + TF_RETURN_IF_ERROR( + ConvertGraphDefToGraph(graph_opts, graphdef, options.graph->get())); + + // Add all devices that were previously configured with AddDevice. + DeviceSet device_set; + for (auto& device : devices_) { + device_set.AddDevice(device.get()); + } + options.device_set = &device_set; + + Status result = errors::NotFound( + "An OptimizationPass was not found with the desired name."); + + // Run the optimization pass specified by the command line flag. + for (const auto& groups_and_passes : + OptimizationPassRegistry::Global()->groups()) { + for (const auto& phase_and_passes : groups_and_passes.second) { + for (const auto& pass : phase_and_passes.second) { + if (pass->name() == optimization_pass) { + result = pass->Run(options); + } + } + } + } + + TF_RETURN_IF_ERROR(result); + + // Write out the result. + options.graph->get()->ToGraphDef(&graphdef); + TF_RETURN_IF_ERROR( + WriteTextProto(Env::Default(), output_file_path, graphdef)); + return Status::OK(); +} + +Status OptimizationPassRunner::SetJitLevel( + OptimizerOptions::GlobalJitLevel jit_level) { + jit_level_ = jit_level; + return Status::OK(); +} + +Status OptimizationPassRunner::AddDevice(const string& name, + const string& type) { + devices_.push_back(FakeDevice::Make(name, type)); + return Status::OK(); +} + +} // namespace tensorflow diff --git a/tensorflow/tools/optimization/optimization_pass_runner.h b/tensorflow/tools/optimization/optimization_pass_runner.h new file mode 100644 index 0000000000..3b26f64bcf --- /dev/null +++ b/tensorflow/tools/optimization/optimization_pass_runner.h @@ -0,0 +1,61 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_TOOLS_OPTIMIZATION_OPTIMIZATION_PASS_RUNNER_H_ +#define TENSORFLOW_TOOLS_OPTIMIZATION_OPTIMIZATION_PASS_RUNNER_H_ + +#include +#include +#include + +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/optimization_registry.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/protobuf/config.pb.h" + +namespace tensorflow { + +// OptimizationPassRunner can be initialized, populated with devices, then run +// to test individual Tensorflow Optimization passes. +class OptimizationPassRunner { + public: + explicit OptimizationPassRunner() + : jit_level_(OptimizerOptions::GlobalJitLevel:: + OptimizerOptions_GlobalJitLevel_DEFAULT) {} + + // Add a fake device to the (initially empty) DeviceSet used for optimization. + // Names are of the form: "/job:localhost/replica:0/task:0/device:CPU:0" + Status AddDevice(const string& name, const string& type); + + // Increasing the Jit level will cause XLA to compile parts of the tensorflow + // graph that it is able to. + Status SetJitLevel(OptimizerOptions::GlobalJitLevel jit_level); + + // This can be called after adding devices and setting the jit level to parse + // command line flags and run the specified job. All 3 flags are required: + // input_file_path, output_file_path, optimization_pass. + // + // If this library becomes heavily used, the caller should be responsible for + // parsing any command line flags desired rather than this Method handling the + // work of a main() function. + Status RunMain(int argc, char** argv); + + private: + OptimizerOptions::GlobalJitLevel jit_level_; + std::vector> devices_; +}; + +} // namespace tensorflow + +#endif // TENSORFLOW_TOOLS_OPTIMIZATION_OPTIMIZATION_PASS_RUNNER_H_ -- GitLab From 044b34b2819ec45ffc34c7b59e921ad4d8aaec9a Mon Sep 17 00:00:00 2001 From: Penporn Koanantakool Date: Mon, 7 Jan 2019 11:13:45 -0800 Subject: [PATCH 0265/2345] Disable MKL-DNN contraction kernels in Eigen when building with config=mkl to avoid duplicate MKL-DNN symbols. (config=mkl links to OpenMP MKL-DNN while tensorflow_mkldnn_contraction_kernel links to serial MKL-DNN.) PiperOrigin-RevId: 228199406 --- .bazelrc | 2 + tensorflow/opensource_only.files | 416 +++++++++++++++---------------- 2 files changed, 210 insertions(+), 208 deletions(-) diff --git a/.bazelrc b/.bazelrc index cd7e13ddfc..d5432aeb17 100644 --- a/.bazelrc +++ b/.bazelrc @@ -25,12 +25,14 @@ build --define framework_shared_object=true # If you would like to use a local MKL instead of downloading, please set the # environment variable "TF_MKL_ROOT" every time before build. build:mkl --define=build_with_mkl=true --define=enable_mkl=true +build:mkl --define=tensorflow_mkldnn_contraction_kernel=0 build:mkl -c opt # This config option is used to enable MKL-DNN open source library only, # without depending on MKL binary version. build:mkl_open_source_only --define=build_with_mkl_dnn_only=true build:mkl_open_source_only --define=build_with_mkl=true --define=enable_mkl=true +build:mkl_open_source_only --define=tensorflow_mkldnn_contraction_kernel=0 build:download_clang --crosstool_top=@local_config_download_clang//:toolchain build:download_clang --define=using_clang=true diff --git a/tensorflow/opensource_only.files b/tensorflow/opensource_only.files index 40f87d6c36..e00d063cfe 100644 --- a/tensorflow/opensource_only.files +++ b/tensorflow/opensource_only.files @@ -1,248 +1,248 @@ -tensorflow/api_template_v1.__init__.py -tensorflow/compat_template_v1.__init__.py -tensorflow/stream_executor/BUILD -tensorflow/third_party/curl.BUILD -tensorflow/third_party/snappy.BUILD -tensorflow/third_party/tflite_mobilenet_float.BUILD -tensorflow/third_party/libxsmm.BUILD -tensorflow/third_party/cub.BUILD -tensorflow/third_party/repo.bzl -tensorflow/third_party/systemlibs/curl.BUILD -tensorflow/third_party/systemlibs/snappy.BUILD -tensorflow/third_party/systemlibs/protobuf.BUILD -tensorflow/third_party/systemlibs/absl_py.BUILD -tensorflow/third_party/systemlibs/google_cloud_cpp.google.cloud.bigtable.BUILD -tensorflow/third_party/systemlibs/astor.BUILD -tensorflow/third_party/systemlibs/absl_py.absl.flags.BUILD -tensorflow/third_party/systemlibs/png.BUILD -tensorflow/third_party/systemlibs/BUILD.tpl -tensorflow/third_party/systemlibs/boringssl.BUILD -tensorflow/third_party/systemlibs/absl_py.absl.testing.BUILD -tensorflow/third_party/systemlibs/swig.BUILD -tensorflow/third_party/systemlibs/double_conversion.BUILD -tensorflow/third_party/systemlibs/gif.BUILD -tensorflow/third_party/systemlibs/cython.BUILD -tensorflow/third_party/systemlibs/google_cloud_cpp.BUILD -tensorflow/third_party/systemlibs/zlib.BUILD -tensorflow/third_party/systemlibs/grpc.BUILD -tensorflow/third_party/systemlibs/re2.BUILD -tensorflow/third_party/systemlibs/build_defs.bzl.tpl -tensorflow/third_party/systemlibs/protobuf.bzl -tensorflow/third_party/systemlibs/gast.BUILD -tensorflow/third_party/systemlibs/syslibs_configure.bzl -tensorflow/third_party/systemlibs/nsync.BUILD -tensorflow/third_party/systemlibs/jsoncpp.BUILD -tensorflow/third_party/systemlibs/six.BUILD -tensorflow/third_party/systemlibs/lmdb.BUILD -tensorflow/third_party/systemlibs/BUILD -tensorflow/third_party/systemlibs/googleapis.BUILD -tensorflow/third_party/systemlibs/sqlite.BUILD -tensorflow/third_party/systemlibs/termcolor.BUILD -tensorflow/third_party/systemlibs/pcre.BUILD -tensorflow/third_party/tflite_mobilenet_quant.BUILD -tensorflow/third_party/common.bzl -tensorflow/third_party/boringssl/BUILD -tensorflow/third_party/kafka/config.patch -tensorflow/third_party/kafka/BUILD -tensorflow/third_party/android/android_configure.bzl -tensorflow/third_party/android/android_configure.BUILD.tpl -tensorflow/third_party/android/android.bzl.tpl -tensorflow/third_party/android/BUILD -tensorflow/third_party/astor.BUILD -tensorflow/third_party/icu/udata.patch -tensorflow/third_party/tflite_mobilenet.BUILD -tensorflow/third_party/png.BUILD -tensorflow/third_party/mpi/.gitignore -tensorflow/third_party/mpi/BUILD -tensorflow/third_party/nanopb.BUILD -tensorflow/third_party/arm_neon_2_x86_sse.BUILD -tensorflow/third_party/linenoise.BUILD -tensorflow/third_party/llvm/llvm.autogenerated.BUILD -tensorflow/third_party/llvm/llvm.bzl -tensorflow/third_party/llvm/BUILD -tensorflow/third_party/llvm/expand_cmake_vars.py -tensorflow/third_party/swig.BUILD -tensorflow/third_party/png_fix_rpi.patch -tensorflow/third_party/double_conversion.BUILD -tensorflow/third_party/gif.BUILD -tensorflow/third_party/grpc/BUILD -tensorflow/third_party/cython.BUILD -tensorflow/third_party/eigen.BUILD -tensorflow/third_party/codegen.BUILD -tensorflow/third_party/zlib.BUILD -tensorflow/third_party/tensorrt/BUILD.tpl -tensorflow/third_party/tensorrt/tensorrt_configure.bzl -tensorflow/third_party/tensorrt/build_defs.bzl.tpl -tensorflow/third_party/tensorrt/remote.BUILD.tpl -tensorflow/third_party/tensorrt/LICENSE -tensorflow/third_party/tensorrt/BUILD -tensorflow/third_party/tflite_ovic_testdata.BUILD -tensorflow/third_party/fft2d/fft.h -tensorflow/third_party/fft2d/fft2d.BUILD -tensorflow/third_party/fft2d/LICENSE -tensorflow/third_party/fft2d/BUILD +tensorflow/contrib/tpu/profiler/pip_package/BUILD +tensorflow/contrib/tpu/profiler/pip_package/setup.py +tensorflow/contrib/tpu/profiler/pip_package/README +tensorflow/contrib/tpu/profiler/pip_package/build_pip_package.sh +tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/main.py +tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/__init__.py +tensorflow/contrib/mpi/BUILD +tensorflow/tools/ci_build/remote/BUILD +tensorflow/tools/pip_package/README +tensorflow/tools/pip_package/MANIFEST.in +tensorflow/tools/pip_package/simple_console.py +tensorflow/tools/pip_package/build_pip_package.sh +tensorflow/tools/pip_package/check_load_py_test.py +tensorflow/tools/pip_package/pip_smoke_test.py +tensorflow/tools/pip_package/simple_console_for_windows.py +tensorflow/tools/pip_package/setup.py +tensorflow/tools/pip_package/BUILD +tensorflow/tools/lib_package/concat_licenses.sh +tensorflow/tools/lib_package/libtensorflow_test.c +tensorflow/tools/lib_package/LibTensorFlowTest.java +tensorflow/tools/lib_package/BUILD +tensorflow/tools/lib_package/libtensorflow_test.sh +tensorflow/tools/lib_package/README.md +tensorflow/tools/lib_package/libtensorflow_java_test.sh +tensorflow/tools/def_file_filter/def_file_filter_configure.bzl +tensorflow/tools/def_file_filter/BUILD +tensorflow/tools/def_file_filter/BUILD.tpl +tensorflow/tools/def_file_filter/def_file_filter.py.tpl +tensorflow/third_party/mkl/MKL_LICENSE +tensorflow/third_party/mkl/LICENSE +tensorflow/third_party/mkl/BUILD +tensorflow/third_party/mkl/mkl.BUILD +tensorflow/third_party/mkl/build_defs.bzl +tensorflow/third_party/backports_weakref.BUILD +tensorflow/third_party/toolchains/clang6/BUILD tensorflow/third_party/toolchains/clang6/README.md tensorflow/third_party/toolchains/clang6/repo.bzl -tensorflow/third_party/toolchains/clang6/clang.BUILD tensorflow/third_party/toolchains/clang6/CROSSTOOL.tpl -tensorflow/third_party/toolchains/clang6/BUILD -tensorflow/third_party/toolchains/preconfig/generate/archives.bzl -tensorflow/third_party/toolchains/preconfig/generate/workspace.bzl -tensorflow/third_party/toolchains/preconfig/generate/generate.bzl -tensorflow/third_party/toolchains/preconfig/generate/containers.bzl -tensorflow/third_party/toolchains/preconfig/generate/BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu14.04/tensorrt5/build_defs.bzl +tensorflow/third_party/toolchains/clang6/clang.BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/tensorrt5/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu14.04/tensorrt5/build_defs.bzl tensorflow/third_party/toolchains/preconfig/ubuntu14.04/py3/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda9.0/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc/BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda9.0-cudnn7/cuda/build_defs.bzl -tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda9.0-cudnn7/cuda/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda10.0-cudnn7/cuda/build_defs.bzl tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda10.0-cudnn7/cuda/BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda9.0/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/gcc-nvcc-cuda10.0/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda9.0-cudnn7/cuda/build_defs.bzl +tensorflow/third_party/toolchains/preconfig/ubuntu14.04/cuda9.0-cudnn7/cuda/BUILD tensorflow/third_party/toolchains/preconfig/ubuntu14.04/nccl2/BUILD +tensorflow/third_party/toolchains/preconfig/generate/workspace.bzl +tensorflow/third_party/toolchains/preconfig/generate/containers.bzl +tensorflow/third_party/toolchains/preconfig/generate/generate.bzl +tensorflow/third_party/toolchains/preconfig/generate/archives.bzl +tensorflow/third_party/toolchains/preconfig/generate/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/BUILD +tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/dummy_toolchain.bzl +tensorflow/third_party/toolchains/preconfig/ubuntu16.04/py3/BUILD +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/preconfig/win_1803/bazel_018/dummy_toolchain.bzl -tensorflow/third_party/toolchains/preconfig/win_1803/bazel_018/BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu16.04/py3/BUILD -tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/dummy_toolchain.bzl -tensorflow/third_party/toolchains/preconfig/ubuntu16.04/clang/BUILD -tensorflow/third_party/toolchains/cpus/py3/BUILD -tensorflow/third_party/toolchains/cpus/arm/CROSSTOOL.tpl -tensorflow/third_party/toolchains/cpus/arm/BUILD -tensorflow/third_party/toolchains/cpus/arm/arm_compiler_configure.bzl -tensorflow/third_party/toolchains/cpus/py/BUILD -tensorflow/third_party/toolchains/gpus/crosstool/CROSSTOOL -tensorflow/third_party/toolchains/gpus/crosstool/BUILD tensorflow/third_party/toolchains/gpus/cuda/build_defs.bzl -tensorflow/third_party/toolchains/gpus/cuda/cuda/cuda_config.h 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 +tensorflow/third_party/toolchains/cpus/py3/BUILD +tensorflow/third_party/toolchains/cpus/py/BUILD tensorflow/third_party/toolchains/BUILD -tensorflow/third_party/nccl/archive.BUILD -tensorflow/third_party/nccl/nccl_configure.bzl -tensorflow/third_party/nccl/system.BUILD.tpl -tensorflow/third_party/nccl/build_defs.bzl.tpl -tensorflow/third_party/nccl/LICENSE -tensorflow/third_party/nccl/BUILD -tensorflow/third_party/farmhash.BUILD -tensorflow/third_party/sycl/crosstool/BUILD -tensorflow/third_party/backports_weakref.BUILD -tensorflow/third_party/gast.BUILD -tensorflow/third_party/clang_toolchain/cc_configure_clang.bzl -tensorflow/third_party/clang_toolchain/download_clang.bzl -tensorflow/third_party/clang_toolchain/BUILD -tensorflow/third_party/mpi_collectives/BUILD -tensorflow/third_party/jsoncpp.BUILD -tensorflow/third_party/mkl_dnn/mkldnn.BUILD -tensorflow/third_party/mkl_dnn/LICENSE -tensorflow/third_party/python_runtime/BUILD -tensorflow/third_party/ngraph/ngraph.BUILD -tensorflow/third_party/ngraph/nlohmann_json.BUILD -tensorflow/third_party/ngraph/build_defs.bzl -tensorflow/third_party/ngraph/ngraph_tf.BUILD -tensorflow/third_party/ngraph/NGRAPH_LICENSE -tensorflow/third_party/ngraph/tbb.BUILD -tensorflow/third_party/ngraph/LICENSE -tensorflow/third_party/ngraph/BUILD -tensorflow/third_party/gpus/crosstool/windows/msvc_wrapper_for_nvcc.py.tpl -tensorflow/third_party/gpus/crosstool/CROSSTOOL.tpl -tensorflow/third_party/gpus/crosstool/BUILD.tpl -tensorflow/third_party/gpus/crosstool/remote.BUILD.tpl +tensorflow/third_party/gpus/BUILD tensorflow/third_party/gpus/crosstool/clang/bin/crosstool_wrapper_driver_rocm.tpl tensorflow/third_party/gpus/crosstool/clang/bin/crosstool_wrapper_driver_is_not_gcc.tpl +tensorflow/third_party/gpus/crosstool/CROSSTOOL.tpl tensorflow/third_party/gpus/crosstool/CROSSTOOL_hipcc.tpl tensorflow/third_party/gpus/crosstool/LICENSE +tensorflow/third_party/gpus/crosstool/remote.BUILD.tpl +tensorflow/third_party/gpus/crosstool/windows/msvc_wrapper_for_nvcc.py.tpl +tensorflow/third_party/gpus/crosstool/BUILD.tpl tensorflow/third_party/gpus/crosstool/BUILD -tensorflow/third_party/gpus/rocm/BUILD.tpl -tensorflow/third_party/gpus/rocm/rocm_config.h.tpl -tensorflow/third_party/gpus/rocm/build_defs.bzl.tpl -tensorflow/third_party/gpus/rocm/BUILD +tensorflow/third_party/gpus/cuda/LICENSE tensorflow/third_party/gpus/cuda/BUILD.tpl -tensorflow/third_party/gpus/cuda/build_defs.bzl.tpl -tensorflow/third_party/gpus/cuda/remote.BUILD.tpl tensorflow/third_party/gpus/cuda/BUILD.windows.tpl tensorflow/third_party/gpus/cuda/cuda_config.h.tpl -tensorflow/third_party/gpus/cuda/LICENSE +tensorflow/third_party/gpus/cuda/remote.BUILD.tpl tensorflow/third_party/gpus/cuda/BUILD +tensorflow/third_party/gpus/cuda/build_defs.bzl.tpl +tensorflow/third_party/gpus/rocm/rocm_config.h.tpl +tensorflow/third_party/gpus/rocm/BUILD +tensorflow/third_party/gpus/rocm/BUILD.tpl +tensorflow/third_party/gpus/rocm/build_defs.bzl.tpl tensorflow/third_party/gpus/cuda_configure.bzl tensorflow/third_party/gpus/rocm_configure.bzl -tensorflow/third_party/gpus/BUILD -tensorflow/third_party/py/python_configure.bzl -tensorflow/third_party/py/BUILD.tpl -tensorflow/third_party/py/numpy/BUILD -tensorflow/third_party/py/remote.BUILD.tpl -tensorflow/third_party/py/BUILD -tensorflow/third_party/mkl/build_defs.bzl -tensorflow/third_party/mkl/mkl.BUILD -tensorflow/third_party/mkl/MKL_LICENSE -tensorflow/third_party/mkl/LICENSE -tensorflow/third_party/mkl/BUILD -tensorflow/third_party/com_google_absl.BUILD -tensorflow/third_party/six.BUILD -tensorflow/third_party/lmdb.BUILD -tensorflow/third_party/BUILD -tensorflow/third_party/googleapis.BUILD -tensorflow/third_party/sqlite.BUILD -tensorflow/third_party/termcolor.BUILD -tensorflow/third_party/protobuf/BUILD -tensorflow/third_party/pcre.BUILD -tensorflow/third_party/git/BUILD.tpl -tensorflow/third_party/git/git_configure.bzl -tensorflow/third_party/git/BUILD -tensorflow/third_party/pprof.BUILD -tensorflow/third_party/tflite_smartreply.BUILD -tensorflow/third_party/eigen3/Eigen/Core +tensorflow/third_party/snappy.BUILD +tensorflow/third_party/cython.BUILD +tensorflow/third_party/farmhash.BUILD tensorflow/third_party/eigen3/Eigen/Cholesky -tensorflow/third_party/eigen3/Eigen/Eigenvalues -tensorflow/third_party/eigen3/Eigen/SVD -tensorflow/third_party/eigen3/Eigen/LU tensorflow/third_party/eigen3/Eigen/QR -tensorflow/third_party/eigen3/unsupported/Eigen/MatrixFunctions +tensorflow/third_party/eigen3/Eigen/LU +tensorflow/third_party/eigen3/Eigen/Core +tensorflow/third_party/eigen3/Eigen/SVD +tensorflow/third_party/eigen3/Eigen/Eigenvalues +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/FixedPoint tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/Tensor -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/ThreadPool +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/PacketMathAVX512.h tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/TypeCastingAVX512.h +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/PacketMathAVX2.h tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatMatProduct.h -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatMatProductNEON.h -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/PacketMathAVX512.h +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/FixedPointTypes.h tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatVecProduct.h +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatMatProductNEON.h tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/TypeCastingAVX2.h -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/FixedPointTypes.h tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/MatMatProductAVX2.h -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/src/FixedPoint/PacketMathAVX2.h -tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/FixedPoint +tensorflow/third_party/eigen3/unsupported/Eigen/CXX11/ThreadPool tensorflow/third_party/eigen3/unsupported/Eigen/SpecialFunctions +tensorflow/third_party/eigen3/unsupported/Eigen/MatrixFunctions tensorflow/third_party/eigen3/LICENSE tensorflow/third_party/eigen3/BUILD -tensorflow/tools/pip_package/simple_console.py -tensorflow/tools/pip_package/setup.py -tensorflow/tools/pip_package/MANIFEST.in -tensorflow/tools/pip_package/README -tensorflow/tools/pip_package/check_load_py_test.py -tensorflow/tools/pip_package/pip_smoke_test.py -tensorflow/tools/pip_package/BUILD -tensorflow/tools/pip_package/build_pip_package.sh -tensorflow/tools/pip_package/simple_console_for_windows.py -tensorflow/tools/lib_package/LibTensorFlowTest.java -tensorflow/tools/lib_package/README.md -tensorflow/tools/lib_package/libtensorflow_test.sh -tensorflow/tools/lib_package/libtensorflow_java_test.sh -tensorflow/tools/lib_package/libtensorflow_test.c -tensorflow/tools/lib_package/concat_licenses.sh -tensorflow/tools/lib_package/BUILD -tensorflow/tools/ci_build/remote/BUILD -tensorflow/tools/def_file_filter/BUILD.tpl -tensorflow/tools/def_file_filter/def_file_filter_configure.bzl -tensorflow/tools/def_file_filter/def_file_filter.py.tpl -tensorflow/tools/def_file_filter/BUILD -tensorflow/contrib/mpi/BUILD -tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/main.py -tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/__init__.py -tensorflow/contrib/tpu/profiler/pip_package/setup.py -tensorflow/contrib/tpu/profiler/pip_package/README -tensorflow/contrib/tpu/profiler/pip_package/BUILD -tensorflow/contrib/tpu/profiler/pip_package/build_pip_package.sh +tensorflow/third_party/systemlibs/build_defs.bzl.tpl +tensorflow/third_party/systemlibs/absl_py.BUILD +tensorflow/third_party/systemlibs/curl.BUILD +tensorflow/third_party/systemlibs/termcolor.BUILD +tensorflow/third_party/systemlibs/absl_py.absl.flags.BUILD +tensorflow/third_party/systemlibs/grpc.BUILD +tensorflow/third_party/systemlibs/swig.BUILD +tensorflow/third_party/systemlibs/protobuf.bzl +tensorflow/third_party/systemlibs/protobuf.BUILD +tensorflow/third_party/systemlibs/BUILD +tensorflow/third_party/systemlibs/google_cloud_cpp.BUILD +tensorflow/third_party/systemlibs/astor.BUILD +tensorflow/third_party/systemlibs/six.BUILD +tensorflow/third_party/systemlibs/absl_py.absl.testing.BUILD +tensorflow/third_party/systemlibs/boringssl.BUILD +tensorflow/third_party/systemlibs/nsync.BUILD +tensorflow/third_party/systemlibs/google_cloud_cpp.google.cloud.bigtable.BUILD +tensorflow/third_party/systemlibs/gif.BUILD +tensorflow/third_party/systemlibs/pcre.BUILD +tensorflow/third_party/systemlibs/BUILD.tpl +tensorflow/third_party/systemlibs/snappy.BUILD +tensorflow/third_party/systemlibs/gast.BUILD +tensorflow/third_party/systemlibs/cython.BUILD +tensorflow/third_party/systemlibs/double_conversion.BUILD +tensorflow/third_party/systemlibs/zlib.BUILD +tensorflow/third_party/systemlibs/jsoncpp.BUILD +tensorflow/third_party/systemlibs/re2.BUILD +tensorflow/third_party/systemlibs/lmdb.BUILD +tensorflow/third_party/systemlibs/googleapis.BUILD +tensorflow/third_party/systemlibs/png.BUILD +tensorflow/third_party/systemlibs/syslibs_configure.bzl +tensorflow/third_party/systemlibs/sqlite.BUILD +tensorflow/third_party/python_runtime/BUILD +tensorflow/third_party/sycl/crosstool/BUILD +tensorflow/third_party/ngraph/LICENSE +tensorflow/third_party/ngraph/tbb.BUILD +tensorflow/third_party/ngraph/BUILD +tensorflow/third_party/ngraph/ngraph.BUILD +tensorflow/third_party/ngraph/build_defs.bzl +tensorflow/third_party/ngraph/NGRAPH_LICENSE +tensorflow/third_party/ngraph/ngraph_tf.BUILD +tensorflow/third_party/ngraph/nlohmann_json.BUILD +tensorflow/third_party/clang_toolchain/download_clang.bzl +tensorflow/third_party/clang_toolchain/BUILD +tensorflow/third_party/clang_toolchain/cc_configure_clang.bzl +tensorflow/third_party/gast.BUILD +tensorflow/third_party/llvm/BUILD +tensorflow/third_party/llvm/expand_cmake_vars.py +tensorflow/third_party/llvm/llvm.autogenerated.BUILD +tensorflow/third_party/llvm/llvm.bzl +tensorflow/third_party/icu/udata.patch +tensorflow/third_party/nccl/archive.BUILD +tensorflow/third_party/nccl/LICENSE +tensorflow/third_party/nccl/system.BUILD.tpl +tensorflow/third_party/nccl/nccl_configure.bzl +tensorflow/third_party/nccl/build_defs.bzl.tpl +tensorflow/third_party/nccl/BUILD +tensorflow/third_party/fft2d/BUILD +tensorflow/third_party/fft2d/fft.h +tensorflow/third_party/fft2d/LICENSE +tensorflow/third_party/fft2d/fft2d.BUILD +tensorflow/third_party/boringssl/BUILD +tensorflow/third_party/mpi/.gitignore +tensorflow/third_party/mpi/BUILD +tensorflow/third_party/tensorrt/LICENSE +tensorflow/third_party/tensorrt/BUILD +tensorflow/third_party/tensorrt/build_defs.bzl.tpl +tensorflow/third_party/tensorrt/BUILD.tpl +tensorflow/third_party/tensorrt/tensorrt_configure.bzl +tensorflow/third_party/tensorrt/remote.BUILD.tpl +tensorflow/third_party/kafka/config.patch +tensorflow/third_party/kafka/BUILD +tensorflow/third_party/android/BUILD +tensorflow/third_party/android/android.bzl.tpl +tensorflow/third_party/android/android_configure.bzl +tensorflow/third_party/android/android_configure.BUILD.tpl +tensorflow/third_party/tflite_smartreply.BUILD +tensorflow/third_party/mkl_dnn/LICENSE +tensorflow/third_party/mkl_dnn/mkldnn.BUILD +tensorflow/third_party/pcre.BUILD +tensorflow/third_party/linenoise.BUILD +tensorflow/third_party/sqlite.BUILD +tensorflow/third_party/common.bzl +tensorflow/third_party/com_google_absl.BUILD +tensorflow/third_party/pprof.BUILD +tensorflow/third_party/BUILD +tensorflow/third_party/tflite_mobilenet_quant.BUILD +tensorflow/third_party/lmdb.BUILD +tensorflow/third_party/git/BUILD.tpl +tensorflow/third_party/git/BUILD +tensorflow/third_party/git/git_configure.bzl +tensorflow/third_party/protobuf/BUILD +tensorflow/third_party/tflite_mobilenet.BUILD +tensorflow/third_party/py/BUILD +tensorflow/third_party/py/BUILD.tpl +tensorflow/third_party/py/remote.BUILD.tpl +tensorflow/third_party/py/numpy/BUILD +tensorflow/third_party/py/python_configure.bzl +tensorflow/third_party/termcolor.BUILD +tensorflow/third_party/png_fix_rpi.patch +tensorflow/third_party/swig.BUILD +tensorflow/third_party/astor.BUILD +tensorflow/third_party/grpc/BUILD +tensorflow/third_party/curl.BUILD +tensorflow/third_party/arm_neon_2_x86_sse.BUILD +tensorflow/third_party/png.BUILD +tensorflow/third_party/googleapis.BUILD +tensorflow/third_party/mpi_collectives/BUILD +tensorflow/third_party/nanopb.BUILD +tensorflow/third_party/gif.BUILD +tensorflow/third_party/double_conversion.BUILD +tensorflow/third_party/six.BUILD +tensorflow/third_party/tflite_mobilenet_float.BUILD +tensorflow/third_party/repo.bzl +tensorflow/third_party/codegen.BUILD +tensorflow/third_party/cub.BUILD +tensorflow/third_party/jsoncpp.BUILD +tensorflow/third_party/tflite_ovic_testdata.BUILD +tensorflow/third_party/libxsmm.BUILD +tensorflow/third_party/zlib.BUILD +tensorflow/third_party/eigen.BUILD +tensorflow/stream_executor/BUILD +tensorflow/api_template_v1.__init__.py +tensorflow/compat_template_v1.__init__.py tensorflow/api_template.__init__.py tensorflow/__init__.py \ No newline at end of file -- GitLab From 68340a644560241ca7098bb2d358a3cb6fb42a4d Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Mon, 7 Jan 2019 11:20:59 -0800 Subject: [PATCH 0266/2345] Move the fit-scope check to only error our when using numpy PiperOrigin-RevId: 228200832 --- .../python/keras/engine/training_distributed.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/python/keras/engine/training_distributed.py b/tensorflow/python/keras/engine/training_distributed.py index 9f63f7bada..92a46e399a 100644 --- a/tensorflow/python/keras/engine/training_distributed.py +++ b/tensorflow/python/keras/engine/training_distributed.py @@ -53,14 +53,14 @@ def fit_distributed(model, steps_per_epoch=None, validation_steps=None): """Fit loop for Distribution Strategies.""" - # TODO(b/122314600): Remove the scope validate. - distributed_training_utils.validate_not_in_strategy_scope() distributed_training_utils.validate_callbacks(callbacks, model.optimizer) distributed_training_utils.validate_inputs( x, y, model._distribution_strategy) first_x_value = nest.flatten(x)[0] if isinstance(first_x_value, np.ndarray): + # TODO(b/122314600): Remove the scope validate. + distributed_training_utils.validate_not_in_strategy_scope() steps_per_epoch, batch_size = ( distributed_training_utils.get_input_params( model._distribution_strategy, first_x_value, steps_per_epoch, @@ -138,11 +138,11 @@ def evaluate_distributed(model, steps=None, callbacks=None): """Evaluate loop for Distribution Strategies.""" - # TODO(b/122314600): Remove the scope validate. - distributed_training_utils.validate_not_in_strategy_scope() distributed_training_utils.validate_inputs(x, y, model._distribution_strategy) first_x_value = nest.flatten(x)[0] if isinstance(first_x_value, np.ndarray): + # TODO(b/122314600): Remove the scope validate. + distributed_training_utils.validate_not_in_strategy_scope() steps, batch_size = distributed_training_utils.get_input_params( model._distribution_strategy, first_x_value, steps, batch_size) batch_size = model._validate_or_infer_batch_size(batch_size, steps, x) @@ -175,12 +175,12 @@ def predict_distributed(model, steps=None, callbacks=None): """Predict loop for Distribution Strategies.""" - # TODO(b/122314600): Remove the scope validate. - distributed_training_utils.validate_not_in_strategy_scope() distributed_training_utils.validate_inputs( x, None, model._distribution_strategy) first_x_value = nest.flatten(x)[0] if isinstance(first_x_value, np.ndarray): + # TODO(b/122314600): Remove the scope validate. + distributed_training_utils.validate_not_in_strategy_scope() steps, batch_size = distributed_training_utils.get_input_params( model._distribution_strategy, first_x_value, steps, batch_size) batch_size = model._validate_or_infer_batch_size(batch_size, steps, x) -- GitLab From 2c0a8c16477999cc791705112f501d4530649938 Mon Sep 17 00:00:00 2001 From: Karim Nosir Date: Mon, 7 Jan 2019 11:29:07 -0800 Subject: [PATCH 0267/2345] Add Unique Op implementation PiperOrigin-RevId: 228202673 --- tensorflow/lite/build_def.bzl | 1 + tensorflow/lite/kernels/BUILD | 12 ++ tensorflow/lite/kernels/register.cc | 2 + tensorflow/lite/kernels/unique.cc | 164 ++++++++++++++++++ tensorflow/lite/kernels/unique_test.cc | 103 +++++++++++ tensorflow/lite/testing/generate_examples.py | 49 ++++++ .../propagate_array_data_types.cc | 8 + .../propagate_fixed_sizes.cc | 17 ++ tensorflow/lite/toco/import_tensorflow.cc | 113 ++++++------ tensorflow/lite/toco/model.h | 14 +- tensorflow/lite/toco/tflite/operator.cc | 28 +++ tensorflow/lite/toco/tflite/operator_test.cc | 9 + tensorflow/lite/toco/tooling_util.cc | 1 + 13 files changed, 467 insertions(+), 54 deletions(-) create mode 100644 tensorflow/lite/kernels/unique.cc create mode 100644 tensorflow/lite/kernels/unique_test.cc diff --git a/tensorflow/lite/build_def.bzl b/tensorflow/lite/build_def.bzl index c17eddf47b..343ec60f2a 100644 --- a/tensorflow/lite/build_def.bzl +++ b/tensorflow/lite/build_def.bzl @@ -311,6 +311,7 @@ def generated_test_models(): "topk", "transpose", "transpose_conv", + "unique", "unpack", "unroll_batch_matmul", "where", diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD index 9abc33d297..6809a4a3d0 100644 --- a/tensorflow/lite/kernels/BUILD +++ b/tensorflow/lite/kernels/BUILD @@ -221,6 +221,7 @@ cc_library( "transpose_conv.cc", "unidirectional_sequence_lstm.cc", "unidirectional_sequence_rnn.cc", + "unique.cc", "unpack.cc", "zeros_like.cc", ], @@ -1233,6 +1234,17 @@ tf_cc_test( ], ) +tf_cc_test( + name = "unique_test", + srcs = ["unique_test.cc"], + deps = [ + ":builtin_ops", + ":test_util", + "//tensorflow/lite:framework", + "@com_google_googletest//:gtest_main", + ], +) + filegroup( name = "all_files", srcs = glob( diff --git a/tensorflow/lite/kernels/register.cc b/tensorflow/lite/kernels/register.cc index 47296fa105..f17f39fc2b 100644 --- a/tensorflow/lite/kernels/register.cc +++ b/tensorflow/lite/kernels/register.cc @@ -129,6 +129,7 @@ TfLiteRegistration* Register_LEAKY_RELU(); TfLiteRegistration* Register_SQUARED_DIFFERENCE(); TfLiteRegistration* Register_FILL(); TfLiteRegistration* Register_MIRROR_PAD(); +TfLiteRegistration* Register_UNIQUE(); TfLiteStatus UnsupportedTensorFlowOp(TfLiteContext* context, TfLiteNode* node) { context->ReportError( @@ -284,6 +285,7 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_SQUARED_DIFFERENCE, Register_SQUARED_DIFFERENCE()); AddBuiltin(BuiltinOperator_FILL, Register_FILL()); AddBuiltin(BuiltinOperator_MIRROR_PAD, Register_MIRROR_PAD()); + AddBuiltin(BuiltinOperator_UNIQUE, Register_UNIQUE()); // TODO(andrewharp, ahentz): Move these somewhere more appropriate so that // custom ops aren't always included by default. diff --git a/tensorflow/lite/kernels/unique.cc b/tensorflow/lite/kernels/unique.cc new file mode 100644 index 0000000000..80c033aa5c --- /dev/null +++ b/tensorflow/lite/kernels/unique.cc @@ -0,0 +1,164 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include + +#include "tensorflow/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 unique { + +void* Init(TfLiteContext* context, const char* buffer, size_t length) { + return nullptr; +} + +void Free(TfLiteContext* context, void* buffer) {} + +TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { + static const int kOutputUniqueTensor = 0; + static const int kOutputIndexTensor = 1; + + TF_LITE_ENSURE_EQ(context, NumInputs(node), 1); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 2); + const TfLiteTensor* input = GetInput(context, node, 0); + TfLiteTensor* output_unique_tensor = + GetOutput(context, node, kOutputUniqueTensor); + TfLiteTensor* output_index_tensor = + GetOutput(context, node, kOutputIndexTensor); + + // The op only supports 1D input. + TF_LITE_ENSURE_EQ(context, NumDimensions(input), 1); + TfLiteIntArray* output_index_shape = TfLiteIntArrayCopy(input->dims); + // The unique values are determined during evaluation, so we don't know yet + // the size of the output tensor. + SetTensorToDynamic(output_unique_tensor); + return context->ResizeTensor(context, output_index_tensor, + output_index_shape); +} + +namespace { + +// Actual evaluation for the unique op. +template +TfLiteStatus EvalImpl(TfLiteContext* context, const TfLiteTensor* input, + TfLiteNode* node) { + // Map from value, to index in the unique elements vector. + // Note that we prefer to use map than unordered_map as it showed less + // increase in the binary size. + std::map unique_values; + TfLiteTensor* output_indexes = GetOutput(context, node, 1); + I* indexes = GetTensorData(output_indexes); + const T* data = GetTensorData(input); + const int num_elements = NumElements(input); + + for (int i = 0; i < num_elements; ++i) { + const auto element_it = unique_values.find(data[i]); + if (element_it != unique_values.end()) { + indexes[i] = element_it->second; + } else { + const int unique_index = unique_values.size(); + unique_values[data[i]] = unique_index; + indexes[i] = unique_index; + } + } + // Allocate output tensor. + TfLiteTensor* unique_output = GetOutput(context, node, 0); + std::unique_ptr shape( + TfLiteIntArrayCreate(NumDimensions(input)), TfLiteIntArrayFree); + shape->data[0] = unique_values.size(); + TF_LITE_ENSURE_STATUS( + context->ResizeTensor(context, unique_output, shape.release())); + // Set the values in the output tensor. + T* output_unique_values = GetTensorData(unique_output); + for (int i = 0; i < unique_values.size(); ++i) { + output_unique_values[i] = data[indexes[i]]; + } + return kTfLiteOk; +} + +template +TfLiteStatus EvalImpl(TfLiteContext* context, const TfLiteTensor* input, + TfLiteNode* node) { + auto* params = reinterpret_cast(node->builtin_data); + if (params == nullptr) { + context->ReportError(context, "Null params passed"); + return kTfLiteError; + } + switch (params->index_out_type) { + case kTfLiteInt32: + return EvalImpl(context, input, node); + case kTfLiteInt64: + return EvalImpl(context, input, node); + default: + context->ReportError( + context, + "Unique index output array can only be Int32 or In64, requested: ", + TfLiteTypeGetName(params->index_out_type)); + } + return kTfLiteError; +} + +} // namespace + +TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { + const TfLiteTensor* input = GetInput(context, node, 0); + TfLiteTensor* output_index_tensor = GetOutput(context, node, 1); + TF_LITE_ENSURE_EQ(context, NumElements(output_index_tensor), + NumElements(input)); + + switch (input->type) { + case kTfLiteInt8: + TF_LITE_ENSURE_STATUS(EvalImpl(context, input, node)); + break; + case kTfLiteInt16: + TF_LITE_ENSURE_STATUS(EvalImpl(context, input, node)); + break; + case kTfLiteInt32: + TF_LITE_ENSURE_STATUS(EvalImpl(context, input, node)); + break; + case kTfLiteInt64: + TF_LITE_ENSURE_STATUS(EvalImpl(context, input, node)); + break; + case kTfLiteFloat32: + TF_LITE_ENSURE_STATUS(EvalImpl(context, input, node)); + break; + case kTfLiteUInt8: + TF_LITE_ENSURE_STATUS(EvalImpl(context, input, node)); + break; + default: + context->ReportError(context, "Currently Unique doesn't support type: %s", + TfLiteTypeGetName(input->type)); + return kTfLiteError; + } + return kTfLiteOk; +} + +} // namespace unique + +TfLiteRegistration* Register_UNIQUE() { + static TfLiteRegistration r = {unique::Init, unique::Free, unique::Prepare, + unique::Eval}; + return &r; +} + +} // namespace builtin +} // namespace ops +} // namespace tflite diff --git a/tensorflow/lite/kernels/unique_test.cc b/tensorflow/lite/kernels/unique_test.cc new file mode 100644 index 0000000000..1df5e6b796 --- /dev/null +++ b/tensorflow/lite/kernels/unique_test.cc @@ -0,0 +1,103 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include +#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::ElementsAreArray; + +template +class UniqueOpModel : public SingleOpModel { + public: + UniqueOpModel(const TensorData& input, TensorType input_type, + TensorType index_out_type) { + input_id_ = AddInput(input); + output_id_ = AddOutput(input_type); + output_index_id_ = AddOutput(index_out_type); + SetBuiltinOp(BuiltinOperator_UNIQUE, BuiltinOptions_UniqueOptions, + CreateUniqueOptions(builder_, index_out_type).Union()); + BuildInterpreter({GetShape(input_id_)}); + } + + int input_tensor_id() { return input_id_; } + + std::vector GetOutput() { return ExtractVector(output_id_); } + std::vector GetIndexesOutput() { + return ExtractVector(output_index_id_); + } + + protected: + int input_id_; + int output_id_; + int output_index_id_; +}; + +TEST(UniqueOpModelTest, OneElement) { + UniqueOpModel model({TensorType_FLOAT32, {1}}, + TensorType_FLOAT32, TensorType_INT32); + model.PopulateTensor(model.input_tensor_id(), {5}); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5})); + EXPECT_THAT(model.GetIndexesOutput(), ElementsAreArray({0})); +} + +TEST(UniqueOpModelTest, MultipleElements_AllUnique) { + UniqueOpModel model({TensorType_FLOAT32, {8}}, + TensorType_FLOAT32, TensorType_INT32); + model.PopulateTensor(model.input_tensor_id(), + {5, 2, 3, 51, 6, 72, 7, 8}); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5, 2, 3, 51, 6, 72, 7, 8})); + EXPECT_THAT(model.GetIndexesOutput(), + ElementsAreArray({0, 1, 2, 3, 4, 5, 6, 7})); +} + +TEST(UniqueOpModelTest, MultipleElements_AllDuplicates) { + UniqueOpModel model({TensorType_FLOAT32, {7}}, + TensorType_FLOAT32, TensorType_INT32); + model.PopulateTensor(model.input_tensor_id(), {5, 5, 5, 5, 5, 5, 5}); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5})); + EXPECT_THAT(model.GetIndexesOutput(), + ElementsAreArray({0, 0, 0, 0, 0, 0, 0})); +} + +TEST(UniqueOpModelTest, MultipleElements_SomeDuplicates) { + UniqueOpModel model({TensorType_FLOAT32, {7}}, + TensorType_FLOAT32, TensorType_INT32); + model.PopulateTensor(model.input_tensor_id(), {2, 3, 5, 7, 2, 7, 3}); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), ElementsAreArray({2, 3, 5, 7})); + EXPECT_THAT(model.GetIndexesOutput(), + ElementsAreArray({0, 1, 2, 3, 0, 3, 1})); +} + +TEST(UniqueOpModelTest, MultipleElements_SomeDuplicates_IndexInt64) { + UniqueOpModel model({TensorType_FLOAT32, {7}}, + TensorType_FLOAT32, TensorType_INT64); + model.PopulateTensor(model.input_tensor_id(), {2, 3, 5, 7, 2, 7, 3}); + model.Invoke(); + EXPECT_THAT(model.GetOutput(), ElementsAreArray({2, 3, 5, 7})); + EXPECT_THAT(model.GetIndexesOutput(), + ElementsAreArray({0, 1, 2, 3, 0, 3, 1})); +} + +} // namespace +} // namespace tflite diff --git a/tensorflow/lite/testing/generate_examples.py b/tensorflow/lite/testing/generate_examples.py index dd7b3d0745..87e7d7eb02 100644 --- a/tensorflow/lite/testing/generate_examples.py +++ b/tensorflow/lite/testing/generate_examples.py @@ -3749,6 +3749,55 @@ def make_placeholder_with_default_tests(zip_path): expected_tf_success=3) +def make_unique_tests(zip_path): + """Make a set of tests for Unique op.""" + + test_parameters = [ + { + "input_shape": [[1]], + "index_type": [tf.int32, tf.int64, None], + "input_values": [3] + }, + { + "input_shape": [[5]], + "index_type": [tf.int32, tf.int64], + "input_values": [[3, 2, 1, 2, 3]] + }, + { + "input_shape": [[7]], + "index_type": [tf.int32, tf.int64], + "input_values": [[1, 1, 1, 1, 1, 1, 1]] + }, + { + "input_shape": [[5]], + "index_type": [tf.int32, tf.int64], + "input_values": [[3, 2, 1, 0, -1]] + }] + + def build_graph(parameters): + """Build the graph for the test case.""" + + input_tensor = tf.placeholder( + dtype=tf.int32, name="input", shape=parameters["input_shape"]) + if parameters["index_type"] is None: + output = tf.unique(input_tensor) + else: + output = tf.unique(input_tensor, parameters["index_type"]) + + return [input_tensor], output + + def build_inputs(parameters, sess, inputs, outputs): + input_values = [create_tensor_data(tf.int32, parameters["input_shape"])] + return input_values, sess.run( + outputs, feed_dict=dict(zip(inputs, input_values))) + + make_zip_of_tests( + zip_path, + test_parameters, + build_graph, + build_inputs, + expected_tf_success=9) + # Toco binary path provided by the generate rule. bin_path = None diff --git a/tensorflow/lite/toco/graph_transformations/propagate_array_data_types.cc b/tensorflow/lite/toco/graph_transformations/propagate_array_data_types.cc index cbae6610d7..6d9aad66b6 100644 --- a/tensorflow/lite/toco/graph_transformations/propagate_array_data_types.cc +++ b/tensorflow/lite/toco/graph_transformations/propagate_array_data_types.cc @@ -252,6 +252,14 @@ void SetDataTypeForAllOutputs(Model* model, Operator* op, SetDataTypeForAllOutputs(model, op, data_type); break; } + case OperatorType::kUnique: { + CHECK_EQ(op->outputs.size(), 2); + const UniqueOperator* unique_op = static_cast(op); + const ArrayDataType data_type = model->GetArray(op->inputs[0]).data_type; + model->GetArray(op->outputs[0]).data_type = data_type; + model->GetArray(op->outputs[1]).data_type = unique_op->idx_out_type; + break; + } default: { // These operators produce outputs with the same type as their 1st input CHECK_GT(op->inputs.size(), 0); diff --git a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc index 0e653f08a0..5185afd22e 100644 --- a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc +++ b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc @@ -1828,6 +1828,20 @@ void ProcessMirrorPadOperator(Model* model, MirrorPadOperator* op) { output_array.copy_shape(output_shape); } +void ProcessUniqueOperator(Model* model, UniqueOperator* op) { + const auto& input_array = model->GetArray(op->inputs[0]); + // We have 2 outputs, the shape of the index tensor, is the same size + // as the input array. The unique values tensor, is unknown until runtime. + CHECK_EQ(op->outputs.size(), 2); + auto& idx_output_array = model->GetArray(op->outputs[1]); + + // Yield until input dims have been resolved, or output already computed + if (!input_array.has_shape() || idx_output_array.has_shape()) { + return; + } + idx_output_array.copy_shape(input_array.shape()); +} + } // namespace ::tensorflow::Status PropagateFixedSizes::Run(Model* model, @@ -2103,6 +2117,9 @@ void ProcessMirrorPadOperator(Model* model, MirrorPadOperator* op) { case OperatorType::kMirrorPad: ProcessMirrorPadOperator(model, static_cast(op)); break; + case OperatorType::kUnique: + ProcessUniqueOperator(model, static_cast(op)); + break; default: // Unimplemented, another graph transformation should drop it. LOG(FATAL) << "Unhandled operator type " << OperatorTypeName(op->type); diff --git a/tensorflow/lite/toco/import_tensorflow.cc b/tensorflow/lite/toco/import_tensorflow.cc index e1f7eb82ee..86e04b2393 100644 --- a/tensorflow/lite/toco/import_tensorflow.cc +++ b/tensorflow/lite/toco/import_tensorflow.cc @@ -1190,7 +1190,7 @@ enum FlexSupport { kFlexOk, kFlexNotOk }; // taken from the given NodeDef, and its number must match NumInputs, unless // kAnyNumInputs is passed in. If kFlexOk is passed in the resulting operator // will be eligible for being exported as a flex op. -template +template tensorflow::Status ConvertSimpleOperatorGeneric( const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, Model* model) { @@ -1203,6 +1203,11 @@ tensorflow::Status ConvertSimpleOperatorGeneric( op->inputs.push_back(node.input(i)); } op->outputs.push_back(node.name()); + if (NumOutputs > 1) { + for (int i = 1; i < NumOutputs; ++i) { + op->outputs.push_back(node.name() + ":" + std::to_string(i)); + } + } if (flex == kFlexOk) { RetainTensorFlowNodeDef(node, op); @@ -1213,20 +1218,20 @@ tensorflow::Status ConvertSimpleOperatorGeneric( } // Convert a simple operator which is not valid as a flex op. -template +template tensorflow::Status ConvertSimpleOperator( const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, Model* model) { - return ConvertSimpleOperatorGeneric( + return ConvertSimpleOperatorGeneric( node, tf_import_flags, model); } // Convert a simple operator which is valid as a flex op. -template +template tensorflow::Status ConvertSimpleOperatorFlexOk( const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, Model* model) { - return ConvertSimpleOperatorGeneric( + return ConvertSimpleOperatorGeneric( node, tf_import_flags, model); } @@ -2333,14 +2338,15 @@ ConverterMapType GetTensorFlowNodeConverterMapForFlex() { ConverterMapType GetTensorFlowNodeConverterMap() { return std::unordered_map({ - {"Abs", ConvertSimpleOperator}, - {"Add", ConvertSimpleOperator}, - {"AddN", ConvertSimpleOperatorFlexOk}, - {"All", ConvertSimpleOperator}, + {"Abs", ConvertSimpleOperator}, + {"Add", ConvertSimpleOperator}, + {"AddN", ConvertSimpleOperatorFlexOk}, + {"All", ConvertSimpleOperator}, {"Any", ConvertReduceOperator}, {"ArgMax", ConvertArgMaxOperator}, {"ArgMin", ConvertArgMinOperator}, - {"Assert", ConvertSimpleOperator}, + {"Assert", + ConvertSimpleOperator}, {"AvgPool", ConvertAvgPoolOperator}, {"BatchMatMul", ConvertBatchMatMulOperator}, {"BatchNormWithGlobalNormalization", @@ -2357,98 +2363,99 @@ ConverterMapType GetTensorFlowNodeConverterMap() { {"CTCBeamSearchDecoder", ConvertCTCBeamSearchDecoderOperator}, {"DepthToSpace", ConvertDepthToSpaceOperator}, {"DepthwiseConv2dNative", ConvertDepthwiseConvOperator}, - {"Div", ConvertSimpleOperator}, + {"Div", ConvertSimpleOperator}, {"DynamicPartition", ConvertDynamicPartitionOperator}, {"DynamicStitch", ConvertDynamicStitchOperator}, - {"Equal", ConvertSimpleOperator}, - {"Exp", ConvertSimpleOperator}, - {"ExpandDims", ConvertSimpleOperator}, + {"Equal", ConvertSimpleOperator}, + {"Exp", ConvertSimpleOperator}, + {"ExpandDims", ConvertSimpleOperator}, {"FakeQuantWithMinMaxArgs", ConvertFakeQuantWithMinMaxArgs}, {"FakeQuantWithMinMaxVars", ConvertFakeQuantWithMinMaxVars}, - {"Fill", ConvertSimpleOperator}, + {"Fill", ConvertSimpleOperator}, {"Floor", ConvertFloorOperator}, - {"FloorDiv", ConvertSimpleOperator}, - {"FloorMod", ConvertSimpleOperator}, + {"FloorDiv", ConvertSimpleOperator}, + {"FloorMod", ConvertSimpleOperator}, {"FusedBatchNorm", ConvertFusedBatchNormOperator}, {"Gather", ConvertGatherOperator}, {"GatherV2", ConvertGatherOperator}, - {"Greater", ConvertSimpleOperator}, + {"Greater", ConvertSimpleOperator}, {"GreaterEqual", - ConvertSimpleOperator}, + ConvertSimpleOperator}, {"Identity", ConvertIdentityOperator}, {"LRN", ConvertLRNOperator}, {"LeakyRelu", ConvertLeakyReluOperator}, {"LegacyFedInput", ConvertPlaceholderOperator}, - {"Less", ConvertSimpleOperator}, - {"LessEqual", ConvertSimpleOperator}, - {"Log", ConvertSimpleOperator}, - {"LogicalAnd", ConvertSimpleOperator}, - {"LogicalOr", ConvertSimpleOperator}, - {"LogicalNot", ConvertSimpleOperator}, - {"LogSoftmax", ConvertSimpleOperator}, + {"Less", ConvertSimpleOperator}, + {"LessEqual", ConvertSimpleOperator}, + {"Log", ConvertSimpleOperator}, + {"LogicalAnd", ConvertSimpleOperator}, + {"LogicalOr", ConvertSimpleOperator}, + {"LogicalNot", ConvertSimpleOperator}, + {"LogSoftmax", ConvertSimpleOperator}, {"MatMul", ConvertMatMulOperator}, {"Max", ConvertReduceOperator}, {"MaxPool", ConvertMaxPoolOperator}, - {"Maximum", ConvertSimpleOperator}, + {"Maximum", ConvertSimpleOperator}, {"Mean", ConvertReduceOperator}, - {"Merge", ConvertSimpleOperator}, + {"Merge", ConvertSimpleOperator}, {"Min", ConvertReduceOperator}, - {"Minimum", ConvertSimpleOperator}, - {"Mul", ConvertSimpleOperator}, - {"Neg", ConvertSimpleOperator}, + {"Minimum", ConvertSimpleOperator}, + {"Mul", ConvertSimpleOperator}, + {"Neg", ConvertSimpleOperator}, {"NextIteration", ConvertOperatorSpecialCasedAsRNNBackEdge}, {"NoOp", ConvertNoOpOperator}, - {"NotEqual", ConvertSimpleOperator}, + {"NotEqual", ConvertSimpleOperator}, {"OneHot", ConvertOneHotOperator}, {"Pack", ConvertPackOperator}, - {"Pad", ConvertSimpleOperator}, - {"PadV2", ConvertSimpleOperator}, + {"Pad", ConvertSimpleOperator}, + {"PadV2", ConvertSimpleOperator}, {"ParallelDynamicStitch", ConvertDynamicStitchOperator}, {"Placeholder", ConvertPlaceholderOperator}, {"PlaceholderWithDefault", ConvertIdentityOperator}, - {"Pow", ConvertSimpleOperator}, + {"Pow", ConvertSimpleOperator}, {"Prod", ConvertReduceOperator}, {"RandomUniform", ConvertRandomUniform}, {"Range", ConvertRangeOperator}, - {"Rank", ConvertSimpleOperator}, - {"RealDiv", ConvertSimpleOperator}, - {"Relu", ConvertSimpleOperator}, - {"Relu6", ConvertSimpleOperator}, - {"Reshape", ConvertSimpleOperator}, + {"Rank", ConvertSimpleOperator}, + {"RealDiv", ConvertSimpleOperator}, + {"Relu", ConvertSimpleOperator}, + {"Relu6", ConvertSimpleOperator}, + {"Reshape", ConvertSimpleOperator}, {"ResizeBilinear", ConvertResizeBilinearOperator}, {"ResizeNearestNeighbor", ConvertResizeNearestNeighborOperator}, - {"Rsqrt", ConvertSimpleOperator}, - {"Select", ConvertSimpleOperator}, + {"Rsqrt", ConvertSimpleOperator}, + {"Select", ConvertSimpleOperator}, {"Shape", ConvertShapeOperator}, - {"Sigmoid", ConvertSimpleOperator}, - {"Sin", ConvertSimpleOperator}, - {"Slice", ConvertSimpleOperator}, + {"Sigmoid", ConvertSimpleOperator}, + {"Sin", ConvertSimpleOperator}, + {"Slice", ConvertSimpleOperator}, {"Softmax", ConvertSoftmaxOperator}, {"SpaceToBatchND", ConvertSpaceToBatchNDOperator}, {"SpaceToDepth", ConvertSpaceToDepthOperator}, {"SparseToDense", ConvertSparseToDenseOperator}, {"Split", ConvertSplitOperator}, {"SplitV", ConvertSplitVOperator}, - {"Sqrt", ConvertSimpleOperator}, - {"Square", ConvertSimpleOperator}, + {"Sqrt", ConvertSimpleOperator}, + {"Square", ConvertSimpleOperator}, {"SquaredDifference", - ConvertSimpleOperator}, + ConvertSimpleOperator}, {"Squeeze", ConvertSqueezeOperator}, {"StopGradient", ConvertIdentityOperator}, {"StridedSlice", ConvertStridedSliceOperator}, - {"Sub", ConvertSimpleOperator}, + {"Sub", ConvertSimpleOperator}, {"Sum", ConvertReduceOperator}, {"Svdf", ConvertSvdfOperator}, {"Switch", ConvertSwitchOperator}, - {"Tanh", ConvertSimpleOperator}, - {"Tile", ConvertSimpleOperator}, + {"Tanh", ConvertSimpleOperator}, + {"Tile", ConvertSimpleOperator}, {"TopK", ConvertTopKV2Operator}, {"TopKV2", ConvertTopKV2Operator}, - {"Transpose", ConvertSimpleOperator}, + {"Transpose", ConvertSimpleOperator}, {"Unpack", ConvertUnpackOperator}, - {"ZerosLike", ConvertSimpleOperator}, + {"ZerosLike", ConvertSimpleOperator}, {"UnidirectionalSequenceLstm", ConvertUnidirectionalSequenceLstm}, {"MirrorPad", ConvertMirrorPadOperator}, + {"Unique", ConvertSimpleOperator}, }); } diff --git a/tensorflow/lite/toco/model.h b/tensorflow/lite/toco/model.h index e71d36583e..bfa86c8059 100644 --- a/tensorflow/lite/toco/model.h +++ b/tensorflow/lite/toco/model.h @@ -157,7 +157,8 @@ enum class OperatorType : uint8 { kResizeNearestNeighbor, kLeakyRelu, kAbs, - kMirrorPad + kMirrorPad, + kUnique }; // Helper to deal with TensorFlow arrays using a different ordering of @@ -1953,6 +1954,17 @@ struct MirrorPadOperator : Operator { MirrorPadMode mode; }; +// Unique Operator: +// +// Inputs: +// inputs[0]: required: the input array +// +// TensorFlow equivalent: Unique +struct UniqueOperator : Operator { + UniqueOperator() : Operator(OperatorType::kUnique) {} + ArrayDataType idx_out_type = ArrayDataType::kInt32; +}; + // Alloc's are used for transient arrays only. An Alloc specifies which interval // of the "transient_data" workspace buffer passed to inference functions, is to // be used for the transient array at hand. The 'start' and 'end' values are diff --git a/tensorflow/lite/toco/tflite/operator.cc b/tensorflow/lite/toco/tflite/operator.cc index d3fce6893f..8eb4d321ef 100644 --- a/tensorflow/lite/toco/tflite/operator.cc +++ b/tensorflow/lite/toco/tflite/operator.cc @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/core/util/ptr_util.h" // TODO(ycling): Consider refactoring to extract the LSTM definition out of // graph_transformation module. +#include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/toco/graph_transformations/lstm_utils.h" #include "tensorflow/lite/toco/model.h" #include "tensorflow/lite/toco/tflite/builtin_operator.h" @@ -1478,6 +1479,31 @@ class MirrorPad : MirrorPadMode::kSymmetric; } + int GetVersion(const OperatorSignature& op) const override { return 1; } +}; + +class Unique : public BuiltinOperator { + public: + using BuiltinOperator::BuiltinOperator; + flatbuffers::Offset WriteOptions( + const TocoOperator& op, + flatbuffers::FlatBufferBuilder* builder) const override { + const UniqueOperator& unique_op = static_cast(op); + return ::tflite::CreateUniqueOptions( + *builder, unique_op.idx_out_type == toco::ArrayDataType::kInt64 + ? ::tflite::TensorType::TensorType_INT64 + : ::tflite::TensorType_INT32); + } + void ReadOptions(const TfLiteOptions& options, + TocoOperator* op) const override { + UniqueOperator* unique_op = static_cast(op); + unique_op->idx_out_type = + options.idx_out_type() == ::tflite::TensorType_INT64 + ? toco::ArrayDataType::kInt64 + : toco::ArrayDataType::kInt32; + } + int GetVersion(const OperatorSignature& op_signature) const override { return 1; } @@ -1819,6 +1845,8 @@ std::vector> BuildOperatorList( OperatorType::kSquaredDifference)); ops.push_back(MakeUnique(::tflite::BuiltinOperator_MIRROR_PAD, OperatorType::kMirrorPad)); + ops.push_back(MakeUnique(::tflite::BuiltinOperator_UNIQUE, + OperatorType::kUnique)); // Custom Operators. ops.push_back( diff --git a/tensorflow/lite/toco/tflite/operator_test.cc b/tensorflow/lite/toco/tflite/operator_test.cc index 849eace8cc..215eda82f6 100644 --- a/tensorflow/lite/toco/tflite/operator_test.cc +++ b/tensorflow/lite/toco/tflite/operator_test.cc @@ -629,6 +629,15 @@ TEST_F(OperatorTest, BuiltinMirrorPad) { EXPECT_EQ(op.mode, output_toco_op->mode); } +TEST_F(OperatorTest, BuiltinUnique) { + UniqueOperator op; + op.idx_out_type = ArrayDataType::kInt64; + auto output_toco_op = + SerializeAndDeserialize(GetOperator("UNIQUE", OperatorType::kUnique), op); + ASSERT_NE(nullptr, output_toco_op.get()); + EXPECT_EQ(output_toco_op->idx_out_type, op.idx_out_type); +} + } // namespace } // namespace tflite diff --git a/tensorflow/lite/toco/tooling_util.cc b/tensorflow/lite/toco/tooling_util.cc index af4cd386a2..2396de1a3d 100644 --- a/tensorflow/lite/toco/tooling_util.cc +++ b/tensorflow/lite/toco/tooling_util.cc @@ -416,6 +416,7 @@ const char* OperatorTypeName(OperatorType type) { HANDLE_OPERATORTYPENAME_CASE(LeakyRelu) HANDLE_OPERATORTYPENAME_CASE(SquaredDifference) HANDLE_OPERATORTYPENAME_CASE(MirrorPad) + HANDLE_OPERATORTYPENAME_CASE(Unique) default: LOG(FATAL) << "Unhandled op type"; #undef HANDLE_OPERATORTYPENAME_CASE -- GitLab From b2db5d4089f6cebfabd461401eeac4ed8a976fe1 Mon Sep 17 00:00:00 2001 From: Gaurav Jain Date: Mon, 7 Jan 2019 11:30:46 -0800 Subject: [PATCH 0268/2345] Fix windows build PiperOrigin-RevId: 228203086 --- tensorflow/core/framework/resource_handle.cc | 3 --- tensorflow/core/framework/resource_handle.h | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/framework/resource_handle.cc b/tensorflow/core/framework/resource_handle.cc index a3c48d63d4..fc3a329b3b 100644 --- a/tensorflow/core/framework/resource_handle.cc +++ b/tensorflow/core/framework/resource_handle.cc @@ -19,9 +19,6 @@ limitations under the License. namespace tensorflow { -const char ResourceHandle::ANONYMOUS_NAME[] = - "cd2c89b7-88b7-44c8-ad83-06c2a9158347"; - ResourceHandle::ResourceHandle() {} ResourceHandle::ResourceHandle(const ResourceHandleProto& proto) { diff --git a/tensorflow/core/framework/resource_handle.h b/tensorflow/core/framework/resource_handle.h index c3beffb8ac..d1f6771bf3 100644 --- a/tensorflow/core/framework/resource_handle.h +++ b/tensorflow/core/framework/resource_handle.h @@ -69,7 +69,8 @@ class ResourceHandle { // GUID for anonymous resources. Resources with this shared_name will have // their shared_name replaced with a GUID at creation time - static const char ANONYMOUS_NAME[]; + static constexpr const char* ANONYMOUS_NAME = + "cd2c89b7-88b7-44c8-ad83-06c2a9158347"; public: string device_; -- GitLab From 2325650d59511ad1e19e0102cf920b48cca0bfc5 Mon Sep 17 00:00:00 2001 From: Jian Li Date: Mon, 7 Jan 2019 11:31:30 -0800 Subject: [PATCH 0269/2345] Remove false comments in portable kernel. PiperOrigin-RevId: 228203258 --- .../lite/kernels/internal/reference/portable_tensor_utils.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/lite/kernels/internal/reference/portable_tensor_utils.cc b/tensorflow/lite/kernels/internal/reference/portable_tensor_utils.cc index d692063a96..1acf0caad0 100644 --- a/tensorflow/lite/kernels/internal/reference/portable_tensor_utils.cc +++ b/tensorflow/lite/kernels/internal/reference/portable_tensor_utils.cc @@ -101,7 +101,6 @@ void PortableMatrixBatchVectorMultiplyAccumulate( __builtin_prefetch(row_ptr, 0 /* prefetch for read */, 3 /* temporal locality */); #endif - // For every block of 16 8-bit elements (128-bit register) from each row. for (col = 0; col < m_cols; ++col, ++row_ptr) { dotprod += (*row_ptr) * (vectors[col]); } // for col -- GitLab From e740529d3f09071ef0691a8af24dc566f96cbbec Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Mon, 7 Jan 2019 11:36:12 -0800 Subject: [PATCH 0270/2345] Ensure tf.linspace(start, stop, num)[-1] == stop The contract of tf.linspace says "A sequence of num evenly-spaced values are generated beginning at start. If num > 1, the values in the sequence increase by stop - start / num - 1, so that the last one is exactly stop." But the existing logic computes the last value as `start + (num - 1) * ((start - stop) / (num - 1))`. This exactly equals `stop` for real numbers, but not for floating point numbers. In particular, for start = 0.0, stop = 1.0, num = 42, this reduces to (1.0/41.0)*41.0, and for IEEE 754 32-bit floats, that resolves to ~0.99999994, not 1.0 (binary values 0x3F7FFFFF vs 0x3F800000). After this change, `tf.linspace(start, stop, num)[-1] == stop` holds for all values assuming num > 1, because we just set it directly to `stop`. This matches what numpy does here: https://github.com/numpy/numpy/blob/v1.15.0/numpy/core/function_base.py#L144-L145 PiperOrigin-RevId: 228204335 --- tensorflow/core/kernels/sequence_ops.cc | 9 ++++---- tensorflow/core/kernels/sequence_ops_test.cc | 21 +++++++++++++++++++ .../python/kernel_tests/init_ops_test.py | 16 ++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/kernels/sequence_ops.cc b/tensorflow/core/kernels/sequence_ops.cc index 9db0bd4d98..21c3b89f54 100644 --- a/tensorflow/core/kernels/sequence_ops.cc +++ b/tensorflow/core/kernels/sequence_ops.cc @@ -143,11 +143,12 @@ class LinSpaceOp : public OpKernel { OP_REQUIRES_OK(context, context->allocate_output(0, TensorShape({num}), &out)); auto flat = out->flat(); - if (num == 1) { - flat(0) = start; - } else { + flat(0) = start; + if (num > 1) { const T step = (stop - start) / (num - 1); - for (Tnum i = 0; i < num; ++i) flat(i) = start + step * i; + for (Tnum i = 1; i < num - 1; ++i) flat(i) = start + step * i; + // Ensure final value == stop; float arithmetic won't guarantee this. + flat(num - 1) = stop; } } }; diff --git a/tensorflow/core/kernels/sequence_ops_test.cc b/tensorflow/core/kernels/sequence_ops_test.cc index 5f0e0a69a8..2247c44750 100644 --- a/tensorflow/core/kernels/sequence_ops_test.cc +++ b/tensorflow/core/kernels/sequence_ops_test.cc @@ -114,6 +114,27 @@ TEST_F(LinSpaceOpTest, Simple_D32) { test::ExpectTensorEqual(expected, *GetOutput(0)); } +TEST_F(LinSpaceOpTest, Exact_Endpoints) { + MakeOp(DT_FLOAT, DT_INT32); + + // Feed and run. The particular values 0., 1., and 42 are chosen to test that + // the last value is not calculated via an intermediate delta as (1./41)*41, + // because for IEEE 32-bit floats that returns 0.99999994 != 1.0. + AddInputFromArray(TensorShape({}), {0.0}); + AddInputFromArray(TensorShape({}), {1.0}); + AddInputFromArray(TensorShape({}), {42}); + TF_ASSERT_OK(RunOpKernel()); + + // Check the output + Tensor output = *GetOutput(0); + float expected_start = 0.0; + float start = output.flat()(0); + EXPECT_EQ(expected_start, start) << expected_start << " vs. " << start; + float expected_stop = 1.0; + float stop = output.flat()(output.NumElements() - 1); + EXPECT_EQ(expected_stop, stop) << expected_stop << " vs. " << stop; +} + TEST_F(LinSpaceOpTest, Single_D64) { MakeOp(DT_FLOAT, DT_INT64); diff --git a/tensorflow/python/kernel_tests/init_ops_test.py b/tensorflow/python/kernel_tests/init_ops_test.py index 09b9944baa..4b9681afd2 100644 --- a/tensorflow/python/kernel_tests/init_ops_test.py +++ b/tensorflow/python/kernel_tests/init_ops_test.py @@ -592,6 +592,22 @@ class LinSpaceTest(test.TestCase): self.assertArrayNear(self._LinSpace(5., 5., 3), np.array([5.] * 3), 1e-5) self.assertArrayNear(self._LinSpace(5., 5., 4), np.array([5.] * 4), 1e-5) + def testEndpointsAreExact(self): + for self.force_gpu in self._gpu_modes(): + # Test some cases that produce last values not equal to "stop" when + # computed via start + (num - 1) * ((stop - start) / (num - 1)), since + # float arithmetic will introduce error through precision loss. + self.assertAllEqual( + self._LinSpace(0., 1., 42)[[0, -1]], np.array([0., 1.], np.float32)) + self.assertAllEqual( + self._LinSpace(-1., 0., 42)[[0, -1]], np.array([-1., 0.], np.float32)) + self.assertAllEqual( + self._LinSpace(.1, .2, 4)[[0, -1]], np.array([.1, .2], np.float32)) + # Check a case for float64 error too. + self.assertAllEqual( + self._LinSpace(np.array(0., np.float64), .1, 12)[[0, -1]], + np.array([0., .1], np.float64)) + class DeviceTest(test.TestCase): -- GitLab From 5167dd3495de7e857db8a2b578a08db8139c7d29 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Mon, 7 Jan 2019 11:53:00 -0800 Subject: [PATCH 0271/2345] Remove a remove_checkpoint deprecation warning showing up in a 2.x API Inlines the only useful bits PiperOrigin-RevId: 228207585 --- tensorflow/python/training/checkpoint_management.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/training/checkpoint_management.py b/tensorflow/python/training/checkpoint_management.py index a7ad1f70e5..21fa6b3b5d 100644 --- a/tensorflow/python/training/checkpoint_management.py +++ b/tensorflow/python/training/checkpoint_management.py @@ -621,7 +621,8 @@ class CheckpointManager(object): >= self._last_preserved_timestamp)): self._last_preserved_timestamp = timestamp continue - remove_checkpoint(filename) + _delete_file_if_exists(filename + ".index") + _delete_file_if_exists(filename + ".data-?????-of-?????") def _record_state(self): """Saves the `CheckpointManager`'s state in `directory`.""" -- GitLab From f75b3374770d75b2f5b412ba4b32b497bec8b6aa Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 12:05:42 -0800 Subject: [PATCH 0272/2345] fix spacing PiperOrigin-RevId: 228210397 --- tensorflow/python/grappler/cluster.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/grappler/cluster.i b/tensorflow/python/grappler/cluster.i index 87795ffcfb..b0c1f71a85 100644 --- a/tensorflow/python/grappler/cluster.i +++ b/tensorflow/python/grappler/cluster.i @@ -132,7 +132,7 @@ struct GCluster { static GCluster TF_NewCluster(bool allow_soft_placement, bool disable_detailed_stats, TF_Status* out_status) { - int num_cpu_cores = tensorflow::grappler::GetNumAvailableLogicalCPUCores(); + int num_cpu_cores = tensorflow::grappler::GetNumAvailableLogicalCPUCores(); int num_gpus = tensorflow::grappler::GetNumAvailableGPUs(); int timeout_s = 60 * 10; tensorflow::grappler::Cluster* cluster_ = -- GitLab From 27822e43c671c7705b2be24891cc40ad628d1c91 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 12:12:45 -0800 Subject: [PATCH 0273/2345] fix typos PiperOrigin-RevId: 228211568 --- tensorflow/core/grappler/clusters/virtual_cluster.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/grappler/clusters/virtual_cluster.cc b/tensorflow/core/grappler/clusters/virtual_cluster.cc index dbd8f26c28..e80d6b54d9 100644 --- a/tensorflow/core/grappler/clusters/virtual_cluster.cc +++ b/tensorflow/core/grappler/clusters/virtual_cluster.cc @@ -67,8 +67,8 @@ Status VirtualCluster::Run(const GraphDef& graph, const std::vector& fetch, RunMetadata* metadata) { // Initialize a virtual scheduler to process the graph. Make sure to use - // static shape inference to prevent the schedulrer from calling the Run - // method on the cluster, and create an infinite loop. + // static shape inference to prevent the scheduler from calling the Run + // method on the cluster and creating an infinite loop. GrapplerItem item; item.graph = graph; item.feed = feed; -- GitLab From 7ba8db889667a757339714253bb70f77bca5b2f0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 12:18:06 -0800 Subject: [PATCH 0274/2345] Add missing include for `std::fegetround()` PiperOrigin-RevId: 228212438 --- tensorflow/core/platform/setround.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/core/platform/setround.cc b/tensorflow/core/platform/setround.cc index 592626bfa1..5573b2fc93 100644 --- a/tensorflow/core/platform/setround.cc +++ b/tensorflow/core/platform/setround.cc @@ -15,6 +15,8 @@ limitations under the License. #include "tensorflow/core/platform/setround.h" +#include // NOLINT + namespace tensorflow { namespace port { -- GitLab From 7236c9bb7612fb1a0212dd81f934e0e68fddc0a2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 12:18:25 -0800 Subject: [PATCH 0275/2345] Fix issue with passing EagerTensors to fit when run_eagerly=False PiperOrigin-RevId: 228212493 --- .../python/keras/engine/training_arrays.py | 4 ++ .../keras/engine/training_eager_test.py | 39 +++++++++++++------ .../python/keras/engine/training_utils.py | 19 +++++++++ 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/tensorflow/python/keras/engine/training_arrays.py b/tensorflow/python/keras/engine/training_arrays.py index 97025a9e18..9887fe34a8 100644 --- a/tensorflow/python/keras/engine/training_arrays.py +++ b/tensorflow/python/keras/engine/training_arrays.py @@ -200,6 +200,10 @@ def model_iteration(model, use_steps = steps_per_epoch is not None do_validation = val_inputs is not None + # Convert Eager Tensors to NumPy arrays to support batching/shuffling. + inputs, targets, sample_weights = training_utils. \ + convert_eager_tensors_to_numpy((inputs, targets, sample_weights)) + # Prepare input data. ins = _prepare_feed_values(model, inputs, targets, sample_weights, mode) num_samples_or_steps = _get_num_samples_or_steps(ins, batch_size, diff --git a/tensorflow/python/keras/engine/training_eager_test.py b/tensorflow/python/keras/engine/training_eager_test.py index 27eaea23ba..a6e2c24ec2 100644 --- a/tensorflow/python/keras/engine/training_eager_test.py +++ b/tensorflow/python/keras/engine/training_eager_test.py @@ -28,13 +28,20 @@ from tensorflow.python.keras import keras_parameterized from tensorflow.python.keras import metrics as metrics_module from tensorflow.python.keras import testing_utils from tensorflow.python.keras.optimizer_v2 import rmsprop +from tensorflow.python.ops import array_ops from tensorflow.python.platform import test class TrainingTest(keras_parameterized.TestCase): @keras_parameterized.run_with_all_model_types(exclude_models='sequential') + @keras_parameterized.run_all_keras_modes def test_model_methods_with_eager_tensors_multi_io(self): + if not context.executing_eagerly(): + # Only test V2 Function and V2 Eager modes, as V1 Graph mode with + # symbolic tensors has different requirements. + return + input_a = keras.layers.Input(shape=(3,), name='input_a') input_b = keras.layers.Input(shape=(3,), name='input_b') @@ -53,13 +60,13 @@ class TrainingTest(keras_parameterized.TestCase): loss, metrics=metrics, loss_weights=loss_weights, - run_eagerly=True, + run_eagerly=testing_utils.should_run_eagerly(), sample_weight_mode=None) - input_a = keras.backend.zeros(shape=(10, 3)) - input_b = keras.backend.zeros(shape=(10, 3)) - target_a = keras.backend.zeros(shape=(10, 4)) - target_b = keras.backend.zeros(shape=(10, 4)) + input_a = array_ops.zeros(shape=(10, 3)) + input_b = array_ops.zeros(shape=(10, 3)) + target_a = array_ops.zeros(shape=(10, 4)) + target_b = array_ops.zeros(shape=(10, 4)) model.fit( [input_a, input_b], [target_a, target_b], @@ -107,16 +114,26 @@ class TrainingTest(keras_parameterized.TestCase): model.test_on_batch([input_a, input_b], [target_a, target_b]) @keras_parameterized.run_with_all_model_types + @keras_parameterized.run_all_keras_modes def test_model_methods_with_eager_tensors_single_io(self): + if not context.executing_eagerly(): + # Only test V2 Function and V2 Eager modes, as V1 Graph mode with + # symbolic tensors has different requirements. + return + model = testing_utils.get_small_mlp(10, 4, 3) optimizer = rmsprop.RMSprop(learning_rate=0.001) loss = 'mse' metrics = ['mae', metrics_module.CategoricalAccuracy()] - model.compile(optimizer, loss, metrics=metrics, run_eagerly=True) + model.compile( + optimizer, + loss, + metrics=metrics, + run_eagerly=testing_utils.should_run_eagerly()) - inputs = keras.backend.zeros(shape=(10, 3)) - targets = keras.backend.zeros(shape=(10, 4)) + inputs = array_ops.zeros(shape=(10, 3)) + targets = array_ops.zeros(shape=(10, 4)) model.fit(inputs, targets, epochs=1, batch_size=2, verbose=0) model.fit(inputs, targets, epochs=1, batch_size=3, verbose=0, shuffle=False) @@ -134,8 +151,8 @@ class TrainingTest(keras_parameterized.TestCase): loss='mse', run_eagerly=True) - x = keras.backend.zeros(shape=(10, 3)) - y = keras.backend.zeros(shape=(10, 4)) + x = array_ops.zeros(shape=(10, 3)) + y = array_ops.zeros(shape=(10, 4)) dataset = dataset_ops.Dataset.from_tensor_slices((x, y)).repeat(10).batch(5) iterator = dataset_ops.make_one_shot_iterator(dataset) validation_dataset = dataset_ops.Dataset.from_tensor_slices( @@ -146,7 +163,7 @@ class TrainingTest(keras_parameterized.TestCase): ValueError, r'specify .* `steps_per_epoch`'): model.fit(iterator, epochs=1, verbose=0) if not context.executing_eagerly(): - # In eager execution, `keras.backend.zeros` returns value tensors + # In eager execution, `array_ops.zeros` returns value tensors # which can be used for validation without a `validation_steps` argument. with self.assertRaisesRegexp( ValueError, r'provide either `batch_size` or `validation_steps`'): diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py index d07e3cc4f7..5edf1de1b0 100644 --- a/tensorflow/python/keras/engine/training_utils.py +++ b/tensorflow/python/keras/engine/training_utils.py @@ -1402,3 +1402,22 @@ def set_run_eagerly_for_dict_structure(model, x): if isinstance(item, dict): model.run_eagerly = True return + + +def convert_eager_tensors_to_numpy(structure): + """Convert every EagerTensor in `structure` to NumPy. + + Arguments: + structure: An arbitrary structure of elements to be converted to NumPy + arrays. + + Returns: + An identical structure with EagerTensors converted to NumPy arrays. + """ + + def _convert(element): + if isinstance(element, ops.EagerTensor): + return element.numpy() + return element + + return nest.map_structure(_convert, structure) -- GitLab From e1a553ba1b10a6f39f01b364d7107d1c77a97776 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Mon, 7 Jan 2019 12:18:27 -0800 Subject: [PATCH 0276/2345] Add v2 huber loss. PiperOrigin-RevId: 228212499 --- tensorflow/python/keras/losses.py | 80 ++++++++++++++++++++ tensorflow/python/keras/losses_test.py | 101 +++++++++++++++++++++++++ 2 files changed, 181 insertions(+) diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index de1605a09c..5f9278823c 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -608,6 +608,53 @@ class KullbackLeiblerDivergence(Loss): return kullback_leibler_divergence(y_true, y_pred) +class HuberLoss(Loss): + """Computes the huber loss between `y_true` and `y_pred`. + + For each value x in `error=y_true-y_pred`, the following is calculated: + + ``` + 0.5 * x^2 if |x| <= d + 0.5 * d^2 + d * (|x| - d) if |x| > d + ``` + where d is `delta`. See: https://en.wikipedia.org/wiki/Huber_loss + + Usage: + + ```python + l = tf.losses.HuberLoss() + loss = l([0., 1., 1.], [1., 0., 1.]) + print('Loss: ', loss.numpy()) # Loss: 0.333 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile('sgd', loss=tf.losses.HuberLoss()) + ``` + + Args: + delta: A float, the point where the huber loss function changes from a + quadratic to linear. + reduction: Type of `tf.losses.Reduction` to apply to loss. Default value is + `SUM_OVER_BATCH_SIZE`. + name: Optional name for the op. + """ + + def __init__(self, + delta=1.0, + reduction=losses_impl.ReductionV2.SUM_OVER_BATCH_SIZE, + name=None): + super(HuberLoss, self).__init__(reduction=reduction, name=name) + self.delta = delta + + def call(self, y_true, y_pred): + y_pred = ops.convert_to_tensor(y_pred) + y_true = math_ops.cast(y_true, y_pred.dtype) + return huber_loss(y_true, y_pred, delta=self.delta) + + @keras_export('keras.metrics.mean_squared_error', 'keras.metrics.mse', 'keras.metrics.MSE', @@ -677,6 +724,39 @@ def logloss(y_true, y_pred): return K.mean(-losses, axis=-1) +def huber_loss(y_true, y_pred, delta=1.0): + """Computes huber loss value. + + For each value x in `error=y_true-y_pred`, the following is calculated: + + ``` + 0.5 * x^2 if |x| <= d + 0.5 * d^2 + d * (|x| - d) if |x| > d + ``` + where d is `delta`. See: https://en.wikipedia.org/wiki/Huber_loss + + Args: + y_true: tensor of true targets. + y_pred: tensor of predicted targets. + delta: A float, the point where the huber loss function changes from a + quadratic to linear. + + Returns: + Tensor with one scalar loss entry per sample. + """ + y_pred = math_ops.cast(y_pred, dtype=K.floatx()) + y_true = math_ops.cast(y_true, dtype=K.floatx()) + error = math_ops.subtract(y_pred, y_true) + abs_error = math_ops.abs(error) + quadratic = math_ops.minimum(abs_error, delta) + linear = math_ops.subtract(abs_error, quadratic) + return math_ops.add( + math_ops.multiply( + ops.convert_to_tensor(0.5, dtype=quadratic.dtype), + math_ops.multiply(quadratic, quadratic)), + math_ops.multiply(delta, linear)) + + @keras_export('keras.losses.logcosh') def logcosh(y_true, y_pred): """Logarithm of the hyperbolic cosine of the prediction error. diff --git a/tensorflow/python/keras/losses_test.py b/tensorflow/python/keras/losses_test.py index caa4ff4c2e..5d6cd1be55 100644 --- a/tensorflow/python/keras/losses_test.py +++ b/tensorflow/python/keras/losses_test.py @@ -1336,5 +1336,106 @@ class KullbackLeiblerDivergenceTest(test.TestCase): self.assertAlmostEqual(self.evaluate(loss), 0., 3) +@test_util.run_all_in_graph_and_eager_modes +class HuberLossTest(test.TestCase): + + def huber_loss(self, y_true, y_pred, delta=1.0): + error = y_pred - y_true + abs_error = np.abs(error) + + quadratic = np.minimum(abs_error, delta) + linear = np.subtract(abs_error, quadratic) + return np.add( + np.multiply(0.5, np.multiply(quadratic, quadratic)), + np.multiply(delta, linear)) + + def setup(self, delta=1.0): + self.np_y_pred = np.asarray([.9, .2, .2, .8, .4, .6]).reshape((2, 3)) + self.np_y_true = np.asarray([1., 0., 1., 1., 0., 0.]).reshape((2, 3)) + + self.batch_size = 6 + self.expected_losses = self.huber_loss(self.np_y_true, self.np_y_pred, + delta) + + self.y_pred = constant_op.constant(self.np_y_pred) + self.y_true = constant_op.constant(self.np_y_true) + + def test_config(self): + h_obj = keras.losses.HuberLoss( + reduction=losses_impl.ReductionV2.SUM, name='huber') + self.assertEqual(h_obj.name, 'huber') + self.assertEqual(h_obj.reduction, losses_impl.ReductionV2.SUM) + + def test_all_correct(self): + self.setup() + h_obj = keras.losses.HuberLoss() + loss = h_obj(self.y_true, self.y_true) + self.assertAlmostEqual(self.evaluate(loss), 0.0, 3) + + def test_unweighted(self): + self.setup() + h_obj = keras.losses.HuberLoss() + loss = h_obj(self.y_true, self.y_pred) + actual_loss = np.sum(self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), actual_loss, 3) + + def test_scalar_weighted(self): + self.setup() + h_obj = keras.losses.HuberLoss() + sample_weight = 2.3 + loss = h_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + actual_loss = sample_weight * np.sum(self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), actual_loss, 3) + + # Verify we get the same output when the same input is given + loss_2 = h_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + self.assertAlmostEqual(self.evaluate(loss), self.evaluate(loss_2), 3) + + def test_sample_weighted(self): + self.setup() + h_obj = keras.losses.HuberLoss() + sample_weight = constant_op.constant((1.2, 3.4), shape=(2, 1)) + + loss = h_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + actual_loss = np.multiply( + self.expected_losses, + np.asarray([1.2, 1.2, 1.2, 3.4, 3.4, 3.4]).reshape((2, 3))) + actual_loss = np.sum(actual_loss) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), actual_loss, 3) + + def test_timestep_weighted(self): + self.setup() + h_obj = keras.losses.HuberLoss() + y_pred = self.np_y_pred.reshape((2, 3, 1)) + y_true = self.np_y_true.reshape((2, 3, 1)) + expected_losses = self.huber_loss(y_true, y_pred) + + y_pred = constant_op.constant(y_pred) + y_true = constant_op.constant(y_true) + sample_weight = np.array([3, 6, 5, 0, 4, 2]).reshape((2, 3, 1)) + loss = h_obj( + y_true, + y_pred, + sample_weight=constant_op.constant(sample_weight, shape=(2, 3))) + actual_loss = np.multiply(expected_losses, sample_weight) + actual_loss = np.sum(actual_loss) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), actual_loss, 3) + + def test_zero_weighted(self): + self.setup() + h_obj = keras.losses.HuberLoss() + sample_weight = 0 + loss = h_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + self.assertAlmostEqual(self.evaluate(loss), 0., 3) + + def test_non_default_delta(self): + self.setup(delta=0.8) + h_obj = keras.losses.HuberLoss(delta=0.8) + sample_weight = 2.3 + loss = h_obj(self.y_true, self.y_pred, sample_weight=sample_weight) + actual_loss = sample_weight * np.sum(self.expected_losses) / self.batch_size + self.assertAlmostEqual(self.evaluate(loss), actual_loss, 3) + + if __name__ == '__main__': test.main() -- GitLab From 7ba04bba51ac7500afd5fe0d8e2f883082cdd5fb Mon Sep 17 00:00:00 2001 From: Bixia Zheng Date: Mon, 7 Jan 2019 12:22:34 -0800 Subject: [PATCH 0277/2345] [XLA:GPU] Enhance column reduction implementation. With KernelMappingScheme, a thread processes n elements, where n=tile_size_x/num_thread_x. Previously, we only support n>1 for transpose and row reduction. This change extends the kernel mapping scheme and code generation to allow each thread to compute n partial results for column reduction as follows: .Add dilated_x to KernelMappingScheme, to indicate whether the multiple elements processed by the same thread are contiguous or dilated. Dilated_x=true is what we currently use for transpose while dilated_x=false is used to support the vectorization of column reduction. .Extend the stack storages that store the partial result address and current output linear index address for each output tensor from a scalar to an array of n elements. .Add curr_iter_index_x to kernelCodegenInfo, to indicate which output element that the compiler is currently generating code for. This information is used to locate the partial result address and output linear index address for the element. .Modify the code generation to use n=2 for column reduction. PiperOrigin-RevId: 228213135 --- .../xla/service/gpu/ir_emitter_unnested.cc | 330 +++++++++++------- .../xla/service/gpu/ir_emitter_unnested.h | 20 +- .../compiler/xla/service/llvm_ir/ir_array.h | 2 + .../xla/service/llvm_ir/kernel_tiling.cc | 3 +- .../xla/service/llvm_ir/kernel_tiling.h | 22 +- 5 files changed, 248 insertions(+), 129 deletions(-) diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc index f190af921e..2bc4912155 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc @@ -88,6 +88,9 @@ namespace xla { namespace gpu { using llvm_ir::KernelMappingScheme; +using EmitElementFunction = + std::function; namespace { @@ -2133,53 +2136,86 @@ int IrEmitterUnnested::ConstructInputReducedShapeAndCastInputIrArrayToShape( namespace { -void EmitFullElementalTile( - const KernelMappingScheme* mapping_scheme, - const IrArray::Index& tile_origin_index, const string& loop_name, - KernelSupportLibrary* ksl, llvm::IRBuilder<>* builder, llvm::Value* y, - llvm::Value* x, llvm::Type* index_ty, - const std::function& emit_elem_function) { +std::tuple GetStartOffsetAndStepForX( + int64 tile_size_x, int64 num_threads_x, + const KernelMappingScheme* mapping_scheme, llvm::IRBuilder<>* builder, + llvm::Value* x, llvm::Type* index_ty) { + llvm::Value* start_offset_x; + int64 step_x; + if (mapping_scheme->DilatedX()) { + start_offset_x = x; + step_x = num_threads_x; + } else { + start_offset_x = builder->CreateMul( + x, llvm::ConstantInt::get(index_ty, tile_size_x / num_threads_x)); + step_x = 1; + } + return std::make_tuple(start_offset_x, step_x); +} + +void EmitFullElementalTile(const KernelMappingScheme* mapping_scheme, + const IrArray::Index& tile_origin_index, + const string& loop_name, KernelSupportLibrary* ksl, + llvm::IRBuilder<>* builder, llvm::Value* y, + llvm::Value* x, llvm::Type* index_ty, + const EmitElementFunction& emit_elem_function) { int64 num_threads_x = mapping_scheme->GetNumberOfThreadsForDimensionX(); int64 num_threads_y = mapping_scheme->GetNumberOfThreadsForDimensionY(); int64 tile_size_x = mapping_scheme->GetTileSizeForDimensionX(); int64 tile_size_y = mapping_scheme->GetTileSizeForDimensionY(); + + llvm::Value* start_offset_x; + int64 step_x; + std::tie(start_offset_x, step_x) = GetStartOffsetAndStepForX( + tile_size_x, num_threads_x, mapping_scheme, builder, x, index_ty); + IrArray::Index source_idx = + tile_origin_index.AddOffsetToDim(y, KernelMappingScheme::DimY, builder) + .AddOffsetToDim(start_offset_x, KernelMappingScheme::DimX, builder); ksl->For(loop_name + "_y", /*start=*/llvm::ConstantInt::get(index_ty, 0), /*end=*/llvm::ConstantInt::get(index_ty, tile_size_y), /*step=*/llvm::ConstantInt::get(index_ty, num_threads_y), [&](llvm::Value* y_indvar) { - IrArray::Index source_idx_y = tile_origin_index.AddOffsetToDim( + IrArray::Index source_idx_y = source_idx.AddOffsetToDim( y_indvar, KernelMappingScheme::DimY, builder); llvm::Value* y_loc = builder->CreateAdd(y_indvar, y); - for (int64 j = 0; j < tile_size_x; j += num_threads_x) { - IrArray::Index source_idx = source_idx_y.AddOffsetToDim( - llvm::ConstantInt::get(index_ty, j), + + for (int64 j = 0; j < tile_size_x / num_threads_x; j++) { + IrArray::Index source_idx_y_x = source_idx_y.AddOffsetToDim( + llvm::ConstantInt::get(index_ty, j * step_x), KernelMappingScheme::DimX, builder); - llvm::Value* x_loc = - builder->CreateAdd(llvm::ConstantInt::get(index_ty, j), x); - emit_elem_function(source_idx, y_loc, x_loc); + llvm::Value* x_loc = builder->CreateAdd( + llvm::ConstantInt::get(index_ty, j * step_x), + start_offset_x); + emit_elem_function(source_idx_y_x, y_loc, x_loc, j); } }); } -void EmitPartialElementalTile( - const KernelMappingScheme* mapping_scheme, - const IrArray::Index& tile_origin_index, const string& loop_name, - KernelSupportLibrary* ksl, llvm::IRBuilder<>* builder, llvm::Value* y, - llvm::Value* x, llvm::Value* tile_height, llvm::Value* tile_width, - llvm::Type* index_ty, - const std::function& emit_elem_function) { +void EmitPartialElementalTile(const KernelMappingScheme* mapping_scheme, + const IrArray::Index& tile_origin_index, + const string& loop_name, + KernelSupportLibrary* ksl, + llvm::IRBuilder<>* builder, llvm::Value* y, + llvm::Value* x, llvm::Value* tile_height, + llvm::Value* tile_width, llvm::Type* index_ty, + const EmitElementFunction& emit_elem_function) { int64 num_threads_x = mapping_scheme->GetNumberOfThreadsForDimensionX(); int64 num_threads_y = mapping_scheme->GetNumberOfThreadsForDimensionY(); int64 tile_size_x = mapping_scheme->GetTileSizeForDimensionX(); - for (int64 j = 0; j < tile_size_x; j += num_threads_x) { - IrArray::Index source_idx = - tile_origin_index.AddOffsetToDim(llvm::ConstantInt::get(index_ty, j), - KernelMappingScheme::DimX, builder); - llvm::Value* x_loc = - builder->CreateAdd(llvm::ConstantInt::get(index_ty, j), x); + llvm::Value* start_offset_x; + int64 step_x; + std::tie(start_offset_x, step_x) = GetStartOffsetAndStepForX( + tile_size_x, num_threads_x, mapping_scheme, builder, x, index_ty); + IrArray::Index source_idx = + tile_origin_index.AddOffsetToDim(y, KernelMappingScheme::DimY, builder) + .AddOffsetToDim(start_offset_x, KernelMappingScheme::DimX, builder); + for (int64 j = 0; j < tile_size_x / num_threads_x; j++) { + IrArray::Index source_idx_x = + source_idx.AddOffsetToDim(llvm::ConstantInt::get(index_ty, j * step_x), + KernelMappingScheme::DimX, builder); + llvm::Value* x_loc = builder->CreateAdd( + llvm::ConstantInt::get(index_ty, j * step_x), start_offset_x); ksl->If( loop_name + "_x_in_tile", builder->CreateICmpULT(x_loc, tile_width), @@ -2199,14 +2235,13 @@ void EmitPartialElementalTile( /*step=*/llvm::ConstantInt::get(index_ty, num_threads_y), [&](llvm::Value* y_indvar) { llvm::Value* y_loc = builder->CreateAdd(y_indvar, y); - ksl->If( - loop_name + "_y_in_tile", - builder->CreateICmpULT(y_loc, tile_height), [&] { - emit_elem_function( - source_idx.AddOffsetToDim( - y_indvar, KernelMappingScheme::DimY, builder), - y_loc, x_loc); - }); + ksl->If(loop_name + "_y_in_tile", + builder->CreateICmpULT(y_loc, tile_height), [&] { + emit_elem_function( + source_idx_x.AddOffsetToDim( + y_indvar, KernelMappingScheme::DimY, builder), + y_loc, x_loc, j); + }); }); }); } @@ -2225,8 +2260,7 @@ void EmitTiledElementalCodeWithBoundsCheck( const IrArray::Index& tile_origin_index, const string& loop_name, KernelSupportLibrary* ksl, llvm::IRBuilder<>* builder, llvm::Value* y, llvm::Value* x, llvm::Value* tile_height, llvm::Value* tile_width, - const std::function& emit_elem_function) { + const EmitElementFunction& emit_elem_function) { int64 tile_size_x = mapping_scheme->GetTileSizeForDimensionX(); int64 tile_size_y = mapping_scheme->GetTileSizeForDimensionY(); llvm::Type* index_ty = tile_width->getType(); @@ -2262,7 +2296,7 @@ void EmitTiledElementalCodeWithBoundsCheck( void IrEmitterUnnested::EmitTileElementForCopy( HloInstruction* hlo, const llvm_ir::IrArray::Index& index, const KernelCodegenInfo* kernel_info, llvm::Value* y_loc, - llvm::Value* x_loc) { + llvm::Value* x_loc, int64 /*x_iter_num*/) { llvm_ir::TiledParameterInfo* tiled_param_info = kernel_info->GetTiledParameterInfo(); // TODO(jlebar): Add AA metadata to this load. @@ -2292,7 +2326,7 @@ void IrEmitterUnnested::EmitTileElementForCopy( void IrEmitterUnnested::EmitTileElementForFusion( HloInstruction* hlo, const llvm_ir::IrArray::Index& index, const KernelCodegenInfo* kernel_info, llvm::Value* y_loc, - llvm::Value* x_loc) { + llvm::Value* x_loc, int64 /*x_iter_num*/) { llvm_ir::TiledParameterInfo* tiled_param_info = kernel_info->GetTiledParameterInfo(); std::vector output_arrays = ConstructIrArrayForOutputs(*hlo); @@ -2393,6 +2427,23 @@ class ReductionCodegenInfo : public IrEmitterUnnested::KernelCodegenInfo { : llvm_ir::KernelMappingScheme::DimX; } + int GetNumberOfPartialResults() const { + if (IsRowReduction()) { + return 1; + } + int64 num_thread = mapping_scheme_->GetNumberOfThreadsForDimensionX(); + int64 tile_size = mapping_scheme_->GetTileSizeForDimensionX(); + CHECK_EQ(tile_size % num_thread, 0); + return tile_size / num_thread; + } + + int GetPartialResultIndex(int64 x_iter_num) const { + if (IsRowReduction()) { + return 0; + } + return x_iter_num; + } + private: AddressVector partial_result_addresses_; AddressVector reduction_input_addresses_; @@ -2452,10 +2503,11 @@ void IrEmitterUnnested::EmitPrologueForOneReduction( llvm::AllocaInst* reduction_input_address = Alloca(element_type); reduction_input_addresses->push_back(reduction_input_address); + int num_partial_results = reduction_info->GetNumberOfPartialResults(); AddressVector* partial_result_addresses = reduction_info->GetMutablePartialResultAddresses(); llvm::AllocaInst* partial_result_address = - Alloca(element_type, /*ArraySize=*/nullptr, + Alloca(element_type, /*ArraySize=*/b_.getInt32(num_partial_results), "partial_reduction_result." + llvm::Twine(reduce_idx)); partial_result_addresses->push_back(partial_result_address); @@ -2478,7 +2530,9 @@ void IrEmitterUnnested::EmitPrologueForOneReduction( .EmitReadArrayElement(IrArray::Index(b_.getInt32Ty()), &b_); } - Store(init_ir_value, partial_result_address); + for (int i = 0; i < num_partial_results; ++i) { + Store(init_ir_value, InBoundsGEP(partial_result_address, {b_.getInt32(i)})); + } } void IrEmitterUnnested::EmitPrologueForReduction( @@ -2516,10 +2570,14 @@ void IrEmitterUnnested::EmitPrologueForReduction( std::move(output_shape_index)); } - // Allocate stack storage to store the current output linear index and record - // the address of the storage. + int num_partial_results = reduction_info->GetNumberOfPartialResults(); + + // Allocate stack storage to store the linear indices for the current output, + // and record the address of the storage. reduction_info->SetCurrentOutputLinearIndexAddress( - Alloca(reduction_info->GetIndexType())); + Alloca(reduction_info->GetIndexType(), + /*ArraySize=*/b_.getInt32(num_partial_results), + "current_output_linear_index_address")); if (!reduction_info->IsRowReduction()) { llvm::Type* bool_ty = b_.getInt1Ty(); @@ -2589,36 +2647,45 @@ void IrEmitterUnnested::EmitEpilogueForReduction( llvm_ir::SetToFirstInsertPoint(if_output_inbound_data.true_block, &b_); } + int num_partial_results = reduction_info->GetNumberOfPartialResults(); + // Emit an atomic operation that accumulates the partial reduction to the // output element. For row reduction, this is only for lane 0 due to the // if-statement emitted above. for (int i = 0; i != num_reduces; ++i) { - IrArray::Index element_index( - /*linear=*/Load(reduction_info->GetCurrentOutputLinearIndexAddress(), - "output_linear_addr"), - ShapeUtil::GetSubshape(unnested_hlo->shape(), - reduction_output_shape_indices[i]), - &b_); - llvm::Value* output_address = - GetIrArray(*unnested_hlo, *unnested_hlo, - reduction_output_shape_indices[i]) - .EmitArrayElementAddress(element_index, &b_, - "output_element_address"); - // Do not emit atomic operations if each element in the reduction result is - // computed by one block, that is the dimension being reduced has only one - // block. - const llvm_ir::KernelMappingScheme* mapping_scheme = - reduction_info->GetKernelMappingScheme(); - if (mapping_scheme->GetTileBlockSizeForDimension( - llvm_ir::KernelMappingScheme::DimZ) == 1 && - mapping_scheme->GetTileBlockSizeForDimension( - reduction_info->GetReducedDimensionEnum()) == 1) { - TF_CHECK_OK(EmitCallToNestedComputation( - *reducers[i], {output_address, partial_result_addresses[i]}, - output_address)); - } else { - TF_CHECK_OK(EmitAtomicOperationForNestedComputation( - *reducers[i], output_address, partial_result_addresses[i])); + for (int j = 0; j < num_partial_results; ++j) { + IrArray::Index element_index( + /*linear=*/Load( + InBoundsGEP(reduction_info->GetCurrentOutputLinearIndexAddress(), + {b_.getInt32(j)}), + "output_linear_addr"), + ShapeUtil::GetSubshape(unnested_hlo->shape(), + reduction_output_shape_indices[i]), + &b_); + llvm::Value* output_address = + GetIrArray(*unnested_hlo, *unnested_hlo, + reduction_output_shape_indices[i]) + .EmitArrayElementAddress(element_index, &b_, + "output_element_address"); + // Do not emit atomic operations if each element in the reduction result + // is computed by one block, that is the dimension being reduced has only + // one block. + const llvm_ir::KernelMappingScheme* mapping_scheme = + reduction_info->GetKernelMappingScheme(); + if (mapping_scheme->GetTileBlockSizeForDimension( + llvm_ir::KernelMappingScheme::DimZ) == 1 && + mapping_scheme->GetTileBlockSizeForDimension( + reduction_info->GetReducedDimensionEnum()) == 1) { + TF_CHECK_OK(EmitCallToNestedComputation( + *reducers[i], + {output_address, + InBoundsGEP(partial_result_addresses[i], {b_.getInt32(j)})}, + output_address)); + } else { + TF_CHECK_OK(EmitAtomicOperationForNestedComputation( + *reducers[i], output_address, + InBoundsGEP(partial_result_addresses[i], {b_.getInt32(j)}))); + } } } } @@ -2626,7 +2693,7 @@ void IrEmitterUnnested::EmitEpilogueForReduction( void IrEmitterUnnested::EmitTileElementForReduction( HloInstruction* unnested_hlo, const llvm_ir::IrArray::Index& index, const KernelCodegenInfo* kernel_info, llvm::Value* y_loc, - llvm::Value* x_loc) { + llvm::Value* x_loc, int64 x_iter_num) { VLOG(10) << "Emit tile element for reduce " << unnested_hlo->ToString(); HloInstruction* reduce_or_tuple = unnested_hlo->opcode() == HloOpcode::kFusion ? unnested_hlo->fused_expression_root() @@ -2639,8 +2706,11 @@ void IrEmitterUnnested::EmitTileElementForReduction( // Record the linear address for the current reduction. const ReductionCodegenInfo* reduction_info = dynamic_cast(kernel_info); + int partial_result_index = reduction_info->IsRowReduction() ? 0 : x_iter_num; + Store(index[reduction_info->GetKeptDimensionEnum()], - reduction_info->GetCurrentOutputLinearIndexAddress()); + InBoundsGEP(reduction_info->GetCurrentOutputLinearIndexAddress(), + {b_.getInt32(partial_result_index)})); if (!reduction_info->IsRowReduction()) { llvm::Type* bool_ty = b_.getInt1Ty(); llvm::AllocaInst* output_inbound_addr = @@ -2687,6 +2757,13 @@ void IrEmitterUnnested::EmitTileElementForReduction( reduction_info->GetKernelMappingScheme()->GetUnnormalizedIndex( index, GetFirstReduceInstruction(output_instructions)->operand(0)->shape()); + int num_partial_results = reduction_info->GetNumberOfPartialResults(); + if (num_partial_results > 1) { + // Clear the linear index field of the IrArray::Index to enable the use of + // GetElementPointer with array types. This enables the vectorization of + // the computation for different partial results. + input_index.ClearLinearIndex(); + } absl::Span partial_reduction_result_addresses = reduction_info->GetPartialResultAddresses(); absl::Span reduction_input_addresses = @@ -2699,10 +2776,12 @@ void IrEmitterUnnested::EmitTileElementForReduction( for (int i = 0; i != reducers.size(); ++i) { llvm::Value* const input_ir_value = input_gens[i](input_index).ValueOrDie(); Store(input_ir_value, reduction_input_addresses[i]); + llvm::Value* partial_result_address = + InBoundsGEP(partial_reduction_result_addresses[i], + {b_.getInt32(partial_result_index)}); TF_CHECK_OK(EmitCallToNestedComputation( - *reducers[i], - {partial_reduction_result_addresses[i], reduction_input_addresses[i]}, - partial_reduction_result_addresses[i])); + *reducers[i], {partial_result_address, reduction_input_addresses[i]}, + partial_result_address)); } // Emit code to generate the output for the non-reduction instructions in the @@ -2713,8 +2792,8 @@ void IrEmitterUnnested::EmitTileElementForReduction( // Emits a kernel for the hlo instruction using the given tiling scheme. void IrEmitterUnnested::EmitBlock(const TileGenerator& emit_one_tile, - const KernelCodegenInfo* kernel_info, - KernelSupportLibrary& ksl, + KernelCodegenInfo* kernel_info, + KernelSupportLibrary* ksl, llvm::Type* index_ty) { KernelMappingScheme* mapping_scheme = kernel_info->GetKernelMappingScheme(); absl::Span dims_in_tile = mapping_scheme->GetDimensionsInTiles(); @@ -2747,15 +2826,14 @@ void IrEmitterUnnested::EmitBlock(const TileGenerator& emit_one_tile, llvm::Value* num_tiles_in_block = Select(ICmpEQ(last_block_for_dim, block_id_for_dim), last_block_size_for_dim, block_size_for_dim); - - ksl.For(loop_name, - /*start=*/index_typed_constant(0), - /*end=*/num_tiles_in_block, - /*step=*/1, [&](llvm::Value* block_dim_induction_var) { - IrArray::Index tile_index = starting_tile.AddOffsetToDim( - block_dim_induction_var, dim_id, &b_); - emit_next_block_dim(tile_index); - }); + ksl->For(loop_name, + /*start=*/index_typed_constant(0), + /*end=*/num_tiles_in_block, + /*step=*/1, [&](llvm::Value* block_dim_induction_var) { + IrArray::Index tile_index = starting_tile.AddOffsetToDim( + block_dim_induction_var, dim_id, &b_); + emit_next_block_dim(tile_index); + }); } }; @@ -2899,8 +2977,7 @@ LaunchDimensions IrEmitterUnnested::EmitKernel( auto emit_tiled_elemental_code_with_bounds_check = [&](const IrArray::Index& index, const string& loop_name, llvm::Value* tile_height, llvm::Value* tile_width, - const std::function& emit_elem_function) { + const EmitElementFunction& emit_elem_function) { EmitTiledElementalCodeWithBoundsCheck(mapping_scheme, index, loop_name, &ksl, &b_, y, x, tile_height, tile_width, emit_elem_function); @@ -2913,10 +2990,6 @@ LaunchDimensions IrEmitterUnnested::EmitKernel( const IrArray::Index input_tile_origin( Permute({0, 2, 1}, output_tile_origin.multidim())); - const IrArray::Index input_index = - input_tile_origin.AddOffsetToDim(x, KernelMappingScheme::DimX, &b_) - .AddOffsetToDim(y, KernelMappingScheme::DimY, &b_); - // If shared memory transpose is needed, wait for all threads to reach this // point, lest we copy a value from tile to output before the other thread // copies it from input to tile. This is `__syncthreads` in CUDA. @@ -2926,9 +2999,10 @@ LaunchDimensions IrEmitterUnnested::EmitKernel( // Note that tile_width and tile_height are flipped here because we are // reading a transposed tile. emit_tiled_elemental_code_with_bounds_check( - input_index, "input", output_tile_bounds[2], output_tile_bounds[1], + input_tile_origin, "input", output_tile_bounds[2], + output_tile_bounds[1], [&](const IrArray::Index& index, llvm::Value* y_loc, - llvm::Value* x_loc) { + llvm::Value* x_loc, int64 /*x_iter_num*/) { for (int64 id : tiled_param_ids) { IrArray& input_in_logical_shape = param_in_reduced_shape_arrays[id]; @@ -2948,18 +3022,15 @@ LaunchDimensions IrEmitterUnnested::EmitKernel( llvm_ir::TiledParameterInfo tiled_param_info(param_shmem_buffers, y, x); kernel_info->SetTiledParamInfo(&tiled_param_info); - const IrArray::Index output_index = - output_tile_origin.AddOffsetToDim(x, KernelMappingScheme::DimX, &b_) - .AddOffsetToDim(y, KernelMappingScheme::DimY, &b_); - // Write to output[index] by emitting code like normal, except that values // for the tiled parameters are read from the shmem buffers. emit_tiled_elemental_code_with_bounds_check( - output_index, "output", output_tile_bounds[1], output_tile_bounds[2], - [&](const IrArray::Index& index, llvm::Value* y_loc, - llvm::Value* x_loc) { - kernel_generator.GetTileElementGenerator()(unnested_hlo, index, - kernel_info, y_loc, x_loc); + output_tile_origin, "output", output_tile_bounds[1], + output_tile_bounds[2], + [&](const IrArray::Index& index, llvm::Value* y_loc, llvm::Value* x_loc, + int64 x_iter_num) { + kernel_generator.GetTileElementGenerator()( + unnested_hlo, index, kernel_info, y_loc, x_loc, x_iter_num); }); // If a tile block contains multiple tiles and shared memory buffers are @@ -2977,7 +3048,7 @@ LaunchDimensions IrEmitterUnnested::EmitKernel( block_prologue_generator(unnested_hlo, kernel_info); } - EmitBlock(std::move(emit_one_tile), kernel_info, ksl, index_ty); + EmitBlock(std::move(emit_one_tile), kernel_info, &ksl, index_ty); const BlockEpilogueGenerator& block_epilogue_generator = kernel_generator.GetBlockEpilogueGenerator(); @@ -3026,17 +3097,19 @@ LaunchDimensions IrEmitterUnnested::EmitHlo021Tile( element_generator = [&](HloInstruction* hlo, const llvm_ir::IrArray::Index& index, const KernelCodegenInfo* kernel_info, - llvm::Value* y_loc, llvm::Value* x_loc) { - EmitTileElementForCopy(hlo, index, kernel_info, y_loc, x_loc); + llvm::Value* y_loc, llvm::Value* x_loc, + int64 x_iter_num) { + EmitTileElementForCopy(hlo, index, kernel_info, y_loc, x_loc, x_iter_num); }; } else { DCHECK_EQ(hlo->opcode(), HloOpcode::kFusion); - element_generator = [&](HloInstruction* hlo, - const llvm_ir::IrArray::Index& index, - const KernelCodegenInfo* kernel_info, - llvm::Value* y_loc, llvm::Value* x_loc) { - EmitTileElementForFusion(hlo, index, kernel_info, y_loc, x_loc); - }; + element_generator = + [&](HloInstruction* hlo, const llvm_ir::IrArray::Index& index, + const KernelCodegenInfo* kernel_info, llvm::Value* y_loc, + llvm::Value* x_loc, int64 x_iter_num) { + EmitTileElementForFusion(hlo, index, kernel_info, y_loc, x_loc, + x_iter_num); + }; } KernelCodegenInfo kernel_info(&mapping_scheme); KernelCodeGenerator kernel_generator(std::move(element_generator)); @@ -3351,6 +3424,7 @@ IrEmitterUnnested::ComputeMappingSchemeAndReductionKind( std::tie(num_reduced_major, num_kept, num_reduced_minor) = GetReductionToVectorDimensions(input_shape, first_reduce->dimensions()); CHECK_EQ(num_output_elems, num_kept); + bool dilated_x = true; if (num_kept == 1) { // Scalar reduction is a special row reduction with depth = height = 1. @@ -3363,15 +3437,31 @@ IrEmitterUnnested::ComputeMappingSchemeAndReductionKind( height = num_reduced_major; width = num_kept; is_row_reduction = false; + // Assume unrolling is beneficial only when we can vectorize the loads + // of small data types. + auto is_unrolling_beneficial = [&] { + // TODO(b/122468062): Need further investigate to see whether we can + // remove the constraint on IsPowerOfTwo. + return IsPowerOfTwo(static_cast(num_kept)) && + primitive_util::BitWidth( + first_reduce->operand(0)->shape().element_type()) <= 16; + }; // Column reduction without transpose doesn't require communication among // threads processing elements in the same tile. The current implementation - // only support the use of on hardware thread block to process one block of - // tiles in the KernelMappingScheme. We try to maximize the values of + // only support the use of one hardware thread block to process one block of + // tiles in the KernelMappingScheme. We try to use one thread to compute + // the partial results for two tensor elements and to maximize the values of // num_threads_x and tile_size_x to allow a bigger hardware thread block. int64 hw_threads_per_block_limit = ThreadsPerBlockLimit(ir_emitter_context_->device_description()); - tile_size_x = std::min(hw_threads_per_block_limit, num_kept); - num_threads_x = tile_size_x; + if (is_unrolling_beneficial()) { + tile_size_x = std::min(2 * hw_threads_per_block_limit, num_kept); + num_threads_x = tile_size_x / 2; + dilated_x = false; + } else { + tile_size_x = std::min(hw_threads_per_block_limit, num_kept); + num_threads_x = tile_size_x; + } int64 kNumElementsPerPartialSum = 128; tile_size_y = kNumElementsPerPartialSum; } else { @@ -3400,6 +3490,7 @@ IrEmitterUnnested::ComputeMappingSchemeAndReductionKind( llvm_ir::KernelMappingScheme mapping_scheme( dims_in_elem, tile_size_y, tile_size_x, req_block_sizes, num_threads_y, num_threads_x, &b_); + mapping_scheme.SetDilatedX(dilated_x); return std::make_tuple(mapping_scheme, is_row_reduction); } @@ -3454,8 +3545,9 @@ Status IrEmitterUnnested::EmitReductionToVector(HloInstruction* unnested_hlo) { /*tile_element_generator=*/ [&](HloInstruction* hlo, const llvm_ir::IrArray::Index& index, const KernelCodegenInfo* kernel_info, llvm::Value* y_loc, - llvm::Value* x_loc) { - EmitTileElementForReduction(hlo, index, kernel_info, y_loc, x_loc); + llvm::Value* x_loc, int64 x_iter_num) { + EmitTileElementForReduction(hlo, index, kernel_info, y_loc, x_loc, + x_iter_num); }, /*block_prologue_generator=*/ [&](HloInstruction* hlo, KernelCodegenInfo* kernel_info) { diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h index d217ee36cf..eb0ea90e14 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h @@ -76,7 +76,6 @@ class IrEmitterUnnested : public IrEmitter { void SetLaneId(llvm::Value* v) { lane_id_ = v; } void SetIndexType(llvm::Type* t) { index_ty_ = t; } void SetTiledParamInfo(llvm_ir::TiledParameterInfo* tiled_param_info) { - CHECK_EQ(tiled_param_info_, nullptr); tiled_param_info_ = tiled_param_info; } @@ -89,7 +88,7 @@ class IrEmitterUnnested : public IrEmitter { } llvm::Type* GetIndexType() const { return index_ty_; } - private: + protected: llvm_ir::KernelMappingScheme* mapping_scheme_; llvm_ir::TiledParameterInfo* tiled_param_info_; llvm::Value* lane_id_; @@ -109,10 +108,12 @@ class IrEmitterUnnested : public IrEmitter { // y_loc: The y coordinate within a tile. // x_loc: The x coordinate within a tile. // kernel_info: Other information to support the kernel code generation. + // x_iter_num: When a thread process N elements in the X dimension, x_iter_num + // has a value of 0..N-1 to identify the element being process. using TileElementGenerator = std::function; + llvm::Value* x_loc, int64 x_iter_num)>; // KernelCodeGenerator records the code generator objects that generate code // for tile elements or tile block prologue/epilogue. @@ -243,26 +244,29 @@ class IrEmitterUnnested : public IrEmitter { const KernelCodeGenerator& kernel_generator, KernelCodegenInfo* kernel_info); void EmitBlock(const TileGenerator& emit_one_tile, - const KernelCodegenInfo* kernel_info, - KernelSupportLibrary& ksl, llvm::Type* index_ty); + KernelCodegenInfo* kernel_info, KernelSupportLibrary* ksl, + llvm::Type* index_ty); // Emits code to process a tensor element in a tile for the given kCopy HLO // that performs a 0-2-1 transpose. void EmitTileElementForCopy(HloInstruction* hlo, const llvm_ir::IrArray::Index& index, const KernelCodegenInfo* kernel_info, - llvm::Value* y_loc, llvm::Value* x_loc); + llvm::Value* y_loc, llvm::Value* x_loc, + int64 x_iter_num); // Emits code to process a tensor element in a tile for the given kLoop fusion // HLO containing parameters that are 0-2-1 transpose of its outputs. void EmitTileElementForFusion(HloInstruction* hlo, const llvm_ir::IrArray::Index& index, const KernelCodegenInfo* kernel_info, - llvm::Value* y_loc, llvm::Value* x_loc); + llvm::Value* y_loc, llvm::Value* x_loc, + int64 x_iter_num); // Emits code to process a tensor element in a tile for the given input hlo // that is either a unnested kReduce or a kInput fusion. void EmitTileElementForReduction(HloInstruction* unnested_hlo, const llvm_ir::IrArray::Index& index, const KernelCodegenInfo* kernel_info, - llvm::Value* y_loc, llvm::Value* x_loc); + llvm::Value* y_loc, llvm::Value* x_loc, + int64 x_iter_num); // Prepares for the code generation for a tile block of a reduction kernel. void EmitPrologueForReduction(HloInstruction* unnested_hlo, KernelCodegenInfo* kernel_info); diff --git a/tensorflow/compiler/xla/service/llvm_ir/ir_array.h b/tensorflow/compiler/xla/service/llvm_ir/ir_array.h index d6d84994ee..a483f7051f 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/ir_array.h +++ b/tensorflow/compiler/xla/service/llvm_ir/ir_array.h @@ -189,6 +189,8 @@ class IrArray { return llvm::ConstantInt::get(index_type_, c); } + void ClearLinearIndex() { linear_ = nullptr; } + private: // Changing the multi-dimensional index invalidates the linear index. std::vector& mutable_multidim() { diff --git a/tensorflow/compiler/xla/service/llvm_ir/kernel_tiling.cc b/tensorflow/compiler/xla/service/llvm_ir/kernel_tiling.cc index cebbc42901..cd8dd72cd7 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/kernel_tiling.cc +++ b/tensorflow/compiler/xla/service/llvm_ir/kernel_tiling.cc @@ -123,7 +123,8 @@ KernelMappingScheme::KernelMappingScheme( dims_in_elems_(dims_in_elems.begin(), dims_in_elems.end()), tile_sizes_{1, tile_size_y, tile_size_x}, num_threads_x_(num_threads_x), - num_threads_y_(num_threads_y) { + num_threads_y_(num_threads_y), + dilated_x_(true) { DCHECK_EQ(dims_in_elems_.size(), 3); DCHECK_EQ(req_block_sizes.size(), 3); diff --git a/tensorflow/compiler/xla/service/llvm_ir/kernel_tiling.h b/tensorflow/compiler/xla/service/llvm_ir/kernel_tiling.h index fb633b12e6..f802cc27d5 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/kernel_tiling.h +++ b/tensorflow/compiler/xla/service/llvm_ir/kernel_tiling.h @@ -117,7 +117,10 @@ class KernelMappingScheme { int64 GetNumberOfTilesInOneBlock() const { return absl::c_accumulate(block_sizes_, 1, std::multiplies()); } - + int64 GetNumberOfTilesInOneBlockForDimension(int d) const { + DCHECK(d >= DimZ && d <= DimX); + return block_sizes_[d]; + } int64 GetNumberOfBlocks() const { return absl::c_accumulate(dims_in_blocks_, 1, std::multiplies()); } @@ -147,6 +150,16 @@ class KernelMappingScheme { GetNumberOfThreadsForDimensionY(); } + bool DilatedX() const { return dilated_x_; } + void SetDilatedX(bool v) { + dilated_x_ = v; + if (!dilated_x_) { + // dilated_x_=false is for the purpose of vectorization, which requires + // GetTileSizeForDimension(DimX) to be a multiplier of num_threads_x_. + CHECK_EQ(GetTileSizeForDimension(DimX) % num_threads_x_, 0); + } + } + IrArray::Index EmitBlockIndex(llvm::Type* index_ty); // Returns the index for the first tile in the block with the given block // index. @@ -186,6 +199,13 @@ class KernelMappingScheme { int64 num_threads_x_; // Number of threads used to process elements in the Y direction of a tile. int64 num_threads_y_; + + // When num_threads_x threads process a total of tile_size_x elements in the + // X dimension of a tile, each threads process n=tile_size_x/num_threads_x + // elements. When dilated_x=false, the n elements processed by a thread are + // contiguous. On the other hand, when dilated_x=true the n elements are + // dilated by a factor of num_threads_x. + bool dilated_x_; }; // A class to represent information for tiled parameters to support IR emission -- GitLab From 021a8fe8a70b880031958f596b6c96e819e14386 Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Mon, 7 Jan 2019 12:23:54 -0800 Subject: [PATCH 0278/2345] Move contrib/tensorboard/db to new core/summary directory PiperOrigin-RevId: 228213314 --- tensorflow/contrib/cmake/tf_core_framework.cmake | 12 ++++++------ tensorflow/core/kernels/BUILD | 10 ++++------ tensorflow/core/kernels/summary_kernels.cc | 6 +++--- .../{contrib/tensorboard/db => core/summary}/BUILD | 2 +- .../tensorboard/db => core/summary}/loader.cc | 4 ++-- .../tensorboard/db => core/summary}/schema.cc | 2 +- .../tensorboard/db => core/summary}/schema.h | 6 +++--- .../tensorboard/db => core/summary}/schema_test.cc | 2 +- .../db => core/summary}/summary_converter.cc | 2 +- .../db => core/summary}/summary_converter.h | 6 +++--- .../db => core/summary}/summary_db_writer.cc | 4 ++-- .../db => core/summary}/summary_db_writer.h | 6 +++--- .../db => core/summary}/summary_db_writer_test.cc | 4 ++-- .../db => core/summary}/summary_file_writer.cc | 4 ++-- .../db => core/summary}/summary_file_writer.h | 6 +++--- .../db => core/summary}/summary_file_writer_test.cc | 2 +- .../tensorboard/db => core/summary}/vacuum.cc | 0 17 files changed, 38 insertions(+), 40 deletions(-) rename tensorflow/{contrib/tensorboard/db => core/summary}/BUILD (98%) rename tensorflow/{contrib/tensorboard/db => core/summary}/loader.cc (97%) rename tensorflow/{contrib/tensorboard/db => core/summary}/schema.cc (99%) rename tensorflow/{contrib/tensorboard/db => core/summary}/schema.h (87%) rename tensorflow/{contrib/tensorboard/db => core/summary}/schema_test.cc (95%) rename tensorflow/{contrib/tensorboard/db => core/summary}/summary_converter.cc (99%) rename tensorflow/{contrib/tensorboard/db => core/summary}/summary_converter.h (89%) rename tensorflow/{contrib/tensorboard/db => core/summary}/summary_db_writer.cc (99%) rename tensorflow/{contrib/tensorboard/db => core/summary}/summary_db_writer.h (89%) rename tensorflow/{contrib/tensorboard/db => core/summary}/summary_db_writer_test.cc (99%) rename tensorflow/{contrib/tensorboard/db => core/summary}/summary_file_writer.cc (98%) rename tensorflow/{contrib/tensorboard/db => core/summary}/summary_file_writer.h (89%) rename tensorflow/{contrib/tensorboard/db => core/summary}/summary_file_writer_test.cc (99%) rename tensorflow/{contrib/tensorboard/db => core/summary}/vacuum.cc (100%) diff --git a/tensorflow/contrib/cmake/tf_core_framework.cmake b/tensorflow/contrib/cmake/tf_core_framework.cmake index d7b2a1339e..9ae5831f47 100644 --- a/tensorflow/contrib/cmake/tf_core_framework.cmake +++ b/tensorflow/contrib/cmake/tf_core_framework.cmake @@ -302,8 +302,8 @@ file(GLOB_RECURSE tf_core_framework_srcs "${tensorflow_source_dir}/tensorflow/core/common_runtime/session.cc" "${tensorflow_source_dir}/tensorflow/core/common_runtime/session_factory.cc" "${tensorflow_source_dir}/tensorflow/core/common_runtime/session_options.cc" - "${tensorflow_source_dir}/tensorflow/contrib/tensorboard/db/*.cc" - "${tensorflow_source_dir}/tensorflow/contrib/tensorboard/db/*.h" + "${tensorflow_source_dir}/tensorflow/core/summary/*.cc" + "${tensorflow_source_dir}/tensorflow/core/summary/*.h" "${tensorflow_source_dir}/public/*.h" ) @@ -317,14 +317,14 @@ file(GLOB_RECURSE tf_core_framework_exclude_srcs "${tensorflow_source_dir}/tensorflow/core/util/*test*.h" "${tensorflow_source_dir}/tensorflow/core/util/*test*.cc" "${tensorflow_source_dir}/tensorflow/core/util/*main.cc" - "${tensorflow_source_dir}/tensorflow/contrib/tensorboard/db/*test*.cc" - "${tensorflow_source_dir}/tensorflow/contrib/tensorboard/db/loader.cc" - "${tensorflow_source_dir}/tensorflow/contrib/tensorboard/db/vacuum.cc" + "${tensorflow_source_dir}/tensorflow/core/summary/*test*.cc" + "${tensorflow_source_dir}/tensorflow/core/summary/loader.cc" + "${tensorflow_source_dir}/tensorflow/core/summary/vacuum.cc" ) # TODO(jart): Why doesn't this work? # set_source_files_properties( -# ${tensorflow_source_dir}/tensorflow/contrib/tensorboard/db/snapfn.cc +# ${tensorflow_source_dir}/tensorflow/core/lib/db/snapfn.cc # PROPERTIES COMPILE_FLAGS -DSQLITE_OMIT_LOAD_EXTENSION) list(REMOVE_ITEM tf_core_framework_srcs ${tf_core_framework_exclude_srcs}) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 3891caf5d3..136902e249 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -31,7 +31,6 @@ load( "//tensorflow:tensorflow.bzl", "cc_header_only_library", "if_android", - "if_not_v2", "if_not_windows", "tf_cc_binary", "tf_cc_test", @@ -6888,11 +6887,10 @@ tf_kernel_library( "//tensorflow/core:protos_all_cc", "//tensorflow/core:summary_ops_op_lib", "//tensorflow/core/lib/db:sqlite", - ] + if_not_v2([ - "//tensorflow/contrib/tensorboard/db:schema", - "//tensorflow/contrib/tensorboard/db:summary_db_writer", - "//tensorflow/contrib/tensorboard/db:summary_file_writer", - ]), + "//tensorflow/core/summary:schema", + "//tensorflow/core/summary:summary_db_writer", + "//tensorflow/core/summary:summary_file_writer", + ], ) tf_kernel_library( diff --git a/tensorflow/core/kernels/summary_kernels.cc b/tensorflow/core/kernels/summary_kernels.cc index b287f0cc2f..5e3465d1dd 100644 --- a/tensorflow/core/kernels/summary_kernels.cc +++ b/tensorflow/core/kernels/summary_kernels.cc @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/contrib/tensorboard/db/schema.h" -#include "tensorflow/contrib/tensorboard/db/summary_db_writer.h" -#include "tensorflow/contrib/tensorboard/db/summary_file_writer.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/resource_mgr.h" #include "tensorflow/core/lib/db/sqlite.h" #include "tensorflow/core/platform/protobuf.h" +#include "tensorflow/core/summary/schema.h" +#include "tensorflow/core/summary/summary_db_writer.h" +#include "tensorflow/core/summary/summary_file_writer.h" #include "tensorflow/core/util/event.pb.h" namespace tensorflow { diff --git a/tensorflow/contrib/tensorboard/db/BUILD b/tensorflow/core/summary/BUILD similarity index 98% rename from tensorflow/contrib/tensorboard/db/BUILD rename to tensorflow/core/summary/BUILD index 6507546ee9..a89175cdb1 100644 --- a/tensorflow/contrib/tensorboard/db/BUILD +++ b/tensorflow/core/summary/BUILD @@ -1,5 +1,5 @@ # Description: -# TensorBoard database code. +# C++ implementation code for the summary writing APIs. package(default_visibility = ["//tensorflow:internal"]) diff --git a/tensorflow/contrib/tensorboard/db/loader.cc b/tensorflow/core/summary/loader.cc similarity index 97% rename from tensorflow/contrib/tensorboard/db/loader.cc rename to tensorflow/core/summary/loader.cc index 6439328022..68535feacf 100644 --- a/tensorflow/contrib/tensorboard/db/loader.cc +++ b/tensorflow/core/summary/loader.cc @@ -15,8 +15,8 @@ limitations under the License. #include #include -#include "tensorflow/contrib/tensorboard/db/schema.h" -#include "tensorflow/contrib/tensorboard/db/summary_db_writer.h" +#include "tensorflow/core/summary/schema.h" +#include "tensorflow/core/summary/summary_db_writer.h" #include "tensorflow/core/lib/db/sqlite.h" #include "tensorflow/core/lib/io/record_reader.h" #include "tensorflow/core/platform/init_main.h" diff --git a/tensorflow/contrib/tensorboard/db/schema.cc b/tensorflow/core/summary/schema.cc similarity index 99% rename from tensorflow/contrib/tensorboard/db/schema.cc rename to tensorflow/core/summary/schema.cc index 3c7bc87e4a..822e2fa3bf 100644 --- a/tensorflow/contrib/tensorboard/db/schema.cc +++ b/tensorflow/core/summary/schema.cc @@ -12,7 +12,7 @@ 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/contrib/tensorboard/db/schema.h" +#include "tensorflow/core/summary/schema.h" #include "tensorflow/core/lib/core/errors.h" diff --git a/tensorflow/contrib/tensorboard/db/schema.h b/tensorflow/core/summary/schema.h similarity index 87% rename from tensorflow/contrib/tensorboard/db/schema.h rename to tensorflow/core/summary/schema.h index 3da4504225..6305f8eabd 100644 --- a/tensorflow/contrib/tensorboard/db/schema.h +++ b/tensorflow/core/summary/schema.h @@ -12,8 +12,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_CONTRIB_TENSORBOARD_DB_SCHEMA_H_ -#define TENSORFLOW_CONTRIB_TENSORBOARD_DB_SCHEMA_H_ +#ifndef TENSORFLOW_CORE_SUMMARY_SCHEMA_H_ +#define TENSORFLOW_CORE_SUMMARY_SCHEMA_H_ #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/db/sqlite.h" @@ -30,4 +30,4 @@ Status SetupTensorboardSqliteDb(Sqlite* db); } // namespace tensorflow -#endif // TENSORFLOW_CONTRIB_TENSORBOARD_DB_SCHEMA_H_ +#endif // TENSORFLOW_CORE_SUMMARY_SCHEMA_H_ diff --git a/tensorflow/contrib/tensorboard/db/schema_test.cc b/tensorflow/core/summary/schema_test.cc similarity index 95% rename from tensorflow/contrib/tensorboard/db/schema_test.cc rename to tensorflow/core/summary/schema_test.cc index 4d3f2880bd..fa21b45b62 100644 --- a/tensorflow/contrib/tensorboard/db/schema_test.cc +++ b/tensorflow/core/summary/schema_test.cc @@ -12,7 +12,7 @@ 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/contrib/tensorboard/db/schema.h" +#include "tensorflow/core/summary/schema.h" #include diff --git a/tensorflow/contrib/tensorboard/db/summary_converter.cc b/tensorflow/core/summary/summary_converter.cc similarity index 99% rename from tensorflow/contrib/tensorboard/db/summary_converter.cc rename to tensorflow/core/summary/summary_converter.cc index 93c1183072..e6e34e9602 100644 --- a/tensorflow/contrib/tensorboard/db/summary_converter.cc +++ b/tensorflow/core/summary/summary_converter.cc @@ -12,7 +12,7 @@ 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/contrib/tensorboard/db/summary_converter.h" +#include "tensorflow/core/summary/summary_converter.h" #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/summary.pb.h" diff --git a/tensorflow/contrib/tensorboard/db/summary_converter.h b/tensorflow/core/summary/summary_converter.h similarity index 89% rename from tensorflow/contrib/tensorboard/db/summary_converter.h rename to tensorflow/core/summary/summary_converter.h index 329c7f9f2f..dc005d2604 100644 --- a/tensorflow/contrib/tensorboard/db/summary_converter.h +++ b/tensorflow/core/summary/summary_converter.h @@ -12,8 +12,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_CONTRIB_TENSORBOARD_DB_SUMMARY_CONVERTER_H_ -#define TENSORFLOW_CONTRIB_TENSORBOARD_DB_SUMMARY_CONVERTER_H_ +#ifndef TENSORFLOW_CORE_SUMMARY_SUMMARY_CONVERTER_H_ +#define TENSORFLOW_CORE_SUMMARY_SUMMARY_CONVERTER_H_ #include "tensorflow/core/framework/summary.pb.h" #include "tensorflow/core/framework/tensor.h" @@ -35,4 +35,4 @@ Status AddTensorAsAudioToSummary(const Tensor& tensor, const string& tag, } // namespace tensorflow -#endif // TENSORFLOW_CONTRIB_TENSORBOARD_DB_SUMMARY_CONVERTER_H_ +#endif // TENSORFLOW_CORE_SUMMARY_SUMMARY_CONVERTER_H_ diff --git a/tensorflow/contrib/tensorboard/db/summary_db_writer.cc b/tensorflow/core/summary/summary_db_writer.cc similarity index 99% rename from tensorflow/contrib/tensorboard/db/summary_db_writer.cc rename to tensorflow/core/summary/summary_db_writer.cc index cfdc884277..7a5d796821 100644 --- a/tensorflow/contrib/tensorboard/db/summary_db_writer.cc +++ b/tensorflow/core/summary/summary_db_writer.cc @@ -12,11 +12,11 @@ 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/contrib/tensorboard/db/summary_db_writer.h" +#include "tensorflow/core/summary/summary_db_writer.h" #include -#include "tensorflow/contrib/tensorboard/db/summary_converter.h" +#include "tensorflow/core/summary/summary_converter.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/register_types.h" diff --git a/tensorflow/contrib/tensorboard/db/summary_db_writer.h b/tensorflow/core/summary/summary_db_writer.h similarity index 89% rename from tensorflow/contrib/tensorboard/db/summary_db_writer.h rename to tensorflow/core/summary/summary_db_writer.h index 746da1533b..5669afe7f6 100644 --- a/tensorflow/contrib/tensorboard/db/summary_db_writer.h +++ b/tensorflow/core/summary/summary_db_writer.h @@ -12,8 +12,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_CONTRIB_TENSORBOARD_DB_SUMMARY_DB_WRITER_H_ -#define TENSORFLOW_CONTRIB_TENSORBOARD_DB_SUMMARY_DB_WRITER_H_ +#ifndef TENSORFLOW_CORE_SUMMARY_SUMMARY_DB_WRITER_H_ +#define TENSORFLOW_CORE_SUMMARY_SUMMARY_DB_WRITER_H_ #include "tensorflow/core/kernels/summary_interface.h" #include "tensorflow/core/lib/core/status.h" @@ -39,4 +39,4 @@ Status CreateSummaryDbWriter(Sqlite* db, const string& experiment_name, } // namespace tensorflow -#endif // TENSORFLOW_CONTRIB_TENSORBOARD_DB_SUMMARY_DB_WRITER_H_ +#endif // TENSORFLOW_CORE_SUMMARY_SUMMARY_DB_WRITER_H_ diff --git a/tensorflow/contrib/tensorboard/db/summary_db_writer_test.cc b/tensorflow/core/summary/summary_db_writer_test.cc similarity index 99% rename from tensorflow/contrib/tensorboard/db/summary_db_writer_test.cc rename to tensorflow/core/summary/summary_db_writer_test.cc index 2e8d4109dd..c4e9ddea2c 100644 --- a/tensorflow/contrib/tensorboard/db/summary_db_writer_test.cc +++ b/tensorflow/core/summary/summary_db_writer_test.cc @@ -12,9 +12,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/contrib/tensorboard/db/summary_db_writer.h" +#include "tensorflow/core/summary/summary_db_writer.h" -#include "tensorflow/contrib/tensorboard/db/schema.h" +#include "tensorflow/core/summary/schema.h" #include "tensorflow/core/framework/function.pb.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/node_def.pb.h" diff --git a/tensorflow/contrib/tensorboard/db/summary_file_writer.cc b/tensorflow/core/summary/summary_file_writer.cc similarity index 98% rename from tensorflow/contrib/tensorboard/db/summary_file_writer.cc rename to tensorflow/core/summary/summary_file_writer.cc index 22b6f09d0c..593ccdd684 100644 --- a/tensorflow/contrib/tensorboard/db/summary_file_writer.cc +++ b/tensorflow/core/summary/summary_file_writer.cc @@ -12,9 +12,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/contrib/tensorboard/db/summary_file_writer.h" +#include "tensorflow/core/summary/summary_file_writer.h" -#include "tensorflow/contrib/tensorboard/db/summary_converter.h" +#include "tensorflow/core/summary/summary_converter.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/resource_mgr.h" diff --git a/tensorflow/contrib/tensorboard/db/summary_file_writer.h b/tensorflow/core/summary/summary_file_writer.h similarity index 89% rename from tensorflow/contrib/tensorboard/db/summary_file_writer.h rename to tensorflow/core/summary/summary_file_writer.h index 73b0a5542b..7d964516da 100644 --- a/tensorflow/contrib/tensorboard/db/summary_file_writer.h +++ b/tensorflow/core/summary/summary_file_writer.h @@ -12,8 +12,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_CONTRIB_TENSORBOARD_DB_SUMMARY_FILE_WRITER_H_ -#define TENSORFLOW_CONTRIB_TENSORBOARD_DB_SUMMARY_FILE_WRITER_H_ +#ifndef TENSORFLOW_CORE_SUMMARY_SUMMARY_FILE_WRITER_H_ +#define TENSORFLOW_CORE_SUMMARY_SUMMARY_FILE_WRITER_H_ #include "tensorflow/core/kernels/summary_interface.h" #include "tensorflow/core/lib/core/status.h" @@ -40,4 +40,4 @@ Status CreateSummaryFileWriter(int max_queue, int flush_millis, } // namespace tensorflow -#endif // TENSORFLOW_CONTRIB_TENSORBOARD_DB_SUMMARY_FILE_WRITER_H_ +#endif // TENSORFLOW_CORE_SUMMARY_SUMMARY_FILE_WRITER_H_ diff --git a/tensorflow/contrib/tensorboard/db/summary_file_writer_test.cc b/tensorflow/core/summary/summary_file_writer_test.cc similarity index 99% rename from tensorflow/contrib/tensorboard/db/summary_file_writer_test.cc rename to tensorflow/core/summary/summary_file_writer_test.cc index ffbfb9533e..d3b19c3abd 100644 --- a/tensorflow/contrib/tensorboard/db/summary_file_writer_test.cc +++ b/tensorflow/core/summary/summary_file_writer_test.cc @@ -12,7 +12,7 @@ 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/contrib/tensorboard/db/summary_file_writer.h" +#include "tensorflow/core/summary/summary_file_writer.h" #include "tensorflow/core/framework/summary.pb.h" #include "tensorflow/core/framework/tensor.pb.h" diff --git a/tensorflow/contrib/tensorboard/db/vacuum.cc b/tensorflow/core/summary/vacuum.cc similarity index 100% rename from tensorflow/contrib/tensorboard/db/vacuum.cc rename to tensorflow/core/summary/vacuum.cc -- GitLab From bd2610f638bd35dca30bb00a6c16da775b3bfcdf Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Mon, 7 Jan 2019 12:31:43 -0800 Subject: [PATCH 0279/2345] [Grappler] Remapper for GPU Conv2d+BiasAdd+Activation PiperOrigin-RevId: 228214620 --- tensorflow/core/grappler/BUILD | 1 + tensorflow/core/grappler/graph_view.cc | 23 +- tensorflow/core/grappler/graph_view.h | 10 +- tensorflow/core/grappler/optimizers/BUILD | 2 + .../core/grappler/optimizers/remapper.cc | 384 ++++++++++++------ tensorflow/core/grappler/utils.cc | 18 +- tensorflow/core/grappler/utils.h | 3 + tensorflow/core/grappler/utils/BUILD | 1 + .../core/grappler/utils/grappler_test.cc | 27 ++ .../core/grappler/utils/grappler_test.h | 16 +- tensorflow/core/kernels/conv_ops_test.cc | 20 + 11 files changed, 353 insertions(+), 152 deletions(-) diff --git a/tensorflow/core/grappler/BUILD b/tensorflow/core/grappler/BUILD index 4d0f02f4d6..55d642612b 100644 --- a/tensorflow/core/grappler/BUILD +++ b/tensorflow/core/grappler/BUILD @@ -27,6 +27,7 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", + "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], ) diff --git a/tensorflow/core/grappler/graph_view.cc b/tensorflow/core/grappler/graph_view.cc index ba9d2eb321..be9b9c36c7 100644 --- a/tensorflow/core/grappler/graph_view.cc +++ b/tensorflow/core/grappler/graph_view.cc @@ -66,28 +66,27 @@ int OpInputPortIdToArgId(const NodeDef& node, const OpDef& op, int port_id) { bool HasSingleFanoutNode(const GraphView& graph_view, const NodeDef* node, int port) { const auto output = GraphView::OutputPort(node, port); - const auto fanout = graph_view.GetFanout(output); - return fanout.size() <= 1; + return graph_view.GetFanout(output).size() <= 1; } bool HasFanouts(const GraphView& graph_view, const NodeDef* node, int port) { const auto output = GraphView::OutputPort(node, port); - const auto fanout = graph_view.GetFanout(output); - return !fanout.empty(); + return !graph_view.GetFanout(output).empty(); } -bool NoControlFanin(const GraphView& graph_view, const NodeDef* node) { - const auto control_port = GraphView::InputPort(node, -1); - return graph_view.GetFanin(control_port).empty(); +bool HasControlFanin(const GraphView& graph_view, const NodeDef* node) { + const auto control_port = GraphView::InputPort(node, Graph::kControlSlot); + return !graph_view.GetFanin(control_port).empty(); } -bool NoControlFanout(const GraphView& graph_view, const NodeDef* node) { - const auto control_port = GraphView::OutputPort(node, -1); - return graph_view.GetFanout(control_port).empty(); +bool HasControlFanout(const GraphView& graph_view, const NodeDef* node) { + const auto control_port = GraphView::OutputPort(node, Graph::kControlSlot); + return !graph_view.GetFanout(control_port).empty(); } -bool NoControlFaninOrFanout(const GraphView& graph_view, const NodeDef* node) { - return NoControlFanin(graph_view, node) && NoControlFanout(graph_view, node); +bool HasControlFaninOrFanout(const GraphView& graph_view, const NodeDef* node) { + return HasControlFanin(graph_view, node) || + HasControlFanout(graph_view, node); } } // end namespace grappler diff --git a/tensorflow/core/grappler/graph_view.h b/tensorflow/core/grappler/graph_view.h index a17d17524a..dc4ab93894 100644 --- a/tensorflow/core/grappler/graph_view.h +++ b/tensorflow/core/grappler/graph_view.h @@ -369,10 +369,12 @@ bool HasSingleFanoutNode(const GraphView& graph_view, const NodeDef* node, // Returns true if node has at least one fanout node at given output port. bool HasFanouts(const GraphView& graph_view, const NodeDef* node, int port = 0); - -bool NoControlFanin(const GraphView& graph_view, const NodeDef* node); -bool NoControlFanout(const GraphView& graph_view, const NodeDef* node); -bool NoControlFaninOrFanout(const GraphView& graph_view, const NodeDef* node); +// Returns true if the node has at least one input control dependency. +bool HasControlFanin(const GraphView& graph_view, const NodeDef* node); +// Returns true if the node has at least one output control dependency. +bool HasControlFanout(const GraphView& graph_view, const NodeDef* node); +// Returns true if the node has at least one input or output control dependency. +bool HasControlFaninOrFanout(const GraphView& graph_view, const NodeDef* node); } // end namespace grappler } // end namespace tensorflow diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index e2b9de7cc5..7e29cee86a 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -700,6 +700,7 @@ cc_library( "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler:utils", "//tensorflow/core/grappler/costs:graph_properties", + "//tensorflow/core/grappler/utils:symbolic_shapes", "//tensorflow/core/grappler/utils:topological_sort", "@com_google_absl//absl/container:flat_hash_set", ], @@ -711,6 +712,7 @@ tf_cuda_cc_test( deps = [ ":remapper", "//tensorflow/cc:cc_ops", + "//tensorflow/core:framework", "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", "//tensorflow/core:test_main", diff --git a/tensorflow/core/grappler/optimizers/remapper.cc b/tensorflow/core/grappler/optimizers/remapper.cc index f0c81f29e6..0869e3b49b 100644 --- a/tensorflow/core/grappler/optimizers/remapper.cc +++ b/tensorflow/core/grappler/optimizers/remapper.cc @@ -23,6 +23,7 @@ limitations under the License. #include "tensorflow/core/grappler/op_types.h" #include "tensorflow/core/grappler/optimizers/constant_folding.h" #include "tensorflow/core/grappler/utils.h" +#include "tensorflow/core/grappler/utils/symbolic_shapes.h" #include "tensorflow/core/grappler/utils/topological_sort.h" #include "tensorflow/core/platform/logging.h" @@ -60,17 +61,30 @@ struct RemapperContext { // FusedBatchNorm that can be replaced with a cheaper set of primitives. struct FusedBatchNorm { + FusedBatchNorm() = default; + explicit FusedBatchNorm(const NodeDef* fused_batch_norm) + : fused_batch_norm(fused_batch_norm) {} + const NodeDef* fused_batch_norm = nullptr; }; // Conv2D node followed by a BiasAdd. struct Conv2DWithBiasAdd { + Conv2DWithBiasAdd() = default; + Conv2DWithBiasAdd(const NodeDef* conv2d, const NodeDef* bias_add) + : conv2d(conv2d), bias_add(bias_add) {} + const NodeDef* conv2d = nullptr; const NodeDef* bias_add = nullptr; }; // Conv2D node followed by a BiasAdd and Relu. struct Conv2DWithBiasAddAndRelu { + Conv2DWithBiasAddAndRelu() = default; + Conv2DWithBiasAddAndRelu(const NodeDef* conv2d, const NodeDef* bias_add, + const NodeDef* relu) + : conv2d(conv2d), bias_add(bias_add), relu(relu) {} + const NodeDef* conv2d = nullptr; const NodeDef* bias_add = nullptr; const NodeDef* relu = nullptr; @@ -78,6 +92,11 @@ struct Conv2DWithBiasAddAndRelu { // Conv2D node followed by a Squeeze and BiasAdd. struct Conv2DWithSqueezeAndBiasAdd { + Conv2DWithSqueezeAndBiasAdd() = default; + Conv2DWithSqueezeAndBiasAdd(const NodeDef* conv2d, const NodeDef* squeeze, + const NodeDef* bias_add) + : conv2d(conv2d), squeeze(squeeze), bias_add(bias_add) {} + const NodeDef* conv2d = nullptr; const NodeDef* squeeze = nullptr; const NodeDef* bias_add = nullptr; @@ -85,6 +104,11 @@ struct Conv2DWithSqueezeAndBiasAdd { // Conv2D node followed by a FusedBatchNorm. struct Conv2DWithBatchNorm { + Conv2DWithBatchNorm() = default; + Conv2DWithBatchNorm(const NodeDef* conv2d, const NodeDef* fused_batch_norm, + float epsilon = 0.0) + : conv2d(conv2d), fused_batch_norm(fused_batch_norm), epsilon(epsilon) {} + const NodeDef* conv2d = nullptr; const NodeDef* fused_batch_norm = nullptr; float epsilon = 0.0; @@ -92,16 +116,23 @@ struct Conv2DWithBatchNorm { // Conv2D node followed by a FusedBatchNorm and Relu. struct Conv2DWithBatchNormAndRelu { + Conv2DWithBatchNormAndRelu() = default; + Conv2DWithBatchNormAndRelu(const NodeDef* conv2d, + const NodeDef* fused_batch_norm, + const NodeDef* relu, float epsilon = 0.0) + : conv2d(conv2d), + fused_batch_norm(fused_batch_norm), + relu(relu), + epsilon(epsilon) {} + const NodeDef* conv2d = nullptr; const NodeDef* fused_batch_norm = nullptr; const NodeDef* relu = nullptr; float epsilon = 0.0; }; -bool IsFloatOrDoubleDataType(const NodeDef* node, - const string& type_attr = "T") { - DataType dtype = GetDataTypeFromAttr(*node, type_attr); - return dtype == DT_FLOAT || dtype == DT_DOUBLE; +bool IsInPreserveSet(const RemapperContext& ctx, const NodeDef* node) { + return ctx.nodes_to_preserve.count(node->name()) > 0; } bool HaveSameDataType(const NodeDef* lhs, const NodeDef* rhs, @@ -119,91 +150,165 @@ bool HasDataType(const NodeDef* node, const DataType& expected, return dtype == expected; } -bool IsInPreserveSet(const RemapperContext& ctx, const NodeDef* node) { - return ctx.nodes_to_preserve.count(node->name()) > 0; +bool IsCpuCompatibleDataType(const NodeDef* node, + const string& type_attr = "T") { + DataType dtype = GetDataTypeFromAttr(*node, type_attr); + return dtype == DT_FLOAT || dtype == DT_DOUBLE; +} + +bool IsGpuCompatibleDataType(const NodeDef* node, + const string& type_attr = "T") { + DataType dtype = GetDataTypeFromAttr(*node, type_attr); + return dtype == DT_FLOAT; +} + +bool IsCpuCompatibleDataFormat(const NodeDef* conv2d) { + DCHECK(IsConv2D(*conv2d)) << "Expected Conv2D op"; + const string& data_format = conv2d->attr().at(kDataFormat).s(); + return data_format == "NHWC"; +} + +bool IsGpuCompatibleDataFormat(const NodeDef* conv2d) { + DCHECK(IsConv2D(*conv2d)) << "Expected Conv2D op"; + const string& data_format = conv2d->attr().at(kDataFormat).s(); + return data_format == "NHWC" || data_format == "NCHW"; } -bool FindConv2DWithBias(const RemapperContext& ctx, const NodeDef* node, - Conv2DWithBiasAdd* matched) { +bool IsCpuCompatibleConv2D(const NodeDef* conv2d) { + DCHECK(IsConv2D(*conv2d)) << "Expected Conv2D op"; + return NodeIsOnCpu(conv2d) && IsCpuCompatibleDataType(conv2d) && + IsCpuCompatibleDataFormat(conv2d); +} + +bool IsGpuCompatibleConv2D(const NodeDef* conv2d) { + DCHECK(IsConv2D(*conv2d)) << "Expected Conv2D op"; + return NodeIsOnGpu(conv2d) && IsGpuCompatibleDataType(conv2d) && + IsGpuCompatibleDataFormat(conv2d); +} + +// Checks if we can rewrite a pattern to the `_FusedConv2D` on CPU device. +template +bool IsCpuCompatible(const Pattern& matched) { + return IsCpuCompatibleConv2D(matched.conv2d); +} + +// Checks if we can rewrite a pattern to the `_FusedConv2D` on GPU device. +bool IsGpuCompatible(const RemapperContext& ctx, + const Conv2DWithBiasAddAndRelu& matched) { + const std::vector& input_props = + ctx.graph_properties.GetInputProperties(matched.conv2d->name()); + const TensorShapeProto& filter_shape = + input_props.size() >= 2 ? input_props[1].shape() : TensorShapeProto(); + + // FusedConv2D on GPU with 1x1 convolution is marginally faster than + // in-graph computation in micro benchmarks (see kernels/conv_ops_test.cc), + // and significantly slower in large scale benchmarks. + bool is_spatial_conv = Rank(filter_shape) == 4 && // + IsKnown(filter_shape.dim(1)) && // + IsKnown(filter_shape.dim(2)) && // + filter_shape.dim(1).size() != 1 && // + filter_shape.dim(2).size() != 1; + + return is_spatial_conv && IsGpuCompatibleConv2D(matched.conv2d); +} +bool IsGpuCompatible(const RemapperContext& ctx, + const Conv2DWithBiasAdd& matched) { + return false; +} +bool IsGpuCompatible(const RemapperContext& ctx, + const Conv2DWithSqueezeAndBiasAdd& matched) { + return false; +} + +// Returns true if the given pattern is supported on the assigned device. +template +bool IsDeviceCompatible(const RemapperContext& ctx, Pattern& matched) { + return IsCpuCompatible(matched) || IsGpuCompatible(ctx, matched); +} + +bool FindConv2DWithBias(const RemapperContext& ctx, const NodeDef* bias_add, + Conv2DWithBiasAdd* matched, + bool check_device_compatible = true) { if (!EigenSupportsContractionOutputKernel()) return false; // Root of the pattern must be a BiasAdd. - if (!node) return false; - if (!IsBiasAdd(*node)) return false; - if (!NodeIsOnCpu(node)) return false; - if (!IsFloatOrDoubleDataType(node)) return false; - if (!NoControlFaninOrFanout(ctx.graph_view, node)) return false; + if (bias_add == nullptr || !IsBiasAdd(*bias_add) || + HasControlFaninOrFanout(ctx.graph_view, bias_add)) + return false; - // Input to the BiasAdd must be a Conv2D in NHWC format. - const auto input_port = GraphView::InputPort(node, 0); + // Input to the BiasAdd must be a Conv2D. + const auto input_port = GraphView::InputPort(bias_add, 0); const auto conv2d = ctx.graph_view.GetRegularFanin(input_port); - if (!conv2d.node) return false; - if (!IsConv2D(*conv2d.node)) return false; - if (conv2d.node->attr().at(kDataFormat).s() != "NHWC") return false; - if (!NodeIsOnCpu(conv2d.node)) return false; - if (!HaveSameDataType(node, conv2d.node)) return false; - if (!NoControlFaninOrFanout(ctx.graph_view, conv2d.node)) return false; - if (!HasSingleFanoutNode(ctx.graph_view, conv2d.node)) return false; - if (IsInPreserveSet(ctx, conv2d.node)) return false; + + if (!conv2d.node || !IsConv2D(*conv2d.node) || + !HaveSameDataType(bias_add, conv2d.node) || + HasControlFaninOrFanout(ctx.graph_view, conv2d.node) || + !HasSingleFanoutNode(ctx.graph_view, conv2d.node) || + IsInPreserveSet(ctx, conv2d.node)) + return false; + + // Check that data type and data format are supported on assigned device. + const Conv2DWithBiasAdd pattern{conv2d.node, bias_add}; + if (check_device_compatible && !IsDeviceCompatible(ctx, pattern)) { + return false; + } // We successfully found a Conv2D+BiasAdd pattern. - matched->conv2d = conv2d.node; - matched->bias_add = node; + *matched = pattern; return true; } -bool FindConv2DWithBiasAndRelu(const RemapperContext& ctx, const NodeDef* node, +bool FindConv2DWithBiasAndRelu(const RemapperContext& ctx, const NodeDef* relu, Conv2DWithBiasAddAndRelu* matched) { if (!EigenSupportsContractionOutputKernel()) return false; // Root of the pattern must be a Relu. - if (!node) return false; - if (!IsRelu(*node)) return false; - if (!NodeIsOnCpu(node)) return false; - if (!IsFloatOrDoubleDataType(node)) return false; - if (!NoControlFaninOrFanout(ctx.graph_view, node)) return false; + if (!relu || !IsRelu(*relu) || HasControlFaninOrFanout(ctx.graph_view, relu)) + return false; // And input to Relu must match Conv2DWithBiasAdd pattern. - const auto input_port = GraphView::InputPort(node, 0); + const auto input_port = GraphView::InputPort(relu, 0); const auto bias_add = ctx.graph_view.GetRegularFanin(input_port); Conv2DWithBiasAdd base; - if (!FindConv2DWithBias(ctx, bias_add.node, &base)) return false; - if (!HasSingleFanoutNode(ctx.graph_view, base.bias_add)) return false; - if (!HaveSameDataType(node, base.bias_add)) return false; - if (IsInPreserveSet(ctx, base.bias_add)) return false; + if (!FindConv2DWithBias(ctx, bias_add.node, &base, + /*check_device_compatible=*/false) || + !HasSingleFanoutNode(ctx.graph_view, base.bias_add) || + !HaveSameDataType(relu, base.bias_add) || + IsInPreserveSet(ctx, base.bias_add)) + return false; + + // Check that data type and data format are supported on assigned device. + const Conv2DWithBiasAddAndRelu pattern{base.conv2d, base.bias_add, relu}; + if (!IsDeviceCompatible(ctx, pattern)) return false; // We successfully found a Conv2D+BiasAdd+Relu pattern. - matched->conv2d = base.conv2d; - matched->bias_add = base.bias_add; - matched->relu = node; + *matched = pattern; return true; } bool FindConv2DWithSqueezeAndBias(const RemapperContext& ctx, - const NodeDef* node, + const NodeDef* bias_add, Conv2DWithSqueezeAndBiasAdd* matched) { if (!EigenSupportsContractionOutputKernel()) return false; // Root of the pattern must be a BiasAdd. - if (node == nullptr) return false; - if (node->op() != "BiasAdd") return false; - if (!NodeIsOnCpu(node)) return false; - if (!IsFloatOrDoubleDataType(node)) return false; - if (!NoControlFaninOrFanout(ctx.graph_view, node)) return false; + if (!bias_add || !IsBiasAdd(*bias_add) || + HasControlFaninOrFanout(ctx.graph_view, bias_add)) + return false; // Input to the BiasAdd must be a Squeeze. - const auto bias_input_port = GraphView::InputPort(node, 0); + const auto bias_input_port = GraphView::InputPort(bias_add, 0); const auto squeeze = ctx.graph_view.GetRegularFanin(bias_input_port); - if (squeeze.node == nullptr) return false; - if (squeeze.node->op() != "Squeeze") return false; - if (!NodeIsOnCpu(squeeze.node)) return false; - if (!HaveSameDataType(node, squeeze.node, "T")) return false; - if (!NoControlFaninOrFanout(ctx.graph_view, squeeze.node)) return false; - if (!HasSingleFanoutNode(ctx.graph_view, squeeze.node)) return false; - if (IsInPreserveSet(ctx, squeeze.node)) return false; + + if (!squeeze.node || !IsSqueeze(*squeeze.node) || + !HaveSameDataType(bias_add, squeeze.node, "T") || + HasControlFaninOrFanout(ctx.graph_view, squeeze.node) || + !HasSingleFanoutNode(ctx.graph_view, squeeze.node) || + IsInPreserveSet(ctx, squeeze.node)) + return false; // Squeeze must not squeeze output channel dimension. std::vector dims; @@ -212,67 +317,72 @@ bool FindConv2DWithSqueezeAndBias(const RemapperContext& ctx, if (dim == 3) return false; } - // Input to the Squeeze must be a Conv2D in NHWC format. + // Input to the Squeeze must be a Conv2D. const auto squeeze_input_port = GraphView::InputPort(squeeze.node, 0); const auto conv2d = ctx.graph_view.GetRegularFanin(squeeze_input_port); - if (conv2d.node == nullptr) return false; - if (conv2d.node->op() != "Conv2D") return false; - if (conv2d.node->attr().at("data_format").s() != "NHWC") return false; - if (!NodeIsOnCpu(conv2d.node)) return false; - if (!HaveSameDataType(node, conv2d.node, "T")) return false; - if (!NoControlFaninOrFanout(ctx.graph_view, conv2d.node)) return false; - if (!HasSingleFanoutNode(ctx.graph_view, conv2d.node)) return false; - if (IsInPreserveSet(ctx, conv2d.node)) return false; + + if (!conv2d.node || !IsConv2D(*conv2d.node) || + !HaveSameDataType(bias_add, conv2d.node, "T") || + HasControlFaninOrFanout(ctx.graph_view, conv2d.node) || + !HasSingleFanoutNode(ctx.graph_view, conv2d.node) || + IsInPreserveSet(ctx, conv2d.node)) + return false; + + // Check that data type and data format are supported on assigned device. + const Conv2DWithSqueezeAndBiasAdd pattern{conv2d.node, squeeze.node, + bias_add}; + if (!IsDeviceCompatible(ctx, pattern)) return false; // We successfully found a Conv2D+Squeeze+BiasAdd pattern. - matched->conv2d = conv2d.node; - matched->squeeze = squeeze.node; - matched->bias_add = node; + *matched = pattern; return true; } -bool FindConv2DWithBatchNorm(const RemapperContext& ctx, const NodeDef* node, +bool FindConv2DWithBatchNorm(const RemapperContext& ctx, + const NodeDef* batch_norm, Conv2DWithBatchNorm* matched) { if (!EigenSupportsContractionOutputKernel()) return false; // Root of the pattern must be a FusedBatchNorm or a FusedBatchNormV2. - if (node == nullptr) return false; - if (!IsFusedBatchNorm(*node)) return false; - if (!NodeIsOnCpu(node)) return false; - if (!HasDataType(node, DT_FLOAT)) return false; + if (!batch_norm || !IsFusedBatchNorm(*batch_norm)) return false; // V2 has a separate data type for the scale/offset/mean/variance inputs. - if (node->op() == "FusedBatchNormV2" && !HasDataType(node, DT_FLOAT, "U")) + if (batch_norm->op() == "FusedBatchNormV2" && + !HasDataType(batch_norm, DT_FLOAT, "U")) return false; // Check that batch normalization is in inference mode. - const auto& attr = node->attr(); + const auto& attr = batch_norm->attr(); if (attr.count(kIsTraining) > 0 && attr.at(kIsTraining).b()) return false; // Check that only 0th output is consumed by other nodes. - if (!NoControlFaninOrFanout(ctx.graph_view, node)) return false; - if (HasFanouts(ctx.graph_view, node, 1)) return false; // batch_mean - if (HasFanouts(ctx.graph_view, node, 2)) return false; // batch_variance - if (HasFanouts(ctx.graph_view, node, 3)) return false; // reserve_space_1 - if (HasFanouts(ctx.graph_view, node, 4)) return false; // reserve_space_2 + if (HasControlFaninOrFanout(ctx.graph_view, batch_norm) || + HasFanouts(ctx.graph_view, batch_norm, 1) || // batch_mean + HasFanouts(ctx.graph_view, batch_norm, 2) || // batch_variance + HasFanouts(ctx.graph_view, batch_norm, 3) || // reserve_space_1 + HasFanouts(ctx.graph_view, batch_norm, 4)) // reserve_space_2 + return false; - // Input to the FusedBatchNorm must be a Conv2D in NHWC format. - const auto input_port = GraphView::InputPort(node, 0); + // Input to the FusedBatchNorm must be a Conv2D. + const auto input_port = GraphView::InputPort(batch_norm, 0); const auto conv2d = ctx.graph_view.GetRegularFanin(input_port); - if (conv2d.node == nullptr) return false; - if (!IsConv2D(*conv2d.node)) return false; - if (conv2d.node->attr().at(kDataFormat).s() != "NHWC") return false; - if (!NodeIsOnCpu(conv2d.node)) return false; - if (!HaveSameDataType(node, conv2d.node)) return false; - if (!NoControlFaninOrFanout(ctx.graph_view, conv2d.node)) return false; - if (!HasSingleFanoutNode(ctx.graph_view, conv2d.node)) return false; - if (IsInPreserveSet(ctx, conv2d.node)) return false; + + if (!conv2d.node || !IsConv2D(*conv2d.node) || // + !NodeIsOnCpu(conv2d.node) || // + !HaveSameDataType(batch_norm, conv2d.node) || // + !IsCpuCompatibleDataType(conv2d.node) || // + !IsCpuCompatibleDataFormat(conv2d.node) || // + HasControlFaninOrFanout(ctx.graph_view, conv2d.node) || // + !HasSingleFanoutNode(ctx.graph_view, conv2d.node) || // + IsInPreserveSet(ctx, conv2d.node)) + return false; // We successfully found a Conv2D+FusedBatchNorm pattern. matched->conv2d = conv2d.node; - matched->fused_batch_norm = node; - if (!GetNodeAttr(*node, "epsilon", &matched->epsilon).ok()) return false; + matched->fused_batch_norm = batch_norm; + if (!GetNodeAttr(*batch_norm, "epsilon", &matched->epsilon).ok()) + return false; return true; } @@ -283,21 +393,19 @@ bool FindConv2DWithBatchNormAndRelu(const RemapperContext& ctx, if (!EigenSupportsContractionOutputKernel()) return false; // Root of the pattern must be a Relu. - if (node == nullptr) return false; - if (!IsRelu(*node)) return false; - if (!NodeIsOnCpu(node)) return false; - if (!IsFloatOrDoubleDataType(node)) return false; - if (!NoControlFaninOrFanout(ctx.graph_view, node)) return false; + if (!node || !IsRelu(*node) || HasControlFaninOrFanout(ctx.graph_view, node)) + return false; // And input to Relu must match Conv2DWithBatchNorm pattern. const auto input_port = GraphView::InputPort(node, 0); const auto batch_norm = ctx.graph_view.GetRegularFanin(input_port); Conv2DWithBatchNorm base; - if (!FindConv2DWithBatchNorm(ctx, batch_norm.node, &base)) return false; - if (!HasSingleFanoutNode(ctx.graph_view, base.fused_batch_norm)) return false; - if (!HaveSameDataType(node, base.fused_batch_norm)) return false; - if (IsInPreserveSet(ctx, base.fused_batch_norm)) return false; + if (!FindConv2DWithBatchNorm(ctx, batch_norm.node, &base) || + !HasSingleFanoutNode(ctx.graph_view, base.fused_batch_norm) || + !HaveSameDataType(node, base.fused_batch_norm) || + IsInPreserveSet(ctx, base.fused_batch_norm)) + return false; // We successfully found a Conv2D+FusedBatchNorm+Relu pattern. matched->conv2d = base.conv2d; @@ -355,9 +463,7 @@ bool FindFusedBatchNorm(const RemapperContext& ctx, const NodeDef* node, return true; } -void CopyConv2DAttributes(const NodeDef* conv2d, NodeDef* fused_conv2d, - const std::vector& fused_ops = {}, - int num_args = 1, float epsilon = 0.0) { +void CopyConv2DAttributes(const NodeDef* conv2d, NodeDef* fused_conv2d) { auto* attr = fused_conv2d->mutable_attr(); auto src_attr = conv2d->attr(); @@ -367,53 +473,65 @@ void CopyConv2DAttributes(const NodeDef* conv2d, NodeDef* fused_conv2d, (*attr)["dilations"] = src_attr.at("dilations"); (*attr)["data_format"] = src_attr.at("data_format"); (*attr)["use_cudnn_on_gpu"] = src_attr.at("use_cudnn_on_gpu"); +} - auto* fused_ops_attr = (*attr)["fused_ops"].mutable_list(); - for (const string& fused_op : fused_ops) { - fused_ops_attr->add_s(fused_op); - } - +void SetFusedConv2DAttributes( + NodeDef* fused_conv2d, const absl::Span fused_ops, + int num_args = 1, float epsilon = 0.0) { + auto* attr = fused_conv2d->mutable_attr(); + SetAttrValue(fused_ops, &(*attr)["fused_ops"]); SetAttrValue(num_args, &(*attr)["num_args"]); - // Required only for FusedBatchNorm. - SetAttrValue(epsilon, &(*attr)["epsilon"]); + SetAttrValue(epsilon, &(*attr)["epsilon"]); // required only for BatchNorm } void AddFusedConv2DNode( - const Conv2DWithBiasAdd& matched, GraphDef* optimized_graph, + const RemapperContext& ctx, const Conv2DWithBiasAdd& matched, + GraphDef* optimized_graph, absl::flat_hash_set* invalidated_nodes) { - VLOG(2) << "Fuse Conv2D with BiasAdd: bias_add=" << matched.bias_add->name() + DCHECK(IsDeviceCompatible(ctx, matched)) + << "Unsupported fused Conv2D pattern"; + + VLOG(2) << "Fuse Conv2D with BiasAdd: " + << " bias_add=" << matched.bias_add->name() << " conv2d=" << matched.conv2d->name(); NodeDef* fused_conv2d = optimized_graph->add_node(); - fused_conv2d->set_name(matched.bias_add->name()); fused_conv2d->set_op(kFusedConv2D); - fused_conv2d->set_device(matched.bias_add->device()); + fused_conv2d->set_name(matched.bias_add->name()); + fused_conv2d->set_device(matched.conv2d->device()); fused_conv2d->add_input(matched.conv2d->input(0)); // 0: input fused_conv2d->add_input(matched.conv2d->input(1)); // 1: filter fused_conv2d->add_input(matched.bias_add->input(1)); // 2: bias - CopyConv2DAttributes(matched.conv2d, fused_conv2d, {"BiasAdd"}); + CopyConv2DAttributes(matched.conv2d, fused_conv2d); + SetFusedConv2DAttributes(fused_conv2d, {"BiasAdd"}); invalidated_nodes->insert(matched.bias_add); invalidated_nodes->insert(matched.conv2d); } void AddFusedConv2DNode( - const Conv2DWithBiasAddAndRelu& matched, GraphDef* optimized_graph, + const RemapperContext& ctx, const Conv2DWithBiasAddAndRelu& matched, + GraphDef* optimized_graph, absl::flat_hash_set* invalidated_nodes) { - VLOG(2) << "Fuse Conv2D with BiasAdd and Relu: relu=" << matched.relu->name() + DCHECK(IsDeviceCompatible(ctx, matched)) + << "Unsupported fused Conv2D pattern"; + + VLOG(2) << "Fuse Conv2D with BiasAdd and Relu: " + << " relu=" << matched.relu->name() << " bias_add=" << matched.bias_add->name() << " conv2d=" << matched.conv2d->name(); NodeDef* fused_conv2d = optimized_graph->add_node(); fused_conv2d->set_name(matched.relu->name()); fused_conv2d->set_op(kFusedConv2D); - fused_conv2d->set_device(matched.relu->device()); + fused_conv2d->set_device(matched.conv2d->device()); fused_conv2d->add_input(matched.conv2d->input(0)); // 0: input fused_conv2d->add_input(matched.conv2d->input(1)); // 1: filter fused_conv2d->add_input(matched.bias_add->input(1)); // 2: bias - CopyConv2DAttributes(matched.conv2d, fused_conv2d, {"BiasAdd", "Relu"}); + CopyConv2DAttributes(matched.conv2d, fused_conv2d); + SetFusedConv2DAttributes(fused_conv2d, {"BiasAdd", "Relu"}); invalidated_nodes->insert(matched.relu); invalidated_nodes->insert(matched.bias_add); @@ -421,8 +539,12 @@ void AddFusedConv2DNode( } void AddFusedConv2DNode( - const Conv2DWithSqueezeAndBiasAdd& matched, GraphDef* optimized_graph, + const RemapperContext& ctx, const Conv2DWithSqueezeAndBiasAdd& matched, + GraphDef* optimized_graph, absl::flat_hash_set* invalidated_nodes) { + DCHECK(IsDeviceCompatible(ctx, matched)) + << "Unsupported fused Conv2D pattern"; + VLOG(2) << "Fuse Conv2D with Squeeze and BiasAdd: " << " bias_add=" << matched.bias_add->name() << " squeeze=" << matched.squeeze->name() @@ -432,13 +554,14 @@ void AddFusedConv2DNode( // has single consumer (only the squeeze node). NodeDef* fused_conv2d = optimized_graph->add_node(); fused_conv2d->set_name(matched.conv2d->name()); - fused_conv2d->set_op("_FusedConv2D"); + fused_conv2d->set_op(kFusedConv2D); fused_conv2d->set_device(matched.conv2d->device()); fused_conv2d->add_input(matched.conv2d->input(0)); // 0: input fused_conv2d->add_input(matched.conv2d->input(1)); // 1: filter fused_conv2d->add_input(matched.bias_add->input(1)); // 2: bias - CopyConv2DAttributes(matched.conv2d, fused_conv2d, {"BiasAdd"}); + CopyConv2DAttributes(matched.conv2d, fused_conv2d); + SetFusedConv2DAttributes(fused_conv2d, {"BiasAdd"}); // Replace BiasAdd node with a Squeeze. NodeDef* remapped_squeeze = optimized_graph->add_node(); @@ -461,7 +584,7 @@ void AddFusedConv2DNode( NodeDef* fused_conv2d = optimized_graph->add_node(); fused_conv2d->set_name(matched.fused_batch_norm->name()); fused_conv2d->set_op(kFusedConv2D); - fused_conv2d->set_device(matched.fused_batch_norm->device()); + fused_conv2d->set_device(matched.conv2d->device()); fused_conv2d->add_input(matched.conv2d->input(0)); // 0: input fused_conv2d->add_input(matched.conv2d->input(1)); // 1: filter fused_conv2d->add_input(matched.fused_batch_norm->input(1)); // 2: scale @@ -469,8 +592,9 @@ void AddFusedConv2DNode( fused_conv2d->add_input(matched.fused_batch_norm->input(3)); // 4: mean fused_conv2d->add_input(matched.fused_batch_norm->input(4)); // 5: variance - CopyConv2DAttributes(matched.conv2d, fused_conv2d, {"FusedBatchNorm"}, - /*num_args*/ 4, /*epsilon*/ matched.epsilon); + CopyConv2DAttributes(matched.conv2d, fused_conv2d); + SetFusedConv2DAttributes(fused_conv2d, {"FusedBatchNorm"}, + /*num_args=*/4, /*epsilon=*/matched.epsilon); invalidated_nodes->insert(matched.fused_batch_norm); invalidated_nodes->insert(matched.conv2d); @@ -487,7 +611,7 @@ void AddFusedConv2DNode( NodeDef* fused_conv2d = optimized_graph->add_node(); fused_conv2d->set_name(matched.relu->name()); fused_conv2d->set_op(kFusedConv2D); - fused_conv2d->set_device(matched.fused_batch_norm->device()); + fused_conv2d->set_device(matched.conv2d->device()); fused_conv2d->add_input(matched.conv2d->input(0)); // 0: input fused_conv2d->add_input(matched.conv2d->input(1)); // 1: filter fused_conv2d->add_input(matched.fused_batch_norm->input(1)); // 2: scale @@ -495,8 +619,9 @@ void AddFusedConv2DNode( fused_conv2d->add_input(matched.fused_batch_norm->input(3)); // 4: mean fused_conv2d->add_input(matched.fused_batch_norm->input(4)); // 5: variance - CopyConv2DAttributes(matched.conv2d, fused_conv2d, {"FusedBatchNorm", "Relu"}, - /*num_args*/ 4, /*epsilon*/ matched.epsilon); + CopyConv2DAttributes(matched.conv2d, fused_conv2d); + SetFusedConv2DAttributes(fused_conv2d, {"FusedBatchNorm", "Relu"}, + /*num_args=*/4, /*epsilon=*/matched.epsilon); invalidated_nodes->insert(matched.relu); invalidated_nodes->insert(matched.fused_batch_norm); @@ -680,13 +805,14 @@ Status Remapper::Optimize(Cluster* /*cluster*/, const GrapplerItem& item, // Remap Conv2D+BiasAdd into the _FusedConv2D. if (FindConv2DWithBias(ctx, &node, &conv2d_with_bias)) { - AddFusedConv2DNode(conv2d_with_bias, optimized_graph, &invalidated_nodes); + AddFusedConv2DNode(ctx, conv2d_with_bias, optimized_graph, + &invalidated_nodes); continue; } // Remap Conv2D+BiasAdd+Relu into the _FusedConv2D. if (FindConv2DWithBiasAndRelu(ctx, &node, &conv2d_with_bias_and_relu)) { - AddFusedConv2DNode(conv2d_with_bias_and_relu, optimized_graph, + AddFusedConv2DNode(ctx, conv2d_with_bias_and_relu, optimized_graph, &invalidated_nodes); continue; } @@ -694,7 +820,7 @@ Status Remapper::Optimize(Cluster* /*cluster*/, const GrapplerItem& item, // Remap Conv2D+Squeeze+BiasAdd into the _FusedConv2D+Squeeze. if (FindConv2DWithSqueezeAndBias(ctx, &node, &conv2d_with_squeeze_and_bias)) { - AddFusedConv2DNode(conv2d_with_squeeze_and_bias, optimized_graph, + AddFusedConv2DNode(ctx, conv2d_with_squeeze_and_bias, optimized_graph, &invalidated_nodes); continue; } diff --git a/tensorflow/core/grappler/utils.cc b/tensorflow/core/grappler/utils.cc index cc9a38b2d2..375c3e56c8 100644 --- a/tensorflow/core/grappler/utils.cc +++ b/tensorflow/core/grappler/utils.cc @@ -20,6 +20,8 @@ limitations under the License. #include #include +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/node_def_util.h" @@ -166,10 +168,10 @@ string AddPrefixToNodeName(const string& name, const string& prefix, const string& delimiter) { if (!name.empty()) { if (name[0] == '^') { - return strings::StrCat("^", prefix, delimiter, name.substr(1)); + return absl::StrCat("^", prefix, delimiter, name.substr(1)); } } - return strings::StrCat(prefix, delimiter, name); + return absl::StrCat(prefix, delimiter, name); } string AddPrefixToNodeName(const string& name, const string& prefix) { @@ -193,20 +195,26 @@ bool ExecuteWithTimeout(std::function fn, const int64 timeout_in_ms, } string AsControlDependency(const NodeDef& node) { - return strings::StrCat("^", node.name()); + return absl::StrCat("^", node.name()); } string AsControlDependency(const string& node_name) { CHECK(!node_name.empty()); return (!node_name.empty() && node_name[0] == '^') ? node_name - : strings::StrCat("^", node_name); + : absl::StrCat("^", node_name); } bool NodeIsOnCpu(const NodeDef* node) { string task, device; return DeviceNameUtils::SplitDeviceName(node->device(), &task, &device) && - str_util::StartsWith(device, DEVICE_CPU); + absl::StartsWith(device, DEVICE_CPU); +} + +bool NodeIsOnGpu(const NodeDef* node) { + string task, device; + return DeviceNameUtils::SplitDeviceName(node->device(), &task, &device) && + absl::StartsWith(device, DEVICE_GPU); } int NumOutputs(const NodeDef& node, GraphDef* graph) { diff --git a/tensorflow/core/grappler/utils.h b/tensorflow/core/grappler/utils.h index 1e820977ae..9053ae4c07 100644 --- a/tensorflow/core/grappler/utils.h +++ b/tensorflow/core/grappler/utils.h @@ -242,6 +242,9 @@ string AsControlDependency(const string& node); // Returns true if the node is assigned to run on CPU device. bool NodeIsOnCpu(const NodeDef* node); +// Returns true if the node is assigned to run on GPU device. +bool NodeIsOnGpu(const NodeDef* node); + // Returns the number of outputs of a node according to its OpDef. Note that // some of the outputs may be unconnected. int NumOutputs(const NodeDef& node, GraphDef* graph); diff --git a/tensorflow/core/grappler/utils/BUILD b/tensorflow/core/grappler/utils/BUILD index 89417f85c2..ff15541cdc 100644 --- a/tensorflow/core/grappler/utils/BUILD +++ b/tensorflow/core/grappler/utils/BUILD @@ -143,6 +143,7 @@ cc_library( "//tensorflow/core:test", "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:utils", + "@com_google_absl//absl/algorithm:container", ], ) diff --git a/tensorflow/core/grappler/utils/grappler_test.cc b/tensorflow/core/grappler/utils/grappler_test.cc index 576494cad5..5ef1cf444b 100644 --- a/tensorflow/core/grappler/utils/grappler_test.cc +++ b/tensorflow/core/grappler/utils/grappler_test.cc @@ -15,6 +15,8 @@ limitations under the License. #include "tensorflow/core/grappler/utils/grappler_test.h" #include +#include "absl/algorithm/container.h" +#include "tensorflow/core/framework/attr_value_util.h" #include "tensorflow/core/grappler/utils.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/protobuf/rewriter_config.pb.h" @@ -125,6 +127,31 @@ void GrapplerTest::CompareGraphs(GraphDef want, GraphDef got) const { } } +void GrapplerTest::CompareNodes(const NodeDef& want, const NodeDef& got) const { + EXPECT_EQ(want.name(), got.name()); + EXPECT_EQ(want.op(), got.op()); + + std::vector want_inputs(want.input().begin(), want.input().end()); + std::vector got_inputs(got.input().begin(), got.input().end()); + EXPECT_EQ(want_inputs, got_inputs); + + const auto attr_name = [](const std::pair& attr) { + return attr.first; + }; + + std::vector want_attrs; + std::vector got_attrs; + absl::c_transform(want.attr(), std::back_inserter(want_attrs), attr_name); + absl::c_transform(got.attr(), std::back_inserter(got_attrs), attr_name); + absl::c_sort(want_attrs); + absl::c_sort(got_attrs); + EXPECT_EQ(want_attrs, got_attrs); + + for (const string& attr : want_attrs) { + EXPECT_TRUE(AreAttrValuesEqual(want.attr().at(attr), got.attr().at(attr))); + } +} + bool GrapplerTest::IsNodesDirectlyConnected(const NodeMap& node_map, const string& src, const string& dst, int position) { diff --git a/tensorflow/core/grappler/utils/grappler_test.h b/tensorflow/core/grappler/utils/grappler_test.h index 0cfd740dcb..20fd04c1c6 100644 --- a/tensorflow/core/grappler/utils/grappler_test.h +++ b/tensorflow/core/grappler/utils/grappler_test.h @@ -49,13 +49,25 @@ class GrapplerTest : public ::testing::Test { const std::vector>& attributes, GraphDef* graph) const; + // Checks if two graphs are equal. Both graphs must have the same set of nodes + // with the same inputs and attributes. Nodes can be in different order. + // + // NOTE: This function uses EXPECT/ASSERT macros to check node properties + // equality, and adds all failuires to the current test. void CompareGraphs(GraphDef want, GraphDef got) const; - // Check if node 'src' is directly connected to the input($position) of 'dst'. + // Checks if two nodes have the same name, op, inputs and attributes. + // + // NOTE: This function uses EXPECT/ASSERT macros to check node properties + // equality, and adds all failuires to the current test. + void CompareNodes(const NodeDef& want, const NodeDef& got) const; + + // Checks if node 'src' is directly connected to the input($position) of + // 'dst'. bool IsNodesDirectlyConnected(const NodeMap& node_map, const string& src, const string& dst, int position = 0); - // Count nodes of the given op-type in a graph. + // Counts nodes of the given op-type in a graph. int CountOpNodes(const GraphDef& graph, const string& op); // Get a random tensor with given shape. diff --git a/tensorflow/core/kernels/conv_ops_test.cc b/tensorflow/core/kernels/conv_ops_test.cc index 3e59219f8f..fc93915e16 100644 --- a/tensorflow/core/kernels/conv_ops_test.cc +++ b/tensorflow/core/kernels/conv_ops_test.cc @@ -1497,6 +1497,26 @@ BM_FusedConv2DWithBatchNormAndRelu(32, 32, 32, 128, 3, 3, 1024, cpu, "3x3 /b 32"); #if GOOGLE_CUDA +// -------------------------------------------------------------------------- // +// 1x1 Convolution +// -------------------------------------------------------------------------- // + +BM_Conv2D(8, 32, 32, 128, 1, 1, 1024, gpu, "1x1 /b 8"); +BM_Conv2D(16, 32, 32, 128, 1, 1, 1024, gpu, "1x1 /b 16"); +BM_Conv2D(32, 32, 32, 128, 1, 1, 1024, gpu, "1x1 /b 32"); + +BM_Conv2DWithBiasAndRelu(8, 32, 32, 128, 1, 1, 1024, gpu, "1x1 /b 8"); +BM_Conv2DWithBiasAndRelu(16, 32, 32, 128, 1, 1, 1024, gpu, "1x1 /b 16"); +BM_Conv2DWithBiasAndRelu(32, 32, 32, 128, 1, 1, 1024, gpu, "1x1 /b 32"); + +BM_FusedConv2DWithBiasAndRelu(8, 32, 32, 128, 1, 1, 1024, gpu, "1x1 /b 8"); +BM_FusedConv2DWithBiasAndRelu(16, 32, 32, 128, 1, 1, 1024, gpu, "1x1 /b 16"); +BM_FusedConv2DWithBiasAndRelu(32, 32, 32, 128, 1, 1, 1024, gpu, "1x1 /b 32"); + +// -------------------------------------------------------------------------- // +// 3x3 Convolution +// -------------------------------------------------------------------------- // + BM_Conv2D(8, 32, 32, 128, 3, 3, 1024, gpu, "3x3 /b 8"); BM_Conv2D(16, 32, 32, 128, 3, 3, 1024, gpu, "3x3 /b 16"); BM_Conv2D(32, 32, 32, 128, 3, 3, 1024, gpu, "3x3 /b 32"); -- GitLab From c7c4a42c4372ca560ea415fe3a798e18286cedec Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 12:36:38 -0800 Subject: [PATCH 0280/2345] Fix an error in keras input_layer.Input() dtype type checking. PiperOrigin-RevId: 228215444 --- tensorflow/python/keras/engine/input_layer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/keras/engine/input_layer.py b/tensorflow/python/keras/engine/input_layer.py index bc2cf2fb6e..c6dcedfce2 100644 --- a/tensorflow/python/keras/engine/input_layer.py +++ b/tensorflow/python/keras/engine/input_layer.py @@ -77,8 +77,9 @@ class InputLayer(base_layer.Layer): dtype = backend.floatx() else: dtype = backend.dtype(input_tensor) - elif input_tensor and input_tensor.dtype != dtype: - raise ValueError('`input_tensor.dtype` differs from `dtype`.') + elif input_tensor is not None and input_tensor.dtype != dtype: + raise ValueError('`input_tensor.dtype` differs from `dtype`: %s vs. %s' % + (input_tensor.dtype, dtype)) super(InputLayer, self).__init__(dtype=dtype, name=name) self.built = True self.sparse = sparse -- GitLab From e7f7f01352be4054a7fb2a872cf2ef335292600c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 12:48:37 -0800 Subject: [PATCH 0281/2345] Prevent the interpreter from continuing execution after an error occurred. PiperOrigin-RevId: 228217675 --- tensorflow/lite/core/subgraph.cc | 6 +++--- tensorflow/lite/interpreter.cc | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tensorflow/lite/core/subgraph.cc b/tensorflow/lite/core/subgraph.cc index 75212cac22..4be80d143e 100644 --- a/tensorflow/lite/core/subgraph.cc +++ b/tensorflow/lite/core/subgraph.cc @@ -670,7 +670,7 @@ TfLiteStatus Subgraph::Invoke() { TfLiteTensor* tensor = &tensors_[tensor_index]; if (tensor->delegate && tensor->delegate != node.delegate && tensor->data_is_stale) { - EnsureTensorDataIsReadable(tensor_index); + TF_LITE_ENSURE_STATUS(EnsureTensorDataIsReadable(tensor_index)); } } @@ -683,8 +683,8 @@ TfLiteStatus Subgraph::Invoke() { EnsureTensorsVectorCapacity(); tensor_resized_since_op_invoke_ = false; if (OpInvoke(registration, &node) == kTfLiteError) { - status = ReportOpError(context_, node, registration, node_index, - "failed to invoke"); + return ReportOpError(context_, node, registration, node_index, + "failed to invoke"); } // Force execution prep for downstream ops if the latest op triggered the diff --git a/tensorflow/lite/interpreter.cc b/tensorflow/lite/interpreter.cc index 2abe062ec6..60fa2130fa 100644 --- a/tensorflow/lite/interpreter.cc +++ b/tensorflow/lite/interpreter.cc @@ -102,15 +102,16 @@ TfLiteStatus Interpreter::ResizeInputTensor(int tensor_index, } TfLiteStatus Interpreter::Invoke() { - TfLiteStatus status = primary_subgraph().Invoke(); + TF_LITE_ENSURE_STATUS(primary_subgraph().Invoke()); if (!allow_buffer_handle_output_) { for (int tensor_index : outputs()) { - primary_subgraph().EnsureTensorDataIsReadable(tensor_index); + TF_LITE_ENSURE_STATUS( + primary_subgraph().EnsureTensorDataIsReadable(tensor_index)); } } - return status; + return kTfLiteOk; } TfLiteStatus Interpreter::AddTensors(int tensors_to_add, -- GitLab From 95d708d7b9e3f22c09ca77004e68f2cadb295226 Mon Sep 17 00:00:00 2001 From: Shanqing Cai Date: Mon, 7 Jan 2019 13:12:01 -0800 Subject: [PATCH 0282/2345] tf.keras: Remove unnecessary backend check in PReLU.call() PiperOrigin-RevId: 228222042 --- tensorflow/python/keras/layers/advanced_activations.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tensorflow/python/keras/layers/advanced_activations.py b/tensorflow/python/keras/layers/advanced_activations.py index be1039a2ac..5095287430 100644 --- a/tensorflow/python/keras/layers/advanced_activations.py +++ b/tensorflow/python/keras/layers/advanced_activations.py @@ -121,11 +121,9 @@ class PReLU(Layer): @tf_utils.shape_type_conversion def build(self, input_shape): param_shape = list(input_shape[1:]) - self.param_broadcast = [False] * len(param_shape) if self.shared_axes is not None: for i in self.shared_axes: param_shape[i - 1] = 1 - self.param_broadcast[i - 1] = True self.alpha = self.add_weight( shape=param_shape, name='alpha', @@ -143,12 +141,7 @@ class PReLU(Layer): def call(self, inputs, mask=None): pos = K.relu(inputs) - if K.backend() == 'theano': - neg = ( - K.pattern_broadcast(self.alpha, self.param_broadcast) * - (inputs - math_ops.abs(inputs)) * 0.5) - else: - neg = -self.alpha * K.relu(-inputs) + neg = -self.alpha * K.relu(-inputs) return pos + neg def get_config(self): -- GitLab From 38491a84a9e38357e400457dbbe408b66e786672 Mon Sep 17 00:00:00 2001 From: Pooya Davoodi Date: Mon, 7 Jan 2019 14:21:21 -0800 Subject: [PATCH 0283/2345] TFTRT: Expand lambda and inline ifs to functions with if and else --- .../contrib/tensorrt/python/trt_convert.py | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/tensorflow/contrib/tensorrt/python/trt_convert.py b/tensorflow/contrib/tensorrt/python/trt_convert.py index 3d2b6b499d..f5d624ef64 100644 --- a/tensorflow/contrib/tensorrt/python/trt_convert.py +++ b/tensorflow/contrib/tensorrt/python/trt_convert.py @@ -45,15 +45,33 @@ from tensorflow.python.saved_model import loader_impl from tensorflow.python.saved_model import tag_constants from tensorflow.python.training import saver -if _six.PY2: - _to_bytes = lambda s: s.encode("utf-8", errors="surrogateescape") \ - if isinstance(s, unicode) else s - _to_string = lambda s: s.decode("utf-8") if isinstance(s, str) else s -else: - _to_bytes = lambda s: s.encode("utf-8", errors="surrogateescape") \ - if isinstance(s, str) else s - _to_string = lambda s: s.decode("utf-8") if isinstance(s, bytes) else s - +def _to_bytes(s): + """Returns encoded of s if s is a sequence of chars otherwise returns s. + """ + if _six.PY2: + if isinstance(s, unicode): + return s.encode("utf-8", errors="surrogateescape") + else: + return s + else: + if isinstance(s, str): + return s.encode("utf-8", errors="surrogateescape") + else: + return s + +def _to_string(s): + """Returns decoded of s if s is a sequence of bytes otherwise returns s. + """ + if _six.PY2: + if isinstance(s, str): + return s.decode("utf-8") + else: + return s + else: + if isinstance(s, bytes): + return s.decode("utf-8") + else: + return s class TrtPrecisionMode(object): FP32 = "FP32" -- GitLab From 46f030b6e275bd9950da91e480bda5f2fa44ad80 Mon Sep 17 00:00:00 2001 From: Michael Case Date: Mon, 7 Jan 2019 13:26:16 -0800 Subject: [PATCH 0284/2345] Clean up example/learn/BUILD a bit Some of the data deps are targets that no longer exist. Pretty sure should be cleaned up. PiperOrigin-RevId: 228224240 --- tensorflow/examples/learn/BUILD | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tensorflow/examples/learn/BUILD b/tensorflow/examples/learn/BUILD index d6ec1f393b..a22d55e5af 100644 --- a/tensorflow/examples/learn/BUILD +++ b/tensorflow/examples/learn/BUILD @@ -28,17 +28,8 @@ sh_test( size = "large", srcs = ["examples_test.sh"], data = [ - ":boston", - ":iris", ":iris_custom_decay_dnn", ":iris_custom_model", - ":iris_run_config", - ":random_forest_mnist", - ":resnet", - ":text_classification", - ":text_classification_character_cnn", - ":text_classification_character_rnn", - ":text_classification_cnn", ], tags = [ "manual", -- GitLab From d2dd3bd43ebbfd2d14f2f7e658b5b5aae201607e Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Mon, 7 Jan 2019 13:35:32 -0800 Subject: [PATCH 0285/2345] while_v2 fixes for better TPU integration. PiperOrigin-RevId: 228226029 --- tensorflow/python/ops/while_v2.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/while_v2.py b/tensorflow/python/ops/while_v2.py index aaed86c1ca..f9fea0e5bd 100644 --- a/tensorflow/python/ops/while_v2.py +++ b/tensorflow/python/ops/while_v2.py @@ -115,12 +115,15 @@ def while_loop(cond, loop_counter < maximum_iterations, cond(*_pack_sequence_as(orig_loop_vars, args))) + # NOTE(skyewm): we set read_only_collections=False for compatibility with + # TPUEstimator. cond_graph = func_graph_module.func_graph_from_py_func( cond_name, wrapped_cond, loop_vars, {}, signature=_build_signature(loop_vars, shape_invariants), - func_graph=util.WhileCondFuncGraph(cond_name), + func_graph=util.WhileCondFuncGraph(cond_name, + read_only_collections=False), add_control_dependencies=add_control_dependencies) # Add external_captures of cond to the list of loop vars. @@ -171,7 +174,8 @@ def while_loop(cond, wrapped_body, loop_vars, {}, signature=_build_signature(loop_vars, shape_invariants), - func_graph=util.WhileBodyFuncGraph(body_name), + func_graph=util.WhileBodyFuncGraph(body_name, + read_only_collections=False), add_control_dependencies=add_control_dependencies) # Add external captures of body to the list of loop vars. # Note that external tensors will be treated as loop invariants, i.e., -- GitLab From 879af3c38d2405db3ff4c96d6104cdf82a1ab368 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Mon, 7 Jan 2019 13:38:36 -0800 Subject: [PATCH 0286/2345] Change dataset rewriting in distribution strategy to avoid a unbatch().batch() call. PiperOrigin-RevId: 228226620 --- .../python/data/experimental/ops/batching.py | 8 ++++++-- tensorflow/python/distribute/input_lib.py | 20 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/data/experimental/ops/batching.py b/tensorflow/python/data/experimental/ops/batching.py index f0cf7f0a99..ff8a9182c3 100644 --- a/tensorflow/python/data/experimental/ops/batching.py +++ b/tensorflow/python/data/experimental/ops/batching.py @@ -553,8 +553,12 @@ class _MapAndBatchDataset(dataset_ops.UnaryDataset): """See `Dataset.map()` for details.""" self._input_dataset = input_dataset - self._map_func = dataset_ops.StructuredFunctionWrapper( - map_func, "tf.data.experimental.map_and_batch()", dataset=input_dataset) + if isinstance(map_func, dataset_ops.StructuredFunctionWrapper): + self._map_func = map_func + else: + self._map_func = dataset_ops.StructuredFunctionWrapper( + map_func, "tf.data.experimental.map_and_batch()", + dataset=input_dataset) self._batch_size_t = ops.convert_to_tensor( batch_size, dtype=dtypes.int64, name="batch_size") self._num_parallel_calls_t = ops.convert_to_tensor( diff --git a/tensorflow/python/distribute/input_lib.py b/tensorflow/python/distribute/input_lib.py index cbe6518e5c..022595146d 100644 --- a/tensorflow/python/distribute/input_lib.py +++ b/tensorflow/python/distribute/input_lib.py @@ -574,12 +574,19 @@ def _split_dataset_batch(dataset, split_batch_by): "The batch operations can be followed by a prefetch.") batched_dataset = _get_batch_dataset(dataset) + prev_dataset = batched_dataset._input_dataset + + num_parallel_calls = None + map_func = None + if isinstance(batched_dataset, dataset_ops.BatchDataset): batch_size = batched_dataset._batch_size drop_remainder = batched_dataset._drop_remainder elif isinstance(batched_dataset, batching._MapAndBatchDataset): batch_size = batched_dataset._batch_size_t drop_remainder = batched_dataset._drop_remainder_t + num_parallel_calls = batched_dataset._num_parallel_calls_t + map_func = batched_dataset._map_func prefetch_buffer = None if isinstance(dataset, dataset_ops.PrefetchDataset): @@ -587,7 +594,6 @@ def _split_dataset_batch(dataset, split_batch_by): elif (isinstance(dataset, dataset_ops.DatasetV1Adapter) and isinstance(dataset._dataset, dataset_ops.PrefetchDataset)): prefetch_buffer = dataset._dataset._buffer_size - # pylint: enable=protected-access if tensor_util.is_tensor(batch_size): batch_size = tensor_util.constant_value(batch_size) @@ -595,14 +601,22 @@ def _split_dataset_batch(dataset, split_batch_by): if tensor_util.is_tensor(drop_remainder): drop_remainder = tensor_util.constant_value(drop_remainder) + if num_parallel_calls is not None and tensor_util.is_tensor(drop_remainder): + num_parallel_calls = tensor_util.constant_value(num_parallel_calls) + if batch_size % split_batch_by: raise ValueError( "Batch size %s cannot be sharded evenly across replicas %s" % ( batch_size, split_batch_by)) new_batch_size = batch_size // split_batch_by - dataset = dataset.apply(batching.unbatch()) - dataset = dataset.batch(new_batch_size, drop_remainder=drop_remainder) + if isinstance(batched_dataset, dataset_ops.BatchDataset): + dataset = prev_dataset.batch(new_batch_size, drop_remainder=drop_remainder) + elif isinstance(batched_dataset, batching._MapAndBatchDataset): + dataset = prev_dataset.apply(batching.map_and_batch( + map_func, new_batch_size, num_parallel_calls, drop_remainder)) + # pylint: enable=protected-access + if prefetch_buffer is not None: dataset = dataset.prefetch(prefetch_buffer) return dataset -- GitLab From 7bc843afd6696fc802fa44f65f0fdf67b0bc56e6 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 13:45:54 -0800 Subject: [PATCH 0287/2345] Unittests to demonstrate three bugs in flex mode: - forwardability is a property of a subgraph, not of the global buffer map - input tensors should never be forwardable, even when they come from another subgraph. That's because they might be outputs of the model, or inputs into another subgraph. - input tensors shouldn't be removed at the end, because they might be used by another subgraph. PiperOrigin-RevId: 228228094 --- tensorflow/lite/delegates/flex/kernel_test.cc | 175 +++++++++++++++++- tensorflow/lite/delegates/flex/test_util.cc | 5 + tensorflow/lite/delegates/flex/test_util.h | 3 + 3 files changed, 179 insertions(+), 4 deletions(-) diff --git a/tensorflow/lite/delegates/flex/kernel_test.cc b/tensorflow/lite/delegates/flex/kernel_test.cc index 647c199d4f..9de3ca67de 100644 --- a/tensorflow/lite/delegates/flex/kernel_test.cc +++ b/tensorflow/lite/delegates/flex/kernel_test.cc @@ -36,13 +36,38 @@ TfLiteStatus GenericPrepare(TfLiteContext* context, TfLiteDelegate* delegate, return kTfLiteOk; } +// There is no easy way to pass a parameter into the TfLiteDelegate's +// 'prepare' function, so we keep a global map for testing purpused. +// To avoid collisions use: GetPrepareFunction<__LINE__>(). +std::map>* GetGlobalOpLists() { + static auto* op_list = new std::map>; + return op_list; +} + class KernelTest : public testing::FlexModelTest { public: + static constexpr int kOnes = 1; // This is the index of a tensor of 1's. + static constexpr int kTwos = 2; // This is the index of a tensor of 2's. + static constexpr int kMaxTensors = 30; + + static void SetUpTestSuite() { GetGlobalOpLists()->clear(); } + KernelTest() { CHECK(delegate_data_.Prepare(tensorflow::SessionOptions{}).ok()); interpreter_.reset(new Interpreter(&error_reporter_)); } + typedef TfLiteStatus (*PrepareFunction)(TfLiteContext* context, + TfLiteDelegate* delegate); + + template + PrepareFunction GetPrepareFunction() { + GetGlobalOpLists()->insert({KEY, tf_ops_}); + return [](TfLiteContext* context, TfLiteDelegate* delegate) { + return GenericPrepare(context, delegate, GetGlobalOpLists()->at(KEY)); + }; + } + template void ConfigureDelegate(T prepare_function) { delegate_.data_ = &delegate_data_; @@ -54,9 +79,13 @@ class KernelTest : public testing::FlexModelTest { TfLiteBufferHandle buffer_handle, TfLiteTensor* output) { auto* delegate_data = reinterpret_cast(delegate->data_); - tensorflow::StringPiece values = delegate_data->GetBufferMap(context) - ->GetTensor(buffer_handle) - .tensor_data(); + auto* buffer_map = delegate_data->GetBufferMap(context); + if (!buffer_map->HasTensor(buffer_handle)) { + context->ReportError(context, "Tensor '%d' not found", buffer_handle); + return kTfLiteError; + } + tensorflow::StringPiece values = + buffer_map->GetTensor(buffer_handle).tensor_data(); memcpy(output->data.raw, values.data(), values.size()); return kTfLiteOk; }; @@ -225,7 +254,7 @@ TEST_F(KernelTest, SplitGraph) { AddTfOp(testing::kAdd, {9, 16}, {17}); // => 16 ConfigureDelegate([](TfLiteContext* context, TfLiteDelegate* delegate) { - // All ops by #3 are TF ops, handled by the delegate. However, because #4 + // All ops but #3 are TF ops, handled by the delegate. However, because #4 // depends on the non-TF op, two subgraphs are necessary: // TF subgraph 1: 0, 1, 2, 6, 7, 8, 9 // TF Lite Op: 3 @@ -260,6 +289,144 @@ TEST_F(KernelTest, SplitGraph) { ASSERT_THAT(GetValues(17), ElementsAre(18.0f)); } +class MultipleSubgraphsTest : public KernelTest { + public: + static constexpr int kInput = 0; + + void PrepareInterpreter(PrepareFunction prepare, + const std::vector& input) { + ConfigureDelegate(prepare); + + SetShape(kOnes, {3}); + SetValues(kOnes, {1.0f, 1.0f, 1.0f}); + SetShape(kTwos, {3}); + SetValues(kTwos, {2.0f, 2.0f, 2.0f}); + + SetValues(kInput, input); + } + + std::vector Apply(const std::vector& input, + std::function function) { + std::vector result; + for (float f : input) { + result.push_back(function(f)); + } + return result; + } +}; + +TEST_F(MultipleSubgraphsTest, ForwardabilityIsLocal) { + AddTensors(kMaxTensors, {kInput, kOnes, kTwos}, {12}, kTfLiteFloat32, {3}); + + // Only TF tensors can be forwarded, so we build a small first graph + // to produce tensor #10. Here #10 is forwardable, because it is only + // used once, as an output. + AddTfOp(testing::kAdd, {0, kOnes}, {3}); + AddTfOp(testing::kAdd, {0, kOnes}, {10}); + + // The second TF graph, separated from the former by a TF Lite + // multiplication, will consume tensor #10, which is not forwardable here + // since it is used by more than one op. The existing code will forward the + // tensor anyway, because it was deemed to be forwardable by the previous + // subgraph. + AddTfLiteMulOp({3, kTwos}, {4}); + AddTfOp(testing::kAdd, {10, 4}, {11}); + AddTfOp(testing::kAdd, {11, 10}, {7}); + + // And a simple TF Lite op trying to access tensor #10, which was removed + // from the buffer map. It will cause Invoke() to fail. + AddTfLiteMulOp({10, 7}, {12}); + + auto input = {3.0f, 4.0f, 5.0f}; + PrepareInterpreter(GetPrepareFunction<__LINE__>(), input); + + ASSERT_FALSE(Invoke()); + ASSERT_THAT(error_reporter().error_messages(), + ContainsRegex("Cannot read from invalid tensor index 10")); + + // TODO(b/122457581): expected result for this test: + // ASSERT_THAT(GetValues(12), ElementsAreArray(Apply(input, [](float in) { + // return (4 * in + 4) * (in + 1); + // }))); +} + +// Subgraphs should not remove input tensors from the buffer_map, since +// they could be necessary for downstream graphs. +TEST_F(MultipleSubgraphsTest, DoNotRemoveInputTensors) { + AddTensors(kMaxTensors, {kInput, kOnes, kTwos}, {12}, kTfLiteFloat32, {3}); + + // Only TF tensors can be removed, so we build a small first graph + // to produce tensor #10. We make sure it is used by more than one + // op, so it is not forwardable here. + AddTfOp(testing::kAdd, {0, kOnes}, {3}); + AddTfOp(testing::kAdd, {0, kOnes}, {10}); + AddTfOp(testing::kAdd, {10, kOnes}, {15}); + AddTfOp(testing::kAdd, {10, kOnes}, {16}); + + // The second TF graph, separated from the former by a TF Lite + // multiplication, will consume tensor #10. The existing code will remove + // from the buffer_map all tensors that are not outputs, so #10 will + // disappear. Note that we are using #10 in two ops, so it is not forwardable + // either. + AddTfLiteMulOp({3, kTwos}, {4}); + AddTfOp(testing::kAdd, {10, 4}, {11}); + AddTfOp(testing::kAdd, {10, 11}, {7}); + + // And a simple TF Lite op trying to access tensor #10, which was removed + // from the buffer map. It will cause Invoke() to fail. + AddTfLiteMulOp({10, 7}, {12}); + + auto input = {3.0f, 4.0f, 5.0f}; + PrepareInterpreter(GetPrepareFunction<__LINE__>(), input); + + ASSERT_FALSE(Invoke()); + ASSERT_THAT(error_reporter().error_messages(), + ContainsRegex("Tensor '10' not found")); + + // TODO(b/122457581): expected result for this test: + // ASSERT_THAT(GetValues(12), ElementsAreArray(Apply(input, [](float in) { + // return (4 * in + 4) * (in + 1); + // }))); +} + +// A tensor is deemed forwardable but it happens to be the input to +// more than one subgraph. It should not be forwarded, otherwise its +// contents will be overwritten. +TEST_F(MultipleSubgraphsTest, DoNotForwardInputTensors) { + AddTensors(kMaxTensors, {kInput, kOnes, kTwos}, {12}, kTfLiteFloat32, {3}); + + // Only TF tensors can be forwarded, so we build a small first graph + // to produce tensor #10. + AddTfOp(testing::kAdd, {0, kOnes}, {3}); + AddTfOp(testing::kAdd, {0, kOnes}, {10}); + + // The second TF graph, separated from the former by a TF Lite + // multiplication, will consume tensor #10 and will think it is forwardable + // because it is used by a single op. However, the subgraph doesn't have + // enough information to make that judgment, as the input tensor could be + // used by another graph further downstream. The existing code will forward + // the tensor and remove it from the buffer_map, causing a failure later. + AddTfLiteMulOp({3, kTwos}, {4}); + AddTfOp(testing::kAdd, {10, 4}, {11}); + AddTfOp(testing::kAdd, {11, 4}, {7}); + + // And a simple TF Lite op trying to access tensor #10, which was removed + // from the buffer map. It will cause Invoke() to fail. + AddTfLiteMulOp({10, 7}, {12}); + + auto input = {3.0f, 4.0f, 5.0f}; + PrepareInterpreter(GetPrepareFunction<__LINE__>(), input); + + ASSERT_FALSE(Invoke()); + ASSERT_THAT(error_reporter().error_messages(), + ContainsRegex("Tensor '10' not found")); + + // TODO(b/122457581): expected result for this test: + // ASSERT_THAT(GetValues(12), ElementsAreArray(Apply(input, [](float in) { + // return (5 * in + 5) * (in + 1); + // }))); +} + } // namespace } // namespace flex } // namespace tflite diff --git a/tensorflow/lite/delegates/flex/test_util.cc b/tensorflow/lite/delegates/flex/test_util.cc index aa24675a7b..a67aeef231 100644 --- a/tensorflow/lite/delegates/flex/test_util.cc +++ b/tensorflow/lite/delegates/flex/test_util.cc @@ -90,6 +90,8 @@ void FlexModelTest::AddTensors(int num_tensors, const std::vector& inputs, void FlexModelTest::AddTfLiteMulOp(const std::vector& inputs, const std::vector& outputs) { + ++next_op_index_; + static TfLiteRegistration reg = {nullptr, nullptr, nullptr, nullptr}; reg.builtin_code = BuiltinOperator_MUL; reg.prepare = [](TfLiteContext* context, TfLiteNode* node) { @@ -114,6 +116,9 @@ void FlexModelTest::AddTfLiteMulOp(const std::vector& inputs, void FlexModelTest::AddTfOp(TfOpType op, const std::vector& inputs, const std::vector& outputs) { + tf_ops_.push_back(next_op_index_); + ++next_op_index_; + auto attr = [](const string& key, const string& value) { return " attr{ key: '" + key + "' value {" + value + "}}"; }; diff --git a/tensorflow/lite/delegates/flex/test_util.h b/tensorflow/lite/delegates/flex/test_util.h index 2cc2dc30e9..1913a406e8 100644 --- a/tensorflow/lite/delegates/flex/test_util.h +++ b/tensorflow/lite/delegates/flex/test_util.h @@ -103,6 +103,7 @@ class FlexModelTest : public ::testing::Test { protected: std::unique_ptr interpreter_; TestErrorReporter error_reporter_; + std::vector tf_ops_; private: // Helper method to add a TensorFlow op. tflite_names needs to start with @@ -112,6 +113,8 @@ class FlexModelTest : public ::testing::Test { const std::vector& outputs); std::vector> flexbuffers_; + + int next_op_index_ = 0; }; } // namespace testing -- GitLab From 2d8379badceae7beae681eb2321ed575ba8ec718 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Mon, 7 Jan 2019 14:17:08 -0800 Subject: [PATCH 0288/2345] [TF:XLA] Bump open source llvm revision to r350438 PiperOrigin-RevId: 228234219 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index b43312abb2..834ec28447 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -500,11 +500,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 = "3a1c8a817d2d9a85b85e1ad776a90faa51f475bb924fbf70383892bb1faceac9", - strip_prefix = "llvm-6da47bee848304d215e3ca08b2a4a4dc9c954310", + sha256 = "20c0ebd54f433d68652a54e328d70b0ec7cf37ce734645a0259cf904d14c2c53", + strip_prefix = "llvm-0e2aef924e02d00a4d3454d63c1921a243d316c6", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/6da47bee848304d215e3ca08b2a4a4dc9c954310.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/6da47bee848304d215e3ca08b2a4a4dc9c954310.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/0e2aef924e02d00a4d3454d63c1921a243d316c6.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/0e2aef924e02d00a4d3454d63c1921a243d316c6.tar.gz", ], ) -- GitLab From 30e5f93366b7ef7f24f7f267d2ad1d75afe4583b Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Mon, 7 Jan 2019 14:24:44 -0800 Subject: [PATCH 0289/2345] [XLA:CPU] Split out the logic to emit tiled GEMM/GEMV into a separate file; NFC PiperOrigin-RevId: 228235618 --- tensorflow/compiler/xla/service/cpu/BUILD | 16 + .../xla/service/cpu/dot_op_emitter.cc | 993 +--------------- .../xla/service/cpu/tiled_dot_emitter.cc | 1014 +++++++++++++++++ .../xla/service/cpu/tiled_dot_emitter.h | 55 + 4 files changed, 1107 insertions(+), 971 deletions(-) create mode 100644 tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.cc create mode 100644 tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.h diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index 5c72e1ff71..86c5f71096 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -364,6 +364,21 @@ cc_library( ], ) +cc_library( + name = "tiled_dot_emitter", + srcs = ["tiled_dot_emitter.cc"], + hdrs = ["tiled_dot_emitter.h"], + deps = [ + ":vector_support_library", + "//tensorflow/compiler/xla:xla_data_proto", + "//tensorflow/compiler/xla/service:hlo", + "//tensorflow/compiler/xla/service/llvm_ir:kernel_support_library", + "//tensorflow/compiler/xla/service/llvm_ir:llvm_util", + "//tensorflow/core:lib", + "@llvm//:core", + ], +) + cc_library( name = "dot_op_emitter", srcs = ["dot_op_emitter.cc"], @@ -373,6 +388,7 @@ cc_library( ":cpu_runtime", ":ir_emission_utils", ":target_machine_features", + ":tiled_dot_emitter", ":vector_support_library", "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:status_macros", diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc index 1525a33af7..bdd688c2b9 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc @@ -26,6 +26,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/cpu/cpu_runtime.h" #include "tensorflow/compiler/xla/service/cpu/ir_emission_utils.h" #include "tensorflow/compiler/xla/service/cpu/target_machine_features.h" +#include "tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.h" #include "tensorflow/compiler/xla/service/cpu/vector_support_library.h" #include "tensorflow/compiler/xla/service/hlo_module.h" #include "tensorflow/compiler/xla/service/llvm_ir/kernel_support_library.h" @@ -41,930 +42,6 @@ namespace xla { using llvm_ir::SetToFirstInsertPoint; namespace cpu { - -namespace { -// Provides tiled access to an in-memory rank 2 array. -class MemoryTile { - public: - // Constructs a MemoryTile that can operate on tiles consisting of - // `tile_size_along_major_dim` vectors from the matrix `matrix`, starting at - // `major_dim_offset` in the major dimension. The tile size along the minor - // dimension is the vector size, and that is implicitly determined by `vsl`. - MemoryTile(VectorSupportLibrary* vsl, llvm::IRBuilder<>* b, - llvm::Value* matrix, int64 matrix_size_along_minor_dim, - llvm::Value* major_dim_offset, int64 tile_size_along_major_dim) - : vsl_(vsl), b_(b) { - pointers_.reserve(tile_size_along_major_dim); - for (int64 i = 0; i < tile_size_along_major_dim; i++) { - llvm::Value* total_offset = - b->CreateMul(b->getInt64(matrix_size_along_minor_dim), - b->CreateAdd(b->getInt64(i), major_dim_offset)); - pointers_.push_back(vsl_->ComputeOffsetPointer(matrix, total_offset)); - } - } - - // Load a tile consisting of `tile_size_along_major_dim` vectors from position - // {major: `major_dim_offset`, minor: `minor_dim_offset`}. - // - // Note: `major_dim_offset` is a parameter to the constructor. - std::vector LoadTile(llvm::Value* minor_dim_offset) const { - std::vector result; - result.reserve(pointers_.size()); - for (const auto& pointer : pointers_) { - result.push_back(vsl_->LoadVector(pointer, minor_dim_offset)); - } - return result; - } - - // Stores `tile` to position {major: `major_dim_offset`, minor: - // `minor_dim_offset`}. - // - // Note: `major_dim_offset` is a parameter to the constructor. - void StoreTile(absl::Span tile, - llvm::Value* minor_dim_offset) const { - CHECK_EQ(tile.size(), pointers_.size()); - for (int64 i = 0; i < pointers_.size(); i++) { - vsl_->StoreVector(tile[i], pointers_[i], minor_dim_offset); - } - } - - // Loads a tile of size [`tile_size_along_major_dim`, - // `tile_size_along_middle_dim`] from position {major: `major_dim_offset`, - // minor: `minor_dim_offset`} and then broadcasts each element into a vector - // of size vsl_.vector_size(). The (i,j)'th element of the return value is - // the (i,j)'th element in the tile broadcasted into an LLVM vector. - // - // Note: `major_dim_offset` is a parameter to the constructor. - std::vector> LoadBroadcastTile( - llvm::Value* minor_dim_offset, int64 tile_size_along_middle_dim) const { - std::vector> result; - result.resize(pointers_.size()); - for (int64 i = 0; i < pointers_.size(); i++) { - for (int64 j = 0; j < tile_size_along_middle_dim; j++) { - result[i].push_back(vsl_->LoadBroadcast( - pointers_[i], b_->CreateAdd(minor_dim_offset, b_->getInt64(j)))); - } - } - return result; - } - - private: - VectorSupportLibrary* vsl_; - llvm::IRBuilder<>* b_; - std::vector pointers_; -}; - -// The base class for the classes representing the GEMV emitter configurations. -// -// The IR emitted (modulo the LLVM values representing the input and output -// buffers) by the row major and column major GEMV emitters should be a function -// of their configuration. This is important because their configuration is -// used as a key to cache the generated IR. -class GemvConfig { - public: - // Mixin for convenience. - template - struct User { - public: - PrimitiveType scalar_type() const { - return derived().config().scalar_type(); - } - int64 tile_rows() const { return derived().config().tile_rows(); } - int64 tile_cols() const { return derived().config().tile_cols(); } - int64 m() const { return derived().config().m(); } - int64 k() const { return derived().config().k(); } - int64 has_addend() const { return derived().config().has_addend(); } - - private: - const T& derived() const { return *static_cast(this); } - }; - - PrimitiveType scalar_type() const { return scalar_type_; } - int64 tile_rows() const { return tile_rows_; } - int64 tile_cols() const { return tile_cols_; } - int64 m() const { return m_; } - int64 k() const { return k_; } - bool has_addend() const { return has_addend_; } - - string GetCacheKey() const { - return absl::StrCat(name_, "_", PrimitiveType_Name(scalar_type()), "_", - tile_rows(), "_", tile_cols(), "_", m(), "_", k(), - has_addend() ? "_with_addend" : ""); - } - - protected: - explicit GemvConfig(string name, PrimitiveType scalar_type, int64 tile_rows, - int64 tile_cols, int64 m, int64 k, bool has_addend) - : name_(std::move(name)), - scalar_type_(scalar_type), - tile_rows_(tile_rows), - tile_cols_(tile_cols), - m_(m), - k_(k), - has_addend_(has_addend) {} - - private: - string name_; - PrimitiveType scalar_type_; - int64 tile_rows_; - int64 tile_cols_; - int64 m_; - int64 k_; - bool has_addend_; -}; - -// Computes a dot product between "[M,K]{0,1} lhs" with a [K,1] vector (the -// layout of the vector does not matter). This implementation uses a tiling -// scheme to improve performance. -// -// We logically separate the LHS matrix into four segments: -// -// +----------------------+---+ -// | | | -// | | | -// | A | B | -// | | | -// | | | -// | | | -// +----------------------+---+ -// | C | D | -// +----------------------+---+ -// -// where A is the largest submatrix of the LHS that can be evenly dividied into -// tiles. For each tile in A, assuming tile_rows_ == tile_cols_ == 4, we have: -// -// +---+---+---+---+ +--+--+--+--+ -// |M00|M10|M20|M30| |V0|V1|V2|V3| -// +---+---+---+---+ +--+--+--+--+ -// |M01|M11|M21|M31| and |V0|V1|V2|V3| -// +---+---+---+---+ +--+--+--+--+ -// |M02|M12|M22|M32| |V0|V1|V2|V3| -// +---+---+---+---+ +--+--+--+--+ -// |M03|M13|M23|M33| |V0|V1|V2|V3| -// +---+---+---+---+ +--+--+--+--+ -// -// (Legend: rows are horizontal and columns are vertical; and each column is one -// llvm::Value of a vector type) -// -// where: -// -// a. The left tile is from the column major left matrix. -// b. The right tile is an elementwise broadcast of a [V0, V1, V2, V3] -// vector loaded from the RHS vector. -// -// As we iterate through the column dimension, we compute the change to the -// result vector by an elementwise multiplication between the two tiles above -// followed by a reduction along the major dimension: -// -// +-----------------------------------+ -// | M00*V0 + M10*V1 + M20*V2 + M30*V3 | -// +-----------------------------------+ -// | M01*V0 + M11*V1 + M21*V2 + M31*V3 | -// Result[R:R+4] += +-----------------------------------+ -// | M02*V0 + M12*V1 + M22*V2 + M32*V3 | -// +-----------------------------------+ -// | M03*V0 + M13*V1 + M23*V2 + M33*V3 | -// +-----------------------------------+ -// -// Where R is the starting row for the tile. -// -// We have an inner epilogue loop to deal with the "C" submatrix and an outer -// epilogue loop to deal with the B,D submarix. -// -// TODO(sanjoy): We should investigate if using gather loads and scatter stores -// can be used here have the same inner loop for both column-major and row-major -// matrix-vector products. -class ColumnMajorMatrixVectorProductEmitter - : public GemvConfig::User { - public: - class Config : public GemvConfig { - public: - explicit Config(PrimitiveType scalar_type, int64 tile_rows, int64 tile_cols, - int64 m, int64 k, bool has_addend) - : GemvConfig(/*name=*/"col_major_gemv", scalar_type, - /*tile_rows=*/tile_rows, /*tile_cols=*/tile_cols, /*m=*/m, - /*k=*/k, /*has_addend=*/has_addend) {} - }; - - ColumnMajorMatrixVectorProductEmitter(const Config& config, llvm::Value* lhs, - llvm::Value* rhs, llvm::Value* addend, - llvm::Value* result, - llvm::IRBuilder<>* b) - : config_(config), - lhs_(lhs), - rhs_(rhs), - addend_(addend), - result_(result), - b_(b), - ksl_(b_), - vsl_(config.scalar_type(), /*vector_size=*/config.tile_rows(), b_, "") { - CHECK(tile_rows() > 0 && IsPowerOfTwo(static_cast(tile_rows()))); - CHECK(!has_addend() || addend != nullptr); - } - - void Emit(); - - const Config& config() const { return config_; } - - private: - void EmitOuterLoopBody(llvm::Value* column, int64 column_count, - bool is_first_column); - - MemoryTile GetLhsMemoryTile(llvm::Value* column_start, int64 column_count) { - return MemoryTile(&vsl_, b_, /*matrix=*/lhs_, - /*matrix_size_along_minor_dim=*/m(), - /*major_dim_offset=*/column_start, - /*tile_size_along_major_dim=*/column_count); - } - - // Load a tile of values from the RHS. For the RHS a "tile" is a contiguous - // sequence of `count` values, each one broadcasted to the vector width. - std::vector LoadRhsTile(llvm::Value* offset, int64 count) { - llvm::Value* base_pointer = vsl_.ComputeOffsetPointer(rhs_, offset); - std::vector result; - result.reserve(count); - for (int64 i = 0; i < count; i++) { - result.push_back(vsl_.LoadBroadcast(base_pointer, i)); - } - return result; - } - - void EmitInnerLoopTiled(MemoryTile* lhs_memory_tile, - const std::vector& rhs_tile, - int64 columns, bool is_first_column); - - void EmitInnerLoopEpilogue(llvm::Value* current_tile_col, int64 columns, - bool is_first_tiled_column); - - Config config_; - llvm::Value* lhs_; - llvm::Value* rhs_; - llvm::Value* addend_; - llvm::Value* result_; - llvm::IRBuilder<>* b_; - KernelSupportLibrary ksl_; - VectorSupportLibrary vsl_; -}; - -void ColumnMajorMatrixVectorProductEmitter::EmitOuterLoopBody( - llvm::Value* column, int64 column_count, bool is_first_column) { - MemoryTile lhs_memory_tile = GetLhsMemoryTile(/*column_start=*/column, - /*column_count=*/column_count); - - std::vector rhs_tile = - LoadRhsTile(column, /*count=*/column_count); - EmitInnerLoopTiled(&lhs_memory_tile, rhs_tile, - /*columns=*/column_count, is_first_column); - EmitInnerLoopEpilogue(column, /*columns=*/column_count, is_first_column); -} - -void ColumnMajorMatrixVectorProductEmitter::Emit() { - // See the comment on the class declaration for the algorithm used here. - int64 column_remainder = k() % tile_cols(); - int64 column_limit = k() - column_remainder; - - ksl_.For("dot.outer.tiled", - /*start=*/0, /*end=*/column_limit, /*step=*/tile_cols(), - [&](llvm::Value* column, bool is_first_column) { - EmitOuterLoopBody(column, tile_cols(), is_first_column); - }); - - if (column_remainder != 0) { - EmitOuterLoopBody(b_->getInt64(column_limit), column_remainder, - column_limit == 0); - } -} - -void ColumnMajorMatrixVectorProductEmitter::EmitInnerLoopTiled( - MemoryTile* lhs_memory_tile, const std::vector& rhs_tile, - int64 columns, bool is_first_column) { - int64 row_limit = m() - (m() % tile_rows()); - - ksl_.For( - "dot.inner.tiled", /*start=*/0, /*end=*/row_limit, - /*step=*/tile_rows(), [&](llvm::Value* row) { - std::vector lhs_tile = - lhs_memory_tile->LoadTile(/*minor_dim_offset=*/row); - llvm::Value* accumulator = - is_first_column ? (addend_ ? vsl_.LoadVector(addend_, row) - : vsl_.GetZeroVector()) - : vsl_.LoadVector(result_, row); - for (int i = 0; i < columns; i++) { - accumulator = vsl_.MulAdd(lhs_tile[i], rhs_tile[i], accumulator); - } - vsl_.StoreVector(accumulator, result_, row); - }); -} - -void ColumnMajorMatrixVectorProductEmitter::EmitInnerLoopEpilogue( - llvm::Value* current_tile_col, int64 columns, bool is_first_tiled_column) { - int64 row_start = m() - (m() % tile_rows()); - if (row_start == m()) { - return; - } - - llvm::Value* columns_llvm = b_->getInt64(columns); - - // for (col = current_tile_col; col < (columns + current_tile_col); col++) - // for (row = row_start, row < m_; row++) { - // result[row] += lhs[row, col] * rhs[col] - // // Also take into account that if col is 0 then result[row] is not - // // initialized. - // } - - ksl_.For( - "dot.inner.epilg.outer", /*start=*/current_tile_col, - /*end=*/b_->CreateAdd(columns_llvm, current_tile_col), - /*step=*/1, /*peel_first_iteration=*/false, - [&](llvm::Value* col, llvm::Value* is_first_scalar_col) { - llvm::Value* rhs_element = vsl_.LoadScalar(rhs_, col); - llvm::Value* total_offset = b_->CreateMul(col, b_->getInt64(m())); - llvm::Value* lhs_base_pointer = - vsl_.ComputeOffsetPointer(lhs_, total_offset); - ksl_.For( - "dot.inner.epilg.inner", /*start=*/row_start, /*end=*/m(), - /*step=*/1, [&](llvm::Value* scalar_row) { - llvm::Value* product = vsl_.Mul( - vsl_.LoadScalar(lhs_base_pointer, scalar_row), rhs_element); - llvm::Value* setting_result_first_time = b_->CreateAnd( - is_first_scalar_col, b_->getInt1(is_first_tiled_column)); - ksl_.If( - setting_result_first_time, - /*true_block_generator=*/ - [&]() { - if (addend_) { - vsl_.StoreScalar( - vsl_.Add(vsl_.LoadScalar(addend_, scalar_row), - product), - result_, scalar_row); - } else { - vsl_.StoreScalar(product, result_, scalar_row); - } - }, - /*false_block_generator=*/ - [&]() { - vsl_.StoreScalar( - vsl_.Add(vsl_.LoadScalar(result_, scalar_row), product), - result_, scalar_row); - }); - }); - }); -} - -// Computes a dot product between "[M,K]{1,0} lhs" with a [K,1] vector (the -// layout of the vector does not matter). This implementation uses a tiling -// scheme to improve performance. -// -// We logically separate the LHS matrix into four segments: -// -// +----------------------+---+ -// | | | -// | | | -// | A | B | -// | | | -// | | | -// | | | -// +----------------------+---+ -// | C | D | -// +----------------------+---+ -// -// where A is the largest submatrix of the LHS that can be evenly dividied into -// tiles. For each tile in A, assuming tile_rows_ == tile_cols_ == 4, we have: -// -// +---+---+---+---+ -// |M00|M10|M20|M30| -// +---+---+---+---+ +--+--+--+--+ -// |M01|M11|M21|M31| and |V0|V1|V2|V3| -// +---+---+---+---+ +--+--+--+--+ -// |M02|M12|M22|M32| -// +---+---+---+---+ -// |M03|M13|M23|M33| -// +---+---+---+---+ -// -// (Legend: rows are horizontal and columns are vertical; and each row is one -// llvm::Value of a vector type) -// -// where: -// -// a. The left tile is loaded from the row major left matrix. -// b. The right vector is loaded from the RHS vector. -// -// We keep 4 vector accumulators accumulating the following four vector -// expressions as we iterate over the row dimension: -// -// +------+------+------+------+ -// |M0I*V0|M1I*V1|M2I*V2|M3I*V3| for I in [0,4) -// +------+------+------+------+ -// -// In the end we do a horizontal reduction over these 4 vector accumulators to -// get 4 values in the result vector. -// -// We have an inner epilogue loop to deal with the "B" sub-matrix and an outer -// epilogue loop to deal with the C,D submatrix. -class RowMajorMatrixVectorProductEmitter - : public GemvConfig::User { - public: - class Config : public GemvConfig { - public: - explicit Config(PrimitiveType scalar_type, int64 tile_rows, int64 tile_cols, - int64 m, int64 k, bool has_addend) - : GemvConfig(/*name=*/"row_major_gemv", scalar_type, - /*tile_rows=*/tile_rows, /*tile_cols=*/tile_cols, /*m=*/m, - /*k=*/k, /*has_addend=*/has_addend) {} - }; - - RowMajorMatrixVectorProductEmitter(const Config& config, llvm::Value* lhs, - llvm::Value* rhs, llvm::Value* addend, - llvm::Value* result, llvm::IRBuilder<>* b) - : config_(config), - lhs_(lhs), - rhs_(rhs), - addend_(addend), - result_(result), - b_(b), - ksl_(b_), - vsl_(scalar_type(), /*vector_size=*/tile_cols(), b_, "") { - CHECK(tile_cols() > 0 && IsPowerOfTwo(static_cast(tile_cols()))); - CHECK(!has_addend() || addend != nullptr); - } - - void Emit(); - - const Config& config() const { return config_; } - - private: - MemoryTile GetLhsMemoryTile(llvm::Value* row_start, int64 row_count) { - return MemoryTile(&vsl_, b_, /*matrix=*/lhs_, - /*matrix_size_along_minor_dim=*/k(), - /*major_dim_offset=*/row_start, - /*tile_size_along_major_dim=*/row_count); - } - - void EmitOuterLoopBody(llvm::Value* row, int64 row_count); - - void EmitInnerLoopTiled(MemoryTile* lhs_memory_tile, int64 rows, - std::vector* vector_accumulators); - - void EmitInnerLoopEpilogue(llvm::Value* current_tile_row, int64 rows, - std::vector* scalar_accumulators); - - Config config_; - llvm::Value* lhs_; - llvm::Value* rhs_; - llvm::Value* addend_; - llvm::Value* result_; - llvm::IRBuilder<>* b_; - KernelSupportLibrary ksl_; - VectorSupportLibrary vsl_; -}; - -void RowMajorMatrixVectorProductEmitter::EmitOuterLoopBody(llvm::Value* row, - int64 row_count) { - MemoryTile lhs_memory_tile = GetLhsMemoryTile(/*row_start=*/row, - /*row_count=*/row_count); - std::vector vector_accumulators; - std::vector scalar_accumulators; - for (int i = 0; i < row_count; i++) { - vector_accumulators.emplace_back(&vsl_, vsl_.GetZeroVector()); - scalar_accumulators.emplace_back(&vsl_, vsl_.GetZeroScalar()); - } - EmitInnerLoopTiled(&lhs_memory_tile, /*rows=*/row_count, - &vector_accumulators); - EmitInnerLoopEpilogue(/*current_tile_row=*/row, /*rows=*/row_count, - &scalar_accumulators); - - std::vector accumulator_values; - std::transform( - vector_accumulators.begin(), vector_accumulators.end(), - std::back_inserter(accumulator_values), - [](const VectorVariable& vector_var) { return vector_var.Get(); }); - - std::vector horizontal_sums; - if (row_count == vsl_.vector_size()) { - if (addend_) { - horizontal_sums = vsl_.ComputeHorizontalSums( - std::move(accumulator_values), vsl_.LoadVector(addend_, row)); - } else { - horizontal_sums = - vsl_.ComputeHorizontalSums(std::move(accumulator_values)); - } - } else { - horizontal_sums = vsl_.ComputeHorizontalSums(std::move(accumulator_values)); - } - - for (int i = 0; i < row_count; i++) { - llvm::Value* result_value = - vsl_.Add(horizontal_sums[i], scalar_accumulators[i].Get()); - llvm::Value* offset = b_->CreateAdd(b_->getInt64(i), row); - if (addend_ && row_count != vsl_.vector_size()) { - result_value = vsl_.Add(vsl_.LoadScalar(addend_, offset), result_value); - } - vsl_.StoreScalar(result_value, result_, offset); - } -} - -void RowMajorMatrixVectorProductEmitter::Emit() { - // See the comment on the class declaration for the algorithm used here. - int64 row_remainder = m() % tile_rows(); - int64 row_limit = m() - row_remainder; - - ksl_.For("dot.outer.tiled", - /*start=*/0, /*end=*/row_limit, /*step=*/tile_rows(), - [&](llvm::Value* row) { EmitOuterLoopBody(row, tile_rows()); }); - - if (row_remainder != 0) { - EmitOuterLoopBody(b_->getInt64(row_limit), row_remainder); - } -} - -void RowMajorMatrixVectorProductEmitter::EmitInnerLoopTiled( - MemoryTile* lhs_memory_tile, int64 rows, - std::vector* vector_accumulators) { - int64 column_limit = k() - (k() % tile_cols()); - - ksl_.For("dot.inner.tiled", /*start=*/0, /*end=*/column_limit, - /*step=*/tile_cols(), [&](llvm::Value* col) { - std::vector lhs_tile = - lhs_memory_tile->LoadTile(/*minor_dim_offset=*/col); - llvm::Value* rhs_value = vsl_.LoadVector(rhs_, col); - for (int i = 0; i < rows; i++) { - llvm::Value* old_sum = (*vector_accumulators)[i].Get(); - (*vector_accumulators)[i].Set( - vsl_.Add(old_sum, vsl_.Mul(rhs_value, lhs_tile[i]))); - } - }); -} - -void RowMajorMatrixVectorProductEmitter::EmitInnerLoopEpilogue( - llvm::Value* current_tile_row, int64 rows, - std::vector* scalar_accumulators) { - int64 column_start = k() - (k() % tile_cols()); - if (column_start == k()) { - return; - } - - for (int r = 0; r < rows; r++) { - llvm::Value* total_offset = b_->CreateMul( - b_->CreateAdd(b_->getInt64(r), current_tile_row), b_->getInt64(k())); - llvm::Value* lhs_base_pointer = - vsl_.ComputeOffsetPointer(lhs_, total_offset); - ksl_.For( - "dot.inner.epilg.inner", /*start=*/column_start, /*end=*/k(), - /*step=*/1, [&](llvm::Value* scalar_col) { - llvm::Value* product = - vsl_.Mul(vsl_.LoadScalar(lhs_base_pointer, scalar_col), - vsl_.LoadScalar(rhs_, scalar_col)); - llvm::Value* old_value = (*scalar_accumulators)[r].Get(); - (*scalar_accumulators)[r].Set(vsl_.Add(old_value, product)); - }); - } -} - -// This class implements a tiled matrix multiplication algorithm, intended for -// multiplying small matrices that don't need cache tiling. -// -// In the future this can be used as the innermost GEBP loop in a GEMM kernel as -// described in "Goto, Kazushige, and Robert A. Geijn. "Anatomy of -// high-performance matrix multiplication." ACM Transactions on Mathematical -// Software (TOMS) 34.3 (2008): 12.". -// -// This only supports canonical dot operations (i.e. where the lhs contraction -// dimension is 1 and the rhs contraction dimension is 0) over row major -// matrices. -class TiledSmallGemmEmitter { - public: - // Describe the dimensions of the kernel. - class Dimensions { - public: - explicit Dimensions(int64 m, int64 k, int64 n) : m_(m), k_(k), n_(n) {} - - int64 m() const { return m_; } - int64 k() const { return k_; } - int64 n() const { return n_; } - - string ToString() const { return absl::StrCat(m(), "x", k(), "x", n()); } - - private: - const int64 m_; - const int64 k_; - const int64 n_; - }; - - // Represents the configuration of the emitter. The LLVM IR emitted by the - // emitter, modulo the LLVM values holding the input and output buffers, must - // be a function of the instance of `Config` passed to it. - // - // `dims` holds the matrix multiplication dimensions. - // - // `max_vectorization_width` is the maximum vector width (i.e. the width of - // the largest vector register we will use). This can be larger than the - // largest vector register supported by the machine -- LLVM will legalize - // these large vector widths into legally sized vectors. - // - // `max_vector_count` is the maximum number of vectors of size - // `max_vectorization_width` that we will attempt to process at once. - // - // `min_vectorization_width` is the smallest vector width the emitter will use - // -- below that it will devolve to using a scalar loop. - // - // The innermost reduction loop executes the matrix multiply in tiles of size - // [`tile_size_m`, `tile_size_k`] from the LHS and [`tile_size_k`, - // ] in the RHS. - class Config { - public: - explicit Config(PrimitiveType scalar_type, Dimensions dims, - int64 max_vectorization_width, int64 max_vector_count, - int64 min_vectorization_width, int64 tile_size_m, - int64 tile_size_k) - : scalar_type_(scalar_type), - dims_(dims), - max_vectorization_width_(max_vectorization_width), - max_vector_count_(max_vector_count), - min_vectorization_width_(min_vectorization_width), - tile_size_m_(tile_size_m), - tile_size_k_(tile_size_k) {} - - string GetCacheKey() const { - return absl::StrCat("gemm_", PrimitiveType_Name(scalar_type()), "_", - dims().ToString(), "_", max_vectorization_width(), - "_", min_vectorization_width(), "_", tile_size_m(), - "_", tile_size_k()); - } - - PrimitiveType scalar_type() const { return scalar_type_; } - Dimensions dims() const { return dims_; } - int64 max_vectorization_width() const { return max_vectorization_width_; } - int64 max_vector_count() const { return max_vector_count_; } - int64 min_vectorization_width() const { return min_vectorization_width_; } - - int64 tile_size_m() const { return tile_size_m_; } - int64 tile_size_k() const { return tile_size_k_; } - - private: - PrimitiveType scalar_type_; - Dimensions dims_; - int64 max_vectorization_width_; - int64 max_vector_count_; - int64 min_vectorization_width_; - int64 tile_size_m_; - int64 tile_size_k_; - }; - - // Creates an instance of TiledSmallGemmEmitter that matrix-multiplies - // `lhs` with `rhs` and stores the result in `result`. - explicit TiledSmallGemmEmitter(Config config, llvm::Value* lhs, - llvm::Value* rhs, llvm::Value* result, - llvm::IRBuilder<>* b) - : lhs_(lhs), - rhs_(rhs), - result_(result), - config_(config), - b_(b), - ksl_(b_) { - CHECK(max_vectorization_width() > 0 && - IsPowerOfTwo(static_cast(max_vectorization_width()))); - CHECK_GT(max_vector_count(), 0); - CHECK(min_vectorization_width() > 0 && - IsPowerOfTwo(static_cast(min_vectorization_width()))); - CHECK_GE(max_vectorization_width(), min_vectorization_width()); - CHECK_GT(tile_size_k(), 0); - } - - void Emit(); - - private: - // The HandleResiduesOnX helpers split the iteration space for dimension X - // into a multiple of the tile size on dimension X and an epilogue. These - // helpers ultimately call into `EmitTiledGemm` for emitting the - // tiled GEMM kernel. - - void HandleResiduesOnN(); - void HandleResiduesOnK(VectorSupportLibrary* vsl, llvm::Value* n_start, - llvm::Value* n_end); - void HandleResiduesOnM(VectorSupportLibrary* vsl, int64 tile_size_k, - llvm::Value* k_start, llvm::Value* k_end, - llvm::Value* n_start, llvm::Value* n_end); - - // This emits a tiled GEMM kernel. For a detailed description see the comment - // on the implementation. - void EmitTiledGemm(VectorSupportLibrary* vsl, int64 tile_size_k, - llvm::Value* k_start, llvm::Value* k_end, - llvm::Value* n_start, llvm::Value* n_end, - int64 tile_size_m, llvm::Value* m_start, - llvm::Value* m_end); - - llvm::Value* GetInt64(int64 value) { return b_->getInt64(value); } - - Config config() const { return config_; } - Dimensions dims() const { return config().dims(); } - - int64 max_vectorization_width() const { - return config().max_vectorization_width(); - } - int64 max_vector_count() const { return config().max_vector_count(); } - int64 min_vectorization_width() const { - return config().min_vectorization_width(); - } - int64 tile_size_m() const { return config().tile_size_m(); } - int64 tile_size_k() const { return config().tile_size_k(); } - PrimitiveType scalar_type() const { return config().scalar_type(); } - - llvm::Value* lhs_; - llvm::Value* rhs_; - llvm::Value* result_; - Config config_; - - llvm::IRBuilder<>* b_; - KernelSupportLibrary ksl_; -}; - -void TiledSmallGemmEmitter::Emit() { HandleResiduesOnN(); } - -void TiledSmallGemmEmitter::HandleResiduesOnN() { - // We can only iterate the `n` dimension for an extent that is divisible by - // the vectorization width. So we emit an outer loop that first processes the - // largest extent in `n` that is divisible by max_vectorization_width, then - // the largest remaining extent that is divisible by max_vectorization_width / - // 2 etc. - - int64 current_vectorization_width = - max_vector_count() * max_vectorization_width(); - int64 current_vector_count = max_vector_count(); - - int64 n_start = 0; - while (n_start != dims().n() && - current_vectorization_width >= min_vectorization_width()) { - int64 n_end = dims().n() - (dims().n() % current_vectorization_width); - if (n_start != n_end) { - VectorSupportLibrary vsl(scalar_type(), current_vectorization_width, b_, - "gemm"); - HandleResiduesOnK(&vsl, GetInt64(n_start), GetInt64(n_end)); - n_start = n_end; - } - if (current_vector_count == 1) { - current_vectorization_width /= 2; - } else { - current_vector_count--; - current_vectorization_width = - current_vector_count * max_vectorization_width(); - } - } - - if (n_start != dims().n()) { - VectorSupportLibrary vsl(scalar_type(), 1, b_, "gemm"); - ksl_.For("epi.n", n_start, dims().n(), 1, [&](llvm::Value* n_i) { - llvm::Value* n_i_next = b_->CreateAdd(n_i, b_->getInt64(1)); - HandleResiduesOnK(&vsl, n_i, n_i_next); - }); - } -} - -void TiledSmallGemmEmitter::HandleResiduesOnK(VectorSupportLibrary* vsl, - llvm::Value* n_start, - llvm::Value* n_end) { - int64 k_start = 0; - int64 k_end = dims().k() - (dims().k() % tile_size_k()); - if (k_end != k_start) { - HandleResiduesOnM(vsl, tile_size_k(), GetInt64(k_start), GetInt64(k_end), - n_start, n_end); - k_start = k_end; - } - - if (k_start != dims().k()) { - HandleResiduesOnM(vsl, dims().k() - k_start, GetInt64(k_start), - GetInt64(dims().k()), n_start, n_end); - } -} - -void TiledSmallGemmEmitter::HandleResiduesOnM( - VectorSupportLibrary* vsl, int64 tile_size_k, llvm::Value* k_start, - llvm::Value* k_end, llvm::Value* n_start, llvm::Value* n_end) { - const int64 m_end = dims().m() - dims().m() % tile_size_m(); - EmitTiledGemm(vsl, tile_size_k, k_start, k_end, n_start, n_end, tile_size_m(), - GetInt64(0), GetInt64(m_end)); - - if (m_end != dims().m()) { - EmitTiledGemm(vsl, tile_size_k, k_start, k_end, n_start, n_end, - dims().m() - m_end, GetInt64(m_end), GetInt64(dims().m())); - } -} - -// The loop structure is: -// -// Iterate over dimension M as m: -// Iterate over dimension N as n: -// Iterate over dimension K as k: -// OutputTile[m,n] += Dot(LhsTile[m,k], RhsTile[k,n]) -// -// I.e. a just a tiled version of a "naive" GEMM. -// -// The tiling scheme is as follows: -// -// Let the LHS be: -// -// +----+----+----+ -// | a0 | b0 | c0 | . -// +----+----+----+ . -// | a1 | b1 | c1 | . -// +----+----+----+ -// .. .. -// -// and the RHS be: -// -// +----+----+----+----+ -// | p0 | p1 | p2 | p3 | . -// +----+----+----+----+ . -// | q0 | q1 | q2 | q3 | . -// +----+----+----+----+ -// | r0 | r1 | r2 | r3 | . -// +----+----+----+----+ . -// ...... ...... -// -// and let tile_size_m=2, tile_size_k=3 and the vector width (implicitly denoted -// by `vsl`) be 4. Then we want to matrix multiply this tile to get a [2,4] -// matrix that we can increment the result matrix by. -// -// First broadcast the rows row in LHS to 3 vectors of width 4, giving us a rank -// 3 array, L, of dimension [2,3,4]: -// -// L[0,_,_] * L[1,_,_] -// * -// +----+----+----+----+ * +----+----+----+----+ -// | a0 | a0 | a0 | a0 | * | a1 | a1 | a1 | a1 | -// +----+----+----+----+ * +----+----+----+----+ -// | b0 | b0 | b0 | b0 | * | b1 | b1 | b1 | b1 | -// +----+----+----+----+ * +----+----+----+----+ -// | c0 | c0 | c0 | c0 | * | c1 | c1 | c1 | c1 | -// +----+----+----+----+ * +----+----+----+----+ -// -// -// Then we FMA L[0,_,_] with the RHS to get the first row of the result and -// L[1,_,_] with the RHS to get the second row of the result. For example, -// L[0,_,_] is computed as: -// -// +----+----+----+----+ +----+----+----+----+ -// | a0 | a0 | a0 | a0 | * | p0 | p1 | p2 | p3 | + -// +----+----+----+----+ +----+----+----+----+ -// -// +----+----+----+----+ +----+----+----+----+ -// | b0 | b0 | b0 | b0 | * | q0 | q1 | q2 | q3 | + -// +----+----+----+----+ +----+----+----+----+ -// -// +----+----+----+----+ +----+----+----+----+ -// | c0 | c0 | c0 | c0 | * | r0 | r1 | r2 | r3 | -// +----+----+----+----+ +----+----+----+----+ -// -// to get: -// -// +-------------------+-------------------+-------------------+--------- -// | a0*p0+b0*q0+c0*r0 | a0*p1+b0*q1+c0*r1 | a0*p2+b0*q2+c0*r2 | ... -// +-------------------+-------------------+-------------------+--------- -void TiledSmallGemmEmitter::EmitTiledGemm( - VectorSupportLibrary* vsl, int64 tile_size_k, llvm::Value* k_start, - llvm::Value* k_end, llvm::Value* n_start, llvm::Value* n_end, - int64 tile_size_m, llvm::Value* m_start, llvm::Value* m_end) { - ksl_.For( - "dot.m", m_start, m_end, tile_size_m, [&](llvm::Value* m_i) { - MemoryTile result_memory_tile( - vsl, b_, /*matrix=*/result_, - /*matrix_size_along_minor_dim=*/dims().n(), - /*major_dim_offset=*/m_i, - /*tile_size_along_major_dim=*/tile_size_m); - MemoryTile lhs_memory_tile(vsl, b_, /*matrix=*/lhs_, - /*matrix_size_along_minor_dim=*/dims().k(), - /*major_dim_offset=*/m_i, - /*tile_size_along_major_dim=*/tile_size_m); - ksl_.For( - "dot.n", n_start, n_end, vsl->vector_size(), [&](llvm::Value* n_i) { - TileVariable result_tile_var(vsl, - result_memory_tile.LoadTile(n_i)); - ksl_.For( - "dot.k", k_start, k_end, tile_size_k, [&](llvm::Value* k_i) { - MemoryTile rhs_memory_tile(vsl, b_, rhs_, dims().n(), k_i, - tile_size_k); - std::vector> lhs_tile = - lhs_memory_tile.LoadBroadcastTile(k_i, tile_size_k); - std::vector rhs_tile = - rhs_memory_tile.LoadTile(n_i); - std::vector result_tile = - result_tile_var.Get(); - for (int64 r_m_i = 0; r_m_i < tile_size_m; r_m_i++) { - for (int64 r_k_i = 0; r_k_i < tile_size_k; r_k_i++) { - result_tile[r_m_i] = - vsl->MulAdd(lhs_tile[r_m_i][r_k_i], rhs_tile[r_k_i], - result_tile[r_m_i]); - } - } - result_tile_var.Set(result_tile); - }); - - result_memory_tile.StoreTile(result_tile_var.Get(), n_i); - }); - }); -} - -} // namespace - DotOpEmitter::DotOpEmitter(const HloInstruction& dot, const llvm_ir::IrArray& target_array, const llvm_ir::IrArray& lhs_array, @@ -1062,32 +139,21 @@ bool DotOpEmitter::EmitSmallGemmIfProfitable( std::tie(tile_size_m, tile_size_k, tile_size_n_in_vector_width) = GetGemmTileSize(); - TiledSmallGemmEmitter::Config config( - /*scalar_type=*/primitive_type, - TiledSmallGemmEmitter::Dimensions{/*m=*/m, /*k=*/k, /*n=*/n}, - /*max_vectorization_width=*/max_target_vector_width, - /*max_vector_count=*/tile_size_n_in_vector_width, - /*min_vectorization_width=*/std::min(4, max_target_vector_width), - /*tile_size_m=*/tile_size_m, /*tile_size_k=*/tile_size_k); - - VLOG(2) << "Emitting GEMM kernel in LLVM IR with config " - << config.GetCacheKey(); - const bool enable_fast_math = hlo_module_config_.debug_options().xla_cpu_enable_fast_math(); const bool optimize_for_size = options::OptimizeForSizeRequested(hlo_module_config_); - KernelSupportLibrary::EmitAndCallOutlinedKernel( + EmitSmallGemm( + /*scalar_type=*/primitive_type, + /*m=*/m, /*k=*/k, /*n=*/n, + /*max_vectorization_width=*/max_target_vector_width, + /*max_vector_count=*/tile_size_n_in_vector_width, + /*min_vectorization_width=*/std::min(4, max_target_vector_width), + /*tile_size_m=*/tile_size_m, /*tile_size_k=*/tile_size_k, /*lhs=*/lhs, + /*rhs=*/rhs, /*result=*/target, b_, /*enable_fast_math=*/enable_fast_math, - /*optimize_for_size=*/optimize_for_size, b_, config.GetCacheKey(), lhs, - rhs, target, - [this, config](llvm::Value* lhs, llvm::Value* rhs, llvm::Value* target) { - TiledSmallGemmEmitter small_gemm_emitter(config, /*lhs=*/lhs, - /*rhs=*/rhs, - /*result=*/target, b_); - small_gemm_emitter.Emit(); - }); + /*optimize_for_size=*/optimize_for_size); return true; } @@ -1177,41 +243,26 @@ bool DotOpEmitter::EmitLlvmIrDotIfProfitable() { if (is_column_major_matrix_vector) { VLOG(2) << "Emitting column major matrix-vector multiply with m = " << m << " and k = " << k; - ColumnMajorMatrixVectorProductEmitter::Config config( + EmitColumnMajorGemv( /*scalar_type=*/primitive_type, /*tile_rows=*/vector_register_element_size, /*tile_cols=*/tiling_factor, - /*m=*/m, /*k=*/k, /*has_addend=*/addend_array_ != nullptr); - - KernelSupportLibrary::EmitAndCallOutlinedKernel( + /*m=*/m, /*k=*/k, /*lhs=*/lhs_op, /*rhs=*/rhs_op, + /*addend=*/addend_array_ ? addend_array_->GetBasePointer() : nullptr, + /*result=*/result_op, b_, /*enable_fast_math=*/enable_fast_math, - /*optimize_for_size=*/optimize_for_size, b_, config.GetCacheKey(), - lhs_op, rhs_op, - addend_array_ ? addend_array_->GetBasePointer() : nullptr, result_op, - [this, config](llvm::Value* lhs_op, llvm::Value* rhs_op, - llvm::Value* addend_op, llvm::Value* result_op) { - ColumnMajorMatrixVectorProductEmitter emitter( - config, lhs_op, rhs_op, addend_op, result_op, b_); - emitter.Emit(); - }); + /*optimize_for_size=*/optimize_for_size); } else { VLOG(2) << "Emitting row major matrix-vector multiply with m = " << m << " and k = " << k; - RowMajorMatrixVectorProductEmitter::Config config( + EmitRowMajorGemv( /*scalar_type=*/primitive_type, - /*tile_rows=*/tiling_factor, /*tile_cols=*/vector_register_element_size, - /*m=*/m, /*k=*/k, /*has_addend=*/addend_array_ != nullptr); - - KernelSupportLibrary::EmitAndCallOutlinedKernel( + /*tile_rows=*/tiling_factor, + /*tile_cols=*/vector_register_element_size, + /*m=*/m, /*k=*/k, /*lhs=*/lhs_op, /*rhs=*/rhs_op, + /*addend=*/addend_array_ ? addend_array_->GetBasePointer() : nullptr, + /*result=*/result_op, b_, /*enable_fast_math=*/enable_fast_math, - /*optimize_for_size=*/optimize_for_size, b_, config.GetCacheKey(), - lhs_op, rhs_op, - addend_array_ ? addend_array_->GetBasePointer() : nullptr, result_op, - [this, config](llvm::Value* lhs_op, llvm::Value* rhs_op, - llvm::Value* addend_op, llvm::Value* result_op) { - RowMajorMatrixVectorProductEmitter emitter(config, lhs_op, rhs_op, - addend_op, result_op, b_); - emitter.Emit(); - }); + /*optimize_for_size=*/optimize_for_size); } return true; diff --git a/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.cc b/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.cc new file mode 100644 index 0000000000..eb6c44b70a --- /dev/null +++ b/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.cc @@ -0,0 +1,1014 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.h" + +#include "tensorflow/compiler/xla/service/cpu/vector_support_library.h" +#include "tensorflow/compiler/xla/service/hlo_module.h" +#include "tensorflow/compiler/xla/service/llvm_ir/kernel_support_library.h" +#include "tensorflow/compiler/xla/service/llvm_ir/llvm_util.h" + +namespace xla { +namespace cpu { +namespace { + +using tensorflow::int64; + +// Provides tiled access to an in-memory rank 2 array. +class MemoryTile { + public: + // Constructs a MemoryTile that can operate on tiles consisting of + // `tile_size_along_major_dim` vectors from the matrix `matrix`, starting at + // `major_dim_offset` in the major dimension. The tile size along the minor + // dimension is the vector size, and that is implicitly determined by `vsl`. + MemoryTile(VectorSupportLibrary* vsl, llvm::IRBuilder<>* b, + llvm::Value* matrix, int64 matrix_size_along_minor_dim, + llvm::Value* major_dim_offset, int64 tile_size_along_major_dim) + : vsl_(vsl), b_(b) { + pointers_.reserve(tile_size_along_major_dim); + for (int64 i = 0; i < tile_size_along_major_dim; i++) { + llvm::Value* total_offset = + b->CreateMul(b->getInt64(matrix_size_along_minor_dim), + b->CreateAdd(b->getInt64(i), major_dim_offset)); + pointers_.push_back(vsl_->ComputeOffsetPointer(matrix, total_offset)); + } + } + + // Load a tile consisting of `tile_size_along_major_dim` vectors from position + // {major: `major_dim_offset`, minor: `minor_dim_offset`}. + // + // Note: `major_dim_offset` is a parameter to the constructor. + std::vector LoadTile(llvm::Value* minor_dim_offset) const { + std::vector result; + result.reserve(pointers_.size()); + for (const auto& pointer : pointers_) { + result.push_back(vsl_->LoadVector(pointer, minor_dim_offset)); + } + return result; + } + + // Stores `tile` to position {major: `major_dim_offset`, minor: + // `minor_dim_offset`}. + // + // Note: `major_dim_offset` is a parameter to the constructor. + void StoreTile(absl::Span tile, + llvm::Value* minor_dim_offset) const { + CHECK_EQ(tile.size(), pointers_.size()); + for (int64 i = 0; i < pointers_.size(); i++) { + vsl_->StoreVector(tile[i], pointers_[i], minor_dim_offset); + } + } + + // Loads a tile of size [`tile_size_along_major_dim`, + // `tile_size_along_middle_dim`] from position {major: `major_dim_offset`, + // minor: `minor_dim_offset`} and then broadcasts each element into a vector + // of size vsl_.vector_size(). The (i,j)'th element of the return value is + // the (i,j)'th element in the tile broadcasted into an LLVM vector. + // + // Note: `major_dim_offset` is a parameter to the constructor. + std::vector> LoadBroadcastTile( + llvm::Value* minor_dim_offset, int64 tile_size_along_middle_dim) const { + std::vector> result; + result.resize(pointers_.size()); + for (int64 i = 0; i < pointers_.size(); i++) { + for (int64 j = 0; j < tile_size_along_middle_dim; j++) { + result[i].push_back(vsl_->LoadBroadcast( + pointers_[i], b_->CreateAdd(minor_dim_offset, b_->getInt64(j)))); + } + } + return result; + } + + private: + VectorSupportLibrary* vsl_; + llvm::IRBuilder<>* b_; + std::vector pointers_; +}; + +// The base class for the classes representing the GEMV emitter configurations. +// +// The IR emitted (modulo the LLVM values representing the input and output +// buffers) by the row major and column major GEMV emitters should be a function +// of their configuration. This is important because their configuration is +// used as a key to cache the generated IR. +class GemvConfig { + public: + // Mixin for convenience. + template + struct User { + public: + PrimitiveType scalar_type() const { + return derived().config().scalar_type(); + } + int64 tile_rows() const { return derived().config().tile_rows(); } + int64 tile_cols() const { return derived().config().tile_cols(); } + int64 m() const { return derived().config().m(); } + int64 k() const { return derived().config().k(); } + int64 has_addend() const { return derived().config().has_addend(); } + + private: + const T& derived() const { return *static_cast(this); } + }; + + PrimitiveType scalar_type() const { return scalar_type_; } + int64 tile_rows() const { return tile_rows_; } + int64 tile_cols() const { return tile_cols_; } + int64 m() const { return m_; } + int64 k() const { return k_; } + bool has_addend() const { return has_addend_; } + + string GetCacheKey() const { + return absl::StrCat(name_, "_", PrimitiveType_Name(scalar_type()), "_", + tile_rows(), "_", tile_cols(), "_", m(), "_", k(), + has_addend() ? "_with_addend" : ""); + } + + protected: + explicit GemvConfig(string name, PrimitiveType scalar_type, int64 tile_rows, + int64 tile_cols, int64 m, int64 k, bool has_addend) + : name_(std::move(name)), + scalar_type_(scalar_type), + tile_rows_(tile_rows), + tile_cols_(tile_cols), + m_(m), + k_(k), + has_addend_(has_addend) {} + + private: + string name_; + PrimitiveType scalar_type_; + int64 tile_rows_; + int64 tile_cols_; + int64 m_; + int64 k_; + bool has_addend_; +}; + +// Computes a dot product between "[M,K]{0,1} lhs" with a [K,1] vector (the +// layout of the vector does not matter). This implementation uses a tiling +// scheme to improve performance. +// +// We logically separate the LHS matrix into four segments: +// +// +----------------------+---+ +// | | | +// | | | +// | A | B | +// | | | +// | | | +// | | | +// +----------------------+---+ +// | C | D | +// +----------------------+---+ +// +// where A is the largest submatrix of the LHS that can be evenly dividied into +// tiles. For each tile in A, assuming tile_rows_ == tile_cols_ == 4, we have: +// +// +---+---+---+---+ +--+--+--+--+ +// |M00|M10|M20|M30| |V0|V1|V2|V3| +// +---+---+---+---+ +--+--+--+--+ +// |M01|M11|M21|M31| and |V0|V1|V2|V3| +// +---+---+---+---+ +--+--+--+--+ +// |M02|M12|M22|M32| |V0|V1|V2|V3| +// +---+---+---+---+ +--+--+--+--+ +// |M03|M13|M23|M33| |V0|V1|V2|V3| +// +---+---+---+---+ +--+--+--+--+ +// +// (Legend: rows are horizontal and columns are vertical; and each column is one +// llvm::Value of a vector type) +// +// where: +// +// a. The left tile is from the column major left matrix. +// b. The right tile is an elementwise broadcast of a [V0, V1, V2, V3] +// vector loaded from the RHS vector. +// +// As we iterate through the column dimension, we compute the change to the +// result vector by an elementwise multiplication between the two tiles above +// followed by a reduction along the major dimension: +// +// +-----------------------------------+ +// | M00*V0 + M10*V1 + M20*V2 + M30*V3 | +// +-----------------------------------+ +// | M01*V0 + M11*V1 + M21*V2 + M31*V3 | +// Result[R:R+4] += +-----------------------------------+ +// | M02*V0 + M12*V1 + M22*V2 + M32*V3 | +// +-----------------------------------+ +// | M03*V0 + M13*V1 + M23*V2 + M33*V3 | +// +-----------------------------------+ +// +// Where R is the starting row for the tile. +// +// We have an inner epilogue loop to deal with the "C" submatrix and an outer +// epilogue loop to deal with the B,D submarix. +// +// TODO(sanjoy): We should investigate if using gather loads and scatter stores +// can be used here have the same inner loop for both column-major and row-major +// matrix-vector products. +class ColumnMajorMatrixVectorProductEmitter + : public GemvConfig::User { + public: + class Config : public GemvConfig { + public: + explicit Config(PrimitiveType scalar_type, int64 tile_rows, int64 tile_cols, + int64 m, int64 k, bool has_addend) + : GemvConfig(/*name=*/"col_major_gemv", scalar_type, + /*tile_rows=*/tile_rows, /*tile_cols=*/tile_cols, /*m=*/m, + /*k=*/k, /*has_addend=*/has_addend) {} + }; + + ColumnMajorMatrixVectorProductEmitter(const Config& config, llvm::Value* lhs, + llvm::Value* rhs, llvm::Value* addend, + llvm::Value* result, + llvm::IRBuilder<>* b) + : config_(config), + lhs_(lhs), + rhs_(rhs), + addend_(addend), + result_(result), + b_(b), + ksl_(b_), + vsl_(config.scalar_type(), /*vector_size=*/config.tile_rows(), b_, "") { + CHECK(tile_rows() > 0 && IsPowerOfTwo(static_cast(tile_rows()))); + CHECK(!has_addend() || addend != nullptr); + } + + void Emit(); + + const Config& config() const { return config_; } + + private: + void EmitOuterLoopBody(llvm::Value* column, int64 column_count, + bool is_first_column); + + MemoryTile GetLhsMemoryTile(llvm::Value* column_start, int64 column_count) { + return MemoryTile(&vsl_, b_, /*matrix=*/lhs_, + /*matrix_size_along_minor_dim=*/m(), + /*major_dim_offset=*/column_start, + /*tile_size_along_major_dim=*/column_count); + } + + // Load a tile of values from the RHS. For the RHS a "tile" is a contiguous + // sequence of `count` values, each one broadcasted to the vector width. + std::vector LoadRhsTile(llvm::Value* offset, int64 count) { + llvm::Value* base_pointer = vsl_.ComputeOffsetPointer(rhs_, offset); + std::vector result; + result.reserve(count); + for (int64 i = 0; i < count; i++) { + result.push_back(vsl_.LoadBroadcast(base_pointer, i)); + } + return result; + } + + void EmitInnerLoopTiled(MemoryTile* lhs_memory_tile, + const std::vector& rhs_tile, + int64 columns, bool is_first_column); + + void EmitInnerLoopEpilogue(llvm::Value* current_tile_col, int64 columns, + bool is_first_tiled_column); + + Config config_; + llvm::Value* lhs_; + llvm::Value* rhs_; + llvm::Value* addend_; + llvm::Value* result_; + llvm::IRBuilder<>* b_; + KernelSupportLibrary ksl_; + VectorSupportLibrary vsl_; +}; + +void ColumnMajorMatrixVectorProductEmitter::EmitOuterLoopBody( + llvm::Value* column, int64 column_count, bool is_first_column) { + MemoryTile lhs_memory_tile = GetLhsMemoryTile(/*column_start=*/column, + /*column_count=*/column_count); + + std::vector rhs_tile = + LoadRhsTile(column, /*count=*/column_count); + EmitInnerLoopTiled(&lhs_memory_tile, rhs_tile, + /*columns=*/column_count, is_first_column); + EmitInnerLoopEpilogue(column, /*columns=*/column_count, is_first_column); +} + +void ColumnMajorMatrixVectorProductEmitter::Emit() { + // See the comment on the class declaration for the algorithm used here. + int64 column_remainder = k() % tile_cols(); + int64 column_limit = k() - column_remainder; + + ksl_.For("dot.outer.tiled", + /*start=*/0, /*end=*/column_limit, /*step=*/tile_cols(), + [&](llvm::Value* column, bool is_first_column) { + EmitOuterLoopBody(column, tile_cols(), is_first_column); + }); + + if (column_remainder != 0) { + EmitOuterLoopBody(b_->getInt64(column_limit), column_remainder, + column_limit == 0); + } +} + +void ColumnMajorMatrixVectorProductEmitter::EmitInnerLoopTiled( + MemoryTile* lhs_memory_tile, const std::vector& rhs_tile, + int64 columns, bool is_first_column) { + int64 row_limit = m() - (m() % tile_rows()); + + ksl_.For("dot.inner.tiled", /*start=*/0, /*end=*/row_limit, + /*step=*/tile_rows(), [&](llvm::Value* row) { + std::vector lhs_tile = + lhs_memory_tile->LoadTile(/*minor_dim_offset=*/row); + llvm::Value* accumulator = + is_first_column ? (addend_ ? vsl_.LoadVector(addend_, row) + : vsl_.GetZeroVector()) + : vsl_.LoadVector(result_, row); + for (int i = 0; i < columns; i++) { + accumulator = vsl_.MulAdd(lhs_tile[i], rhs_tile[i], accumulator); + } + vsl_.StoreVector(accumulator, result_, row); + }); +} + +void ColumnMajorMatrixVectorProductEmitter::EmitInnerLoopEpilogue( + llvm::Value* current_tile_col, int64 columns, bool is_first_tiled_column) { + int64 row_start = m() - (m() % tile_rows()); + if (row_start == m()) { + return; + } + + llvm::Value* columns_llvm = b_->getInt64(columns); + + // for (col = current_tile_col; col < (columns + current_tile_col); col++) + // for (row = row_start, row < m_; row++) { + // result[row] += lhs[row, col] * rhs[col] + // // Also take into account that if col is 0 then result[row] is not + // // initialized. + // } + + ksl_.For( + "dot.inner.epilg.outer", /*start=*/current_tile_col, + /*end=*/b_->CreateAdd(columns_llvm, current_tile_col), + /*step=*/1, /*peel_first_iteration=*/false, + [&](llvm::Value* col, llvm::Value* is_first_scalar_col) { + llvm::Value* rhs_element = vsl_.LoadScalar(rhs_, col); + llvm::Value* total_offset = b_->CreateMul(col, b_->getInt64(m())); + llvm::Value* lhs_base_pointer = + vsl_.ComputeOffsetPointer(lhs_, total_offset); + ksl_.For( + "dot.inner.epilg.inner", /*start=*/row_start, /*end=*/m(), + /*step=*/1, [&](llvm::Value* scalar_row) { + llvm::Value* product = vsl_.Mul( + vsl_.LoadScalar(lhs_base_pointer, scalar_row), rhs_element); + llvm::Value* setting_result_first_time = b_->CreateAnd( + is_first_scalar_col, b_->getInt1(is_first_tiled_column)); + ksl_.If( + setting_result_first_time, + /*true_block_generator=*/ + [&]() { + if (addend_) { + vsl_.StoreScalar( + vsl_.Add(vsl_.LoadScalar(addend_, scalar_row), + product), + result_, scalar_row); + } else { + vsl_.StoreScalar(product, result_, scalar_row); + } + }, + /*false_block_generator=*/ + [&]() { + vsl_.StoreScalar( + vsl_.Add(vsl_.LoadScalar(result_, scalar_row), product), + result_, scalar_row); + }); + }); + }); +} + +// Computes a dot product between "[M,K]{1,0} lhs" with a [K,1] vector (the +// layout of the vector does not matter). This implementation uses a tiling +// scheme to improve performance. +// +// We logically separate the LHS matrix into four segments: +// +// +----------------------+---+ +// | | | +// | | | +// | A | B | +// | | | +// | | | +// | | | +// +----------------------+---+ +// | C | D | +// +----------------------+---+ +// +// where A is the largest submatrix of the LHS that can be evenly dividied into +// tiles. For each tile in A, assuming tile_rows_ == tile_cols_ == 4, we have: +// +// +---+---+---+---+ +// |M00|M10|M20|M30| +// +---+---+---+---+ +--+--+--+--+ +// |M01|M11|M21|M31| and |V0|V1|V2|V3| +// +---+---+---+---+ +--+--+--+--+ +// |M02|M12|M22|M32| +// +---+---+---+---+ +// |M03|M13|M23|M33| +// +---+---+---+---+ +// +// (Legend: rows are horizontal and columns are vertical; and each row is one +// llvm::Value of a vector type) +// +// where: +// +// a. The left tile is loaded from the row major left matrix. +// b. The right vector is loaded from the RHS vector. +// +// We keep 4 vector accumulators accumulating the following four vector +// expressions as we iterate over the row dimension: +// +// +------+------+------+------+ +// |M0I*V0|M1I*V1|M2I*V2|M3I*V3| for I in [0,4) +// +------+------+------+------+ +// +// In the end we do a horizontal reduction over these 4 vector accumulators to +// get 4 values in the result vector. +// +// We have an inner epilogue loop to deal with the "B" sub-matrix and an outer +// epilogue loop to deal with the C,D submatrix. +class RowMajorMatrixVectorProductEmitter + : public GemvConfig::User { + public: + class Config : public GemvConfig { + public: + explicit Config(PrimitiveType scalar_type, int64 tile_rows, int64 tile_cols, + int64 m, int64 k, bool has_addend) + : GemvConfig(/*name=*/"row_major_gemv", scalar_type, + /*tile_rows=*/tile_rows, /*tile_cols=*/tile_cols, /*m=*/m, + /*k=*/k, /*has_addend=*/has_addend) {} + }; + + RowMajorMatrixVectorProductEmitter(const Config& config, llvm::Value* lhs, + llvm::Value* rhs, llvm::Value* addend, + llvm::Value* result, llvm::IRBuilder<>* b) + : config_(config), + lhs_(lhs), + rhs_(rhs), + addend_(addend), + result_(result), + b_(b), + ksl_(b_), + vsl_(scalar_type(), /*vector_size=*/tile_cols(), b_, "") { + CHECK(tile_cols() > 0 && IsPowerOfTwo(static_cast(tile_cols()))); + CHECK(!has_addend() || addend != nullptr); + } + + void Emit(); + + const Config& config() const { return config_; } + + private: + MemoryTile GetLhsMemoryTile(llvm::Value* row_start, int64 row_count) { + return MemoryTile(&vsl_, b_, /*matrix=*/lhs_, + /*matrix_size_along_minor_dim=*/k(), + /*major_dim_offset=*/row_start, + /*tile_size_along_major_dim=*/row_count); + } + + void EmitOuterLoopBody(llvm::Value* row, int64 row_count); + + void EmitInnerLoopTiled(MemoryTile* lhs_memory_tile, int64 rows, + std::vector* vector_accumulators); + + void EmitInnerLoopEpilogue(llvm::Value* current_tile_row, int64 rows, + std::vector* scalar_accumulators); + + Config config_; + llvm::Value* lhs_; + llvm::Value* rhs_; + llvm::Value* addend_; + llvm::Value* result_; + llvm::IRBuilder<>* b_; + KernelSupportLibrary ksl_; + VectorSupportLibrary vsl_; +}; + +void RowMajorMatrixVectorProductEmitter::EmitOuterLoopBody(llvm::Value* row, + int64 row_count) { + MemoryTile lhs_memory_tile = GetLhsMemoryTile(/*row_start=*/row, + /*row_count=*/row_count); + std::vector vector_accumulators; + std::vector scalar_accumulators; + for (int i = 0; i < row_count; i++) { + vector_accumulators.emplace_back(&vsl_, vsl_.GetZeroVector()); + scalar_accumulators.emplace_back(&vsl_, vsl_.GetZeroScalar()); + } + EmitInnerLoopTiled(&lhs_memory_tile, /*rows=*/row_count, + &vector_accumulators); + EmitInnerLoopEpilogue(/*current_tile_row=*/row, /*rows=*/row_count, + &scalar_accumulators); + + std::vector accumulator_values; + std::transform( + vector_accumulators.begin(), vector_accumulators.end(), + std::back_inserter(accumulator_values), + [](const VectorVariable& vector_var) { return vector_var.Get(); }); + + std::vector horizontal_sums; + if (row_count == vsl_.vector_size()) { + if (addend_) { + horizontal_sums = vsl_.ComputeHorizontalSums( + std::move(accumulator_values), vsl_.LoadVector(addend_, row)); + } else { + horizontal_sums = + vsl_.ComputeHorizontalSums(std::move(accumulator_values)); + } + } else { + horizontal_sums = vsl_.ComputeHorizontalSums(std::move(accumulator_values)); + } + + for (int i = 0; i < row_count; i++) { + llvm::Value* result_value = + vsl_.Add(horizontal_sums[i], scalar_accumulators[i].Get()); + llvm::Value* offset = b_->CreateAdd(b_->getInt64(i), row); + if (addend_ && row_count != vsl_.vector_size()) { + result_value = vsl_.Add(vsl_.LoadScalar(addend_, offset), result_value); + } + vsl_.StoreScalar(result_value, result_, offset); + } +} + +void RowMajorMatrixVectorProductEmitter::Emit() { + // See the comment on the class declaration for the algorithm used here. + int64 row_remainder = m() % tile_rows(); + int64 row_limit = m() - row_remainder; + + ksl_.For("dot.outer.tiled", + /*start=*/0, /*end=*/row_limit, /*step=*/tile_rows(), + [&](llvm::Value* row) { EmitOuterLoopBody(row, tile_rows()); }); + + if (row_remainder != 0) { + EmitOuterLoopBody(b_->getInt64(row_limit), row_remainder); + } +} + +void RowMajorMatrixVectorProductEmitter::EmitInnerLoopTiled( + MemoryTile* lhs_memory_tile, int64 rows, + std::vector* vector_accumulators) { + int64 column_limit = k() - (k() % tile_cols()); + + ksl_.For("dot.inner.tiled", /*start=*/0, /*end=*/column_limit, + /*step=*/tile_cols(), [&](llvm::Value* col) { + std::vector lhs_tile = + lhs_memory_tile->LoadTile(/*minor_dim_offset=*/col); + llvm::Value* rhs_value = vsl_.LoadVector(rhs_, col); + for (int i = 0; i < rows; i++) { + llvm::Value* old_sum = (*vector_accumulators)[i].Get(); + (*vector_accumulators)[i].Set( + vsl_.Add(old_sum, vsl_.Mul(rhs_value, lhs_tile[i]))); + } + }); +} + +void RowMajorMatrixVectorProductEmitter::EmitInnerLoopEpilogue( + llvm::Value* current_tile_row, int64 rows, + std::vector* scalar_accumulators) { + int64 column_start = k() - (k() % tile_cols()); + if (column_start == k()) { + return; + } + + for (int r = 0; r < rows; r++) { + llvm::Value* total_offset = b_->CreateMul( + b_->CreateAdd(b_->getInt64(r), current_tile_row), b_->getInt64(k())); + llvm::Value* lhs_base_pointer = + vsl_.ComputeOffsetPointer(lhs_, total_offset); + ksl_.For("dot.inner.epilg.inner", /*start=*/column_start, /*end=*/k(), + /*step=*/1, [&](llvm::Value* scalar_col) { + llvm::Value* product = + vsl_.Mul(vsl_.LoadScalar(lhs_base_pointer, scalar_col), + vsl_.LoadScalar(rhs_, scalar_col)); + llvm::Value* old_value = (*scalar_accumulators)[r].Get(); + (*scalar_accumulators)[r].Set(vsl_.Add(old_value, product)); + }); + } +} + +// This class implements a tiled matrix multiplication algorithm, intended for +// multiplying small matrices that don't need cache tiling. +// +// In the future this can be used as the innermost GEBP loop in a GEMM kernel as +// described in "Goto, Kazushige, and Robert A. Geijn. "Anatomy of +// high-performance matrix multiplication." ACM Transactions on Mathematical +// Software (TOMS) 34.3 (2008): 12.". +// +// This only supports canonical dot operations (i.e. where the lhs contraction +// dimension is 1 and the rhs contraction dimension is 0) over row major +// matrices. +class TiledSmallGemmEmitter { + public: + // Describe the dimensions of the kernel. + class Dimensions { + public: + explicit Dimensions(int64 m, int64 k, int64 n) : m_(m), k_(k), n_(n) {} + + int64 m() const { return m_; } + int64 k() const { return k_; } + int64 n() const { return n_; } + + string ToString() const { return absl::StrCat(m(), "x", k(), "x", n()); } + + private: + const int64 m_; + const int64 k_; + const int64 n_; + }; + + // Represents the configuration of the emitter. The LLVM IR emitted by the + // emitter, modulo the LLVM values holding the input and output buffers, must + // be a function of the instance of `Config` passed to it. + // + // `dims` holds the matrix multiplication dimensions. + // + // `max_vectorization_width` is the maximum vector width (i.e. the width of + // the largest vector register we will use). This can be larger than the + // largest vector register supported by the machine -- LLVM will legalize + // these large vector widths into legally sized vectors. + // + // `max_vector_count` is the maximum number of vectors of size + // `max_vectorization_width` that we will attempt to process at once. + // + // `min_vectorization_width` is the smallest vector width the emitter will use + // -- below that it will devolve to using a scalar loop. + // + // The innermost reduction loop executes the matrix multiply in tiles of size + // [`tile_size_m`, `tile_size_k`] from the LHS and [`tile_size_k`, + // ] in the RHS. + class Config { + public: + explicit Config(PrimitiveType scalar_type, Dimensions dims, + int64 max_vectorization_width, int64 max_vector_count, + int64 min_vectorization_width, int64 tile_size_m, + int64 tile_size_k) + : scalar_type_(scalar_type), + dims_(dims), + max_vectorization_width_(max_vectorization_width), + max_vector_count_(max_vector_count), + min_vectorization_width_(min_vectorization_width), + tile_size_m_(tile_size_m), + tile_size_k_(tile_size_k) {} + + string GetCacheKey() const { + return absl::StrCat("gemm_", PrimitiveType_Name(scalar_type()), "_", + dims().ToString(), "_", max_vectorization_width(), + "_", min_vectorization_width(), "_", tile_size_m(), + "_", tile_size_k()); + } + + PrimitiveType scalar_type() const { return scalar_type_; } + Dimensions dims() const { return dims_; } + int64 max_vectorization_width() const { return max_vectorization_width_; } + int64 max_vector_count() const { return max_vector_count_; } + int64 min_vectorization_width() const { return min_vectorization_width_; } + + int64 tile_size_m() const { return tile_size_m_; } + int64 tile_size_k() const { return tile_size_k_; } + + private: + PrimitiveType scalar_type_; + Dimensions dims_; + int64 max_vectorization_width_; + int64 max_vector_count_; + int64 min_vectorization_width_; + int64 tile_size_m_; + int64 tile_size_k_; + }; + + // Creates an instance of TiledSmallGemmEmitter that matrix-multiplies + // `lhs` with `rhs` and stores the result in `result`. + explicit TiledSmallGemmEmitter(Config config, llvm::Value* lhs, + llvm::Value* rhs, llvm::Value* result, + llvm::IRBuilder<>* b) + : lhs_(lhs), + rhs_(rhs), + result_(result), + config_(config), + b_(b), + ksl_(b_) { + CHECK(max_vectorization_width() > 0 && + IsPowerOfTwo(static_cast(max_vectorization_width()))); + CHECK_GT(max_vector_count(), 0); + CHECK(min_vectorization_width() > 0 && + IsPowerOfTwo(static_cast(min_vectorization_width()))); + CHECK_GE(max_vectorization_width(), min_vectorization_width()); + CHECK_GT(tile_size_k(), 0); + } + + void Emit(); + + private: + // The HandleResiduesOnX helpers split the iteration space for dimension X + // into a multiple of the tile size on dimension X and an epilogue. These + // helpers ultimately call into `EmitTiledGemm` for emitting the + // tiled GEMM kernel. + + void HandleResiduesOnN(); + void HandleResiduesOnK(VectorSupportLibrary* vsl, llvm::Value* n_start, + llvm::Value* n_end); + void HandleResiduesOnM(VectorSupportLibrary* vsl, int64 tile_size_k, + llvm::Value* k_start, llvm::Value* k_end, + llvm::Value* n_start, llvm::Value* n_end); + + // This emits a tiled GEMM kernel. For a detailed description see the comment + // on the implementation. + void EmitTiledGemm(VectorSupportLibrary* vsl, int64 tile_size_k, + llvm::Value* k_start, llvm::Value* k_end, + llvm::Value* n_start, llvm::Value* n_end, + int64 tile_size_m, llvm::Value* m_start, + llvm::Value* m_end); + + llvm::Value* GetInt64(int64 value) { return b_->getInt64(value); } + + Config config() const { return config_; } + Dimensions dims() const { return config().dims(); } + + int64 max_vectorization_width() const { + return config().max_vectorization_width(); + } + int64 max_vector_count() const { return config().max_vector_count(); } + int64 min_vectorization_width() const { + return config().min_vectorization_width(); + } + int64 tile_size_m() const { return config().tile_size_m(); } + int64 tile_size_k() const { return config().tile_size_k(); } + PrimitiveType scalar_type() const { return config().scalar_type(); } + + llvm::Value* lhs_; + llvm::Value* rhs_; + llvm::Value* result_; + Config config_; + + llvm::IRBuilder<>* b_; + KernelSupportLibrary ksl_; +}; + +void TiledSmallGemmEmitter::Emit() { HandleResiduesOnN(); } + +void TiledSmallGemmEmitter::HandleResiduesOnN() { + // We can only iterate the `n` dimension for an extent that is divisible by + // the vectorization width. So we emit an outer loop that first processes the + // largest extent in `n` that is divisible by max_vectorization_width, then + // the largest remaining extent that is divisible by max_vectorization_width / + // 2 etc. + + int64 current_vectorization_width = + max_vector_count() * max_vectorization_width(); + int64 current_vector_count = max_vector_count(); + + int64 n_start = 0; + while (n_start != dims().n() && + current_vectorization_width >= min_vectorization_width()) { + int64 n_end = dims().n() - (dims().n() % current_vectorization_width); + if (n_start != n_end) { + VectorSupportLibrary vsl(scalar_type(), current_vectorization_width, b_, + "gemm"); + HandleResiduesOnK(&vsl, GetInt64(n_start), GetInt64(n_end)); + n_start = n_end; + } + if (current_vector_count == 1) { + current_vectorization_width /= 2; + } else { + current_vector_count--; + current_vectorization_width = + current_vector_count * max_vectorization_width(); + } + } + + if (n_start != dims().n()) { + VectorSupportLibrary vsl(scalar_type(), 1, b_, "gemm"); + ksl_.For("epi.n", n_start, dims().n(), 1, [&](llvm::Value* n_i) { + llvm::Value* n_i_next = b_->CreateAdd(n_i, b_->getInt64(1)); + HandleResiduesOnK(&vsl, n_i, n_i_next); + }); + } +} + +void TiledSmallGemmEmitter::HandleResiduesOnK(VectorSupportLibrary* vsl, + llvm::Value* n_start, + llvm::Value* n_end) { + int64 k_start = 0; + int64 k_end = dims().k() - (dims().k() % tile_size_k()); + if (k_end != k_start) { + HandleResiduesOnM(vsl, tile_size_k(), GetInt64(k_start), GetInt64(k_end), + n_start, n_end); + k_start = k_end; + } + + if (k_start != dims().k()) { + HandleResiduesOnM(vsl, dims().k() - k_start, GetInt64(k_start), + GetInt64(dims().k()), n_start, n_end); + } +} + +void TiledSmallGemmEmitter::HandleResiduesOnM( + VectorSupportLibrary* vsl, int64 tile_size_k, llvm::Value* k_start, + llvm::Value* k_end, llvm::Value* n_start, llvm::Value* n_end) { + const int64 m_end = dims().m() - dims().m() % tile_size_m(); + EmitTiledGemm(vsl, tile_size_k, k_start, k_end, n_start, n_end, tile_size_m(), + GetInt64(0), GetInt64(m_end)); + + if (m_end != dims().m()) { + EmitTiledGemm(vsl, tile_size_k, k_start, k_end, n_start, n_end, + dims().m() - m_end, GetInt64(m_end), GetInt64(dims().m())); + } +} + +// The loop structure is: +// +// Iterate over dimension M as m: +// Iterate over dimension N as n: +// Iterate over dimension K as k: +// OutputTile[m,n] += Dot(LhsTile[m,k], RhsTile[k,n]) +// +// I.e. a just a tiled version of a "naive" GEMM. +// +// The tiling scheme is as follows: +// +// Let the LHS be: +// +// +----+----+----+ +// | a0 | b0 | c0 | . +// +----+----+----+ . +// | a1 | b1 | c1 | . +// +----+----+----+ +// .. .. +// +// and the RHS be: +// +// +----+----+----+----+ +// | p0 | p1 | p2 | p3 | . +// +----+----+----+----+ . +// | q0 | q1 | q2 | q3 | . +// +----+----+----+----+ +// | r0 | r1 | r2 | r3 | . +// +----+----+----+----+ . +// ...... ...... +// +// and let tile_size_m=2, tile_size_k=3 and the vector width (implicitly denoted +// by `vsl`) be 4. Then we want to matrix multiply this tile to get a [2,4] +// matrix that we can increment the result matrix by. +// +// First broadcast the rows row in LHS to 3 vectors of width 4, giving us a rank +// 3 array, L, of dimension [2,3,4]: +// +// L[0,_,_] * L[1,_,_] +// * +// +----+----+----+----+ * +----+----+----+----+ +// | a0 | a0 | a0 | a0 | * | a1 | a1 | a1 | a1 | +// +----+----+----+----+ * +----+----+----+----+ +// | b0 | b0 | b0 | b0 | * | b1 | b1 | b1 | b1 | +// +----+----+----+----+ * +----+----+----+----+ +// | c0 | c0 | c0 | c0 | * | c1 | c1 | c1 | c1 | +// +----+----+----+----+ * +----+----+----+----+ +// +// +// Then we FMA L[0,_,_] with the RHS to get the first row of the result and +// L[1,_,_] with the RHS to get the second row of the result. For example, +// L[0,_,_] is computed as: +// +// +----+----+----+----+ +----+----+----+----+ +// | a0 | a0 | a0 | a0 | * | p0 | p1 | p2 | p3 | + +// +----+----+----+----+ +----+----+----+----+ +// +// +----+----+----+----+ +----+----+----+----+ +// | b0 | b0 | b0 | b0 | * | q0 | q1 | q2 | q3 | + +// +----+----+----+----+ +----+----+----+----+ +// +// +----+----+----+----+ +----+----+----+----+ +// | c0 | c0 | c0 | c0 | * | r0 | r1 | r2 | r3 | +// +----+----+----+----+ +----+----+----+----+ +// +// to get: +// +// +-------------------+-------------------+-------------------+--------- +// | a0*p0+b0*q0+c0*r0 | a0*p1+b0*q1+c0*r1 | a0*p2+b0*q2+c0*r2 | ... +// +-------------------+-------------------+-------------------+--------- +void TiledSmallGemmEmitter::EmitTiledGemm( + VectorSupportLibrary* vsl, int64 tile_size_k, llvm::Value* k_start, + llvm::Value* k_end, llvm::Value* n_start, llvm::Value* n_end, + int64 tile_size_m, llvm::Value* m_start, llvm::Value* m_end) { + ksl_.For("dot.m", m_start, m_end, tile_size_m, [&](llvm::Value* m_i) { + MemoryTile result_memory_tile(vsl, b_, /*matrix=*/result_, + /*matrix_size_along_minor_dim=*/dims().n(), + /*major_dim_offset=*/m_i, + /*tile_size_along_major_dim=*/tile_size_m); + MemoryTile lhs_memory_tile(vsl, b_, /*matrix=*/lhs_, + /*matrix_size_along_minor_dim=*/dims().k(), + /*major_dim_offset=*/m_i, + /*tile_size_along_major_dim=*/tile_size_m); + ksl_.For( + "dot.n", n_start, n_end, vsl->vector_size(), [&](llvm::Value* n_i) { + TileVariable result_tile_var(vsl, result_memory_tile.LoadTile(n_i)); + ksl_.For("dot.k", k_start, k_end, tile_size_k, [&](llvm::Value* k_i) { + MemoryTile rhs_memory_tile(vsl, b_, rhs_, dims().n(), k_i, + tile_size_k); + std::vector> lhs_tile = + lhs_memory_tile.LoadBroadcastTile(k_i, tile_size_k); + std::vector rhs_tile = rhs_memory_tile.LoadTile(n_i); + std::vector result_tile = result_tile_var.Get(); + for (int64 r_m_i = 0; r_m_i < tile_size_m; r_m_i++) { + for (int64 r_k_i = 0; r_k_i < tile_size_k; r_k_i++) { + result_tile[r_m_i] = + vsl->MulAdd(lhs_tile[r_m_i][r_k_i], rhs_tile[r_k_i], + result_tile[r_m_i]); + } + } + result_tile_var.Set(result_tile); + }); + + result_memory_tile.StoreTile(result_tile_var.Get(), n_i); + }); + }); +} + +} // namespace + +void EmitRowMajorGemv(PrimitiveType scalar_type, int64 tile_rows, + int64 tile_cols, int64 m, int64 k, llvm::Value* lhs, + llvm::Value* rhs, llvm::Value* addend, + llvm::Value* result, llvm::IRBuilder<>* b, + bool enable_fast_math, bool optimize_for_size) { + RowMajorMatrixVectorProductEmitter::Config config( + /*scalar_type=*/scalar_type, + /*tile_rows=*/tile_rows, /*tile_cols=*/tile_cols, + /*m=*/m, /*k=*/k, /*has_addend=*/addend != nullptr); + + KernelSupportLibrary::EmitAndCallOutlinedKernel( + /*enable_fast_math=*/enable_fast_math, + /*optimize_for_size=*/optimize_for_size, b, config.GetCacheKey(), lhs, + rhs, addend, result, + [&](llvm::Value* lhs, llvm::Value* rhs, llvm::Value* addend, + llvm::Value* result) { + RowMajorMatrixVectorProductEmitter emitter(config, lhs, rhs, addend, + result, b); + emitter.Emit(); + }); +} + +void EmitColumnMajorGemv(PrimitiveType scalar_type, int64 tile_rows, + int64 tile_cols, int64 m, int64 k, llvm::Value* lhs, + llvm::Value* rhs, llvm::Value* addend, + llvm::Value* result, llvm::IRBuilder<>* b, + bool enable_fast_math, bool optimize_for_size) { + ColumnMajorMatrixVectorProductEmitter::Config config( + /*scalar_type=*/scalar_type, + /*tile_rows=*/tile_rows, /*tile_cols=*/tile_cols, + /*m=*/m, /*k=*/k, /*has_addend=*/addend != nullptr); + + KernelSupportLibrary::EmitAndCallOutlinedKernel( + /*enable_fast_math=*/enable_fast_math, + /*optimize_for_size=*/optimize_for_size, b, config.GetCacheKey(), lhs, + rhs, addend, result, + [&](llvm::Value* lhs, llvm::Value* rhs, llvm::Value* addend, + llvm::Value* result) { + ColumnMajorMatrixVectorProductEmitter emitter(config, lhs, rhs, addend, + result, b); + emitter.Emit(); + }); +} + +void EmitSmallGemm(PrimitiveType scalar_type, int64 m, int64 k, int64 n, + int64 max_vectorization_width, int64 max_vector_count, + int64 min_vectorization_width, int64 tile_size_m, + int64 tile_size_k, llvm::Value* lhs, llvm::Value* rhs, + llvm::Value* result, llvm::IRBuilder<>* b, + bool enable_fast_math, bool optimize_for_size) { + TiledSmallGemmEmitter::Config config( + /*scalar_type=*/scalar_type, + TiledSmallGemmEmitter::Dimensions{/*m=*/m, /*k=*/k, /*n=*/n}, + /*max_vectorization_width=*/max_vectorization_width, + /*max_vector_count=*/max_vector_count, + /*min_vectorization_width=*/min_vectorization_width, + /*tile_size_m=*/tile_size_m, /*tile_size_k=*/tile_size_k); + + KernelSupportLibrary::EmitAndCallOutlinedKernel( + /*enable_fast_math=*/enable_fast_math, + /*optimize_for_size=*/optimize_for_size, b, config.GetCacheKey(), lhs, + rhs, result, + [&](llvm::Value* lhs, llvm::Value* rhs, llvm::Value* result) { + TiledSmallGemmEmitter small_gemm_emitter(config, /*lhs=*/lhs, + /*rhs=*/rhs, + /*result=*/result, b); + small_gemm_emitter.Emit(); + }); +} + +} // namespace cpu +} // namespace xla diff --git a/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.h b/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.h new file mode 100644 index 0000000000..0a82326cc3 --- /dev/null +++ b/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.h @@ -0,0 +1,55 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_COMPILER_XLA_SERVICE_CPU_TILED_DOT_EMITTER_H_ +#define TENSORFLOW_COMPILER_XLA_SERVICE_CPU_TILED_DOT_EMITTER_H_ + +#include "llvm/IR/IRBuilder.h" +#include "tensorflow/compiler/xla/xla_data.pb.h" +#include "tensorflow/core/platform/types.h" + +namespace xla { +namespace cpu { + +// These routines emit LLVM IR implementing tiled GEMM and GEMV routines. + +void EmitRowMajorGemv(PrimitiveType scalar_type, tensorflow::int64 tile_rows, + tensorflow::int64 tile_cols, tensorflow::int64 m, + tensorflow::int64 k, llvm::Value* lhs, llvm::Value* rhs, + llvm::Value* addend, llvm::Value* result, + llvm::IRBuilder<>* b, bool enable_fast_math, + bool optimize_for_size); + +void EmitColumnMajorGemv(PrimitiveType scalar_type, tensorflow::int64 tile_rows, + tensorflow::int64 tile_cols, tensorflow::int64 m, + tensorflow::int64 k, llvm::Value* lhs, + llvm::Value* rhs, llvm::Value* addend, + llvm::Value* result, llvm::IRBuilder<>* b, + bool enable_fast_math, bool optimize_for_size); + +void EmitSmallGemm(PrimitiveType scalar_type, tensorflow::int64 m, + tensorflow::int64 k, tensorflow::int64 n, + tensorflow::int64 max_vectorization_width, + tensorflow::int64 max_vector_count, + tensorflow::int64 min_vectorization_width, + tensorflow::int64 tile_size_m, tensorflow::int64 tile_size_k, + llvm::Value* lhs, llvm::Value* rhs, llvm::Value* result, + llvm::IRBuilder<>* b, bool enable_fast_math, + bool optimize_for_size); + +} // namespace cpu +} // namespace xla + +#endif // TENSORFLOW_COMPILER_XLA_SERVICE_CPU_TILED_DOT_EMITTER_H_ -- GitLab From 0ebce89605326c602b3dd4d8cfe3f3a29284b390 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 14:39:51 -0800 Subject: [PATCH 0290/2345] Optimize test combinations to prevent timeout during testing. PiperOrigin-RevId: 228238454 --- tensorflow/contrib/distribute/python/BUILD | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index de6b6f1f84..7ee12812af 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -674,7 +674,9 @@ cuda_py_test( additional_deps = [ ":keras_correctness_test_lib", ], - shard_count = 16, + # Shard count is set to an odd number to distribute tasks across + # shards more evenly. + shard_count = 19, tags = [ "multi_and_single_gpu", "no_oss", # TODO(b/117919883): Fix python error. -- GitLab From 67a7f04d010c50fb3d6d1bde7793d3f8d350decf Mon Sep 17 00:00:00 2001 From: Russell Power Date: Mon, 7 Jan 2019 14:43:32 -0800 Subject: [PATCH 0291/2345] Graph partitioning: use a sensible hash function for device memory info. PiperOrigin-RevId: 228239154 --- tensorflow/core/BUILD | 2 ++ tensorflow/core/graph/graph_partition.cc | 44 +++++++++--------------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 5e6c8d4dd8..e6af9211b5 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -1750,6 +1750,7 @@ cc_library( cc_library( name = "mobile_additional_lib_deps", deps = tf_additional_lib_deps() + [ + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", ], @@ -2817,6 +2818,7 @@ tf_cuda_library( ":proto_text", ":protos_all_cc", "//third_party/eigen3", + "@com_google_absl//absl/container:flat_hash_map", ], ) diff --git a/tensorflow/core/graph/graph_partition.cc b/tensorflow/core/graph/graph_partition.cc index f213eb7c10..be0cac50a1 100644 --- a/tensorflow/core/graph/graph_partition.cc +++ b/tensorflow/core/graph/graph_partition.cc @@ -22,6 +22,7 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_map.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/memory_types.h" #include "tensorflow/core/framework/node_def_builder.h" @@ -58,29 +59,24 @@ struct DupRecvKey { int src_output_slot; // Edge's src node output slot GraphDef* dst_graph; // Edge's dst node is in this subgraph bool recv_output_on_host; // The output of recv is on host -}; -struct DupRecvKeyHash { - size_t operator()(const DupRecvKey& k) const { - size_t h = Hash64(reinterpret_cast(&k.src_node_id), - sizeof(k.src_node_id), k.src_output_slot); - h = Hash64(reinterpret_cast(&k.dst_graph), sizeof(k.dst_graph), - h); - h = Hash64(reinterpret_cast(&k.recv_output_on_host), - sizeof(k.recv_output_on_host), h); - return h; + template + friend H AbslHashValue(H h, const DupRecvKey& c) { + return H::combine(std::move(h), c.src_node_id, c.src_output_slot, + reinterpret_cast(c.dst_graph), + c.recv_output_on_host); } -}; -struct DupRecvKeyEq { - bool operator()(const DupRecvKey& x, const DupRecvKey& y) const { - return (x.src_node_id == y.src_node_id) && - (x.src_output_slot == y.src_output_slot) && - (x.dst_graph == y.dst_graph) && - (x.recv_output_on_host == y.recv_output_on_host); - } + friend bool operator==(const DupRecvKey& x, const DupRecvKey& y); }; +bool operator==(const DupRecvKey& x, const DupRecvKey& y) { + return (x.src_node_id == y.src_node_id) && + (x.src_output_slot == y.src_output_slot) && + (x.dst_graph == y.dst_graph) && + (x.recv_output_on_host == y.recv_output_on_host); +} + // struct used to store the recvs, so that start times can be properly updated struct RecvInfo { NodeDef* recv; @@ -88,19 +84,11 @@ struct RecvInfo { int64 start_time; }; -typedef std::unordered_map - DupRecvTable; +typedef absl::flat_hash_map DupRecvTable; -struct PairIntHash { - public: - std::size_t operator()(const std::pair& x) const { - return std::hash()(x.first) ^ std::hash()(x.second); - } -}; // A map used to store memory types for the inputs/outputs of every node. // The key is a pair of ints consisting of a node id and input/output index. -typedef std::unordered_map, MemoryType, PairIntHash> - MemoryTypeMap; +typedef absl::flat_hash_map, MemoryType> MemoryTypeMap; // We collect the following information about the graph before performing // graph partitioning. -- GitLab From 106b3c183546d2f4bde8c209ef9d01ba0fb2904b Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Mon, 7 Jan 2019 14:55:46 -0800 Subject: [PATCH 0292/2345] [tf.data] Implementation of a "standalone" C++ API that encapsulates TensorFlow runtime internals. The new API facilitates efficient execution of a dataset input pipeline (represented as a GraphDef) in C++. PiperOrigin-RevId: 228241484 --- tensorflow/core/common_runtime/data/BUILD | 35 ++++ .../core/common_runtime/data/standalone.cc | 128 ++++++++++++ .../core/common_runtime/data/standalone.h | 122 ++++++++++++ .../common_runtime/data/standalone_test.cc | 188 ++++++++++++++++++ 4 files changed, 473 insertions(+) create mode 100644 tensorflow/core/common_runtime/data/BUILD create mode 100644 tensorflow/core/common_runtime/data/standalone.cc create mode 100644 tensorflow/core/common_runtime/data/standalone.h create mode 100644 tensorflow/core/common_runtime/data/standalone_test.cc diff --git a/tensorflow/core/common_runtime/data/BUILD b/tensorflow/core/common_runtime/data/BUILD new file mode 100644 index 0000000000..124862dbb7 --- /dev/null +++ b/tensorflow/core/common_runtime/data/BUILD @@ -0,0 +1,35 @@ +package( + default_visibility = [ + "//tensorflow:internal", + "//tensorflow_models:__subpackages__", + ], +) + +licenses(["notice"]) # Apache 2.0 + +load("//tensorflow:tensorflow.bzl", "tf_cc_test") +load("//tensorflow/core:platform/default/build_config.bzl", "tf_protos_all") + +cc_library( + name = "standalone", + srcs = ["standalone.cc"], + hdrs = ["standalone.h"], + visibility = ["//visibility:public"], + deps = [ + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:session_options", + ], +) + +tf_cc_test( + name = "standalone_test", + srcs = ["standalone_test.cc"], + deps = [ + ":standalone", + "//tensorflow/core:all_kernels", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + ] + tf_protos_all(), +) diff --git a/tensorflow/core/common_runtime/data/standalone.cc b/tensorflow/core/common_runtime/data/standalone.cc new file mode 100644 index 0000000000..b05bff566f --- /dev/null +++ b/tensorflow/core/common_runtime/data/standalone.cc @@ -0,0 +1,128 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/common_runtime/data/standalone.h" + +#include + +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/device_mgr.h" +#include "tensorflow/core/common_runtime/function.h" +#include "tensorflow/core/common_runtime/graph_runner.h" +#include "tensorflow/core/common_runtime/process_util.h" +#include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/graph/graph.h" +#include "tensorflow/core/graph/graph_constructor.h" +#include "tensorflow/core/public/version.h" +#include "tensorflow/core/util/ptr_util.h" + +namespace tensorflow { +namespace data { +namespace standalone { + +Status Iterator::GetNext(std::vector* outputs, bool* end_of_input) { + return iterator_->GetNext(ctx_.get(), outputs, end_of_input); +} + +Iterator::Iterator(IteratorBase* iterator, IteratorContext* ctx) + : iterator_(iterator), ctx_(ctx) {} + +Status Dataset::FromGraph(Params params, const GraphDef& graph_def, + const string& fetch_node, + std::unique_ptr* result) { + Graph graph(OpRegistry::Global()); + TF_RETURN_IF_ERROR(ImportGraphDef({}, graph_def, &graph, nullptr)); + + // Instantiate enough of the TensorFlow runtime to run `graph` on a single CPU + // device. + std::unique_ptr device_mgr = MakeUnique( + DeviceFactory::NewDevice("CPU", params.session_options, "")); + Device* device = device_mgr->ListDevices()[0]; + // Clone the `FunctionLibraryDefinition` to extend its lifetime extends beyond + // the lifetime of `graph`. + std::unique_ptr flib_def = + MakeUnique(graph.flib_def()); + std::unique_ptr pflr = + MakeUnique( + device_mgr.get(), Env::Default(), TF_GRAPH_DEF_VERSION, + flib_def.get(), OptimizerOptions{}, nullptr /* parent */); + + // Run graph up to `output_node` and extract the `DatasetBase` stored in the + // DT_VARIANT output tensor. + data::DatasetBase* dataset; + { + std::vector outputs; + GraphRunner graph_runner(device); + TF_RETURN_IF_ERROR(graph_runner.Run(&graph, pflr->GetFLR("/device:CPU:0"), + {}, {fetch_node}, &outputs)); + TF_RETURN_IF_ERROR(GetDatasetFromVariantTensor(outputs[0], &dataset)); + // NOTE(mrry): The dataset is currently owned by `outputs[0]`, so acquire an + // additional reference. + dataset->Ref(); + } + + std::unique_ptr pool( + NewThreadPoolFromSessionOptions(params.session_options)); + *result = + WrapUnique(new Dataset(dataset, device_mgr.release(), pflr.release(), + flib_def.release(), pool.release())); + return Status::OK(); +} // static + +Status Dataset::MakeIterator(std::unique_ptr* result) { + // Create an `IteratorContext`, which bundles together the necessary runtime + // support to create and get elements from an iterator. + std::unique_ptr ctx; + { + // NOTE(mrry): In the current API, an `IteratorContext` is always initially + // created from an `OpKernelContext*`, so we need to create a fake + // `OpKernelContext` with the appropriate subset of parameters. + OpKernelContext::Params op_params; + op_params.function_library = pflr_->GetFLR("/device:CPU:0"); + op_params.device = device_mgr_->ListDevices()[0]; + op_params.runner = &runner_; + OpKernelContext op_ctx(&op_params, 0); + IteratorContext::Params params(&op_ctx); + params.function_handle_cache = function_handle_cache_.get(); + ctx = MakeUnique(std::move(params)); + } + + // Create the iterator from the dataset. + std::unique_ptr iterator; + TF_RETURN_IF_ERROR(dataset_->MakeIterator(ctx.get(), "iterator", &iterator)); + + *result = WrapUnique(new Iterator(iterator.release(), ctx.release())); + + return Status::OK(); +} + +Dataset::Dataset(DatasetBase* dataset, DeviceMgr* device_mgr, + ProcessFunctionLibraryRuntime* pflr, + FunctionLibraryDefinition* flib_def, thread::ThreadPool* pool) + : dataset_(dataset), + device_mgr_(device_mgr), + flib_def_(flib_def), + pflr_(pflr), + pool_(pool) { + runner_ = [this](std::function c) { pool_->Schedule(std::move(c)); }; + function_handle_cache_ = + MakeUnique(pflr_->GetFLR("/device:CPU:0")); +} + +Dataset::~Dataset() { dataset_->Unref(); } + +} // namespace standalone +} // namespace data +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/data/standalone.h b/tensorflow/core/common_runtime/data/standalone.h new file mode 100644 index 0000000000..ecea5ba21d --- /dev/null +++ b/tensorflow/core/common_runtime/data/standalone.h @@ -0,0 +1,122 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_DATA_STANDALONE_H_ +#define TENSORFLOW_CORE_COMMON_RUNTIME_DATA_STANDALONE_H_ + +#include +#include "tensorflow/core/common_runtime/device_mgr.h" +#include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/framework/function_handle_cache.h" +#include "tensorflow/core/lib/core/threadpool.h" +#include "tensorflow/core/public/session_options.h" + +namespace tensorflow { +namespace data { +namespace standalone { + +// The purpose of the API in this file is to facilitate standalone execution of +// a tf.data input pipeline graph. +// +// The API exposes two abstractions -- a `Dataset` and an `Iterator` -- which +// encapsulate TensorFlow runtime. +// +// The `Dataset` abstraction represents an input pipeline as a collection +// of data sources and a logical plan of transformations that operate over the +// data. +// +// The `Iterator` abstraction represents an execution of an input pipeline that +// can be used to enumerate its elements. +// +// Example usage: +// +// // Create a `Dataset` by running the `graph_def` graph and fetching the +// // output of the `fetch_node` node. +// tensorflow::data:standalone::Dataset::Params params; +// std::unique_ptr dataset; +// Status s = tensorflow::data::standalone::Dataset::FromGraph( +// params, graph_def, fetch_node, &dataset); +// if (!s.ok()) { /* error handling */ } +// +// std::unique_ptr iterator; +// s = dataset->MakeIterator(&iterator); +// if (!s.ok()) { /* error handling */ } +// +// bool end_of_input = false; +// while (!end_of_input) { +// std::vector outputs; +// s = iterator->GetNext(&outputs, &end_of_input); +// if (!s.ok()) { /* error handling */ } +// if (!end_of_input) { /* output handling */ } +// } + +class Dataset; + +// Represents an execution of an input pipeline that can be used to enumerate +// its elements. +class Iterator { + public: + // Returns the next element of the input pipeline (if there is one) and an + // indication of whether the end of the input pipeline has been reached. + Status GetNext(std::vector* outputs, bool* end_of_input); + + private: + friend class Dataset; + + Iterator(IteratorBase* iterator, IteratorContext* ctx); + + std::unique_ptr iterator_; + std::unique_ptr ctx_; +}; + +// Represents an input pipeline as a collection of data sources and a logical +// plan of transformations that operate over the data. +class Dataset { + public: + // Parameters for `Dataset` creation (e.g. TensorFlow runtime configuration). + struct Params { + SessionOptions session_options; + }; + + // Creates a new `Dataset` instance by running the TensorFlow graph `graph` + // and fetching the output of the `fetch_node` node. + static Status FromGraph(Params params, const GraphDef& graph_def, + const string& fetch_node, + std::unique_ptr* result); + + ~Dataset(); + + // Creates an iterator for this dataset. + Status MakeIterator(std::unique_ptr* result); + + private: + Dataset(DatasetBase* dataset, DeviceMgr* device_mgr, + ProcessFunctionLibraryRuntime* pflr, + FunctionLibraryDefinition* flib_def, thread::ThreadPool* pool); + + DatasetBase* dataset_; // owned + std::unique_ptr device_mgr_; + std::unique_ptr flib_def_; + std::unique_ptr pflr_; + std::unique_ptr pool_; + std::unique_ptr function_handle_cache_; + std::function)> runner_; +}; + +} // namespace standalone +} // namespace data +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_COMMON_RUNTIME_DATA_STANDALONE_H_ diff --git a/tensorflow/core/common_runtime/data/standalone_test.cc b/tensorflow/core/common_runtime/data/standalone_test.cc new file mode 100644 index 0000000000..7e7a7a9b61 --- /dev/null +++ b/tensorflow/core/common_runtime/data/standalone_test.cc @@ -0,0 +1,188 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include +#include + +#include "tensorflow/core/common_runtime/data/standalone.h" +#include "tensorflow/core/framework/graph.pb.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test.h" + +namespace tensorflow { +namespace data { +namespace standalone { +namespace { + +constexpr const char* const kGraphProto = R"proto( + node { + name: "Const/_0" + op: "Const" + attr { + key: "dtype" + value { type: DT_INT64 } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape {} + int64_val: 0 + } + } + } + } + node { + name: "Const/_1" + op: "Const" + attr { + key: "dtype" + value { type: DT_INT64 } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape {} + int64_val: 10 + } + } + } + } + node { + name: "Const/_2" + op: "Const" + attr { + key: "dtype" + value { type: DT_INT64 } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape {} + int64_val: 1 + } + } + } + } + node { + name: "RangeDataset/_3" + op: "RangeDataset" + input: "Const/_0" + input: "Const/_1" + input: "Const/_2" + attr { + key: "output_shapes" + value { list { shape { unknown_rank: true } } } + } + attr { + key: "output_types" + value { list { type: DT_INT64 } } + } + } + node { + name: "MapDataset/_4" + op: "MapDataset" + input: "RangeDataset/_3" + attr { + key: "Targuments" + value { list {} } + } + attr { + key: "f" + value { func { name: "Dataset_map__10" } } + } + attr { + key: "output_shapes" + value { list { shape {} } } + } + attr { + key: "output_types" + value { list { type: DT_INT64 } } + } + attr { + key: "preserve_cardinality" + value { b: false } + } + attr { + key: "use_inter_op_parallelism" + value { b: true } + } + } + library { + function { + signature { + name: "Dataset_map__10" + input_arg { name: "arg0" type: DT_INT64 } + output_arg { name: "mul" type: DT_INT64 } + description: "Wrapper for passing nested structures to and from tf.data functions." + } + node_def { + name: "mul_0" + op: "Mul" + input: "arg0" + input: "arg0" + attr { + key: "T" + value { type: DT_INT64 } + } + } + ret { key: "mul" value: "mul_0:z:0" } + } + } + versions { producer: 27 min_consumer: 12 } +)proto"; + +TEST(Scalar, Standalone) { + GraphDef graph_def; + protobuf::TextFormat::ParseFromString(kGraphProto, &graph_def); + struct TestCase { + string fetch_node; + std::vector expected_outputs; + }; + auto test_cases = { + TestCase{"RangeDataset/_3", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}, + TestCase{"MapDataset/_4", {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}}, + }; + for (auto test_case : test_cases) { + std::unique_ptr dataset; + auto s = Dataset::FromGraph({}, graph_def, test_case.fetch_node, &dataset); + TF_EXPECT_OK(s); + std::unique_ptr iterator; + s = dataset->MakeIterator(&iterator); + TF_EXPECT_OK(s); + bool end_of_input = false; + for (int num_outputs = 0; !end_of_input; ++num_outputs) { + std::vector outputs; + s = iterator->GetNext(&outputs, &end_of_input); + TF_EXPECT_OK(s); + if (!end_of_input) { + EXPECT_EQ(outputs[0].scalar()(), + test_case.expected_outputs[num_outputs]); + } else { + EXPECT_EQ(test_case.expected_outputs.size(), num_outputs); + } + } + } +} + +} // namespace +} // namespace standalone +} // namespace data +} // namespace tensorflow -- GitLab From 17efadd4556c242efaa68b30134d3e79d2412922 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 14:57:23 -0800 Subject: [PATCH 0293/2345] Internal copybara changes. PiperOrigin-RevId: 228241768 --- tensorflow/lite/experimental/swift/BUILD | 1 + .../Configs/TensorFlowLite.tulsigen | 20 +++++++++---------- .../project.tulsiconf | 4 ++-- .../Base.lproj/Main.storyboard | 6 +++--- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tensorflow/lite/experimental/swift/BUILD b/tensorflow/lite/experimental/swift/BUILD index 3bd288a1e1..53bcb0ecbd 100644 --- a/tensorflow/lite/experimental/swift/BUILD +++ b/tensorflow/lite/experimental/swift/BUILD @@ -83,6 +83,7 @@ ios_application( swift_library( name = "TensorFlowLiteAppLib", srcs = glob(["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/*.swift"]), + module_name = "TensorFlowLiteAppLib", tags = ["manual"], deps = [ ":TensorFlowLite", diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen index 4010fab49e..16bc6cbfe8 100644 --- a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen +++ b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen @@ -1,16 +1,16 @@ { "sourceFilters" : [ - "third_party/tensorflow/lite/experimental/c", - "third_party/tensorflow/lite/experimental/swift", - "third_party/tensorflow/lite/experimental/swift/Sources", - "third_party/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp", - "third_party/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj", - "third_party/tensorflow/lite/experimental/swift/Tests", + "tensorflow/lite/experimental/c", + "tensorflow/lite/experimental/swift", + "tensorflow/lite/experimental/swift/Sources", + "tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp", + "tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj", + "tensorflow/lite/experimental/swift/Tests", ], "buildTargets" : [ - "//third_party/tensorflow/lite/experimental/swift:TensorFlowLite", - "//third_party/tensorflow/lite/experimental/swift:TensorFlowLiteApp", - "//third_party/tensorflow/lite/experimental/swift:TensorFlowLiteTests", + "//tensorflow/lite/experimental/swift:TensorFlowLite", + "//tensorflow/lite/experimental/swift:TensorFlowLiteApp", + "//tensorflow/lite/experimental/swift:TensorFlowLiteTests", ], "projectName" : "TensorFlowLite", "optionSet" : { @@ -52,6 +52,6 @@ } }, "additionalFilePaths" : [ - "third_party/tensorflow/lite/experimental/swift/BUILD" + "tensorflow/lite/experimental/swift/BUILD" ] } diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf index 14cff94453..82ac8aa381 100644 --- a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf +++ b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf @@ -8,7 +8,7 @@ }, "projectName" : "TensorFlowLite", "packages" : [ - "third_party/tensorflow/lite/experimental/swift" + "tensorflow/lite/experimental/swift" ], - "workspaceRoot" : "../../../../../.." + "workspaceRoot" : "../../../../.." } diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard index d0e91d63c4..10cae6e855 100644 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard +++ b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard @@ -1,17 +1,17 @@ - + - + - + -- GitLab From a0dfedcca7c766404735720b9d675e1a04f40350 Mon Sep 17 00:00:00 2001 From: Yunxing Dai Date: Mon, 7 Jan 2019 15:02:16 -0800 Subject: [PATCH 0294/2345] [XLA] Use DynamicDimensionInference in HloEvaluator - Use DynamicDimensionInference in HloEvaluator so that it can correctly evaluate GetDimensionSize. - Change ComputeConstant to first calculate DynamicDimensionInference and pass it into evaluator. PiperOrigin-RevId: 228242693 --- tensorflow/compiler/xla/service/BUILD | 2 + .../compiler/xla/service/hlo_evaluator.cc | 34 ++++++++++++++++ .../compiler/xla/service/hlo_evaluator.h | 12 ++++++ .../xla/service/hlo_evaluator_test.cc | 40 +++++++++++++++++-- tensorflow/compiler/xla/service/service.cc | 5 +++ 5 files changed, 89 insertions(+), 4 deletions(-) diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index 3c18c450e6..9bc6218f75 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -236,6 +236,7 @@ cc_library( ], hdrs = ["hlo_evaluator.h"], deps = [ + ":dynamic_dimension_inference", ":hlo", ":hlo_casting_utils", ":hlo_query", @@ -697,6 +698,7 @@ cc_library( ":compiler", ":computation_layout", ":device_memory_allocator", + ":dynamic_dimension_inference", ":executable", ":execution_tracker", ":hlo", diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.cc b/tensorflow/compiler/xla/service/hlo_evaluator.cc index 3a97b56c66..b8d95fb244 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.cc +++ b/tensorflow/compiler/xla/service/hlo_evaluator.cc @@ -361,6 +361,31 @@ Status HloEvaluator::HandleBitcast(HloInstruction* bitcast) { return Status::OK(); } +Status HloEvaluator::HandleGetDimensionSize( + HloInstruction* get_dimension_size) { + HloInstruction* operand = get_dimension_size->mutable_operand(0); + int64 dim = get_dimension_size->dimension(); + if (dynamic_dimension_inference_ == nullptr) { + return InvalidArgument( + "Evaluator cannot evaluate get_dimension_size without " + "set_dynamic_dimension_inference."); + } + HloInstruction* dynamic_size = + dynamic_dimension_inference_->GetDynamicSize(operand, {}, dim); + if (dynamic_size != nullptr) { + evaluated_[get_dimension_size] = + GetEvaluatedLiteralFor(dynamic_size).Clone(); + return Status::OK(); + } + + const Shape& shape = get_dimension_size->operand(0)->shape(); + Literal output(ShapeUtil::MakeShape(U32, {})); + output.PopulateWithValue( + static_cast(shape.dimensions(get_dimension_size->dimension()))); + evaluated_[get_dimension_size] = std::move(output); + return Status::OK(); +} + Status HloEvaluator::HandleParameter(HloInstruction* parameter) { CHECK_LT(parameter->parameter_number(), arg_literals_.size()); const Literal* input_literal = arg_literals_[parameter->parameter_number()]; @@ -1080,6 +1105,8 @@ Status HloEvaluator::HandleCall(HloInstruction* call) { } HloEvaluator embedded_evaluator; + embedded_evaluator.set_dynamic_dimension_inference( + dynamic_dimension_inference_); Literal result = embedded_evaluator.Evaluate(*computation, arg_literals) .ConsumeValueOrDie(); @@ -1113,6 +1140,8 @@ Status HloEvaluator::HandleFusion(HloInstruction* fusion) { } HloEvaluator embedded_evaluator; + embedded_evaluator.set_dynamic_dimension_inference( + dynamic_dimension_inference_); Literal result = embedded_evaluator.Evaluate(*readded_computation, arg_literals) .ConsumeValueOrDie(); @@ -1132,6 +1161,8 @@ Status HloEvaluator::HandleConditional(HloInstruction* conditional) { auto* false_computation = conditional->false_computation(); HloEvaluator embedded_evaluator; + embedded_evaluator.set_dynamic_dimension_inference( + dynamic_dimension_inference_); Literal result; if (pred.Get({})) { result = @@ -1186,7 +1217,10 @@ Status HloEvaluator::HandleWhile(HloInstruction* while_hlo) { bool keep_going = true; int64 iteration_count = 0; HloEvaluator cond_evaluator(max_loop_iterations_); + cond_evaluator.set_dynamic_dimension_inference(dynamic_dimension_inference_); HloEvaluator loop_body_evaluator(max_loop_iterations_); + loop_body_evaluator.set_dynamic_dimension_inference( + dynamic_dimension_inference_); while (keep_going) { if (max_loop_iterations_ >= 0 && iteration_count++ > max_loop_iterations_) { return InvalidArgument("Loop %s exceeded loop iteration limit (%d).", diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.h b/tensorflow/compiler/xla/service/hlo_evaluator.h index 4fc5d10aed..604b861913 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.h +++ b/tensorflow/compiler/xla/service/hlo_evaluator.h @@ -23,6 +23,7 @@ limitations under the License. #include "absl/types/span.h" #include "tensorflow/compiler/xla/array2d.h" #include "tensorflow/compiler/xla/service/dfs_hlo_visitor_with_default.h" +#include "tensorflow/compiler/xla/service/dynamic_dimension_inference.h" #include "tensorflow/compiler/xla/service/hlo_computation.h" #include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/service/hlo_module.h" @@ -123,6 +124,11 @@ class HloEvaluator : public DfsHloVisitorWithDefault { const PrecisionConfig& precision_config, const Literal& lhs, const Literal& rhs); + void set_dynamic_dimension_inference( + DynamicDimensionInference* dynamic_dimension_inference) { + dynamic_dimension_inference_ = dynamic_dimension_inference; + } + // Enable the fast path for certain operations like dot or convolution. void set_use_fast_path(bool value) { use_fast_path_ = value; } @@ -161,6 +167,8 @@ class HloEvaluator : public DfsHloVisitorWithDefault { // Status HandleBitcast(HloInstruction* bitcast) override; + Status HandleGetDimensionSize(HloInstruction* get_dimension_size) override; + Status HandleParameter(HloInstruction* parameter) override; Status HandleConstant(HloInstruction* constant) override; @@ -295,6 +303,10 @@ class HloEvaluator : public DfsHloVisitorWithDefault { // RNG engine. std::minstd_rand0 engine_; + // DynamicDimensionInference is used to evaluate GetDimensionSize, which + // returns the dynamic dimension size of its operand. + DynamicDimensionInference* dynamic_dimension_inference_; + TF_DISALLOW_COPY_AND_ASSIGN(HloEvaluator); }; diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc index 8056193d99..319f0db97a 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc +++ b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc @@ -62,8 +62,7 @@ class HloEvaluatorTest : public HloTestBase { if (use_bfloat16_) { HloElementTypeConverter(F32, BF16).Run(m_.get()).ValueOrDie(); } - return HloEvaluator() - .Evaluate(*m_->entry_computation(), arg_literals) + return evaluator_.Evaluate(*m_->entry_computation(), arg_literals) .ConsumeValueOrDie(); } @@ -75,8 +74,7 @@ class HloEvaluatorTest : public HloTestBase { if (use_bfloat16_) { HloElementTypeConverter(F32, BF16).Run(m_.get()).ValueOrDie(); } - return HloEvaluator() - .Evaluate(*module->entry_computation(), arg_literals) + return evaluator_.Evaluate(*module->entry_computation(), arg_literals) .ConsumeValueOrDie(); } @@ -115,6 +113,7 @@ class HloEvaluatorTest : public HloTestBase { protected: explicit HloEvaluatorTest(bool use_bfloat16) : use_bfloat16_(use_bfloat16) {} + HloEvaluator evaluator_; const bool use_bfloat16_; std::unique_ptr m_ = CreateNewVerifiedModule(); @@ -2874,6 +2873,39 @@ ENTRY main { static_cast(pow31 * pow31)); } +TEST_F(HloEvaluatorTest, GetDimensionSize) { + constexpr absl::string_view hlo_text = R"( +HloModule Test + +ENTRY main { + size = u32[] parameter(0) + + data = s32[4] parameter(1) + + sum = s32[4] add(data, data) + + ROOT dynamic_size = u32[] get-dimension-size(sum), dimensions={0} +} +)"; + TF_ASSERT_OK_AND_ASSIGN(m_, ParseAndReturnVerifiedModule(hlo_text)); + + // Set up dynamic parameter binding. + TF_CHECK_OK(m_->dynamic_parameter_binding().Bind( + DynamicParameterBinding::DynamicParameter{0, {}}, + DynamicParameterBinding::DynamicDimension{1, {}, 0})); + + TF_ASSERT_OK_AND_ASSIGN(DynamicDimensionInference dynamic_dimension_inference, + DynamicDimensionInference::Run(m_.get())); + + evaluator_.set_dynamic_dimension_inference(&dynamic_dimension_inference); + Literal size_arg = LiteralUtil::CreateR0(3); + Literal data_arg = LiteralUtil::CreateR1({1, 2, 3, 4}); + + Literal actual = Evaluate({&size_arg, &data_arg}); + + EXPECT_EQ(actual.GetFirstElement(), static_cast(3)); +} + // Check that we get a useful error if we pass inputs of the wrong shape. TEST_F(HloEvaluatorTest, EvaluateWithWrongInputShapes) { constexpr absl::string_view hlo_text = R"( diff --git a/tensorflow/compiler/xla/service/service.cc b/tensorflow/compiler/xla/service/service.cc index efefe9003f..4a8d272261 100644 --- a/tensorflow/compiler/xla/service/service.cc +++ b/tensorflow/compiler/xla/service/service.cc @@ -29,6 +29,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/compiler.h" #include "tensorflow/compiler/xla/service/computation_layout.h" #include "tensorflow/compiler/xla/service/device_memory_allocator.h" +#include "tensorflow/compiler/xla/service/dynamic_dimension_inference.h" #include "tensorflow/compiler/xla/service/executable.h" #include "tensorflow/compiler/xla/service/hlo_computation.h" #include "tensorflow/compiler/xla/service/hlo_cost_analysis.h" @@ -1097,7 +1098,11 @@ Status Service::ComputeConstantGraph(const ComputeConstantGraphRequest* arg, TF_ASSIGN_OR_RETURN(std::unique_ptr module, CreateModuleFromProto(arg->computation(), config)); + TF_ASSIGN_OR_RETURN(DynamicDimensionInference dynamic_dimension_inference, + DynamicDimensionInference::Run(module.get())); + HloEvaluator evaluator; + evaluator.set_dynamic_dimension_inference(&dynamic_dimension_inference); TF_ASSIGN_OR_RETURN(auto result_literal, evaluator.Evaluate(*module, {})); // Since the result layout is non-effective to the Evaluator results, explicit -- GitLab From b94da8cca6fc657a77ab920e7feeb05cd57f37d9 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Mon, 7 Jan 2019 15:04:18 -0800 Subject: [PATCH 0295/2345] Print a more ergonomic error message when the CPU JIT is not linked in PiperOrigin-RevId: 228243176 --- tensorflow/compiler/xla/service/compiler.cc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/service/compiler.cc b/tensorflow/compiler/xla/service/compiler.cc index 8f08c24490..653f4555a7 100644 --- a/tensorflow/compiler/xla/service/compiler.cc +++ b/tensorflow/compiler/xla/service/compiler.cc @@ -98,10 +98,17 @@ Compiler::GetPlatformCompilers() { auto* factories = GetPlatformCompilerFactories(); auto it = factories->find(platform->id()); if (it == factories->end()) { + string hint; + if (platform->Name() == "Host") { + hint = " (hint: try linking in tensorflow/compiler/jit:xla_cpu_jit)"; + } else if (platform->Name() == "CUDA") { + hint = " (hint: try linking in tensorflow/compiler/jit:xla_gpu_jit)"; + } + return NotFound( "could not find registered compiler for platform %s -- check " - "target linkage", - platform->Name()); + "target linkage%s", + platform->Name(), hint); } // And then we invoke the factory, placing the result into the mapping. -- GitLab From 35938dbf10a7695ace5fa1707b5e6ccdd22869a0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 15:34:00 -0800 Subject: [PATCH 0296/2345] Fixes for flex issues with multiple subgraphs. PiperOrigin-RevId: 228248671 --- tensorflow/lite/delegates/flex/buffer_map.h | 3 ++ .../lite/delegates/flex/buffer_map_test.cc | 2 + tensorflow/lite/delegates/flex/kernel.cc | 11 +++++- tensorflow/lite/delegates/flex/kernel_test.cc | 37 +++++++------------ 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/tensorflow/lite/delegates/flex/buffer_map.h b/tensorflow/lite/delegates/flex/buffer_map.h index 6c1df4c836..d4724a011d 100644 --- a/tensorflow/lite/delegates/flex/buffer_map.h +++ b/tensorflow/lite/delegates/flex/buffer_map.h @@ -66,6 +66,9 @@ class BufferMap { // be use by TF's forwarding optimizations. void SetForwardable(int tensor_index) { forwardable_.insert(tensor_index); } + // Removes all information about which tensors are forwardable. + void ClearForwardable() { forwardable_.clear(); } + // Returns true if this tensor has been explicitly marks as forwardable by // a call to SetForwardable(). bool IsForwardable(int tensor_index) const { diff --git a/tensorflow/lite/delegates/flex/buffer_map_test.cc b/tensorflow/lite/delegates/flex/buffer_map_test.cc index 2148bfe8e2..7b4acbd69d 100644 --- a/tensorflow/lite/delegates/flex/buffer_map_test.cc +++ b/tensorflow/lite/delegates/flex/buffer_map_test.cc @@ -278,6 +278,8 @@ TEST(BufferMapTest, Forwardable) { EXPECT_FALSE(buffer_map.IsForwardable(0)); buffer_map.SetForwardable(0); EXPECT_TRUE(buffer_map.IsForwardable(0)); + buffer_map.ClearForwardable(); + EXPECT_FALSE(buffer_map.IsForwardable(0)); } } // namespace diff --git a/tensorflow/lite/delegates/flex/kernel.cc b/tensorflow/lite/delegates/flex/kernel.cc index 0005b6e5b0..84745ba9cb 100644 --- a/tensorflow/lite/delegates/flex/kernel.cc +++ b/tensorflow/lite/delegates/flex/kernel.cc @@ -350,7 +350,11 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { buffer_map->SetFromTfLite(tensor_index, tensor); } } - ++tensor_ref_count[tensor_index]; + + // Input tensors should never be forwarded so we increment their ref counts + // twice: once for this graph and another for the possibility of them being + // used by another subgraph, or being an output of the full graph. + tensor_ref_count[tensor_index] += 2; } // All output tensors are allocated by TensorFlow/Eager, so we @@ -372,6 +376,7 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { } } + buffer_map->ClearForwardable(); for (const auto& x : tensor_ref_count) { if (x.second == 1) { // This tensor is referenced once by a single op. We can allow the TF @@ -429,10 +434,12 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { } // We don't need to keep track of internal TF tensors any longer, so take - // them out of the buffer_map, but make sure we keep all the one we might + // them out of the buffer_map, but make sure we keep all the ones we might // need for other subgraphs, or as final output of inference. const auto& outputs = op_data->subgraph_outputs; std::set keep(outputs.begin(), outputs.end()); + const auto& inputs = op_data->subgraph_inputs; + keep.insert(inputs.begin(), inputs.end()); buffer_map->RemoveTensorsNotInSet(keep); return kTfLiteOk; diff --git a/tensorflow/lite/delegates/flex/kernel_test.cc b/tensorflow/lite/delegates/flex/kernel_test.cc index 9de3ca67de..cef8017e6e 100644 --- a/tensorflow/lite/delegates/flex/kernel_test.cc +++ b/tensorflow/lite/delegates/flex/kernel_test.cc @@ -25,6 +25,7 @@ namespace { using ::testing::ContainsRegex; using ::testing::ElementsAre; +using ::testing::ElementsAreArray; TfLiteStatus GenericPrepare(TfLiteContext* context, TfLiteDelegate* delegate, const std::vector& supported_nodes) { @@ -340,14 +341,10 @@ TEST_F(MultipleSubgraphsTest, ForwardabilityIsLocal) { auto input = {3.0f, 4.0f, 5.0f}; PrepareInterpreter(GetPrepareFunction<__LINE__>(), input); - ASSERT_FALSE(Invoke()); - ASSERT_THAT(error_reporter().error_messages(), - ContainsRegex("Cannot read from invalid tensor index 10")); - - // TODO(b/122457581): expected result for this test: - // ASSERT_THAT(GetValues(12), ElementsAreArray(Apply(input, [](float in) { - // return (4 * in + 4) * (in + 1); - // }))); + ASSERT_TRUE(Invoke()); + ASSERT_THAT(GetValues(12), ElementsAreArray(Apply(input, [](float in) { + return (4 * in + 4) * (in + 1); + }))); } // Subgraphs should not remove input tensors from the buffer_map, since @@ -379,14 +376,10 @@ TEST_F(MultipleSubgraphsTest, DoNotRemoveInputTensors) { auto input = {3.0f, 4.0f, 5.0f}; PrepareInterpreter(GetPrepareFunction<__LINE__>(), input); - ASSERT_FALSE(Invoke()); - ASSERT_THAT(error_reporter().error_messages(), - ContainsRegex("Tensor '10' not found")); - - // TODO(b/122457581): expected result for this test: - // ASSERT_THAT(GetValues(12), ElementsAreArray(Apply(input, [](float in) { - // return (4 * in + 4) * (in + 1); - // }))); + ASSERT_TRUE(Invoke()); + ASSERT_THAT(GetValues(12), ElementsAreArray(Apply(input, [](float in) { + return (4 * in + 4) * (in + 1); + }))); } // A tensor is deemed forwardable but it happens to be the input to @@ -417,14 +410,10 @@ TEST_F(MultipleSubgraphsTest, DoNotForwardInputTensors) { auto input = {3.0f, 4.0f, 5.0f}; PrepareInterpreter(GetPrepareFunction<__LINE__>(), input); - ASSERT_FALSE(Invoke()); - ASSERT_THAT(error_reporter().error_messages(), - ContainsRegex("Tensor '10' not found")); - - // TODO(b/122457581): expected result for this test: - // ASSERT_THAT(GetValues(12), ElementsAreArray(Apply(input, [](float in) { - // return (5 * in + 5) * (in + 1); - // }))); + ASSERT_TRUE(Invoke()); + ASSERT_THAT(GetValues(12), ElementsAreArray(Apply(input, [](float in) { + return (5 * in + 5) * (in + 1); + }))); } } // namespace -- GitLab From 6d26622733a3f55861c135b857b2cbce981b2467 Mon Sep 17 00:00:00 2001 From: Karim Nosir Date: Mon, 7 Jan 2019 15:34:34 -0800 Subject: [PATCH 0297/2345] Fix some tests failures. PiperOrigin-RevId: 228248776 --- tensorflow/lite/kernels/BUILD | 2 ++ tensorflow/lite/kernels/dequantize_test.cc | 1 + tensorflow/lite/kernels/internal/BUILD | 4 ++++ tensorflow/lite/kernels/sparse_output_fully_connected_test.cc | 1 + 4 files changed, 8 insertions(+) diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD index 6809a4a3d0..50deac9bb6 100644 --- a/tensorflow/lite/kernels/BUILD +++ b/tensorflow/lite/kernels/BUILD @@ -352,6 +352,7 @@ tf_cc_test( ":builtin_ops", "//tensorflow/lite:framework", "//tensorflow/lite/kernels:test_util", + "//tensorflow/lite/kernels/internal:types", "@com_google_googletest//:gtest", "@flatbuffers", ], @@ -513,6 +514,7 @@ tf_cc_test( ":builtin_ops", "//tensorflow/lite:framework", "//tensorflow/lite/kernels:test_util", + "//tensorflow/lite/kernels/internal:types", "@com_google_absl//absl/memory", "@com_google_googletest//:gtest", ], diff --git a/tensorflow/lite/kernels/dequantize_test.cc b/tensorflow/lite/kernels/dequantize_test.cc index bb5f1e74a8..6343745eef 100644 --- a/tensorflow/lite/kernels/dequantize_test.cc +++ b/tensorflow/lite/kernels/dequantize_test.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include #include "tensorflow/lite/interpreter.h" +#include "tensorflow/lite/kernels/internal/types.h" #include "tensorflow/lite/kernels/register.h" #include "tensorflow/lite/kernels/test_util.h" #include "tensorflow/lite/model.h" diff --git a/tensorflow/lite/kernels/internal/BUILD b/tensorflow/lite/kernels/internal/BUILD index 92b4705908..885f650dd5 100644 --- a/tensorflow/lite/kernels/internal/BUILD +++ b/tensorflow/lite/kernels/internal/BUILD @@ -545,6 +545,10 @@ cc_library( name = "test_util", srcs = ["test_util.cc"], hdrs = ["test_util.h"], + linkopts = select({ + "//tensorflow:windows": [], + "//conditions:default": ["-lm"], + }), deps = [ ":types", "//tensorflow/lite:string", diff --git a/tensorflow/lite/kernels/sparse_output_fully_connected_test.cc b/tensorflow/lite/kernels/sparse_output_fully_connected_test.cc index fb71cb7f7b..8152ae6685 100644 --- a/tensorflow/lite/kernels/sparse_output_fully_connected_test.cc +++ b/tensorflow/lite/kernels/sparse_output_fully_connected_test.cc @@ -19,6 +19,7 @@ limitations under the License. #include #include "flatbuffers/flexbuffers.h" // TF:flatbuffers +#include "tensorflow/lite/kernels/internal/types.h" #include "tensorflow/lite/kernels/register.h" #include "tensorflow/lite/kernels/test_util.h" -- GitLab From 3b74e1c16ab280a4e0cbd5138a0c73710cb9804c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 15:46:39 -0800 Subject: [PATCH 0298/2345] Clarified that variables_to_restore function chooses whether to use a moving average version based on provided parameters rather than based on the existence of a given name. PiperOrigin-RevId: 228250786 --- tensorflow/python/training/moving_averages.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/training/moving_averages.py b/tensorflow/python/training/moving_averages.py index 72670f0ca3..4b267dfb98 100644 --- a/tensorflow/python/training/moving_averages.py +++ b/tensorflow/python/training/moving_averages.py @@ -505,13 +505,13 @@ class ExponentialMovingAverage(object): ``` Args: moving_avg_variables: a list of variables that require to use of the - moving variable name to be restored. If None, it will default to + moving average variable name to be restored. If None, it will default to variables.moving_average_variables() + variables.trainable_variables() Returns: - A map from restore_names to variables. The restore_name can be the - moving_average version of the variable name if it exist, or the original - variable name. + A map from restore_names to variables. The restore_name is either the + original or the moving average version of the variable name, depending + on whether the variable name is in the `moving_avg_variables`. """ name_map = {} if moving_avg_variables is None: -- GitLab From 0de09568916f31d77ef0093285a058e901195919 Mon Sep 17 00:00:00 2001 From: Peter Ma Date: Mon, 7 Jan 2019 16:09:22 -0800 Subject: [PATCH 0299/2345] Replace op_features with op_info to correctly reflect the data type, and some minor fix on function argument names. PiperOrigin-RevId: 228254456 --- .../grappler/costs/op_level_cost_estimator.cc | 246 +++++++++--------- .../grappler/costs/op_level_cost_estimator.h | 34 ++- .../costs/op_level_cost_estimator_test.cc | 17 +- 3 files changed, 143 insertions(+), 154 deletions(-) diff --git a/tensorflow/core/grappler/costs/op_level_cost_estimator.cc b/tensorflow/core/grappler/costs/op_level_cost_estimator.cc index 55eb391d2b..96bac8d0cb 100644 --- a/tensorflow/core/grappler/costs/op_level_cost_estimator.cc +++ b/tensorflow/core/grappler/costs/op_level_cost_estimator.cc @@ -72,25 +72,25 @@ static const Costs::Duration kMinComputeTime(1); namespace { -string GetDataFormat(const OpInfo& op_features) { +string GetDataFormat(const OpInfo& op_info) { string data_format = "NHWC"; // Default format. - if (op_features.attr().find("data_format") != op_features.attr().end()) { - data_format = op_features.attr().at("data_format").s(); + if (op_info.attr().find("data_format") != op_info.attr().end()) { + data_format = op_info.attr().at("data_format").s(); } return data_format; } -string GetFilterFormat(const OpInfo& op_features) { +string GetFilterFormat(const OpInfo& op_info) { string filter_format = "HWIO"; // Default format. - if (op_features.attr().find("filter_format") != op_features.attr().end()) { - filter_format = op_features.attr().at("filter_format").s(); + if (op_info.attr().find("filter_format") != op_info.attr().end()) { + filter_format = op_info.attr().at("filter_format").s(); } return filter_format; } -Padding GetPadding(const OpInfo& op_features) { - if (op_features.attr().find("padding") != op_features.attr().end() && - op_features.attr().at("padding").s() == "VALID") { +Padding GetPadding(const OpInfo& op_info) { + if (op_info.attr().find("padding") != op_info.attr().end() && + op_info.attr().at("padding").s() == "VALID") { return Padding::VALID; } return Padding::SAME; // Default padding. @@ -107,11 +107,11 @@ bool IsTraining(const OpInfo& op_info) { // TODO(dyoon): support non-4D tensors in the c ost functions of convolution // related ops (Conv, Pool, BatchNorm, and their backprops) and the related // helper functions. -std::vector GetStrides(const OpInfo& op_features) { - if (op_features.attr().find("strides") != op_features.attr().end()) { - const auto strides = op_features.attr().at("strides").list().i(); - CHECK(strides.size() == 4) << "Attr strides is not a length-4 vector: " - << op_features.DebugString(); +std::vector GetStrides(const OpInfo& op_info) { + if (op_info.attr().find("strides") != op_info.attr().end()) { + const auto strides = op_info.attr().at("strides").list().i(); + CHECK(strides.size() == 4) + << "Attr strides is not a length-4 vector: " << op_info.DebugString(); return {strides[0], strides[1], strides[2], strides[3]}; } return {1, 1, 1, 1}; @@ -359,21 +359,21 @@ OpLevelCostEstimator::OpLevelCostEstimator() { } Costs OpLevelCostEstimator::PredictCosts(const OpContext& op_context) const { - const auto& op_features = op_context.op_info; - auto it = device_cost_impl_.find(op_features.op()); + const auto& op_info = op_context.op_info; + auto it = device_cost_impl_.find(op_info.op()); if (it == device_cost_impl_.end()) { - if (elementwise_ops_.find(op_features.op()) != elementwise_ops_.end()) { + if (elementwise_ops_.find(op_info.op()) != elementwise_ops_.end()) { return PredictCwiseOp(op_context); } - VLOG(1) << "Missing accurate estimator for op: " << op_features.op(); + VLOG(1) << "Missing accurate estimator for op: " << op_info.op(); return PredictCostOfAnUnknownOp(op_context); } std::function estimator = it->second; Costs costs = estimator(op_context); - VLOG(1) << "Operation " << op_features.op() << " takes " + VLOG(1) << "Operation " << op_info.op() << " takes " << costs.execution_time.count() << " ns."; return costs; } @@ -430,39 +430,38 @@ DeviceInfo OpLevelCostEstimator::GetDeviceInfo( } Costs OpLevelCostEstimator::PredictCwiseOp(const OpContext& op_context) const { - const auto& op_features = op_context.op_info; + const auto& op_info = op_context.op_info; bool found_unknown_shapes = false; // For unary or binary element-wise operations, op count is the element count // of any input. We use the count for the largest input here to be more robust // in case that the shape is unknown or partially known for other input. - int64 op_count = - CalculateLargestInputCount(op_features, &found_unknown_shapes); + int64 op_count = CalculateLargestInputCount(op_info, &found_unknown_shapes); // If output shape is available, try use the element count calcuated from // that. - if (op_features.outputs_size() > 0) { - op_count = - std::max(op_count, CalculateTensorElementCount(op_features.outputs(0), - &found_unknown_shapes)); + if (op_info.outputs_size() > 0) { + op_count = std::max( + op_count, + CalculateTensorElementCount(op_info.outputs(0), &found_unknown_shapes)); } // For binary ops, calculate the output shape possibly resulting from // broadcasting. - if (op_features.inputs_size() >= 2) { - op_count = std::max(op_count, - CwiseOutputElementCount(op_features.inputs(0).shape(), - op_features.inputs(1).shape())); + if (op_info.inputs_size() >= 2) { + op_count = + std::max(op_count, CwiseOutputElementCount(op_info.inputs(0).shape(), + op_info.inputs(1).shape())); } int op_cost = 1; bool is_known_elementwise_op = false; - auto it = elementwise_ops_.find(op_features.op()); + auto it = elementwise_ops_.find(op_info.op()); if (it != elementwise_ops_.end()) { op_cost = it->second; is_known_elementwise_op = true; } else { - LOG(WARNING) << "Not a cwise op: " << op_features.op(); + LOG(WARNING) << "Not a cwise op: " << op_info.op(); } - Costs costs = PredictOpCountBasedCost(op_count * op_cost, op_features); + Costs costs = PredictOpCountBasedCost(op_count * op_cost, op_info); if (found_unknown_shapes || !is_known_elementwise_op) { costs.inaccurate = true; } @@ -542,17 +541,17 @@ Costs OpLevelCostEstimator::PredictOpCountBasedCost( } int64 OpLevelCostEstimator::CountConv2DOperations( - const OpInfo& op_features, bool* found_unknown_shapes) const { - return CountConv2DOperations(op_features, nullptr, found_unknown_shapes); + const OpInfo& op_info, bool* found_unknown_shapes) const { + return CountConv2DOperations(op_info, nullptr, found_unknown_shapes); } // Helper to translate the positional arguments into named fields. OpLevelCostEstimator::ConvolutionDimensions OpLevelCostEstimator::ConvolutionDimensionsFromInputs( const TensorShapeProto& original_image_shape, - const TensorShapeProto& original_filter_shape, const OpInfo& op_features, + const TensorShapeProto& original_filter_shape, const OpInfo& op_info, bool* found_unknown_shapes) { - VLOG(2) << "op features: " << op_features.DebugString(); + VLOG(2) << "op features: " << op_info.DebugString(); VLOG(2) << "Original image shape: " << original_image_shape.DebugString(); VLOG(2) << "Original filter shape: " << original_filter_shape.DebugString(); auto image_shape = @@ -563,7 +562,7 @@ OpLevelCostEstimator::ConvolutionDimensionsFromInputs( VLOG(2) << "Filter shape: " << filter_shape.DebugString(); int x_index, y_index, channel_index; - const string& data_format = GetDataFormat(op_features); + const string& data_format = GetDataFormat(op_info); if (data_format == "NCHW") { x_index = 2; y_index = 3; @@ -574,7 +573,7 @@ OpLevelCostEstimator::ConvolutionDimensionsFromInputs( y_index = 2; channel_index = 3; } - const string& filter_format = GetFilterFormat(op_features); + const string& filter_format = GetFilterFormat(op_info); int filter_x_index, filter_y_index, in_channel_index, out_channel_index; if (filter_format == "HWIO") { filter_x_index = 0; @@ -594,8 +593,8 @@ OpLevelCostEstimator::ConvolutionDimensionsFromInputs( int64 iz = image_shape.dim(channel_index).size(); int64 kx = filter_shape.dim(filter_x_index).size(); int64 ky = filter_shape.dim(filter_y_index).size(); - std::vector strides = GetStrides(op_features); - const auto padding = GetPadding(op_features); + std::vector strides = GetStrides(op_info); + const auto padding = GetPadding(op_info); int64 sx = strides[x_index]; int64 sy = strides[y_index]; int64 ox = GetOutputSize(ix, kx, sx, padding); @@ -623,14 +622,13 @@ OpLevelCostEstimator::ConvolutionDimensionsFromInputs( } int64 OpLevelCostEstimator::CountConv2DOperations( - const OpInfo& op_features, ConvolutionDimensions* conv_info, + const OpInfo& op_info, ConvolutionDimensions* conv_info, bool* found_unknown_shapes) const { - DCHECK(op_features.op() == kConv2d || - op_features.op() == kDepthwiseConv2dNative) + DCHECK(op_info.op() == kConv2d || op_info.op() == kDepthwiseConv2dNative) << "Invalid Operation: not Conv2D nor DepthwiseConv2dNative"; ConvolutionDimensions conv_dims = ConvolutionDimensionsFromInputs( - op_features.inputs(0).shape(), op_features.inputs(1).shape(), op_features, + op_info.inputs(0).shape(), op_info.inputs(1).shape(), op_info, found_unknown_shapes); // in DepthwiseConv2dNative conv_dims.oz is actually the channel depth @@ -641,7 +639,7 @@ int64 OpLevelCostEstimator::CountConv2DOperations( int64 ops = conv_dims.batch; ops *= conv_dims.ox * conv_dims.oy; ops *= conv_dims.kx * conv_dims.ky; - if (op_features.op() == kConv2d) { + if (op_info.op() == kConv2d) { ops *= conv_dims.iz * conv_dims.oz; } else { // To ensure output tensor dims to be correct for DepthwiseConv2DNative, @@ -658,32 +656,32 @@ int64 OpLevelCostEstimator::CountConv2DOperations( } int64 OpLevelCostEstimator::CountMatMulOperations( - const OpInfo& op_features, bool* found_unknown_shapes) const { - return CountMatMulOperations(op_features, nullptr, found_unknown_shapes); + const OpInfo& op_info, bool* found_unknown_shapes) const { + return CountMatMulOperations(op_info, nullptr, found_unknown_shapes); } // TODO(nishantpatil): Create separate estimator for Sparse Matmul int64 OpLevelCostEstimator::CountMatMulOperations( - const OpInfo& op_features, MatMulDimensions* mat_mul, + const OpInfo& op_info, MatMulDimensions* mat_mul, bool* found_unknown_shapes) const { double ops = 0; - if (op_features.inputs_size() < 2) { - LOG(ERROR) << "Need 2 inputs but got " << op_features.inputs_size(); + if (op_info.inputs_size() < 2) { + LOG(ERROR) << "Need 2 inputs but got " << op_info.inputs_size(); // TODO(pcma): Try to separate invalid inputs from unknown shapes *found_unknown_shapes = true; return 0; } - auto& a_matrix = op_features.inputs(0); - auto& b_matrix = op_features.inputs(1); + auto& a_matrix = op_info.inputs(0); + auto& b_matrix = op_info.inputs(1); bool transpose_a = false; bool transpose_b = false; double m_dim, n_dim, k_dim, k_dim_b = 0; - for (const auto& item : op_features.attr()) { + for (const auto& item : op_info.attr()) { VLOG(1) << "Key:" << item.first << " Value:" << SummarizeAttrValue(item.second); if (item.first == "transpose_a" && item.second.b() == true) @@ -735,23 +733,23 @@ int64 OpLevelCostEstimator::CountMatMulOperations( } int64 OpLevelCostEstimator::CountBatchMatMulOperations( - const OpInfo& op_features, bool* found_unknown_shapes) const { - if (op_features.op() != kBatchMatMul) { - LOG(ERROR) << "Invalid Operation: " << op_features.op(); + const OpInfo& op_info, bool* found_unknown_shapes) const { + if (op_info.op() != kBatchMatMul) { + LOG(ERROR) << "Invalid Operation: " << op_info.op(); // TODO(pcma): Try to separate invalid inputs from unknown shapes *found_unknown_shapes = true; return 0; } - if (op_features.inputs_size() != 2) { - LOG(ERROR) << "Expected 2 inputs but got " << op_features.inputs_size(); + if (op_info.inputs_size() != 2) { + LOG(ERROR) << "Expected 2 inputs but got " << op_info.inputs_size(); // TODO(pcma): Try to separate invalid inputs from unknown shapes *found_unknown_shapes = true; return 0; } double ops = 0; - const auto& a_input = op_features.inputs(0); - const auto& b_input = op_features.inputs(1); + const auto& a_input = op_info.inputs(0); + const auto& b_input = op_info.inputs(1); // BatchMatMul requires inputs of at least matrix shape (rank 2). // The two most minor dimensions of each input are matrices that @@ -801,24 +799,24 @@ int64 OpLevelCostEstimator::CountBatchMatMulOperations( // Build the MatMul. Note that values are ignored here since we are just // counting ops (e.g. only shapes matter). - OpInfo matmul_op_features; - matmul_op_features.set_op("MatMul"); + OpInfo matmul_op_info; + matmul_op_info.set_op("MatMul"); AttrValue transpose_a; transpose_a.set_b(false); - if (op_features.attr().find("adj_x") != op_features.attr().end()) { - transpose_a.set_b(op_features.attr().at("adj_x").b()); + if (op_info.attr().find("adj_x") != op_info.attr().end()) { + transpose_a.set_b(op_info.attr().at("adj_x").b()); } - (*matmul_op_features.mutable_attr())["transpose_a"] = transpose_a; + (*matmul_op_info.mutable_attr())["transpose_a"] = transpose_a; AttrValue transpose_b; transpose_b.set_b(false); - if (op_features.attr().find("adj_y") != op_features.attr().end()) { - transpose_b.set_b(op_features.attr().at("adj_y").b()); + if (op_info.attr().find("adj_y") != op_info.attr().end()) { + transpose_b.set_b(op_info.attr().at("adj_y").b()); } - (*matmul_op_features.mutable_attr())["transpose_b"] = transpose_b; + (*matmul_op_info.mutable_attr())["transpose_b"] = transpose_b; - OpInfo::TensorProperties* a_matrix = matmul_op_features.add_inputs(); + OpInfo::TensorProperties* a_matrix = matmul_op_info.add_inputs(); a_matrix->set_dtype(a_input.dtype()); TensorShapeProto* a_matrix_shape = a_matrix->mutable_shape(); for (int i = std::max(0, a_input_shape.dim_size() - matrix_rank); @@ -826,7 +824,7 @@ int64 OpLevelCostEstimator::CountBatchMatMulOperations( *(a_matrix_shape->add_dim()) = a_input_shape.dim(i); } - OpInfo::TensorProperties* b_matrix = matmul_op_features.add_inputs(); + OpInfo::TensorProperties* b_matrix = matmul_op_info.add_inputs(); b_matrix->set_dtype(b_input.dtype()); TensorShapeProto* b_matrix_shape = b_matrix->mutable_shape(); for (int i = std::max(0, b_input_shape.dim_size() - matrix_rank); @@ -836,7 +834,7 @@ int64 OpLevelCostEstimator::CountBatchMatMulOperations( for (int i = 0; i < num_matmuls; ++i) { bool matmul_unknown_shapes = false; - ops += CountMatMulOperations(matmul_op_features, &matmul_unknown_shapes); + ops += CountMatMulOperations(matmul_op_info, &matmul_unknown_shapes); *found_unknown_shapes |= matmul_unknown_shapes; } return ops; @@ -894,16 +892,16 @@ bool GetTensorShapeProtoFromTensorProto(const TensorProto& tensor_proto, // TODO(cliffy): Dedup this method and CountConv2DBackpropFilterOperations. int64 OpLevelCostEstimator::CountConv2DBackpropInputOperations( - const OpInfo& op_features, ConvolutionDimensions* returned_conv_dims, + const OpInfo& op_info, ConvolutionDimensions* returned_conv_dims, bool* found_unknown_shapes) const { int64 ops = 0; - DCHECK(op_features.op() == kConv2dBackpropInput || - op_features.op() == kDepthwiseConv2dNativeBackpropInput) + DCHECK(op_info.op() == kConv2dBackpropInput || + op_info.op() == kDepthwiseConv2dNativeBackpropInput) << "Invalid Operation: not kConv2dBackpropInput nor" "kDepthwiseConv2dNativeBackpropInput"; - if (op_features.inputs_size() < 2) { + if (op_info.inputs_size() < 2) { // TODO(pcma): Try to separate invalid inputs from unknown shapes *found_unknown_shapes = true; return ops; @@ -911,12 +909,12 @@ int64 OpLevelCostEstimator::CountConv2DBackpropInputOperations( TensorShapeProto input_shape; bool shape_found = false; - if (op_features.inputs(0).has_value()) { - const TensorProto& value = op_features.inputs(0).value(); + if (op_info.inputs(0).has_value()) { + const TensorProto& value = op_info.inputs(0).value(); shape_found = GetTensorShapeProtoFromTensorProto(value, &input_shape); } - if (!shape_found && op_features.outputs_size() == 1) { - input_shape = op_features.outputs(0).shape(); + if (!shape_found && op_info.outputs_size() == 1) { + input_shape = op_info.outputs(0).shape(); shape_found = true; } if (!shape_found) { @@ -929,13 +927,12 @@ int64 OpLevelCostEstimator::CountConv2DBackpropInputOperations( } ConvolutionDimensions conv_dims = ConvolutionDimensionsFromInputs( - input_shape, op_features.inputs(1).shape(), op_features, - found_unknown_shapes); + input_shape, op_info.inputs(1).shape(), op_info, found_unknown_shapes); ops = conv_dims.batch; ops *= conv_dims.ox * conv_dims.oy; ops *= conv_dims.kx * conv_dims.ky; - if (op_features.op() == kConv2dBackpropInput) { + if (op_info.op() == kConv2dBackpropInput) { ops *= conv_dims.iz * conv_dims.oz; } else { // conv_dims always use forward path definition regardless @@ -944,7 +941,7 @@ int64 OpLevelCostEstimator::CountConv2DBackpropInputOperations( } ops *= kOpsPerMac; - VLOG(1) << "Operations for" << op_features.op() << " " << ops; + VLOG(1) << "Operations for" << op_info.op() << " " << ops; if (returned_conv_dims != nullptr) { *returned_conv_dims = conv_dims; @@ -953,23 +950,23 @@ int64 OpLevelCostEstimator::CountConv2DBackpropInputOperations( } int64 OpLevelCostEstimator::CountConv2DBackpropFilterOperations( - const OpInfo& op_features, ConvolutionDimensions* returned_conv_dims, + const OpInfo& op_info, ConvolutionDimensions* returned_conv_dims, bool* found_unknown_shapes) const { int64 ops = 0; - DCHECK(op_features.op() == kConv2dBackpropFilter || - op_features.op() == kDepthwiseConv2dNativeBackpropFilter) + DCHECK(op_info.op() == kConv2dBackpropFilter || + op_info.op() == kDepthwiseConv2dNativeBackpropFilter) << "Invalid Operation: not kConv2dBackpropFilter nor" "kDepthwiseConv2dNativeBackpropFilter"; TensorShapeProto filter_shape; bool shape_found = false; - if (op_features.inputs_size() >= 2 && op_features.inputs(1).has_value()) { - const TensorProto& value = op_features.inputs(1).value(); + if (op_info.inputs_size() >= 2 && op_info.inputs(1).has_value()) { + const TensorProto& value = op_info.inputs(1).value(); shape_found = GetTensorShapeProtoFromTensorProto(value, &filter_shape); } - if (!shape_found && op_features.outputs_size() == 1) { - filter_shape = op_features.outputs(0).shape(); + if (!shape_found && op_info.outputs_size() == 1) { + filter_shape = op_info.outputs(0).shape(); shape_found = true; } if (!shape_found) { @@ -981,19 +978,18 @@ int64 OpLevelCostEstimator::CountConv2DBackpropFilterOperations( *found_unknown_shapes = true; } - if (op_features.inputs_size() < 1) { + if (op_info.inputs_size() < 1) { // TODO(pcma): Try to separate invalid inputs from unknown shapes *found_unknown_shapes = true; return ops; } ConvolutionDimensions conv_dims = ConvolutionDimensionsFromInputs( - op_features.inputs(0).shape(), filter_shape, op_features, - found_unknown_shapes); + op_info.inputs(0).shape(), filter_shape, op_info, found_unknown_shapes); ops = conv_dims.batch; ops *= conv_dims.ox * conv_dims.oy; ops *= conv_dims.kx * conv_dims.ky; - if (op_features.op() == kConv2dBackpropFilter) { + if (op_info.op() == kConv2dBackpropFilter) { ops *= conv_dims.iz * conv_dims.oz; } else { // conv_dims always use forward path definition regardless @@ -1001,7 +997,7 @@ int64 OpLevelCostEstimator::CountConv2DBackpropFilterOperations( ops *= conv_dims.oz; } ops *= kOpsPerMac; - VLOG(1) << "Operations for" << op_features.op() << " " << ops; + VLOG(1) << "Operations for" << op_info.op() << " " << ops; if (returned_conv_dims != nullptr) { *returned_conv_dims = conv_dims; @@ -1032,9 +1028,9 @@ int64 OpLevelCostEstimator::CalculateTensorSize( } int64 OpLevelCostEstimator::CalculateInputSize( - const OpInfo& op_features, bool* found_unknown_shapes) const { + const OpInfo& op_info, bool* found_unknown_shapes) const { int64 total_input_size = 0; - for (auto& input : op_features.inputs()) { + for (auto& input : op_info.inputs()) { int64 input_size = CalculateTensorSize(input, found_unknown_shapes); total_input_size += input_size; VLOG(1) << "Input Size: " << input_size @@ -1044,9 +1040,9 @@ int64 OpLevelCostEstimator::CalculateInputSize( } int64 OpLevelCostEstimator::CalculateLargestInputCount( - const OpInfo& op_features, bool* found_unknown_shapes) const { + const OpInfo& op_info, bool* found_unknown_shapes) const { int64 largest_input_count = 0; - for (auto& input : op_features.inputs()) { + for (auto& input : op_info.inputs()) { int64 input_count = CalculateTensorElementCount(input, found_unknown_shapes); if (input_count > largest_input_count) { @@ -1059,10 +1055,10 @@ int64 OpLevelCostEstimator::CalculateLargestInputCount( } int64 OpLevelCostEstimator::CalculateOutputSize( - const OpInfo& op_features, bool* found_unknown_shapes) const { + const OpInfo& op_info, bool* found_unknown_shapes) const { int64 total_output_size = 0; // use float as default for calculations - for (const auto& output : op_features.outputs()) { + for (const auto& output : op_info.outputs()) { DataType dt = output.dtype(); const auto& original_output_shape = output.shape(); int64 output_size = DataTypeSize(BaseType(dt)); @@ -1080,10 +1076,10 @@ int64 OpLevelCostEstimator::CalculateOutputSize( } Costs OpLevelCostEstimator::PredictConv2D(const OpContext& op_context) const { - const auto& op_features = op_context.op_info; + const auto& op_info = op_context.op_info; bool found_unknown_shapes = false; auto costs = PredictOpCountBasedCost( - CountConv2DOperations(op_features, &found_unknown_shapes), op_features); + CountConv2DOperations(op_info, &found_unknown_shapes), op_info); costs.inaccurate = found_unknown_shapes; costs.num_ops_with_unknown_shapes = found_unknown_shapes; return costs; @@ -1091,12 +1087,12 @@ Costs OpLevelCostEstimator::PredictConv2D(const OpContext& op_context) const { Costs OpLevelCostEstimator::PredictConv2DBackpropInput( const OpContext& op_context) const { - const auto& op_features = op_context.op_info; + const auto& op_info = op_context.op_info; bool found_unknown_shapes = false; auto costs = PredictOpCountBasedCost(CountConv2DBackpropInputOperations( - op_features, nullptr, &found_unknown_shapes), - op_features); + op_info, nullptr, &found_unknown_shapes), + op_info); costs.inaccurate = found_unknown_shapes; costs.num_ops_with_unknown_shapes = found_unknown_shapes; return costs; @@ -1104,12 +1100,12 @@ Costs OpLevelCostEstimator::PredictConv2DBackpropInput( Costs OpLevelCostEstimator::PredictConv2DBackpropFilter( const OpContext& op_context) const { - const auto& op_features = op_context.op_info; + const auto& op_info = op_context.op_info; bool found_unknown_shapes = false; auto costs = PredictOpCountBasedCost(CountConv2DBackpropFilterOperations( - op_features, nullptr, &found_unknown_shapes), - op_features); + op_info, nullptr, &found_unknown_shapes), + op_info); costs.inaccurate = found_unknown_shapes; costs.num_ops_with_unknown_shapes = found_unknown_shapes; return costs; @@ -1204,26 +1200,26 @@ Costs OpLevelCostEstimator::PredictFusedConv2DBiasActivation( } Costs OpLevelCostEstimator::PredictMatMul(const OpContext& op_context) const { - const auto& op_features = op_context.op_info; + const auto& op_info = op_context.op_info; bool found_unknown_shapes = false; auto costs = PredictOpCountBasedCost( - CountMatMulOperations(op_features, &found_unknown_shapes), op_features); + CountMatMulOperations(op_info, &found_unknown_shapes), op_info); costs.inaccurate = found_unknown_shapes; costs.num_ops_with_unknown_shapes = found_unknown_shapes; return costs; } Costs OpLevelCostEstimator::PredictNoOp(const OpContext& op_context) const { - const auto& op_features = op_context.op_info; - VLOG(1) << "Op:" << op_features.op() << " Execution Time 0 (ns)"; + const auto& op_info = op_context.op_info; + VLOG(1) << "Op:" << op_info.op() << " Execution Time 0 (ns)"; return Costs::ZeroCosts(); } Costs OpLevelCostEstimator::PredictIdentity(const OpContext& op_context) const { - const auto& op_features = op_context.op_info; - VLOG(1) << "Op:" << op_features.op() << " Execution Time 0 (ns)"; + const auto& op_info = op_context.op_info; + VLOG(1) << "Op:" << op_info.op() << " Execution Time 0 (ns)"; Costs result = Costs::ZeroCosts(); - result.max_memory = CalculateOutputSize(op_features, &result.inaccurate); + result.max_memory = CalculateOutputSize(op_info, &result.inaccurate); result.num_ops_with_unknown_shapes = result.inaccurate; // Assign the minimum amount of time we can represent to the identity op since // it tends to be really cheap. @@ -1233,11 +1229,10 @@ Costs OpLevelCostEstimator::PredictIdentity(const OpContext& op_context) const { } Costs OpLevelCostEstimator::PredictVariable(const OpContext& op_context) const { - const auto& op_features = op_context.op_info; - VLOG(1) << "Op:" << op_features.op() << " Execution Time 0 (ns)"; + const auto& op_info = op_context.op_info; + VLOG(1) << "Op:" << op_info.op() << " Execution Time 0 (ns)"; Costs result = Costs::ZeroCosts(); - result.persistent_memory = - CalculateOutputSize(op_features, &result.inaccurate); + result.persistent_memory = CalculateOutputSize(op_info, &result.inaccurate); result.num_ops_with_unknown_shapes = result.inaccurate; result.compute_time = kMinComputeTime; @@ -1247,20 +1242,19 @@ Costs OpLevelCostEstimator::PredictVariable(const OpContext& op_context) const { Costs OpLevelCostEstimator::PredictBatchMatMul( const OpContext& op_context) const { - const auto& op_features = op_context.op_info; + const auto& op_info = op_context.op_info; bool found_unknown_shapes = false; Costs costs = PredictOpCountBasedCost( - CountBatchMatMulOperations(op_features, &found_unknown_shapes), - op_features); + CountBatchMatMulOperations(op_info, &found_unknown_shapes), op_info); costs.inaccurate = found_unknown_shapes; costs.num_ops_with_unknown_shapes = found_unknown_shapes; return costs; } Costs OpLevelCostEstimator::PredictMetadata(const OpContext& op_context) const { - const auto& op_features = op_context.op_info; + const auto& op_info = op_context.op_info; Costs costs = Costs::ZeroCosts(); - costs.max_memory = CalculateOutputSize(op_features, &costs.inaccurate); + costs.max_memory = CalculateOutputSize(op_info, &costs.inaccurate); costs.num_ops_with_unknown_shapes = costs.inaccurate; // Metadata operations are so cheap we assume they take the minimum amount of // time we can represent (1 ns). diff --git a/tensorflow/core/grappler/costs/op_level_cost_estimator.h b/tensorflow/core/grappler/costs/op_level_cost_estimator.h index 84dd9213f7..f8ba8c6637 100644 --- a/tensorflow/core/grappler/costs/op_level_cost_estimator.h +++ b/tensorflow/core/grappler/costs/op_level_cost_estimator.h @@ -16,10 +16,6 @@ limitations under the License. #ifndef TENSORFLOW_CORE_GRAPPLER_COSTS_OP_LEVEL_COST_ESTIMATOR_H_ #define TENSORFLOW_CORE_GRAPPLER_COSTS_OP_LEVEL_COST_ESTIMATOR_H_ -#include -#include -#include - #include "tensorflow/core/grappler/costs/cost_estimator.h" #include "tensorflow/core/grappler/costs/op_context.h" #include "tensorflow/core/grappler/costs/op_performance_data.pb.h" @@ -79,24 +75,23 @@ class OpLevelCostEstimator { int64 sy; // Stride y. Padding padding; // SAME or VALID. }; - int64 CountConv2DOperations(const OpInfo& op_features, + int64 CountConv2DOperations(const OpInfo& op_info, bool* found_unknown_shapes) const; - int64 CountConv2DOperations(const OpInfo& op_features, + int64 CountConv2DOperations(const OpInfo& op_info, ConvolutionDimensions* conv_info, bool* found_unknown_shapes) const; - int64 CountMatMulOperations(const OpInfo& op_features, + int64 CountMatMulOperations(const OpInfo& op_info, bool* found_unknown_shapes) const; - int64 CountMatMulOperations(const OpInfo& op_features, - MatMulDimensions* mat_mul, + int64 CountMatMulOperations(const OpInfo& op_info, MatMulDimensions* mat_mul, bool* found_unknown_shapes) const; - int64 CountBatchMatMulOperations(const OpInfo& op_features, + int64 CountBatchMatMulOperations(const OpInfo& op_info, bool* found_unknown_shapes) const; - int64 CountConv2DBackpropInputOperations(const OpInfo& op_features, - ConvolutionDimensions* conv_info, - bool* found_unknown_shapes) const; - int64 CountConv2DBackpropFilterOperations(const OpInfo& op_features, - ConvolutionDimensions* conv_info, - bool* found_unknown_shapes) const; + int64 CountConv2DBackpropInputOperations( + const OpInfo& op_info, ConvolutionDimensions* returned_conv_dims, + bool* found_unknown_shapes) const; + int64 CountConv2DBackpropFilterOperations( + const OpInfo& op_info, ConvolutionDimensions* returned_conv_dims, + bool* found_unknown_shapes) const; // Calculate the element count of an input/output tensor. int64 CalculateTensorElementCount(const OpInfo::TensorProperties& tensor, @@ -108,17 +103,17 @@ class OpLevelCostEstimator { // Calculate the element count of the largest // input of specified TensorFlow op. - int64 CalculateLargestInputCount(const OpInfo& op_features, + int64 CalculateLargestInputCount(const OpInfo& op_info, bool* found_unknown_shapes) const; // Calculate the total size in bytes of the all // the inputs of specified TensorFlow op. - int64 CalculateInputSize(const OpInfo& op_features, + int64 CalculateInputSize(const OpInfo& op_info, bool* found_unknown_shapes) const; // Calculate the total size in bytes of the all // the outputs of specified TensorFlow op. - int64 CalculateOutputSize(const OpInfo& op_features, + int64 CalculateOutputSize(const OpInfo& op_info, bool* found_unknown_shapes) const; // This family of routines predicts the costs to @@ -205,4 +200,5 @@ class OpLevelCostEstimator { } // end namespace grappler } // end namespace tensorflow + #endif // TENSORFLOW_CORE_GRAPPLER_COSTS_OP_LEVEL_COST_ESTIMATOR_H_ diff --git a/tensorflow/core/grappler/costs/op_level_cost_estimator_test.cc b/tensorflow/core/grappler/costs/op_level_cost_estimator_test.cc index 9a59877ac5..6a9bf13b93 100644 --- a/tensorflow/core/grappler/costs/op_level_cost_estimator_test.cc +++ b/tensorflow/core/grappler/costs/op_level_cost_estimator_test.cc @@ -29,8 +29,8 @@ namespace grappler { namespace { // Wrangles the minimum number of proto fields to set up a matrix. -void DescribeMatrix(int rows, int columns, OpInfo* op_features) { - auto input = op_features->add_inputs(); +void DescribeMatrix(int rows, int columns, OpInfo* op_info) { + auto input = op_info->add_inputs(); auto shape = input->mutable_shape(); auto shape_rows = shape->add_dim(); shape_rows->set_size(rows); @@ -39,8 +39,8 @@ void DescribeMatrix(int rows, int columns, OpInfo* op_features) { input->set_dtype(DT_FLOAT); } -void SetCpuDevice(OpInfo* op_features) { - auto device = op_features->mutable_device(); +void SetCpuDevice(OpInfo* op_info) { + auto device = op_info->mutable_device(); device->set_type("CPU"); device->set_num_cores(10); device->set_bandwidth(10000000); // 10000000 KB/s = 10 GB/s @@ -413,15 +413,14 @@ class OpLevelCostEstimatorTest : public ::testing::Test { return estimator_.PredictCosts(op_context); } - int64 CountMatMulOperations(const OpInfo& op_features, + int64 CountMatMulOperations(const OpInfo& op_info, bool* found_unknown_shapes) const { - return estimator_.CountMatMulOperations(op_features, found_unknown_shapes); + return estimator_.CountMatMulOperations(op_info, found_unknown_shapes); } - int64 CountBatchMatMulOperations(const OpInfo& op_features, + int64 CountBatchMatMulOperations(const OpInfo& op_info, bool* found_unknown_shapes) const { - return estimator_.CountBatchMatMulOperations(op_features, - found_unknown_shapes); + return estimator_.CountBatchMatMulOperations(op_info, found_unknown_shapes); } void SetComputeMemoryOverlap(bool value) { -- GitLab From 7cadc29b011e2dd73f70034f280a13931c73900e Mon Sep 17 00:00:00 2001 From: Doe Hyun Yoon Date: Mon, 7 Jan 2019 16:10:33 -0800 Subject: [PATCH 0300/2345] Add PredictCostsAndReturnRunMetadata method to CostEstimator. PiperOrigin-RevId: 228254629 --- tensorflow/core/grappler/costs/BUILD | 1 + .../costs/analytical_cost_estimator.cc | 37 +++++++++++-------- .../costs/analytical_cost_estimator.h | 14 +++---- .../core/grappler/costs/cost_estimator.h | 14 +++++++ 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/tensorflow/core/grappler/costs/BUILD b/tensorflow/core/grappler/costs/BUILD index 15dc7074b9..92294379b5 100644 --- a/tensorflow/core/grappler/costs/BUILD +++ b/tensorflow/core/grappler/costs/BUILD @@ -171,6 +171,7 @@ cc_library( visibility = ["//visibility:public"], deps = [ "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", ], ) diff --git a/tensorflow/core/grappler/costs/analytical_cost_estimator.cc b/tensorflow/core/grappler/costs/analytical_cost_estimator.cc index b7804ffaa5..eac6a0de3f 100644 --- a/tensorflow/core/grappler/costs/analytical_cost_estimator.cc +++ b/tensorflow/core/grappler/costs/analytical_cost_estimator.cc @@ -104,17 +104,15 @@ AnalyticalCostEstimator::AnalyticalCostEstimator(Cluster* cluster, bool use_static_shapes) : AnalyticalCostEstimator( cluster, absl::make_unique(), - ReadyNodeManagerFactory("FirstReady"), use_static_shapes, nullptr) {} + ReadyNodeManagerFactory("FirstReady"), use_static_shapes) {} AnalyticalCostEstimator::AnalyticalCostEstimator( Cluster* cluster, std::unique_ptr node_estimator, - std::unique_ptr node_manager, bool use_static_shapes, - RunMetadata* run_metadata) + std::unique_ptr node_manager, bool use_static_shapes) : cluster_(cluster), node_estimator_(std::move(node_estimator)), node_manager_(std::move(node_manager)), - use_static_shapes_(use_static_shapes), - run_metadata_(run_metadata) { + use_static_shapes_(use_static_shapes) { scheduler_ = absl::make_unique(use_static_shapes_, cluster_, node_manager_.get()); } @@ -128,6 +126,18 @@ Status AnalyticalCostEstimator::Initialize(const GrapplerItem& item) { Status AnalyticalCostEstimator::PredictCosts(const GraphDef& optimized_graph, CostGraphDef* cost_graph, Costs* costs) const { + RunMetadata run_metadata; + auto s = + PredictCostsAndReturnRunMetadata(optimized_graph, &run_metadata, costs); + if (s.ok() && cost_graph) { + cost_graph->Swap(run_metadata.mutable_cost_graph()); + } + return s; +} + +Status AnalyticalCostEstimator::PredictCostsAndReturnRunMetadata( + const GraphDef& optimized_graph, RunMetadata* run_metadata, + Costs* costs) const { GrapplerItem item = item_; item.graph = optimized_graph; @@ -138,7 +148,9 @@ Status AnalyticalCostEstimator::PredictCosts(const GraphDef& optimized_graph, } gtl::FlatMap name_to_cost_node; - if (cost_graph) { + CostGraphDef* cost_graph = nullptr; + if (run_metadata) { + cost_graph = run_metadata->mutable_cost_graph(); // TODO(pcma): Clear nodes in cost_graph after we make sure we always pass // in an empty cost_graph (a non-empty but incomplete cost_graph will cause // problems, e.g., no node_id in cost_graph) @@ -179,18 +191,13 @@ Status AnalyticalCostEstimator::PredictCosts(const GraphDef& optimized_graph, } } - *costs = scheduler_->Summary(run_metadata_); - // run_metadata_ gets step_stats and parition_graphs from Summary. - // Note that cost_graph could already point to the cost_graph field of - // run_metadata_, since both are set by the caller. - if (run_metadata_ && cost_graph && - run_metadata_->mutable_cost_graph() != cost_graph) - *run_metadata_->mutable_cost_graph() = *cost_graph; + // run_metadata gets step_stats and partition_graphs from Summary. + *costs = scheduler_->Summary(run_metadata); if (VLOG_IS_ON(1)) { bool verbose = VLOG_IS_ON(2); - if (run_metadata_) { - VLOG(1) << GetStatsStringFromRunMetadata(*run_metadata_, verbose); + if (run_metadata) { + VLOG(1) << GetStatsStringFromRunMetadata(*run_metadata, verbose); } else { RunMetadata run_metadata; scheduler_->GenerateRunMetadata(&run_metadata); diff --git a/tensorflow/core/grappler/costs/analytical_cost_estimator.h b/tensorflow/core/grappler/costs/analytical_cost_estimator.h index 2629672459..ade61f4051 100644 --- a/tensorflow/core/grappler/costs/analytical_cost_estimator.h +++ b/tensorflow/core/grappler/costs/analytical_cost_estimator.h @@ -39,16 +39,10 @@ class AnalyticalCostEstimator : public CostEstimator { public: // Does not take ownership of cluster. AnalyticalCostEstimator(Cluster* cluster, bool use_static_shapes); - // Does not take ownership of cluster or run_metadata - // - // When metadata is provided, step_stats and partition_graphs fields will - // always be filled during PredictCosts, and the cost_graph field of metadata - // will be filled only when cost_graph is not nullptr when invoking - // PredictCosts. AnalyticalCostEstimator(Cluster* cluster, std::unique_ptr node_estimator, std::unique_ptr node_manager, - bool use_static_shapes, RunMetadata* run_metadata); + bool use_static_shapes); ~AnalyticalCostEstimator() override {} // Initializes the estimator for the specified grappler item. @@ -61,6 +55,10 @@ class AnalyticalCostEstimator : public CostEstimator { Status PredictCosts(const GraphDef& optimized_graph, CostGraphDef* cost_graph, Costs* cost) const override; + Status PredictCostsAndReturnRunMetadata(const GraphDef& optimized_graph, + RunMetadata* run_metadata, + Costs* cost) const override; + const VirtualScheduler* GetScheduler() const { return scheduler_.get(); } private: @@ -70,8 +68,6 @@ class AnalyticalCostEstimator : public CostEstimator { std::unique_ptr node_manager_; bool use_static_shapes_; std::unique_ptr scheduler_; - - RunMetadata* run_metadata_; }; } // end namespace grappler diff --git a/tensorflow/core/grappler/costs/cost_estimator.h b/tensorflow/core/grappler/costs/cost_estimator.h index e3b3a36b09..725d81a881 100644 --- a/tensorflow/core/grappler/costs/cost_estimator.h +++ b/tensorflow/core/grappler/costs/cost_estimator.h @@ -20,6 +20,7 @@ limitations under the License. #include #include #include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/protobuf/config.pb.h" namespace tensorflow { class GraphDef; @@ -223,6 +224,19 @@ class CostEstimator { // not. virtual Status PredictCosts(const GraphDef& optimized_graph, CostGraphDef* cost_graph, Costs* cost) const = 0; + + // TODO(dyoon): Delete PredictCosts() with CostGraphDef as RunMetadata is a + // superset of CostGraphDef. + // Same method, but returns RunMetadata. + virtual Status PredictCostsAndReturnRunMetadata( + const GraphDef& optimized_graph, RunMetadata* run_metadata, + Costs* cost) const { + CostGraphDef* cost_graph = nullptr; + if (run_metadata) { + cost_graph = run_metadata->mutable_cost_graph(); + } + return PredictCosts(optimized_graph, cost_graph, cost); + } }; } // end namespace grappler -- GitLab From acfc65f0110e272abc8bd1abce867f2f534ba263 Mon Sep 17 00:00:00 2001 From: Yunxing Dai Date: Mon, 7 Jan 2019 16:11:20 -0800 Subject: [PATCH 0301/2345] [TF2XLA] Use dynamic dimension in SizeOp. This is needed if the number of elements in a tensor can be dynamic. PiperOrigin-RevId: 228254723 --- tensorflow/compiler/tf2xla/kernels/shape_op.cc | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/shape_op.cc b/tensorflow/compiler/tf2xla/kernels/shape_op.cc index 12830816ec..85b0367f73 100644 --- a/tensorflow/compiler/tf2xla/kernels/shape_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/shape_op.cc @@ -20,6 +20,7 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/xla_helpers.h" #include "tensorflow/compiler/tf2xla/xla_op_kernel.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" +#include "tensorflow/compiler/xla/client/lib/constants.h" #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/core/framework/kernel_def_builder.h" #include "tensorflow/core/framework/tensor_shape.h" @@ -91,14 +92,20 @@ class SizeOp : public XlaOpKernel { void Compile(XlaOpKernelContext* ctx) override { const TensorShape input_shape = ctx->InputShape(0); - const int64 size = input_shape.num_elements(); - OP_REQUIRES(ctx, FastBoundsCheck(size, std::numeric_limits::max()), + OP_REQUIRES(ctx, + FastBoundsCheck(input_shape.num_elements(), + std::numeric_limits::max()), errors::InvalidArgument("Size does not work for tensors > " "int32 max.")); Tensor size_constant(DT_INT32, TensorShape({})); - size_constant.scalar()() = static_cast(size); - - ctx->SetConstantOutput(0, size_constant); + const int rank = input_shape.dims(); + xla::XlaBuilder* builder = ctx->builder(); + auto size = xla::One(builder, xla::U32); + for (int64 i = 0; i < rank; ++i) { + size = xla::Mul(size, xla::GetDimensionSize(ctx->Input(0), i)); + } + size = xla::ConvertElementType(size, xla::S32); + ctx->SetOutput(0, size); } }; -- GitLab From ec569e692435fb68191553fd7ffa5b633fdf1ef2 Mon Sep 17 00:00:00 2001 From: Andy Ly Date: Mon, 7 Jan 2019 16:20:48 -0800 Subject: [PATCH 0302/2345] [Grappler] Replace NodeMap with MutableGraphView in DependencyOptimizer. PiperOrigin-RevId: 228256183 --- tensorflow/core/grappler/optimizers/BUILD | 4 + .../optimizers/dependency_optimizer.cc | 456 +++++++----------- .../optimizers/dependency_optimizer.h | 24 +- .../optimizers/dependency_optimizer_test.cc | 9 +- 4 files changed, 202 insertions(+), 291 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 7e29cee86a..5a328a91c5 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -303,14 +303,18 @@ 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", ], ) diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc index 7fee3ae9d5..9bceb6b906 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc @@ -18,10 +18,14 @@ limitations under the License. #include #include +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #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,20 +42,15 @@ namespace grappler { namespace { -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; - } +// 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; } - return removed_input; + return node_to_idx; } } // namespace @@ -68,7 +67,10 @@ bool DependencyOptimizer::SafeToRemoveIdentity(const NodeDef& node) const { // The output values of this node may be needed. return false; } - const NodeDef* input = node_map_->GetNode(NodeName(node.input(0))); + + MutableGraphView::OutputPort port = graph_view_->GetRegularFanin( + MutableGraphView::InputPort(const_cast(&node), 0)); + NodeDef* input = port.node; CHECK(input != nullptr) << "node = " << node.name() << " input = " << node.input(0); // Don't remove Identity nodes corresponding to Variable reads or following @@ -79,20 +81,23 @@ bool DependencyOptimizer::SafeToRemoveIdentity(const NodeDef& node) const { // Don't turn Identity nodes following Switch into NoOp or remove them // if it requires anchoring a control dependencies the Switch node, which // is not valid. - if (str_util::StartsWith(node.name(), kConstantFoldingCtrl)) { - // TODO(rmlarsen): Try to remove this artificial contraint. + MutableGraphView::OutputPort control_port(const_cast(&node), + Graph::kControlSlot); + if (!graph_view_->GetFanout(control_port).empty()) { return false; } } - for (auto consumer : node_map_->GetOutputs(node.name())) { - if (node.input_size() > 1 && IsMerge(*consumer)) { + bool node_has_multiple_inputs = + graph_view_->NumFanins(node, /*include_controlling_nodes=*/true) > 1; + for (auto consumer : + graph_view_->GetFanouts(node, /*include_controlled_nodes=*/true)) { + if (node_has_multiple_inputs && IsMerge(*consumer.node)) { return false; } if (IsSwitch(*input)) { - for (const string& consumer_input : consumer->input()) { - if (consumer_input == AsControlDependency(node.name())) { - return false; - } + if (graph_view_->HasFanin(*consumer.node, + {node.name(), Graph::kControlSlot})) { + return false; } } } @@ -126,7 +131,7 @@ bool DependencyOptimizer::SafeToConvertToNoOp(const NodeDef& node) const { if (!SafeToRemoveIdentity(node)) { return false; } - if (NumNonControlOutputs(node, *node_map_) > 0) { + if (graph_view_->NumFanouts(node, /*include_controlled_nodes=*/false) > 0) { // The output values of this node may be needed. return false; } @@ -134,61 +139,56 @@ bool DependencyOptimizer::SafeToConvertToNoOp(const NodeDef& node) const { } int DependencyOptimizer::NumEdgesIfBypassed( - const NodeDef& node, const std::vector& output_nodes) const { + const NodeDef& node, int num_fanins, + const absl::flat_hash_set& fanout_edges) const { const bool is_multi_input_identity_n = IsIdentityN(node) && !IsIdentityNSingleInput(node); - const int num_outputs = output_nodes.size(); - const int num_inputs = node.input_size(); + const int num_fanouts = fanout_edges.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); - for (string input_node_name : node.input()) { - if (IsControlInput(input_node_name)) { - num_edges_if_bypassed += num_outputs; + int num_non_controlling_fanins = + graph_view_->NumFanins(node, /*include_controlling_nodes=*/false); + num_edges_if_bypassed += num_non_controlling_fanins; + num_edges_if_bypassed += + (num_fanins - num_non_controlling_fanins) * num_fanouts; + + for (const auto& fanout : fanout_edges) { + if (fanout.dst.port_id == Graph::kControlSlot) { + num_edges_if_bypassed += num_fanins; } 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_inputs * num_outputs; + return num_fanins * num_fanouts; } } bool DependencyOptimizer::BypassingNodeIsBeneficial( - const NodeDef& node, const std::vector& input_nodes, - const std::vector& output_nodes) const { + const NodeDef& node, + const absl::flat_hash_set& fanins, + const absl::flat_hash_set& fanout_edges) const { const bool is_identity = IsIdentity(node) || IsIdentityNSingleInput(node); const bool is_multi_input_identity_n = IsIdentityN(node) && !IsIdentityNSingleInput(node); - const int num_outputs = output_nodes.size(); - const int num_inputs = node.input_size(); + const int num_outputs = fanout_edges.size(); + const int num_inputs = fanins.size(); - if (NumEdgesIfBypassed(node, output_nodes) > num_inputs + num_outputs) { + if (NumEdgesIfBypassed(node, num_inputs, fanout_edges) > + num_inputs + num_outputs) { return false; } // Make sure that we don't increase the number of edges that cross // device boundaries. if ((num_inputs == 1 && num_outputs > 1 && - input_nodes[0]->device() != node.device()) || + fanins.begin()->node->device() != node.device()) || (num_inputs > 1 && num_outputs == 1 && - output_nodes[0]->device() != node.device())) { + fanout_edges.begin()->dst.node->device() != node.device())) { return false; } @@ -197,12 +197,12 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( // cost before and after. const string& node_dev = node.device(); int num_cross_in = 0; - for (NodeDef* input_node : input_nodes) { - num_cross_in += static_cast(input_node->device() != node_dev); + for (const auto& fanin : fanins) { + num_cross_in += static_cast(fanin.node->device() != node_dev); } int num_cross_out = 0; - for (NodeDef* output_node : output_nodes) { - num_cross_out += static_cast(output_node->device() != node_dev); + for (const auto& fanout : fanout_edges) { + num_cross_out += static_cast(fanout.dst.node->device() != node_dev); } if ((is_identity || is_multi_input_identity_n) && num_cross_in > 0 && @@ -216,10 +216,10 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( // 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 (NodeDef* input_node : input_nodes) { - for (NodeDef* output_node : output_nodes) { + for (const auto& fanin : fanins) { + for (const auto& fanout : fanout_edges) { num_cross_after += - static_cast(input_node->device() != output_node->device()); + static_cast(fanin.node->device() != fanout.dst.node->device()); } } if (num_cross_after > num_cross_before) { @@ -228,45 +228,32 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( return true; } -void DependencyOptimizer::OptimizeNode(int node_idx, - SetVector* nodes_to_simplify, - std::set* nodes_to_delete) { - NodeDef* node = optimized_graph_->mutable_node(node_idx); +void DependencyOptimizer::OptimizeNode(NodeDef* node, + SetVector* nodes_to_simplify, + std::set* nodes_to_delete) { + const string node_name = node->name(); const bool is_noop = IsNoOp(*node); const bool is_identity = IsIdentity(*node) || IsIdentityNSingleInput(*node); const bool is_multi_input_identity = IsIdentityN(*node) && !IsIdentityNSingleInput(*node); - 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 (IsConstant(*node) && + graph_view_->NumFanins(*node, /*include_controlling_nodes=*/true) == 0) { + for (const auto& fanout : + graph_view_->GetFanouts(*node, /*include_controlled_nodes=*/true)) { + if (graph_view_->RemoveFanin(fanout.node->name(), + {node_name, Graph::kControlSlot})) { + nodes_to_simplify->PushBack(fanout.node); } } - if (node_map_->GetOutputs(node_name).empty() && fetch_nodes_known_ && + + if (graph_view_->NumFanouts(*node, /*include_controlled_nodes=*/true) == + 0 && + fetch_nodes_known_ && nodes_to_preserve_.find(node_name) == nodes_to_preserve_.end()) { // Mark the node for deletion. - nodes_to_delete->insert(node_to_idx_[node]); + nodes_to_delete->insert(node->name()); } return; } @@ -277,33 +264,23 @@ void DependencyOptimizer::OptimizeNode(int node_idx, << ") 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. - 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; + int num_non_controlling_fanins = + graph_view_->NumFanins(*node, /*include_controlling_nodes=*/false); + if (num_non_controlling_fanins > 0) { + absl::flat_hash_set non_controlling_fanins( + node->input().begin(), + node->input().begin() + num_non_controlling_fanins); + graph_view_->RemoveAllFanins(node_name, + /*keep_controlling_fanins=*/true); + for (const string& fanin : non_controlling_fanins) { + TensorId tensor_id = ParseTensorName(fanin); + graph_view_->AddControllingFanin(node_name, tensor_id); + nodes_to_simplify->PushBack(graph_view_->GetNode(tensor_id.node())); } - // 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; } node->set_op("NoOp"); node->clear_attr(); - nodes_to_simplify->PushBack(node_to_idx_[node]); + nodes_to_simplify->PushBack(node); return; } @@ -357,110 +334,80 @@ void DependencyOptimizer::OptimizeNode(int node_idx, if (is_noop || ((is_identity || is_multi_input_identity) && SafeToRemoveIdentity(*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 fanins = + graph_view_->GetFanins(*node, /*include_controlling_nodes=*/true); + auto fanout_edges = + graph_view_->GetFanoutEdges(*node, /*include_controlled_edges=*/true); - if (!BypassingNodeIsBeneficial(*node, input_nodes, output_nodes)) { + if (!BypassingNodeIsBeneficial(*node, fanins, fanout_edges)) { return; } + int num_non_controlling_fanins = + graph_view_->NumFanins(*node, /*include_controlling_nodes=*/false); VLOG(1) << "***** Rerouting input around\n" << node->DebugString(); // Now remove the node and re-wire its inputs to its outputs. - for (auto consumer : output_nodes) { + for (auto fanout_edge : fanout_edges) { bool updated_consumer = false; + NodeDef* consumer = fanout_edge.dst.node; 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); - } + if (fanout_edge.src.port_id == Graph::kControlSlot) { + updated_consumer = graph_view_->RemoveFanin( + consumer->name(), {node_name, Graph::kControlSlot}); + if (updated_consumer) { + // Add all non controlling fanins of node as controlling fanins to + // consumer. + for (int i = 0; i < num_non_controlling_fanins; ++i) { + TensorId input = ParseTensorName(node->input(i)); + if (graph_view_->AddControllingFanin(consumer->name(), input)) { + nodes_to_simplify->PushBack(graph_view_->GetNode(input.node())); } } - 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; - } } + } else { + updated_consumer = graph_view_->UpdateFanin( + consumer->name(), {node_name, fanout_edge.src.port_id}, + ParseTensorName(node->input(fanout_edge.src.port_id))); } - // 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]); + // Forward all controlling fanins of node to consumer. + for (int i = num_non_controlling_fanins; i < node->input_size(); ++i) { + TensorId input = ParseTensorName(node->input(i)); + if (graph_view_->AddFanin(consumer->name(), input)) { + nodes_to_simplify->PushBack(graph_view_->GetNode(input.node())); + } + } + nodes_to_simplify->PushBack(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()) { // Mark the node for deletion. - nodes_to_delete->insert(node_idx); + nodes_to_delete->insert(node_name); // Disconnect the node from its inputs to enable further optimizations. - node_map_->RemoveInputs(node_name); - node->clear_input(); + graph_view_->RemoveAllFanins(node_name, + /*keep_controlling_fanins=*/false); } } } -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; - 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(i); + SetVector nodes_to_simplify; + std::set nodes_to_delete; + for (int i = 0; i < graph_view_->graph()->node_size(); ++i) { + NodeDef* node = graph_view_->graph()->mutable_node(i); + if (IsNoOp(*node) || IsIdentity(*node) || IsIdentityN(*node) || + IsConstant(*node) || SafeToConvertToNoOp(*node)) { + nodes_to_simplify.PushBack(node); } } while (!nodes_to_simplify.Empty()) { - int node_to_simplify = nodes_to_simplify.PopBack(); + NodeDef* 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()) { + while (nodes_to_delete.find(node_to_simplify->name()) != + nodes_to_delete.end()) { node_to_simplify = nodes_to_simplify.PopBack(); } OptimizeNode(node_to_simplify, &nodes_to_simplify, &nodes_to_delete); @@ -468,17 +415,17 @@ Status DependencyOptimizer::OptimizeDependencies() { if (fetch_nodes_known_) { VLOG(1) << "Deleted " << nodes_to_delete.size() << " out of " - << optimized_graph_->node_size() << " nodes."; - EraseNodesFromGraph(nodes_to_delete, optimized_graph_); - node_map_.reset(new NodeMap(optimized_graph_)); - BuildNodeToIdx(); + << graph_view_->graph()->node_size() << " nodes."; + graph_view_->DeleteNodes(nodes_to_delete); } return Status::OK(); } Status DependencyOptimizer::TransitiveReduction() { // PRECONDITION: optimized_graph_ must be sorted topologically. - const int num_nodes = optimized_graph_->node_size(); + GraphDef* graph = graph_view_->graph(); + auto node_to_idx = BuildNodeToIdx(*graph); + const int num_nodes = 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. @@ -487,20 +434,20 @@ Status DependencyOptimizer::TransitiveReduction() { std::vector, 2>> control_outputs( num_nodes); for (int node_idx = 0; node_idx < num_nodes; ++node_idx) { - const NodeDef& node = optimized_graph_->node(node_idx); + const NodeDef& node = graph->node(node_idx); if (ModifiesFrameInfo(node) || !HasOpDef(node)) { // Ignore function nodes and nodes that modify frame info. continue; } 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); + const NodeDef* input_node = graph_view_->GetNode(NodeName(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; @@ -566,16 +513,12 @@ Status DependencyOptimizer::TransitiveReduction() { for (const auto& it : control_edges_to_remove) { const int target = it.first; - NodeDef* target_node = optimized_graph_->mutable_node(target); + const NodeDef& target_node = graph->node(target); + const string target_node_name = target_node.name(); 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(); + const TensorId tensor_id = ParseTensorName(target_node.input(input_slot)); + graph_view_->RemoveFanin(target_node_name, tensor_id); ++num_controls_removed; } } @@ -584,26 +527,17 @@ 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 +// A->C', B->C', C'->C void DependencyOptimizer::GroupCrossDeviceControlEdges() { - const int num_nodes = optimized_graph_->node_size(); + const int num_nodes = graph_view_->graph()->node_size(); for (int i = 0; i < num_nodes; ++i) { - NodeDef* node = optimized_graph_->mutable_node(i); + NodeDef* node = graph_view_->graph()->mutable_node(i); if (node->device().empty()) continue; // Creates new noop nodes for devices on which multiple control inputs are @@ -614,66 +548,50 @@ void DependencyOptimizer::GroupCrossDeviceControlEdges() { // that device. std::map noops; int num_noops = 0; - 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; - } + 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)); } } } // Reroute existing control edges to go via the newly introduced NoOp nodes. - 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& controlling_fanin : controlling_fanins) { + auto it = noops.find(controlling_fanin.node->device()); + if (it != noops.end() && it->second != nullptr) { + TensorId tensor_id(controlling_fanin.node->name(), Graph::kControlSlot); + graph_view_->RemoveFanin(node->name(), tensor_id); + graph_view_->AddFanin(it->second->name(), tensor_id); } } for (const auto& entry : noops) { if (entry.second) { - node->add_input(AsControlDependency(*entry.second)); - node_map_->AddOutput(entry.second->name(), node->name()); + graph_view_->AddFanin(node->name(), + {entry.second->name(), Graph::kControlSlot}); } } } @@ -681,22 +599,17 @@ void DependencyOptimizer::GroupCrossDeviceControlEdges() { Status DependencyOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, GraphDef* optimized_graph) { - optimized_graph_ = optimized_graph; - *optimized_graph_ = item.graph; + *optimized_graph = item.graph; nodes_to_preserve_ = item.NodesToPreserve(); fetch_nodes_known_ = !item.fetch.empty(); - CleanControlInputs(); + graph_view_.reset(new MutableGraphView(optimized_graph)); 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_); - // Set up index-based graph datastructures to speed up analysis steps below. - node_map_.reset(new NodeMap(optimized_graph_)); - BuildNodeToIdx(); - + topo_sort_status = TopologicalSort(optimized_graph); if (topo_sort_status.ok()) { // Remove redundant control dependencies. TF_RETURN_IF_ERROR(TransitiveReduction()); @@ -709,9 +622,6 @@ Status DependencyOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, // nodes. TF_RETURN_IF_ERROR(OptimizeDependencies()); - // Dedup control inputs. - CleanControlInputs(); - GroupCrossDeviceControlEdges(); } diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer.h b/tensorflow/core/grappler/optimizers/dependency_optimizer.h index 7b032673fb..eb7fc89080 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer.h +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer.h @@ -17,6 +17,8 @@ limitations under the License. #define TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DEPENDENCY_OPTIMIZER_H_ #include + +#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" @@ -46,24 +48,22 @@ 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, const std::vector& input_nodes, - const std::vector& output_nodes) const; - int NumEdgesIfBypassed(const NodeDef& node, - const std::vector& output_nodes) const; + const NodeDef& node, + const absl::flat_hash_set& fanins, + const absl::flat_hash_set& fanout_edges) const; + int NumEdgesIfBypassed( + const NodeDef& node, int num_fanins, + const absl::flat_hash_set& fanout_edges) 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; - // 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. - void OptimizeNode(int node_idx, SetVector* nodes_to_simplify, - std::set* nodes_to_delete); + void OptimizeNode(NodeDef* node, SetVector* nodes_to_simplify, + std::set* nodes_to_delete); // Eliminates redundant control dependencies by computing the transitive // reduction of the graph. Status TransitiveReduction(); @@ -76,9 +76,7 @@ class DependencyOptimizer : public GraphOptimizer { RewriterConfig::Toggle opt_level_; bool fetch_nodes_known_; std::unordered_set nodes_to_preserve_; - std::unique_ptr node_map_; - std::unordered_map node_to_idx_; - GraphDef* optimized_graph_; // Not owned. + std::unique_ptr graph_view_; }; } // end namespace grappler diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc b/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc index 8d70d9d5c7..673fc987a8 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc @@ -91,7 +91,7 @@ TEST_F(DependencyOptimizerTest, DependenciesDrivenByConstants) { // The 'z' node should have been optimized away leaving only 5 nodes. EXPECT_EQ(5, output.node_size()); - for (const NodeDef& node : item.graph.node()) { + for (const NodeDef& node : output.node()) { if (node.name() == "id1" || node.name() == "id2") { EXPECT_EQ(1, node.input_size()); EXPECT_EQ("add", node.input(0)); @@ -125,8 +125,8 @@ TEST_F(DependencyOptimizerTest, ChangeToNoop) { 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); + for (int i = 0; i < output.node_size(); ++i) { + const NodeDef& node = output.node(i); // "add" should get turned into a NoOp and removed. EXPECT_NE("add", node.name()); if (node.name() == "id1") { @@ -164,7 +164,6 @@ TEST_F(DependencyOptimizerTest, ChangeToNoop_RepeatedInput) { item.graph.Swap(&output); status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - LOG(INFO) << output.DebugString(); EXPECT_EQ(item.graph.node_size(), output.node_size()); int found = 0; @@ -748,7 +747,7 @@ TEST_F(DependencyOptimizerTest, Identity_DeviceCrossing_ConsumerOnSameDevice) { GraphDef output; Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - LOG(INFO) << output.DebugString(); + EXPECT_EQ(3, output.node_size()); for (const auto& node : output.node()) { EXPECT_NE("x_on_2", node.name()); -- GitLab From fed73306b650d0b5c99689e8b142a610fd8a3488 Mon Sep 17 00:00:00 2001 From: Jian Li Date: Mon, 7 Jan 2019 16:32:46 -0800 Subject: [PATCH 0303/2345] Update unidirectional rnn test case. PiperOrigin-RevId: 228257798 --- tensorflow/lite/kernels/unidirectional_sequence_rnn_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/unidirectional_sequence_rnn_test.cc b/tensorflow/lite/kernels/unidirectional_sequence_rnn_test.cc index 494c449589..de1f7818bd 100644 --- a/tensorflow/lite/kernels/unidirectional_sequence_rnn_test.cc +++ b/tensorflow/lite/kernels/unidirectional_sequence_rnn_test.cc @@ -255,7 +255,7 @@ class HybridUnidirectionalRNNOpModel : public UnidirectionalRNNOpModel { tensor_type_ = tensor_type; } - void SetWeights(int weights_idx, std::vector f) { + void SetWeights(int weights_idx, const std::vector& f) { if (tensor_type_ == TensorType_UINT8) { SymmetricQuantizeAndPopulate(weights_idx, f); } else { -- GitLab From b9762fed18234b2d87efc9786384e467008fc674 Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Mon, 7 Jan 2019 16:50:53 -0800 Subject: [PATCH 0304/2345] Add benchmarks for pack_lhs PiperOrigin-RevId: 228260001 --- tensorflow/core/kernels/BUILD | 1 + .../eigen_spatial_convolutions_test.cc | 226 +++++++++++++++++- 2 files changed, 214 insertions(+), 13 deletions(-) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 136902e249..701bda7ef6 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -2511,6 +2511,7 @@ tf_cc_tests( ":eigen_helpers", "//tensorflow/core:test", "//tensorflow/core:test_main", + "@com_google_absl//absl/strings", ], ) diff --git a/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc b/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc index 22f71d6260..03002adec4 100644 --- a/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc +++ b/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc @@ -14,6 +14,8 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/kernels/eigen_spatial_convolutions.h" + +#include "absl/strings/str_cat.h" #include "tensorflow/core/kernels/eigen_cuboid_convolution.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/platform/test_benchmark.h" @@ -1540,22 +1542,187 @@ static void PackRhsHelper(int iters, pack_rhs(packed.data() + packed_offset, sub_mapper, depth, cols); } tensorflow::testing::StopTiming(); + tensorflow::testing::SetLabel( + absl::StrCat("patch: ", patch_rows, "x", patch_cols, " D", patch_depth, + "; num_patches=", num_patches, " patch_size=", patch_size, + " num_inputs=", num_inputs)); +} + +static void PackLhsHelper(int iters, + /* Input dimensions: */ + int input_depth, + /* Filter (kernel) dimensions: */ + int filter_count, int filter_cols, int filter_rows, + /* Block dimensions: */ + Index block_rows, Index block_cols) { + // Set random seed for benchmark repeatability. + srand(12345); + + eigen_assert(block_rows <= filter_count); + eigen_assert(block_cols <= input_depth * filter_rows * filter_cols); + + tensorflow::testing::UseRealTime(); + tensorflow::testing::StopTiming(); + + using Dimensions = Eigen::DSizes; + + // Default Eigen::Tensor layout is column major, so we configure dimensions + // starting from the inner most (`filter count` aka `kernel filers`). + Dimensions filter_dims(filter_count, filter_rows, filter_cols, input_depth); + + static const int packet_size = Eigen::internal::packet_traits::size; + + // We are going to reshape filter into 2D tensor. + using NewDimension = Eigen::DSizes; + + // Contraction dimensions. + using nocontract_t = Eigen::array; + using contract_t = Eigen::array; + + // Input to the ReshapeOp. It is the tensorflow TTypes::Tensor + // with ColMajor layout, instead of RowMajor. But that doesn't make any + // difference, because TensorContraction swaps LHS with RHS for row major + // inputs, and contraction mapper always works with column major data. + using ArgType = TensorMap, Eigen::Aligned>; + + using Evaluator = + TensorEvaluator, + Eigen::DefaultDevice>; + + using InputMapper = Eigen::internal::TensorContractionInputMapper< + float, Index, Eigen::internal::Lhs, Evaluator, // + nocontract_t, contract_t, // + packet_size, // + /*inner_dim_contiguous*/ true, // + /*inner_dim_reordered*/ false, // + /*Alignment*/ 0>; + + using SubMapper = Eigen::internal::TensorContractionSubMapper< + float, Index, Eigen::internal::Lhs, Evaluator, // + nocontract_t, contract_t, // + packet_size, // + /*inner_dim_contiguous*/ true, // + /*inner_dim_reordered*/ false, // + /*Alignment*/ 0>; + +#if defined(TENSORFLOW_USE_MKLDNN_CONTRACTION_KERNEL) + using PackLhsImpl = Eigen::internal::mkldnn_gemm_pack; +#else + using PackLhsImpl = + Eigen::internal::gemm_pack_lhs; +#endif + + Eigen::DefaultDevice device; - std::ostringstream stringStream; - stringStream << "patch: " << patch_rows << "x" << patch_cols << " D" - << patch_depth << "; num_patches=" << num_patches - << " patch_size=" << patch_size << " num_inputs=" << num_inputs; - tensorflow::testing::SetLabel(stringStream.str()); + // We will reshape kernel into 2D tensor. + NewDimension reshape_dims; + reshape_dims[0] = filter_count; + reshape_dims[1] = input_depth * filter_rows * filter_cols; + + // We are going to contract along the 'in_depth * filter_rows * filter_cols`. + nocontract_t nocontract_dim = {0}; + contract_t contract_dim = {1}; + + // These values computed using the algorithm in TensorContraction.h, with + // 'nocontract_dim' and 'contract_dim' values specified above. + nocontract_t nocontract_strides = {1}; + contract_t contract_strides = {filter_count}; + nocontract_t i_strides = {1}; + contract_t k_strides = {1}; + + // We use tensor of the same dimensions to store packed data. + Tensor packed(filter_dims); + + // We generate multiple filter tensors, around 512mb in total size to measure + // realistic workload when input data in not in L1-L3 cache. + size_t input_bytes = filter_dims.TotalSize() * sizeof(float); + size_t mem_size_bytes = 1024 * 1024 * 512; + size_t num_filters = + std::max(static_cast(1), mem_size_bytes / input_bytes); + + std::vector> filters; + std::vector evaluators; + std::vector input_mappers; + + for (int i = 0; i < num_filters; ++i) { + filters.emplace_back(filter_dims); + filters[i].setRandom(); + + ArgType tensor_map(filters[i].data(), filter_dims); + + const auto reshape_op = + TensorReshapingOp(tensor_map, reshape_dims); + + evaluators.emplace_back(reshape_op, device); + + input_mappers.emplace_back(evaluators[i], nocontract_strides, i_strides, + contract_strides, k_strides); + } + + PackLhsImpl pack_lhs; + + const Index packed_total_size = filter_dims.TotalSize(); + + // Round up row/col/memory offsets to make them multiple of packet size. + const auto round_up = [](const Index idx) { + return (idx / packet_size) * packet_size; + }; + + // Block rows is in the [0, filter_count) range. + // Block cols is in the [0, filter_rows * filter_cols * input_depth) range. + + const Index max_row = filter_count; + const Index max_col = filter_rows * filter_cols * input_depth; + + tensorflow::testing::StartTiming(); + for (int i = 0; i < iters; ++i) { + int filter_idx = + num_filters == 1 ? 1 : internal::random(0, num_filters - 1); + + Index row_offset = round_up(internal::random(0, max_row - 10)); + Index col_offset = round_up(internal::random(0, max_col - 10)); + + Index rows = std::min(block_rows, max_row - row_offset); + Index cols = std::min(block_cols, max_col - col_offset); + + // Write packed data to random memory location to emulate cold caches. + Index packed_offset = round_up( + internal::random(0, packed_total_size - rows * cols - 1)); + + SubMapper sub_mapper = + input_mappers[filter_idx].getSubMapper(row_offset, col_offset); + + // NOTE: Eigen gemm_pack_lhs accepts contraction depth (k-th dimension) as a + // first argument (aka block cols). MKL-DNN pack is generic for lhs and rhs + // and accepts block rows and cols in the same order for lhs and rhs. +#if defined(TENSORFLOW_USE_MKLDNN_CONTRACTION_KERNEL) + pack_lhs(packed.data() + packed_offset, sub_mapper, rows, cols); +#else + pack_lhs(packed.data() + packed_offset, sub_mapper, cols, rows); +#endif + } + tensorflow::testing::StopTiming(); + tensorflow::testing::SetLabel(absl::StrCat( + "filter: count=", filter_count, " dims=", filter_rows, "x", filter_cols, + "; input: depth=", input_depth, "; num_filers=", num_filters)); } // -------------------------------------------------------------------------- // -// Macro argumentnames: +// Pack RHS +// +// Macro argument names: // N: batch size // H: height // W: width // C: input channels // FC: filter channles // FH: filter height +// FW: filter width // SH: stride in height dimensions // SW: stride in width dimensions // BR: block rows @@ -1563,16 +1730,16 @@ static void PackRhsHelper(int iters, #define BM_CONCAT(a, b) a##b -#define BM_NAME(prefix, N, H, W, C, FC, FH, FW, SH, SW, BR, BC) \ +#define BM_RHS_NAME(prefix, N, H, W, C, FC, FH, FW, SH, SW, BR, BC) \ BM_CONCAT(BM_##prefix##_##N##_##H##x##W##_IC##C##_FC##FC##_##FH##x##FW, \ _s##SH##x##SW##_B##BR##x##BC) -#define BM_PackRhs(N, H, W, C, FC, FH, FW, SH, SW, BR, BC) \ - static void BM_NAME(PackRhs, N, H, W, C, FC, FH, FW, SH, SW, BR, \ - BC)(int iters) { \ - PackRhsHelper(iters, N, H, W, C, FC, FH, FW, SH, SW, BR, BC); \ - } \ - BENCHMARK(BM_NAME(PackRhs, N, H, W, C, FC, FH, FW, SH, SW, BR, BC)) +#define BM_PackRhs(N, H, W, C, FC, FH, FW, SH, SW, BR, BC) \ + static void BM_RHS_NAME(PackRhs, N, H, W, C, FC, FH, FW, SH, SW, BR, \ + BC)(int iters) { \ + PackRhsHelper(iters, N, H, W, C, FC, FH, FW, SH, SW, BR, BC); \ + } \ + BENCHMARK(BM_RHS_NAME(PackRhs, N, H, W, C, FC, FH, FW, SH, SW, BR, BC)) // Number of input channel (input depth) it equal to the number of patch // channels (patch depth). @@ -1645,4 +1812,37 @@ BM_PackRhs(/*batch*/ 32, // /*filter*/ 3, 3, // /*stride*/ 2, 2, // /*block*/ 36, 432); + +// -------------------------------------------------------------------------- // +// Pack LHS +// +// Macro argument names: +// C: input channels +// FC: filter channels +// FH: filter height +// FW: filter width +// BR: block rows +// BC: block cols + +#define BM_LHS_NAME(prefix, C, FC, FH, FW, BR, BC) \ + BM_CONCAT(BM_##prefix##_##C##_FC##FC##_##FH##x##FW, _B##BR##x##BC) + +#define BM_PackLhs(C, FC, FH, FW, BR, BC) \ + static void BM_LHS_NAME(PackLhs, C, FC, FH, FW, BR, BC)(int iters) { \ + PackLhsHelper(iters, C, FC, FH, FW, BR, BC); \ + } \ + BENCHMARK(BM_LHS_NAME(PackLhs, C, FC, FH, FW, BR, BC)) + +// Number of input channel (input depth) it equal to the number of patch +// channels (patch depth). + +BM_PackLhs(/*input channels*/ 128, // + /*filter channels*/ 1024, // + /*filter dims*/ 3, 3, // + /*block*/ 256, 56); + +BM_PackLhs(/*input channels*/ 128, // + /*filter channels*/ 1024, // + /*filter dims*/ 3, 3, // + /*block*/ 56, 256); } // namespace Eigen -- GitLab From a253b9eab582307315567ba36d2b25033ad29b8b Mon Sep 17 00:00:00 2001 From: Anna R Date: Mon, 7 Jan 2019 16:54:22 -0800 Subject: [PATCH 0305/2345] Change tf_upgrade_v2.py script to wrap labels argument with tf.stop_gradients in tf.softmax_cross_entropy_with_logits call. PiperOrigin-RevId: 228260462 --- tensorflow/tools/compatibility/BUILD | 1 + tensorflow/tools/compatibility/reorders_v2.py | 1 + .../compatibility/testdata/test_file_v1_12.py | 9 ++++ .../tools/compatibility/tf_upgrade_v2.py | 45 +++++++++++++++++-- .../tools/compatibility/tf_upgrade_v2_test.py | 28 +++++++----- 5 files changed, 69 insertions(+), 15 deletions(-) diff --git a/tensorflow/tools/compatibility/BUILD b/tensorflow/tools/compatibility/BUILD index 452e6db941..31dbc02963 100644 --- a/tensorflow/tools/compatibility/BUILD +++ b/tensorflow/tools/compatibility/BUILD @@ -69,6 +69,7 @@ py_library( ":ast_edits", ":renames_v2", ":reorders_v2", + "@six_archive//:six", ], ) diff --git a/tensorflow/tools/compatibility/reorders_v2.py b/tensorflow/tools/compatibility/reorders_v2.py index 3f05aea6ca..5c11388516 100644 --- a/tensorflow/tools/compatibility/reorders_v2.py +++ b/tensorflow/tools/compatibility/reorders_v2.py @@ -65,6 +65,7 @@ reorders = { 'tf.nn.moments': ['x', 'axes', 'shift', 'name', 'keep_dims'], 'tf.nn.pool': ['input', 'window_shape', 'pooling_type', 'padding', 'dilation_rate', 'strides', 'name', 'data_format'], 'tf.nn.separable_conv2d': ['input', 'depthwise_filter', 'pointwise_filter', 'strides', 'padding', 'rate', 'name', 'data_format'], + 'tf.nn.softmax_cross_entropy_with_logits': ['_sentinel', 'labels', 'logits', 'dim', 'name'], 'tf.nn.space_to_batch': ['input', 'paddings', 'block_size', 'name'], 'tf.nn.space_to_depth': ['input', 'block_size', 'name', 'data_format'], 'tf.nn.weighted_moments': ['x', 'axes', 'frequency_weights', 'name', 'keep_dims'], diff --git a/tensorflow/tools/compatibility/testdata/test_file_v1_12.py b/tensorflow/tools/compatibility/testdata/test_file_v1_12.py index 5ce4dd49ad..2663762aa7 100644 --- a/tensorflow/tools/compatibility/testdata/test_file_v1_12.py +++ b/tensorflow/tools/compatibility/testdata/test_file_v1_12.py @@ -70,6 +70,15 @@ 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]) + self.assertAllClose(out, 0.40318608) + out = tf.nn.softmax_cross_entropy_with_logits_v2( + logits=[0.1, 0.8], labels=[0, 1]) + self.assertAllClose(out, 0.40318608) + 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 9d9c5878f7..2dbbe27598 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2.py @@ -21,6 +21,7 @@ from __future__ import print_function import ast import pasta +import six from tensorflow.tools.compatibility import ast_edits from tensorflow.tools.compatibility import renames_v2 @@ -94,6 +95,10 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.convert_to_tensor": { "preferred_dtype": "dtype_hint" }, + "tf.nn.softmax_cross_entropy_with_logits": { + "dim": "axis", + "_sentinel": None, + }, "tf.nn.softmax_cross_entropy_with_logits_v2": { "dim": "axis" }, @@ -682,6 +687,10 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.norm", "tf.reverse_sequence", "tf.sparse_split", + # tf.nn.softmax_cross_entropy_with_logits *must* be called with + # keyword arguments. Add keyword arguments in rare case when they + # are not specified. + "tf.nn.softmax_cross_entropy_with_logits", } # Functions that were reordered should be changed to the new keyword args @@ -714,6 +723,8 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.to_float": self._cast_transformer, "tf.to_int32": self._cast_transformer, "tf.to_int64": self._cast_transformer, + "tf.nn.softmax_cross_entropy_with_logits": + self._softmax_cross_entropy_with_logits_transformer, } decay_function_comment = ( @@ -950,10 +961,6 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "'deterministic' arguments. Now it takes a single 'seed' arg. If " "'seed' is zero, the execution is random and deterministic " "otherwise", - "tf.nn.softmax_cross_entropy_with_logits": - "tf.nn.softmax_cross_entropy_with_logits behavior has changed. " - "'labels' needs to be wrapped with tf.stop_gradient to keep the " - "old behavior. Also, 'dim' argument has been renamed to 'axis'.", "tf.test.assert_equal_graph_def": "tf.assert_equal_graph_def no longer takes 'checkpoint_v2' " "argument. 'checkpoint_v2' now defaults to True.", @@ -1228,6 +1235,36 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): dtype_str))) return node + @staticmethod + def _softmax_cross_entropy_with_logits_transformer( + parent, node, full_name, name, logs, errors): + def _wrap_label(parent, old_value): + """Wrap labels with tf.stop_gradient.""" + if six.PY3: + new_value = ast.Call( + ast.Name(id="tf.stop_gradient", ctx=ast.Load()), + [old_value], []) + else: + new_value = ast.Call( + ast.Name(id="tf.stop_gradient", ctx=ast.Load()), + [old_value], [], None, None) + + # This copies the prefix and suffix on old_value to new_value. + pasta.ast_utils.replace_child(parent, old_value, new_value) + ast.copy_location(new_value, old_value) + + # Check if we have a labels keyword arg + for karg in node.keywords: + if karg.arg == "labels": + logs.append((node.lineno, node.col_offset, + "Changing labels arg of " + "tf.nn.softmax_cross_entropy_with_logits to " + "tf.stop_gradient(labels). Please check this " + "transformation.\n")) + _wrap_label(karg, karg.value) + return node + return node + @staticmethod def _batch_gather_transformer(parent, node, full_name, name, logs, errors): # Check if the call already has a batch_dims argument diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py index d8bc0ba75a..80d86d7a2b 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py @@ -684,26 +684,32 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map self.assertEqual(new_text, expected_text) def testSoftMaxCrossEntropyWithLogitsV2(self): - text = "tf.nn.softmax_cross_entropy_with_logits_v2(labels, logits, dim=2)" + text = ( + "tf.nn.softmax_cross_entropy_with_logits_v2(" + "labels=labels, logits=logits, dim=2)") expected_text = ( - "tf.nn.softmax_cross_entropy_with_logits(labels, logits, axis=2)") + "tf.nn.softmax_cross_entropy_with_logits(" + "labels=labels, logits=logits, axis=2)") _, unused_report, errors, new_text = self._upgrade(text) self.assertEqual(new_text, expected_text) self.assertFalse(errors) def testSoftMaxCrossEntropyWithLogits(self): - text = "tf.nn.softmax_cross_entropy_with_logits(labels, logits, dim=2)" + text = ("tf.nn.softmax_cross_entropy_with_logits(" + "labels=labels, logits=logits, dim=2)") expected_text = ( - "tf.nn.softmax_cross_entropy_with_logits(labels, logits, dim=2)") - _, report, errors, new_text = self._upgrade(text) + "tf.nn.softmax_cross_entropy_with_logits(" + "labels=tf.stop_gradient(labels), logits=logits, axis=2)") + _, unused_report, unused_errors, new_text = self._upgrade(text) self.assertEqual(new_text, expected_text) - self.assertIn( - "tf.nn.softmax_cross_entropy_with_logits requires manual check", - errors[0]) - self.assertIn( - "tf.nn.softmax_cross_entropy_with_logits behavior has changed. ", - report) + + text = ("tf.nn.softmax_cross_entropy_with_logits(" + "labels=foo(bar))") + expected_text = ("tf.nn.softmax_cross_entropy_with_logits(" + "labels=tf.stop_gradient(foo(bar)))") + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) def testSparseMatmul(self): text = ("tf.sparse_matmul(a, b, c, d, e, f, g)\n") -- GitLab From 09713e439363d763ca7c12d0c279b8d55d5b6053 Mon Sep 17 00:00:00 2001 From: Davide Libenzi Date: Mon, 7 Jan 2019 17:10:18 -0800 Subject: [PATCH 0306/2345] We should be using on host shape as the device one can have tuples in place of complex or S64 types. PiperOrigin-RevId: 228262394 --- tensorflow/compiler/xrt/tests/raw_api_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/compiler/xrt/tests/raw_api_test.cc b/tensorflow/compiler/xrt/tests/raw_api_test.cc index c8479cb778..be0c4b9392 100644 --- a/tensorflow/compiler/xrt/tests/raw_api_test.cc +++ b/tensorflow/compiler/xrt/tests/raw_api_test.cc @@ -956,6 +956,7 @@ TEST(RawApiTest, CompileAndExecuteWithS64Argument) { xrt::XRTExecutionConfig e; e.set_release_input_handles(true); e.set_release_compilation_handle(true); + e.set_return_exploded_tuple(true); Scope root = Scope::NewRootScope().WithDevice(DeviceFromFlag()); auto e_config = -- GitLab From fbbecc81be3b1eab0c15020ce3b0566e60e25c7f Mon Sep 17 00:00:00 2001 From: Yunxing Dai Date: Mon, 7 Jan 2019 17:48:53 -0800 Subject: [PATCH 0307/2345] Dynamic Padder - Change HloGetDimensionSizeRewriter to use DynamicDimensionInference, when trying to get the size of a dynamic dimension, GetDimensionSize is replaced by the hlo instruction the represents the dynamic size instead. - Implements Dynamic Padder, which uses DynamicDimensionInference to analyze the graph and inserts certain operations to make sure the result is the same as if there is no padding in the bounded shapes. PiperOrigin-RevId: 228266743 --- tensorflow/compiler/xla/service/BUILD | 41 +++++ .../compiler/xla/service/dynamic_padder.cc | 161 ++++++++++++++++++ .../compiler/xla/service/dynamic_padder.h | 44 +++++ .../xla/service/dynamic_padder_test.cc | 152 +++++++++++++++++ .../hlo_get_dimension_size_rewriter.cc | 26 ++- .../service/hlo_get_dimension_size_rewriter.h | 4 +- 6 files changed, 421 insertions(+), 7 deletions(-) create mode 100644 tensorflow/compiler/xla/service/dynamic_padder.cc create mode 100644 tensorflow/compiler/xla/service/dynamic_padder.h create mode 100644 tensorflow/compiler/xla/service/dynamic_padder_test.cc diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index 9bc6218f75..ec63ae1ac6 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -1938,6 +1938,46 @@ cc_library( ], ) +cc_library( + name = "dynamic_padder", + srcs = ["dynamic_padder.cc"], + hdrs = ["dynamic_padder.h"], + deps = [ + ":dynamic_dimension_inference", + ":hlo_dce", + ":hlo_pass", + "//tensorflow/compiler/xla:literal", + "//tensorflow/compiler/xla:literal_util", + "//tensorflow/compiler/xla:shape_util", + "//tensorflow/compiler/xla:status_macros", + "//tensorflow/compiler/xla:util", + "//tensorflow/core:lib", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + ], +) + +tf_cc_test( + name = "dynamic_padder_test", + srcs = ["dynamic_padder_test.cc"], + deps = [ + ":dynamic_padder", + "//tensorflow/compiler/xla:debug_options_flags", + "//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:xla_builder", + "//tensorflow/compiler/xla/service:hlo", + "//tensorflow/compiler/xla/service:hlo_matchers", + "//tensorflow/compiler/xla/service:hlo_runner", + "//tensorflow/compiler/xla/tests:hlo_test_base", + "//tensorflow/core:test", + ], +) + tf_cc_test( name = "dynamic_dimension_inference_test", srcs = ["dynamic_dimension_inference_test.cc"], @@ -2981,6 +3021,7 @@ cc_library( srcs = ["hlo_get_dimension_size_rewriter.cc"], hdrs = ["hlo_get_dimension_size_rewriter.h"], deps = [ + ":dynamic_dimension_inference", ":hlo", ":hlo_pass", ":shape_inference", diff --git a/tensorflow/compiler/xla/service/dynamic_padder.cc b/tensorflow/compiler/xla/service/dynamic_padder.cc new file mode 100644 index 0000000000..4db280f817 --- /dev/null +++ b/tensorflow/compiler/xla/service/dynamic_padder.cc @@ -0,0 +1,161 @@ +/* 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/dynamic_padder.h" + +#include +#include + +#include "absl/algorithm/container.h" +#include "absl/container/flat_hash_map.h" + +#include "absl/container/flat_hash_set.h" +#include "tensorflow/compiler/xla/literal.h" +#include "tensorflow/compiler/xla/literal_util.h" +#include "tensorflow/compiler/xla/service/dynamic_dimension_inference.h" +#include "tensorflow/compiler/xla/service/hlo_dce.h" +#include "tensorflow/compiler/xla/shape_util.h" +#include "tensorflow/compiler/xla/status_macros.h" +#include "tensorflow/compiler/xla/util.h" + +#include "tensorflow/core/lib/core/errors.h" + +namespace xla { + +namespace { + +// ChooseIdentityValue looks at the instruction and returns a identity value +// which, when padded, doesn't change the result of the instruction. +// +// nullopt is returned if padding doesn't need to be reset. +StatusOr ChooseIdentityValue(HloInstruction* inst) { + HloComputation* comp = inst->parent(); + // Padding on elementwise operation doesn't affect the result of the effective + // data. + if (inst->IsElementwise()) { + return nullptr; + } + + switch (inst->opcode()) { + case HloOpcode::kReduce: + case HloOpcode::kReduceWindow: { + // Because of the way we do reduce, we already require the `init` operand + // of hlo reduce instruction to be identity value. Here we reuse the + // operand. + return inst->mutable_operand(1); + } + + case HloOpcode::kConvolution: + case HloOpcode::kDot: { + // Use 0 as padding value for convolution and dot. + PrimitiveType ptype = inst->shape().element_type(); + return comp->AddInstruction( + HloInstruction::CreateConstant(LiteralUtil::Zero(ptype))); + } + + case HloOpcode::kPad: { + return inst->mutable_operand(1); + } + case HloOpcode::kParameter: + case HloOpcode::kGetDimensionSize: + case HloOpcode::kReshape: + case HloOpcode::kTuple: + case HloOpcode::kAllReduce: + case HloOpcode::kBroadcast: + return nullptr; + default: + return UnimplementedStrCat("Unimplimented padding for instruction: ", + inst->ToString()); + } +} + +} // namespace + +StatusOr DynamicPadder::Run(HloModule* module) { + bool changed = false; + VLOG(2) << "Pre DynamicPadder HLO:"; + XLA_VLOG_LINES(2, module->ToString()); + TF_ASSIGN_OR_RETURN(DynamicDimensionInference dynamic_dimension_inference, + DynamicDimensionInference::Run(module)); + + for (HloComputation* computation : module->computations()) { + for (HloInstruction* inst : computation->instructions()) { + for (int64 operand_num = 0; operand_num < inst->operand_count(); + ++operand_num) { + HloInstruction* operand = inst->mutable_operand(operand_num); + if (!operand->shape().IsArray()) { + continue; + } + for (int64 dim = 0; dim < operand->shape().rank(); ++dim) { + HloInstruction* dynamic_size = + dynamic_dimension_inference.GetDynamicSize(operand, {}, dim); + if (dynamic_size == nullptr) { + continue; + } + VLOG(1) << "Has dynamic dimension of operand" << operand_num << " @" + << dim; + TF_ASSIGN_OR_RETURN(HloInstruction * identity_value, + ChooseIdentityValue(inst)); + if (identity_value == nullptr) { + continue; + } + + // For each dimension, first generates a mask representing the + // effective area of data and padded area of data using iota and + // dynamic_size. For example, given a dimension of 7 elements and 5 + // effective elements: + // + // iota = [0, 1, 2, 3, 4, 5, 6] + // broadcast_dynamic_size = [5, 5, 5, 5, 5, 5, 5] + // mask = lt(iota, broadcast_dynamic_size) = [t, t, t, t, t, f, f] + // + // Once the mask is generated, the input data is then padded using the + // mask and pad value. + // + const Shape mask_shape = + ShapeUtil::ChangeElementType(operand->shape(), xla::U32); + const Shape pred_shape = + ShapeUtil::ChangeElementType(operand->shape(), xla::PRED); + HloInstruction* iota = computation->AddInstruction( + HloInstruction::CreateIota(mask_shape, dim)); + + HloInstruction* broadcasted_effective_size = + computation->AddInstruction(HloInstruction::CreateBroadcast( + mask_shape, dynamic_size, {})); + HloInstruction* pred = computation->AddInstruction( + HloInstruction::CreateBinary(pred_shape, HloOpcode::kLt, iota, + broadcasted_effective_size)); + + HloInstruction* broadcasted_identity_value = + computation->AddInstruction(HloInstruction::CreateBroadcast( + operand->shape(), identity_value, {})); + HloInstruction* padded = + computation->AddInstruction(HloInstruction::CreateTernary( + operand->shape(), HloOpcode::kSelect, pred, operand, + broadcasted_identity_value)); + TF_RETURN_IF_ERROR(inst->ReplaceOperandWith(operand_num, padded)); + operand = inst->mutable_operand(operand_num); + changed = true; + } + } + } + } + HloDCE dce; + TF_ASSIGN_OR_RETURN(changed, dce.Run(module)); + VLOG(2) << "Post DynamicPadder HLO:"; + XLA_VLOG_LINES(2, module->ToString()); + return changed; +} + +} // namespace xla diff --git a/tensorflow/compiler/xla/service/dynamic_padder.h b/tensorflow/compiler/xla/service/dynamic_padder.h new file mode 100644 index 0000000000..509269f7f5 --- /dev/null +++ b/tensorflow/compiler/xla/service/dynamic_padder.h @@ -0,0 +1,44 @@ +/* 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_DYNAMIC_PADDER_H_ +#define TENSORFLOW_COMPILER_XLA_SERVICE_DYNAMIC_PADDER_H_ + +#include "tensorflow/compiler/xla/service/dynamic_dimension_inference.h" +#include "tensorflow/compiler/xla/service/hlo_pass_interface.h" + +namespace xla { + +// With bounded shapes, only part of the shape contains effective data and the +// rest contains padded data, whose value can be anything depending on the +// source of the data. When a bounded shape is directly consumed by an +// instruction that collapses dimensions (reduce for example), the padding data +// would affect result of the instruction. +// +// DynamicPadder uses DynamicDimensionInference to detect bounded shapes in a +// hlo module, it then inserts certain instructions to reset the padding into an +// identity value so that in doesn't affect the result of subsequent +// instruction. For example, it'd reset the padding to 0 before a bounded shape +// is consumed by a reduce-sum. +class DynamicPadder : public HloModulePass { + public: + absl::string_view name() const override { return "dynamic_padder"; } + + StatusOr Run(HloModule* module) override; +}; + +} // namespace xla + +#endif // TENSORFLOW_COMPILER_XLA_SERVICE_DYNAMIC_PADDER_H_ diff --git a/tensorflow/compiler/xla/service/dynamic_padder_test.cc b/tensorflow/compiler/xla/service/dynamic_padder_test.cc new file mode 100644 index 0000000000..55a11286e4 --- /dev/null +++ b/tensorflow/compiler/xla/service/dynamic_padder_test.cc @@ -0,0 +1,152 @@ +/* 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/dynamic_padder.h" + +#include "tensorflow/compiler/xla/client/xla_builder.h" +#include "tensorflow/compiler/xla/literal.h" +#include "tensorflow/compiler/xla/service/hlo_computation.h" +#include "tensorflow/compiler/xla/service/hlo_instruction.h" +#include "tensorflow/compiler/xla/service/hlo_matchers.h" +#include "tensorflow/compiler/xla/service/hlo_module.h" +#include "tensorflow/compiler/xla/service/hlo_opcode.h" +#include "tensorflow/compiler/xla/service/hlo_runner.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/xla_data.pb.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test_benchmark.h" + +namespace op = xla::testing::opcode_matchers; + +namespace xla { +namespace { + +class DynamicPadderTest : public HloTestBase { + protected: + DynamicPadderTest() : HloTestBase() { module_ = CreateNewVerifiedModule(); } + + StatusOr RunPadder() { + hlo_graph_dumper::MaybeDumpHloModule(*module_, "Before padder"); + + DynamicPadder padder; + + return padder.Run(module_.get()); + } + + void ExpectPadded(const HloInstruction* inst) { + EXPECT_THAT(inst, + op::Select(op::Lt(op::Iota(), op::Broadcast(op::Parameter())), + ::testing::_, op::Broadcast())); + } + + HloComputation* GetScalarAddComputation() { + auto embedded_builder = HloComputation::Builder("add"); + auto lhs = embedded_builder.AddInstruction(HloInstruction::CreateParameter( + 0, ShapeUtil::MakeShape(F32, {}), "lhs")); + auto rhs = embedded_builder.AddInstruction(HloInstruction::CreateParameter( + 1, ShapeUtil::MakeShape(F32, {}), "rhs")); + embedded_builder.AddInstruction( + HloInstruction::CreateBinary(lhs->shape(), HloOpcode::kAdd, lhs, rhs)); + return module_->AddEmbeddedComputation(embedded_builder.Build()); + } + + std::unique_ptr module_; + const Shape scalar_shape_ = ShapeUtil::MakeShape(U32, {}); +}; + +TEST_F(DynamicPadderTest, ReduceTest) { + auto builder = HloComputation::Builder(TestName()); + auto input_shape = ShapeUtil::MakeShape(F32, {1, 2, 2}); + auto reduce_shape = ShapeUtil::MakeShape(F32, {2}); + + auto data_param = builder.AddInstruction( + HloInstruction::CreateParameter(0, input_shape, "data_param")); + builder.AddInstruction( + HloInstruction::CreateParameter(1, scalar_shape_, "size_param")); + + auto negate = builder.AddInstruction( + HloInstruction::CreateUnary(input_shape, HloOpcode::kNegate, data_param)); + + auto init = builder.AddInstruction( + HloInstruction::CreateConstant(LiteralUtil::CreateR0(0.0))); + + auto reduce = builder.AddInstruction(HloInstruction::CreateReduce( + reduce_shape, negate, init, {0, 2}, GetScalarAddComputation())); + + module_->AddEntryComputation(builder.Build()); + + // Set up dynamic parameter binding. + TF_CHECK_OK(module_->dynamic_parameter_binding().Bind( + DynamicParameterBinding::DynamicParameter{1, {}}, + DynamicParameterBinding::DynamicDimension{0, {}, 1})); + + TF_ASSERT_OK(RunPadder().status()); + + ExpectPadded(reduce->operand(0)); +} + +TEST_F(DynamicPadderTest, ConvolutionTest) { + auto builder = HloComputation::Builder(TestName()); + constexpr int xdim = 3; + constexpr int ydim = 2; + constexpr int zdim = 1; + auto xy_shape = ShapeUtil::MakeShape(F32, {xdim, ydim}); + auto yz_shape = ShapeUtil::MakeShape(F32, {ydim, zdim}); + auto zx_shape = ShapeUtil::MakeShape(F32, {zdim, xdim}); + + auto* a_param = builder.AddInstruction(HloInstruction::CreateParameter( + /*parameter_number=*/0, xy_shape, "A")); + auto* b_param = builder.AddInstruction(HloInstruction::CreateParameter( + /*parameter_number=*/1, yz_shape, "B")); + builder.AddInstruction(HloInstruction::CreateParameter( + /*parameter_number=*/2, scalar_shape_, "size_param")); + + auto dnums = XlaBuilder::CreateDefaultConvDimensionNumbers(0); + + dnums.set_kernel_input_feature_dimension(0); + dnums.set_kernel_output_feature_dimension(1); + dnums.set_input_batch_dimension(0); + dnums.set_output_batch_dimension(1); + dnums.set_output_feature_dimension(0); + + Window window; + + auto* conv = builder.AddInstruction(HloInstruction::CreateConvolve( + zx_shape, a_param, b_param, /*feature_group_count=*/1, + /*batch_group_count=*/1, window, dnums, + HloTestBase::DefaultPrecisionConfig(2))); + + module_->AddEntryComputation(builder.Build()); + + // Set up dynamic parameter binding for non-contracting dimension. + TF_CHECK_OK(module_->dynamic_parameter_binding().Bind( + DynamicParameterBinding::DynamicParameter{2, {}}, + DynamicParameterBinding::DynamicDimension{0, {}, 0})); + + // Set up binding for contracting dimensions. + TF_CHECK_OK(module_->dynamic_parameter_binding().Bind( + DynamicParameterBinding::DynamicParameter{2, {}}, + DynamicParameterBinding::DynamicDimension{0, {}, 1})); + + TF_ASSERT_OK(RunPadder().status()); + + ExpectPadded(conv->operand(0)); +} + +} // namespace +} // namespace xla diff --git a/tensorflow/compiler/xla/service/hlo_get_dimension_size_rewriter.cc b/tensorflow/compiler/xla/service/hlo_get_dimension_size_rewriter.cc index c919dbd82d..862b202971 100644 --- a/tensorflow/compiler/xla/service/hlo_get_dimension_size_rewriter.cc +++ b/tensorflow/compiler/xla/service/hlo_get_dimension_size_rewriter.cc @@ -17,6 +17,7 @@ limitations under the License. #include "absl/algorithm/container.h" #include "tensorflow/compiler/xla/literal_util.h" +#include "tensorflow/compiler/xla/service/dynamic_dimension_inference.h" #include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/service/hlo_opcode.h" #include "tensorflow/compiler/xla/service/shape_inference.h" @@ -25,7 +26,9 @@ namespace xla { namespace { -StatusOr ReplaceGetSize(HloInstruction* instr) { +StatusOr ReplaceGetSize( + HloInstruction* instr, + const DynamicDimensionInference* dynamic_dimension_inference) { if (instr->opcode() != HloOpcode::kGetDimensionSize) { return false; } @@ -36,10 +39,18 @@ StatusOr ReplaceGetSize(HloInstruction* instr) { instr->operand(0)->shape(), instr->dimension())); TF_RET_CHECK(ShapeUtil::Equal(instr->shape(), legal_shape)); TF_RET_CHECK(ShapeUtil::HasPrimitiveType(instr->shape(), U32)); - uint32 size = instr->operand(0)->shape().dimensions(instr->dimension()); - HloInstruction* new_instr = computation->AddInstruction( - HloInstruction::CreateConstant(LiteralUtil::CreateR0(size))); - TF_RETURN_IF_ERROR(instr->ReplaceAllUsesWith(new_instr)); + HloInstruction* operand = instr->mutable_operand(0); + int64 dim = instr->dimension(); + HloInstruction* dynamic_size = + dynamic_dimension_inference->GetDynamicSize(operand, {}, dim); + if (dynamic_size != nullptr) { + TF_RETURN_IF_ERROR(instr->ReplaceAllUsesWith(dynamic_size)); + } else { + uint32 size = instr->operand(0)->shape().dimensions(dim); + HloInstruction* new_instr = computation->AddInstruction( + HloInstruction::CreateConstant(LiteralUtil::CreateR0(size))); + TF_RETURN_IF_ERROR(instr->ReplaceAllUsesWith(new_instr)); + } return true; } @@ -48,10 +59,13 @@ StatusOr ReplaceGetSize(HloInstruction* instr) { StatusOr HloGetDimensionSizeRewriter::Run(HloModule* module) { bool changed = false; HloProto proto; + TF_ASSIGN_OR_RETURN(DynamicDimensionInference inference, + DynamicDimensionInference::Run(module)); *proto.mutable_hlo_module() = module->ToProto(); for (auto* computation : module->computations()) { for (auto instruction : computation->instructions()) { - TF_ASSIGN_OR_RETURN(bool replaced, ReplaceGetSize(instruction)); + TF_ASSIGN_OR_RETURN(bool replaced, + ReplaceGetSize(instruction, &inference)); changed = changed || replaced; } } diff --git a/tensorflow/compiler/xla/service/hlo_get_dimension_size_rewriter.h b/tensorflow/compiler/xla/service/hlo_get_dimension_size_rewriter.h index 30f44c23a8..9aa79fe66b 100644 --- a/tensorflow/compiler/xla/service/hlo_get_dimension_size_rewriter.h +++ b/tensorflow/compiler/xla/service/hlo_get_dimension_size_rewriter.h @@ -21,7 +21,9 @@ limitations under the License. namespace xla { -// Pass to replace a kGetDimensionSize instruction with a constant instruction. +// Pass to replace a kGetDimensionSize instruction with a hlo instruction +// representing the dynamic size if the dimension is dynamic, otherwise a +// constant instruction representing the static size. class HloGetDimensionSizeRewriter : public HloModulePass { public: absl::string_view name() const override { -- GitLab From 5ed2c498eddc0b95ae43d6f4c39e29011cd97694 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Mon, 7 Jan 2019 18:49:39 -0800 Subject: [PATCH 0308/2345] [XLA] Add testcase for ptxas bug. This ptxas bug caused the wrong output on this testcase. We never received a clear explanation of what was going on from nvidia, so I can't say more than "it used to not work, now it does work." PiperOrigin-RevId: 228271902 --- tensorflow/compiler/xla/tests/BUILD | 15 ++++ .../compiler/xla/tests/ptxas_bug_120501638.cc | 82 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tensorflow/compiler/xla/tests/ptxas_bug_120501638.cc diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 9433264917..106b3fd6c9 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -2118,3 +2118,18 @@ tf_cc_test( "@com_google_absl//absl/synchronization", ], ) + +xla_test( + name = "ptxas_bug_120501638", + srcs = ["ptxas_bug_120501638.cc"], + tags = [ + # Disabled in OSS until nvidia publicly releases a fixed ptxas. + "no_oss", + ], + deps = [ + ":hlo_test_base", + ":xla_internal_test_main", # fixdeps: keep + "//tensorflow/compiler/xla:debug_options_flags", + "//tensorflow/compiler/xla:test", + ], +) diff --git a/tensorflow/compiler/xla/tests/ptxas_bug_120501638.cc b/tensorflow/compiler/xla/tests/ptxas_bug_120501638.cc new file mode 100644 index 0000000000..0e5d7db97e --- /dev/null +++ b/tensorflow/compiler/xla/tests/ptxas_bug_120501638.cc @@ -0,0 +1,82 @@ +/* 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/debug_options_flags.h" +#include "tensorflow/compiler/xla/test.h" +#include "tensorflow/compiler/xla/tests/hlo_test_base.h" +#include "tensorflow/compiler/xla/tests/test_macros.h" + +namespace xla { +namespace { + +class PtxasBugTest : public HloTestBase {}; + +// Checks for a bug in ptxas, tracked as Google bug 120501638, and nvidia bug +// 2459377. We never received an explanation of what exactly was going wrong +// here in ptxas. Known-bad in ptxas 10.0.145, known-good in ptxas 10.0.249. +TEST_F(PtxasBugTest, DoIt) { + const char* const kModuleStr = R"( +HloModule test + +add_F32.14 { + lhs.15 = f32[] parameter(0) + rhs.16 = f32[] parameter(1) + ROOT add.17 = f32[] add(lhs.15, rhs.16) +} + +ENTRY testcase { + arg0.1 = f32[2,5,2]{2,1,0} parameter(0) + reshape.2 = f32[2,5,2]{2,1,0} reshape(arg0.1) + constant.3 = f32[] constant(0) + pad.4 = f32[2,6,2]{2,1,0} pad(reshape.2, constant.3), padding=0_0x0_1x0_0 + reshape.5 = f32[2,3,2,2]{3,2,1,0} reshape(pad.4) + transpose.6 = f32[2,2,3,2]{3,0,2,1} transpose(reshape.5), dimensions={2,0,1,3} + reshape.7 = f32[4,3,2]{2,1,0} reshape(transpose.6) + reshape.8 = f32[4,1,3,2]{3,2,1,0} reshape(reshape.7) + transpose.9 = f32[4,2,1,3]{1,3,2,0} transpose(reshape.8), dimensions={0,3,1,2} + convert.10 = f32[4,2,1,3]{1,3,2,0} convert(transpose.9) + constant.12 = f32[] constant(0) + pad.13 = f32[4,2,1,3]{3,2,1,0} pad(convert.10, constant.12), padding=0_0x0_0x0_0x0_0 + constant.11 = f32[] constant(0) + reduce-window.18 = f32[4,2,1,3]{3,2,1,0} reduce-window(pad.13, constant.11), + window={size=1x1x1x1}, to_apply=add_F32.14 + constant.19 = f32[] constant(1) + broadcast.20 = f32[4,2,1,3]{3,2,1,0} broadcast(constant.19), dimensions={} + divide.21 = f32[4,2,1,3]{3,2,1,0} divide(reduce-window.18, broadcast.20) + convert.22 = f32[4,2,1,3]{3,2,1,0} convert(divide.21) + transpose.23 = f32[4,1,3,2]{2,1,3,0} transpose(convert.22), dimensions={0,2,3,1} + reshape.24 = f32[4,3,2]{2,1,0} reshape(transpose.23) + reshape.25 = f32[2,2,3,2]{3,2,1,0} reshape(reshape.24) + transpose.26 = f32[2,3,2,2]{3,1,0,2} transpose(reshape.25), dimensions={1,2,0,3} + reshape.27 = f32[2,6,2]{2,1,0} reshape(transpose.26) + slice.28 = f32[2,5,2]{2,1,0} slice(reshape.27), slice={[0:2], [0:5], [0:2]} + reshape.29 = f32[2,5,2]{2,1,0} reshape(slice.28) + tuple.30 = (f32[2,5,2]{2,1,0}) tuple(reshape.29) + ROOT get-tuple-element.31 = f32[2,5,2]{2,1,0} get-tuple-element(tuple.30), index=0 +})"; + + // Create a module with the true-default flags, not the default-for-testing + // flags. In particular, true-default flags enable unrolling, whereas for + // testing we disable unrolling, and this bug doesn't trigger without + // unrolling. + HloModuleConfig config; + config.set_debug_options(DefaultDebugOptionsIgnoringFlags()); + TF_ASSERT_OK_AND_ASSIGN(auto module, + ParseAndReturnVerifiedModule(kModuleStr, config)); + EXPECT_TRUE(RunAndCompare(std::move(module), ErrorSpec{0.01, 0.01})); +} + +} // anonymous namespace +} // namespace xla -- GitLab From 9b884d8392706a82af674adbbff862ca2de593a6 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Mon, 7 Jan 2019 19:02:54 -0800 Subject: [PATCH 0309/2345] [XLA] Don't constant-fold instructions which transitively contain kRng or kAfterAll. Constant-folding avoids folding kRng/kAfterAll instructions, but it also needs to avoid folding instructions which *may contain* kRng/kAfterAll, because e.g. folding a call that contains an rng is morally equivalent to folding an rng. PiperOrigin-RevId: 228272774 --- .../xla/service/hlo_constant_folding.cc | 66 +++++++++++++++---- .../xla/service/hlo_constant_folding_test.cc | 46 +++++++++++++ 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/tensorflow/compiler/xla/service/hlo_constant_folding.cc b/tensorflow/compiler/xla/service/hlo_constant_folding.cc index 799cc9fd91..e7ed858e8c 100644 --- a/tensorflow/compiler/xla/service/hlo_constant_folding.cc +++ b/tensorflow/compiler/xla/service/hlo_constant_folding.cc @@ -35,6 +35,34 @@ limitations under the License. namespace xla { +// Checks whether instr is or transitively contains an instruction that we +// shouldn't fold. +// +// Specifically, we don't fold kRng or kAfterAll instructions: +// +// - kRng is already marked as side-effecting and so is skipped elsewhere, but +// we check for it here. Even kRng weren't side-effecting and took an +// explicit seed, we *still* wouldn't want to constant-fold it, because the +// evaluator's handling of rng is not guaranteed to be identical to any +// particular backend's rng. +// +// - kAfterAll needs to be skipped because a kAfterAll op with no args can +// currently materialize a token "out of thin air". TODO(b/110532604): +// Remove this check once AfterAll requires at least one operand, in which +// case constant folding will be impossible. +static bool IsOrContainsIllegalInstr(const HloInstruction* instr) { + if (instr->opcode() == HloOpcode::kAfterAll || + instr->opcode() == HloOpcode::kRng) { + return true; + } + for (const HloComputation* c : instr->called_computations()) { + if (absl::c_any_of(c->instructions(), IsOrContainsIllegalInstr)) { + return true; + } + } + return false; +} + StatusOr HloConstantFolding::Run(HloModule* module) { // Limit the constant folding to 0 iterations to skip folding loops. This // retains the behavior from before while loop support in HloEvaluator and may @@ -52,26 +80,24 @@ StatusOr HloConstantFolding::Run(HloModule* module) { computation->root_instruction() != instruction) { continue; } - // Skip Constant, Parameter, Tuple, AfterAll, Rng operations. - // Tuple constants are not directly supported by any backends, hence - // folding Tuple is not useful and would in fact be expanded back into - // kTuple by Algebraic Simplifier. - // TODO(b/110532604): Enable AfterAll once AfterAll requires at least one - // operand in which case constant folding will be impossible and this - // special case is not necessary. - if (instruction->opcode() == HloOpcode::kParameter || - instruction->opcode() == HloOpcode::kConstant || - instruction->opcode() == HloOpcode::kTuple || - instruction->opcode() == HloOpcode::kAfterAll || - instruction->opcode() == HloOpcode::kRng) { - continue; - } // Skip instructions with non-constant operands. if (!hlo_query::AllOperandsAreConstants(*instruction)) { continue; } + // Don't fold Constant, Parameter, and Tuple instructions. Tuple + // constants are not directly supported by any backends, hence folding + // Tuple is not useful and would in fact be expanded back into kTuple by + // Algebraic Simplifier. + // + // (We do allow folding subcomputations that contain these instructions.) + if (instruction->opcode() == HloOpcode::kParameter || + instruction->opcode() == HloOpcode::kConstant || + instruction->opcode() == HloOpcode::kTuple) { + continue; + } + // Broadcasts dramatically increase the size of constants, which is often // detrimental to performance and memory capacity, so do not fold // broadcasts. @@ -80,6 +106,18 @@ StatusOr HloConstantFolding::Run(HloModule* module) { continue; } + // Check for instructions that we can't fold even if they appear inside of + // a subcomputation (e.g. a kCall). + if (IsOrContainsIllegalInstr(instruction)) { + continue; + } + + // Don't constant-fold side-effecting instructions or instructions which + // contain side-effecting instructions. + if (instruction->HasSideEffect()) { + continue; + } + // Don't constant fold unless it's a net positive or the output is small. if (instruction->shape().IsArray()) { int64 elements_in_removed_operands = 0; diff --git a/tensorflow/compiler/xla/service/hlo_constant_folding_test.cc b/tensorflow/compiler/xla/service/hlo_constant_folding_test.cc index 92b748d813..4bdc980c9a 100644 --- a/tensorflow/compiler/xla/service/hlo_constant_folding_test.cc +++ b/tensorflow/compiler/xla/service/hlo_constant_folding_test.cc @@ -268,5 +268,51 @@ TEST_F(HloConstantFoldingTest, DoesNotFoldLargePad) { GmockMatch(m::Pad(m::Constant(), m::Constant()))); } +TEST_F(HloConstantFoldingTest, DontFoldSubcomputationContainingAfterAll) { + const char* const kModuleStr = R"( + HloModule test + + Fn { + tok = token[] after-all() + ROOT root = f32[10] iota(), iota_dimension=0 + } + + ENTRY entry { + ROOT call = f32[10] call(), to_apply=Fn + })"; + TF_ASSERT_OK_AND_ASSIGN(auto module, + ParseAndReturnVerifiedModule(kModuleStr)); + HloConstantFolding constant_folding; + TF_ASSERT_OK_AND_ASSIGN(bool result, + RunHloPass(&constant_folding, module.get())); + EXPECT_FALSE(result); +} + +TEST_F(HloConstantFoldingTest, + DontFoldSubcomputationTransitivelyContainingRng) { + const char* const kModuleStr = R"( + HloModule test + + InnerFn { + c0 = f32[] constant(0) + c1 = f32[] constant(1) + ROOT rng = f32[10] rng(c0, c1), distribution=rng_uniform + } + + Fn { + ROOT fusion = f32[10] fusion(), kind=kLoop, calls=InnerFn + } + + ENTRY entry { + ROOT call = f32[10] call(), to_apply=Fn + })"; + TF_ASSERT_OK_AND_ASSIGN(auto module, + ParseAndReturnVerifiedModule(kModuleStr)); + HloConstantFolding constant_folding; + TF_ASSERT_OK_AND_ASSIGN(bool result, + RunHloPass(&constant_folding, module.get())); + EXPECT_FALSE(result); +} + } // namespace } // namespace xla -- GitLab From 5265604a31dcad7e37a326b1f7d5f1ff006968fb Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 7 Jan 2019 19:17:05 -0800 Subject: [PATCH 0310/2345] [convergence_tools]: Moving the norm, nan, and max computations inside of TPU. PiperOrigin-RevId: 228273700 --- .../contrib/tpu/python/tpu/tensor_tracer.py | 124 ++++++++++++------ 1 file changed, 83 insertions(+), 41 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py b/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py index 66689cde2f..c7e29860e5 100644 --- a/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py +++ b/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py @@ -377,10 +377,7 @@ class TensorTracer(object): return True # Reasons for not including following op types: # Assign: cause incorrect result with CPU tracing. - # others: compilation problems. - # TODO(deveci): Check if 'Pack', 'Shape', 'Reshape', 'ArgMin', 'ArgMax' - # are still unsafe now that we have handled int64 tensors. - if op.type in ['Assign', 'Pack', 'Shape', 'Reshape', 'ArgMin', 'ArgMax']: + if op.type in ['Assign']: return True return False @@ -471,7 +468,7 @@ class TensorTracer(object): temporarily_marked_ops, sorted_ops) # pylint: disable=protected-access for ctrl_output_op in op._control_outputs: - # pylint: enable=protected-access + # pylint: enable=protected-access visit(ctrl_output_op, cycle, permanently_marked_ops, temporarily_marked_ops, sorted_ops) temporarily_marked_ops.remove(op) @@ -737,6 +734,59 @@ class TensorTracer(object): 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: + output_tensor = math_ops.reduce_any( + gen_math_ops.logical_or( + gen_math_ops.is_nan(tensor), gen_math_ops.is_inf(tensor))) + else: + output_tensor = constant_op.constant(False) + # 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. @@ -787,29 +837,6 @@ class TensorTracer(object): with ops.control_dependencies([print_op]): return array_ops.identity(tensor).op - def _detect_nan_inf(tensor): - """Trace function for detecting any NaN/Inf in the tensor.""" - - if tensor.dtype.is_floating: - output_tensor = math_ops.reduce_any( - gen_math_ops.logical_or(gen_math_ops.is_nan(tensor), - gen_math_ops.is_inf(tensor))) - else: - output_tensor = constant_op.constant(False) - - return _print_tensor(tensor_name, -1, tensor, output_tensor) - - def _show_norm(tensor): - tensor = math_ops.cast(tensor, dtypes.float64) - output_tensor = linalg_ops.norm(tensor) - return _print_tensor(tensor_name, -1, tensor, output_tensor) - - def _show_max_abs(tensor): - output_tensor = math_ops.cast(math_ops.reduce_max(math_ops.abs(tensor)), - dtypes.float64) - zero = constant_op.constant(0, dtypes.float64) - output_tensor = gen_math_ops.maximum(zero, output_tensor) - return _print_tensor(tensor_name, -1, tensor, output_tensor) def _show_part_tensor(tensor): """Trace function for printing part of the tensor.""" @@ -822,16 +849,17 @@ class TensorTracer(object): return _print_tensor(tensor_name, -1, tensor, tensor) - if self._trace_mode == _TRACE_MODE_NAN_INF: - return _detect_nan_inf if self._trace_mode == _TRACE_MODE_PART_TENSOR: return _show_part_tensor - if self._trace_mode == _TRACE_MODE_FULL_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 - if self._trace_mode == _TRACE_MODE_NORM: - return _show_norm - if self._trace_mode == _TRACE_MODE_MAX_ABS: - return _show_max_abs raise RuntimeError('Tensor trace fun for %s is not yet implemented' %self._trace_mode) @@ -889,9 +917,18 @@ class TensorTracer(object): op_id, _REASON_USER_EXCLUDED) return True if not out_tensor.get_shape().is_fully_defined(): - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_DYNAMIC_SHAPE) - return True + # 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 @@ -1000,11 +1037,15 @@ class TensorTracer(object): if self._skip_tensor(op_id, out_tensor, user_included, user_excluded): 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() tensor_name = out_tensor.name - out_tensor = _cast_unsupported_dtypes(out_tensor) + processed_out_tensor = self._preprocess_traced_tensor(out_tensor) + processed_out_tensor = _cast_unsupported_dtypes(processed_out_tensor) trace_op = tpu.outside_compilation( - self._make_tensor_trace_fun(tensor_name), out_tensor) + self._make_tensor_trace_fun(tensor_name), processed_out_tensor) if consumers: for consumer_op in consumers: # pylint: disable=protected-access @@ -1050,8 +1091,9 @@ class TensorTracer(object): if self._skip_tensor(op_id, out_tensor, user_included, user_excluded): continue + processed_out_tensor = self._preprocess_traced_tensor(out_tensor) trace_fun = self._make_tensor_trace_fun(out_tensor.name) - trace_call = (trace_fun, [out_tensor]) + trace_call = (trace_fun, [processed_out_tensor]) trace_call_key = 'tensor_tracing_cpu-%s:%d'%(op.name, i) tracing_calls[trace_call_key] = trace_call self._post_tracing(succeed, sorted_or_cycle) -- GitLab From d95400ecd815c493e4241a96bea1c483387d0059 Mon Sep 17 00:00:00 2001 From: Pooya Davoodi Date: Mon, 7 Jan 2019 19:42:00 -0800 Subject: [PATCH 0311/2345] TFTRT: use six.text_type and six.binary_type --- .../contrib/tensorrt/python/trt_convert.py | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/tensorflow/contrib/tensorrt/python/trt_convert.py b/tensorflow/contrib/tensorrt/python/trt_convert.py index f5d624ef64..ac39a570d8 100644 --- a/tensorflow/contrib/tensorrt/python/trt_convert.py +++ b/tensorflow/contrib/tensorrt/python/trt_convert.py @@ -48,30 +48,18 @@ from tensorflow.python.training import saver def _to_bytes(s): """Returns encoded of s if s is a sequence of chars otherwise returns s. """ - if _six.PY2: - if isinstance(s, unicode): - return s.encode("utf-8", errors="surrogateescape") - else: - return s + if isinstance(s, _six.text_type): + return s.encode("utf-8", errors="surrogateescape") else: - if isinstance(s, str): - return s.encode("utf-8", errors="surrogateescape") - else: - return s + return s def _to_string(s): """Returns decoded of s if s is a sequence of bytes otherwise returns s. """ - if _six.PY2: - if isinstance(s, str): - return s.decode("utf-8") - else: - return s + if isinstance(s, _six.binary_type): + return s.decode("utf-8") else: - if isinstance(s, bytes): - return s.decode("utf-8") - else: - return s + return s class TrtPrecisionMode(object): FP32 = "FP32" -- GitLab From 8c3f0f594c8323c8f5bd004200e738ffd4a5a64a Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 8 Jan 2019 09:28:38 +0530 Subject: [PATCH 0312/2345] Update ceil.cc removed space as part of clang-format --- tensorflow/lite/kernels/ceil.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/ceil.cc b/tensorflow/lite/kernels/ceil.cc index e0ea061f25..6bb763255b 100644 --- a/tensorflow/lite/kernels/ceil.cc +++ b/tensorflow/lite/kernels/ceil.cc @@ -42,7 +42,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* output = GetOutput(context, node, kOutputTensor); optimized_ops::Ceil(GetTensorShape(input), GetTensorData(input), - GetTensorShape(output), GetTensorData(output)); + GetTensorShape(output), GetTensorData(output)); return kTfLiteOk; } -- GitLab From 95b18ddcbf0527e8da892e5a665e4d2547b47d67 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 8 Jan 2019 09:31:36 +0530 Subject: [PATCH 0313/2345] Update ceil_test.cc updated using clang-format --- tensorflow/lite/kernels/ceil_test.cc | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tensorflow/lite/kernels/ceil_test.cc b/tensorflow/lite/kernels/ceil_test.cc index e120105082..74d2bfb483 100644 --- a/tensorflow/lite/kernels/ceil_test.cc +++ b/tensorflow/lite/kernels/ceil_test.cc @@ -55,18 +55,11 @@ TEST(CeilOpTest, SingleDim) { TEST(CeilOpTest, MultiDims) { CeilOpModel model({2, 1, 1, 5}, TensorType_FLOAT32); - model.PopulateTensor(model.input(), { - 0.0001, - 8.0001, - 0.9999, - 9.9999, - 0.5, - -0.0001, - -8.0001, - -0.9999, - -9.9999, - -0.5, - }); + model.PopulateTensor( + model.input(), { + 0.0001, 8.0001, 0.9999, 9.9999, 0.5, -0.0001, -8.0001, + -0.9999, -9.9999, -0.5, + }); model.Invoke(); EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 9, 1, 10, 1, 0, -8, 0, -9, 0})); -- GitLab From 4cff2f464cf195fedd8165a11319e8379746456f Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Mon, 7 Jan 2019 19:59:50 -0800 Subject: [PATCH 0314/2345] Organize the dot op emitter a bit better; NFC This CL pays down some technical debt around dot_op_emitter's organization. It - Extracts out a GetDotImplementationStrategy that decides the lowering strategy for a dot operation. Earlier this logic was scattered through the lowering code. - Removes calls to PotentiallyImplementedAsEigenDot. This is an implementation detail for dot_op_emitter and exposing it like this has aged poorly in the face of the various tiled LLVM IR lowerings we now have. Instead, we expose two more specific hooks: DotOperandsAndResultMustHaveRowMajorLayout and DotImplementationCanHandleTranspose. - Renames xla_enable_experimental_llvm_ir_gemm to xla_force_enable_experimental_llvm_ir_gemm since its behavior is that it enables the tiled LLVM IR GEMM even when it does not look profitable. Note: In parallel_task_assignment the call to PotentiallyImplementedAsEigenDot was redundant since we already check for non-loop fusions and dots. PiperOrigin-RevId: 228276282 --- tensorflow/compiler/xla/service/cpu/BUILD | 5 +- .../compiler/xla/service/cpu/cpu_compiler.cc | 3 +- .../cpu/cpu_eigen_tensor_alignment_test.cc | 16 +- .../xla/service/cpu/cpu_layout_assignment.cc | 64 ++-- .../compiler/xla/service/cpu/cpu_options.cc | 8 +- .../compiler/xla/service/cpu/cpu_options.h | 2 +- .../xla/service/cpu/dot_op_emitter.cc | 301 ++++++++++-------- .../compiler/xla/service/cpu/dot_op_emitter.h | 40 ++- .../xla/service/cpu/dot_op_emitter_internal.h | 88 +++++ .../service/cpu/parallel_task_assignment.cc | 2 - 10 files changed, 318 insertions(+), 211 deletions(-) create mode 100644 tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index 86c5f71096..1eb42cf5ad 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -382,7 +382,10 @@ cc_library( cc_library( name = "dot_op_emitter", srcs = ["dot_op_emitter.cc"], - hdrs = ["dot_op_emitter.h"], + hdrs = [ + "dot_op_emitter.h", + "dot_op_emitter_internal.h", + ], deps = [ ":cpu_options", ":cpu_runtime", diff --git a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc index 91c64e45b9..a6d92ce10e 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc @@ -304,7 +304,8 @@ Status CpuCompiler::RunHloPassesThroughLayoutAssn( pipeline.AddPass( [&](const HloInstruction& dot, const TransposeFolding::OperandIndices& candidate_operands) { - return PotentiallyImplementedAsEigenDot(dot, *target_machine_features) + return DotImplementationCanHandleTranspose(dot, + *target_machine_features) ? candidate_operands : TransposeFolding::OperandIndices{}; }, diff --git a/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc b/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc index 8727c72b6e..823bdf259c 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/compiler/xla/service/cpu/dot_op_emitter.h" +#include "tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h" #include "tensorflow/compiler/xla/service/cpu/ir_emission_utils.h" #include "tensorflow/compiler/xla/service/cpu/target_machine_features_fake.h" #include "tensorflow/compiler/xla/service/hlo_parser.h" @@ -23,6 +23,10 @@ namespace xla { namespace cpu { namespace { +using internal::DotImplementationStrategy; +using internal::DotInfo; +using internal::GetDotImplementationStrategy; + // Test that we don't call into Eigen with tensors too small to be aligned // reliably. @@ -47,16 +51,18 @@ ENTRY DotOperation { TargetMachineFeaturesWithFakeAlignmentLogic target_machine_with_no_alignment( [](int64 size) { return 1; }); - EXPECT_FALSE( - PotentiallyImplementedAsEigenDot(*dot, target_machine_with_no_alignment)); + EXPECT_EQ(GetDotImplementationStrategy(HloModuleConfig{}, DotInfo(*dot), + target_machine_with_no_alignment), + DotImplementationStrategy::kNaiveLlvmIr); TargetMachineFeaturesWithFakeAlignmentLogic target_machine_with_full_alignment([](int64 size) { return TargetMachineFeatures::kEigenExpectedTensorAlignment; }); - EXPECT_TRUE(PotentiallyImplementedAsEigenDot( - *dot, target_machine_with_full_alignment)); + EXPECT_NE(GetDotImplementationStrategy(HloModuleConfig{}, DotInfo(*dot), + target_machine_with_full_alignment), + DotImplementationStrategy::kNaiveLlvmIr); } TEST_F(CpuEigenTensorAlignmentTest, EigenConvAlignment) { diff --git a/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc b/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc index ff11409482..95b8025f87 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc @@ -93,60 +93,38 @@ static Shape ColMajorShape(const Shape& old_shape) { return new_shape; } +static bool OperandsAndResultMustHaveRowMajorLayout( + const HloInstruction& instr, + const TargetMachineFeatures& target_machine_features) { + if (instr.opcode() == HloOpcode::kConvolution) { + return PotentiallyImplementedAsEigenConvolution(instr, + target_machine_features); + } else if (instr.opcode() == HloOpcode::kDot) { + return DotOperandsAndResultMustHaveRowMajorLayout(instr, + target_machine_features); + } + return false; +} + Status CpuLayoutAssignment::AddBackendConstraints( LayoutConstraints* constraints) { ShouldMakeOperandColMajorCache cache; const HloComputation* computation = constraints->computation(); for (auto* instruction : computation->instructions()) { - if (instruction->opcode() == HloOpcode::kConvolution && - PotentiallyImplementedAsEigenConvolution(*instruction, - target_machine_features_)) { - const HloInstruction* convolution = instruction; - const HloInstruction* lhs_instruction = convolution->operand(0); - const HloInstruction* rhs_instruction = convolution->operand(1); - - // In order to implement `convolution` with Eigen convolution, the layouts - // of the input, filter, and output need to be row-major. - // - // These constraints are not hard constraints. Ideally, we should decide - // which layouts to choose according to some cost model. - Shape output_shape(RowMajorShape(convolution->shape())); - Shape input_shape(RowMajorShape(lhs_instruction->shape())); - Shape filter_shape(RowMajorShape(rhs_instruction->shape())); - - // Set layouts of the instructions' shapes. - TF_RETURN_IF_ERROR( - constraints->SetOperandLayout(input_shape, convolution, 0)); - TF_RETURN_IF_ERROR( - constraints->SetOperandLayout(filter_shape, convolution, 1)); - TF_RETURN_IF_ERROR( - constraints->SetInstructionLayout(output_shape, convolution)); + if (OperandsAndResultMustHaveRowMajorLayout(*instruction, + target_machine_features_)) { + TF_RETURN_IF_ERROR(constraints->SetInstructionLayout( + RowMajorShape(instruction->shape()), instruction)); + for (int i = 0; i < instruction->operand_count(); i++) { + TF_RETURN_IF_ERROR(constraints->SetOperandLayout( + RowMajorShape(instruction->operand(i)->shape()), instruction, i)); + } } else if (optional op_idx = ShouldMakeOperandColumnMajor(&cache, *instruction)) { const HloInstruction* op = instruction->operand(*op_idx); TF_RETURN_IF_ERROR(constraints->SetOperandLayout( ColMajorShape(op->shape()), instruction, *op_idx)); - } else if (PotentiallyImplementedAsEigenDot(*instruction, - target_machine_features_)) { - const HloInstruction* dot = instruction; - // In order to implement `dot` with Eigen dot, the layouts of the lhs, - // rhs, and output need to be row-major. - // - // These constraints are not hard constraints. Ideally, we should decide - // which layouts to choose according to some cost model. - Shape output_shape(RowMajorShape(dot->shape())); - - const HloInstruction* lhs_instruction = dot->operand(0); - Shape lhs_shape(RowMajorShape(lhs_instruction->shape())); - TF_RETURN_IF_ERROR(constraints->SetOperandLayout(lhs_shape, dot, 0)); - - const HloInstruction* rhs_instruction = dot->operand(1); - Shape rhs_shape(RowMajorShape(rhs_instruction->shape())); - TF_RETURN_IF_ERROR(constraints->SetOperandLayout(rhs_shape, dot, 1)); - - // Set layouts of the instructions' shapes. - TF_RETURN_IF_ERROR(constraints->SetInstructionLayout(output_shape, dot)); } else { for (int64 operand_no = 0; operand_no < instruction->operand_count(); ++operand_no) { diff --git a/tensorflow/compiler/xla/service/cpu/cpu_options.cc b/tensorflow/compiler/xla/service/cpu/cpu_options.cc index 92debb83e3..ff654c83d6 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_options.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_options.cc @@ -23,8 +23,8 @@ namespace { const char* const kXlaOptimizeForSizeCpuOption = "xla_cpu_optimize_for_size"; const char* const kLlvmIrDotTilingFactor = "xla_llvm_dot_tiling_factor"; -const char* const kXlaEnableExperimentalLlvmIrGemm = - "xla_enable_experimental_llvm_ir_gemm"; +const char* const kXlaForceEnableExperimentalLlvmIrGemm = + "xla_force_enable_experimental_llvm_ir_gemm"; const char* const kLlvmIrGemmTileSize = "xla_llvm_ir_gemm_tile_size"; } // namespace @@ -57,10 +57,10 @@ absl::optional LlvmIrGemvTilingFactor(const HloModuleConfig& config) { return absl::nullopt; } -bool EnableExperimentalLlvmIrGemm(const HloModuleConfig& config) { +bool ForceEnableExperimentalLlvmIrGemm(const HloModuleConfig& config) { const auto& extra_options_map = config.debug_options().xla_backend_extra_options(); - return extra_options_map.count(kXlaEnableExperimentalLlvmIrGemm) > 0; + return extra_options_map.count(kXlaForceEnableExperimentalLlvmIrGemm) > 0; } static absl::string_view RemoveSuffix(absl::string_view str, diff --git a/tensorflow/compiler/xla/service/cpu/cpu_options.h b/tensorflow/compiler/xla/service/cpu/cpu_options.h index 47c7eb13b6..99e6702d14 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_options.h +++ b/tensorflow/compiler/xla/service/cpu/cpu_options.h @@ -26,7 +26,7 @@ namespace options { bool OptimizeForSizeRequested(const HloModuleConfig& config); bool VectorizedReduceDisabled(const HloModuleConfig& config); -bool EnableExperimentalLlvmIrGemm(const HloModuleConfig& config); +bool ForceEnableExperimentalLlvmIrGemm(const HloModuleConfig& config); absl::optional LlvmIrGemvTilingFactor(const HloModuleConfig& config); absl::optional> LlvmIrGemmTileSize( const HloModuleConfig& config); diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc index bdd688c2b9..70d8fee265 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/compiler/xla/service/cpu/dot_op_emitter.h" +#include "tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h" #include #include @@ -42,6 +43,18 @@ namespace xla { using llvm_ir::SetToFirstInsertPoint; namespace cpu { + +using internal::DotImplementationStrategy; +using internal::DotInfo; +using internal::GetDotImplementationStrategy; + +namespace { +// Returns true if we should call into multi-threaded Eigen routines. +bool ShouldUseMultiThreadedEigen(const HloModuleConfig& config) { + return config.debug_options().xla_cpu_multi_thread_eigen(); +} +} // namespace + DotOpEmitter::DotOpEmitter(const HloInstruction& dot, const llvm_ir::IrArray& target_array, const llvm_ir::IrArray& lhs_array, @@ -76,43 +89,9 @@ DotOpEmitter::DotOpEmitter(const HloInstruction& dot, return dot_emitter.Emit(); } -bool DotOpEmitter::EmitSmallGemmIfProfitable( - const DotOpEmitter::MatMultDims& mat_mult_dims) { - if (ShouldUseMultiThreadedEigen()) { - return false; - } - - if (!EnableExperimentalLlvmIrGemm()) { - // TODO(sanjoy): We should make these numbers micro-arch specific. - bool small_gemm = mat_mult_dims.k <= 128 && - ((mat_mult_dims.m <= 32 && mat_mult_dims.n <= 128) || - (mat_mult_dims.m <= 128 && mat_mult_dims.n <= 32)); - if (!small_gemm) { - return false; - } - } - - if (mat_mult_dims.lhs_non_canonical || mat_mult_dims.rhs_non_canonical) { - return false; - } - +void DotOpEmitter::EmitTiledLlvmIrGemm() { PrimitiveType primitive_type = dot_.shape().element_type(); - - switch (primitive_type) { - default: - return false; - - case F32: - case F64: - case S32: - case S64: - break; - } - - if (!(mat_mult_dims.lhs_column_major == mat_mult_dims.rhs_column_major && - mat_mult_dims.rhs_column_major == mat_mult_dims.target_column_major)) { - return false; - } + MatMultDims mat_mult_dims = GetMatMultDims(); llvm::Value* lhs = lhs_array_.GetBasePointer(); llvm::Value* rhs = rhs_array_.GetBasePointer(); @@ -154,21 +133,13 @@ bool DotOpEmitter::EmitSmallGemmIfProfitable( /*rhs=*/rhs, /*result=*/target, b_, /*enable_fast_math=*/enable_fast_math, /*optimize_for_size=*/optimize_for_size); - - return true; } -bool DotOpEmitter::EmitLlvmIrDotIfProfitable() { - if (dot_.shape().dimensions_size() != 2) { - return false; - } - +void DotOpEmitter::EmitTiledLlvmIrGemv() { PrimitiveType primitive_type = dot_.shape().element_type(); - if (!primitive_util::IsFloatingPointType(primitive_type) && - !primitive_util::IsIntegralType(primitive_type)) { - return false; - } + CHECK(primitive_util::IsFloatingPointType(primitive_type) || + primitive_util::IsIntegralType(primitive_type)); MatMultDims mat_mult_dims = GetMatMultDims(); bool is_column_major_matrix_vector = false; @@ -209,9 +180,7 @@ bool DotOpEmitter::EmitLlvmIrDotIfProfitable() { } } - if (!is_column_major_matrix_vector && !is_row_major_matrix_vector) { - return EmitSmallGemmIfProfitable(mat_mult_dims); - } + CHECK(is_column_major_matrix_vector || is_row_major_matrix_vector); int64 tiling_factor = GetGemvTilingFactor(); CHECK_GT(tiling_factor, 0); @@ -264,8 +233,6 @@ bool DotOpEmitter::EmitLlvmIrDotIfProfitable() { /*enable_fast_math=*/enable_fast_math, /*optimize_for_size=*/optimize_for_size); } - - return true; } Status DotOpEmitter::Emit() { @@ -306,27 +273,41 @@ Status DotOpEmitter::Emit() { return EmitScalarDot(); } - if (EmitLlvmIrDotIfProfitable()) { - return Status::OK(); + switch (GetDotImplementationStrategy(hlo_module_config_, DotInfo(dot_), + target_machine_features_)) { + case DotImplementationStrategy::kNaiveLlvmIr: + EmitNaiveLlvmIrGemm(); + return Status::OK(); + + case DotImplementationStrategy::kTiledLlvmIrGemv: + EmitTiledLlvmIrGemv(); + return Status::OK(); + + case DotImplementationStrategy::kTiledLlvmIrGemm: + EmitTiledLlvmIrGemm(); + return Status::OK(); + + case DotImplementationStrategy::kEigen: + return EmitCallToRuntime(); } +} +void DotOpEmitter::EmitNaiveLlvmIrGemm() { CHECK_EQ(addend_array_, nullptr); - if (PotentiallyImplementedAsEigenDot(dot_, target_machine_features_)) { - return EmitCallToRuntime(); - } + const Shape& lhs_shape = lhs_array_.GetShape(); + const Shape& rhs_shape = rhs_array_.GetShape(); + const DotDimensionNumbers& dim_nums = dot_.dot_dimension_numbers(); // Reduce along dimension 0 of the LHS and 1 of the RHS. Vectors are a special // case where the reduction dimension is 0 for both LHS and RHS. This results // in a vector dot product producing a scalar. - int64 lhs_reduction_dimension = - dot_.dot_dimension_numbers().lhs_contracting_dimensions(0); - int64 rhs_reduction_dimension = - dot_.dot_dimension_numbers().rhs_contracting_dimensions(0); + int64 lhs_reduction_dimension = dim_nums.lhs_contracting_dimensions(0); + int64 rhs_reduction_dimension = dim_nums.rhs_contracting_dimensions(0); // Verify the reduction dimension in the two operands are the same size. - TF_RET_CHECK(lhs_shape.dimensions(lhs_reduction_dimension) == - rhs_shape.dimensions(rhs_reduction_dimension)); + CHECK_EQ(lhs_shape.dimensions(lhs_reduction_dimension), + rhs_shape.dimensions(rhs_reduction_dimension)); bool lhs_reduction_along_minor_dimension = lhs_reduction_dimension == LayoutUtil::Minor(lhs_shape.layout(), 0); @@ -441,8 +422,6 @@ Status DotOpEmitter::Emit() { // Set the IR builder insert point to the exit basic block of the outer most // loop. b_->SetInsertPoint(loop_nest.GetOuterLoopExitBasicBlock()); - - return Status::OK(); } Status DotOpEmitter::EmitScalarDot() { @@ -489,7 +468,7 @@ Status DotOpEmitter::EmitCallToRuntime() { // The two transpose_... parameters are actually booleans, but we use int32 // to avoid target-dependent calling convention details. - bool multi_threaded = ShouldUseMultiThreadedEigen(); + bool multi_threaded = ShouldUseMultiThreadedEigen(hlo_module_config_); bool use_mkl_dnn = hlo_module_config_.debug_options().xla_cpu_use_mkl_dnn(); PrimitiveType type = target_array_.GetShape().element_type(); llvm::Type* float_type; @@ -600,12 +579,52 @@ DotOpEmitter::MatMultDims DotOpEmitter::GetMatMultDims() const { LayoutUtil::Minor(target_array_.GetShape().layout(), 0) == 0}; } +// For vector-matrix dot products, it is always profitable to make the Rhs +// column major. +absl::optional ProfitableToMakeDotOperandColumnMajor( + const HloInstruction& hlo) { + if (hlo.opcode() == HloOpcode::kDot && hlo.shape().dimensions_size() == 2 && + hlo.shape().dimensions(0) == 1) { + if (hlo.dot_dimension_numbers().rhs_contracting_dimensions(0) == 0) { + return 1; + } + return {}; + } + + if (hlo.opcode() == HloOpcode::kFusion && + hlo.fusion_kind() == HloInstruction::FusionKind::kOutput) { + auto* fusion_root = + hlo.fused_instructions_computation()->root_instruction(); + if (fusion_root->opcode() != HloOpcode::kAdd) { + return {}; + } + + for (auto* fusion_root_op : fusion_root->operands()) { + if (fusion_root_op->opcode() != HloOpcode::kDot) { + continue; + } + if (auto operand_num = + ProfitableToMakeDotOperandColumnMajor(*fusion_root_op)) { + auto* operand = fusion_root_op->operand(*operand_num); + if (operand->opcode() == HloOpcode::kParameter && + operand->user_count() == 1) { + return operand->parameter_number(); + } + } + } + } + + return {}; +} + +namespace internal { +namespace { // Return whether the given shape is rank 2. -static bool IsRank2(const Shape& shape) { return shape.rank() == 2; } +bool IsRank2(const Shape& shape) { return shape.rank() == 2; } // In a gemm operation where output = lhs * rhs, check whether the given shapes // are valid for the operation. -static bool AreValidGemmShapes( +bool AreAlignedGemmShapes( const Shape& lhs_shape, const Shape& rhs_shape, const Shape& output_shape, const TargetMachineFeatures& target_machine_features) { // The inputs and the output must @@ -634,88 +653,106 @@ static bool AreValidGemmShapes( return true; } -bool PotentiallyImplementedAsEigenDot( - const HloInstruction& hlo, - const TargetMachineFeatures& target_machine_features) { - // For certain types of Dot, we can call Eigen - if (hlo.opcode() == HloOpcode::kDot) { - const Shape& lhs_shape = hlo.operand(0)->shape(); - const Shape& rhs_shape = hlo.operand(1)->shape(); +bool IsAlignedGemm(const DotInfo& dot_info, + const TargetMachineFeatures& target_machine_features) { + if (ShapeUtil::IsZeroElementArray(dot_info.lhs_shape) || + ShapeUtil::IsZeroElementArray(dot_info.rhs_shape)) { + return false; + } - if (ShapeUtil::IsZeroElementArray(lhs_shape) || - ShapeUtil::IsZeroElementArray(rhs_shape)) { - return false; - } + return AreAlignedGemmShapes(dot_info.lhs_shape, dot_info.rhs_shape, + dot_info.result_shape, target_machine_features); +} - if (ProfitableToImplementDotInTiledLlvmIr(hlo)) { - return false; - } +bool CanEmitTiledLlvmIrGemm( + const HloModuleConfig& config, const DotInfo& dot_info, + const TargetMachineFeatures& target_machine_features) { + CHECK(IsAlignedGemm(dot_info, target_machine_features)); - // If gemm can accept the operand shapes, use it rather than a custom - // kernel. - if (AreValidGemmShapes(lhs_shape, rhs_shape, hlo.shape(), - target_machine_features)) { - const DotDimensionNumbers& dim_numbers = hlo.dot_dimension_numbers(); - // The size of the reduction dimension should match. The shape inference - // guarantees this invariant, so the check here is for programming - // errors. - CHECK_EQ(lhs_shape.dimensions(dim_numbers.lhs_contracting_dimensions(0)), - rhs_shape.dimensions(dim_numbers.rhs_contracting_dimensions(0))); - return true; - } + if (ShouldUseMultiThreadedEigen(config)) { + return false; } - return false; -} + int m = dot_info.result_shape.dimensions(0); + int k = dot_info.lhs_shape.dimensions( + dot_info.dim_nums.lhs_contracting_dimensions(0)); + int n = dot_info.result_shape.dimensions(1); -// For vector-matrix dot products, it is always profitable to make the Rhs -// column major. -absl::optional ProfitableToMakeDotOperandColumnMajor( - const HloInstruction& hlo) { - if (hlo.opcode() == HloOpcode::kDot && hlo.shape().dimensions_size() == 2 && - hlo.shape().dimensions(0) == 1) { - if (hlo.dot_dimension_numbers().rhs_contracting_dimensions(0) == 0) { - return 1; + if (!options::ForceEnableExperimentalLlvmIrGemm(config)) { + // TODO(sanjoy): We should make these numbers micro-arch specific. + bool small_gemm = + k <= 128 && ((m <= 32 && n <= 128) || (m <= 128 && n <= 32)); + if (!small_gemm) { + return false; } - return {}; } - if (hlo.opcode() == HloOpcode::kFusion && - hlo.fusion_kind() == HloInstruction::FusionKind::kOutput) { - auto* fusion_root = - hlo.fused_instructions_computation()->root_instruction(); - if (fusion_root->opcode() != HloOpcode::kAdd) { - return {}; - } + bool lhs_non_canonical = dot_info.dim_nums.lhs_contracting_dimensions(0) == 0; + bool rhs_non_canonical = dot_info.dim_nums.rhs_contracting_dimensions(0) == 1; - for (auto* fusion_root_op : fusion_root->operands()) { - if (fusion_root_op->opcode() != HloOpcode::kDot) { - continue; - } - if (auto operand_num = - ProfitableToMakeDotOperandColumnMajor(*fusion_root_op)) { - auto* operand = fusion_root_op->operand(*operand_num); - if (operand->opcode() == HloOpcode::kParameter && - operand->user_count() == 1) { - return operand->parameter_number(); - } - } - } + if (lhs_non_canonical || rhs_non_canonical) { + return false; } - return {}; + if (dot_info.result_shape.element_type() == F16) { + // TODO(sanjoy): This is probably easy to fix, but I want to keep the CL + // adding this comment NFC. + return false; + } + + return true; } +} // namespace -bool ProfitableToImplementDotInTiledLlvmIr(const HloInstruction& dot) { +DotImplementationStrategy GetDotImplementationStrategy( + const HloModuleConfig& config, const DotInfo& dot_info, + const TargetMachineFeatures& target_machine_features) { + PrimitiveType element_type = dot_info.result_shape.element_type(); // Any Matrix-Vector product of floating point or integral type, or // a transpose-dot fusion of the same can be lowered to a tiled LLVM // IR implementation. - const Shape& shape = dot.shape(); - return shape.dimensions_size() == 2 && - (shape.dimensions(0) == 1 || shape.dimensions(1) == 1) && - (primitive_util::IsFloatingPointType(shape.element_type()) || - primitive_util::IsIntegralType(shape.element_type())); + if (dot_info.result_shape.dimensions_size() == 2 && + (dot_info.result_shape.dimensions(0) == 1 || + dot_info.result_shape.dimensions(1) == 1) && + (primitive_util::IsFloatingPointType(element_type) || + primitive_util::IsIntegralType(element_type))) { + return DotImplementationStrategy::kTiledLlvmIrGemv; + } + + if (IsAlignedGemm(dot_info, target_machine_features)) { + return CanEmitTiledLlvmIrGemm(config, dot_info, target_machine_features) + ? DotImplementationStrategy::kTiledLlvmIrGemm + : DotImplementationStrategy::kEigen; + } + + return DotImplementationStrategy::kNaiveLlvmIr; } +} // namespace internal +bool DotImplementationCanHandleTranspose( + const HloInstruction& dot_instr, + const TargetMachineFeatures& target_machine_features) { + DotImplementationStrategy impl_strategy = + GetDotImplementationStrategy(dot_instr.parent()->parent()->config(), + DotInfo(dot_instr), target_machine_features); + + // TODO(sanjoy): This is not quite right, it should be `impl_strategy == + // kEigen || impl_strategy == kTiledLlvmIrGemv || impl_strategy == + // kNaiveLlvmIr` but I'll fix this in a later CL in the interest of keeping + // the CL adding this comment NFC. + return impl_strategy == DotImplementationStrategy::kTiledLlvmIrGemm || + impl_strategy == DotImplementationStrategy::kEigen; +} + +bool DotOperandsAndResultMustHaveRowMajorLayout( + const HloInstruction& dot_instr, + const TargetMachineFeatures& target_machine_features) { + DotImplementationStrategy impl_strategy = + GetDotImplementationStrategy(dot_instr.parent()->parent()->config(), + DotInfo(dot_instr), target_machine_features); + + return impl_strategy == DotImplementationStrategy::kTiledLlvmIrGemm || + impl_strategy == DotImplementationStrategy::kEigen; +} } // namespace cpu } // namespace xla diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h index 4c2041b556..eaa226a4c2 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h @@ -30,9 +30,16 @@ limitations under the License. namespace xla { namespace cpu { +// Returns true if the two operands and the output of `dot_instr` must have row +// major layout. +bool DotOperandsAndResultMustHaveRowMajorLayout( + const HloInstruction& dot_instr, + const TargetMachineFeatures& target_machine_features); -bool PotentiallyImplementedAsEigenDot( - const HloInstruction& hlo, +// Returns true our lowering strategy for `dot_instr` can fold in transposes to +// the either of the inputs. +bool DotImplementationCanHandleTranspose( + const HloInstruction& dot_instr, const TargetMachineFeatures& target_machine_features); // Returns the index for an operand to `hlo` that should ideally be column @@ -41,10 +48,6 @@ bool PotentiallyImplementedAsEigenDot( absl::optional ProfitableToMakeDotOperandColumnMajor( const HloInstruction& hlo); -// Returns true to indicate that we can generate a tiled LLVM IR implementation -// for |dot|. -bool ProfitableToImplementDotInTiledLlvmIr(const HloInstruction& dot); - // Helper class for emitting LLVM IR to perform the dot operation. class DotOpEmitter { public: @@ -81,10 +84,6 @@ class DotOpEmitter { // LHS and RHS) and store the results in the target. Status EmitScalarDot(); - // Emit an LLVM IR implementation of the dot operation if we can. Returns - // true if an LLVM IR implementation was emitted. - bool EmitLlvmIrDotIfProfitable(); - // Emits a call to the CPU runtime to perform the matrix multiply. Status EmitCallToRuntime(); @@ -121,7 +120,15 @@ class DotOpEmitter { // of rank 2 as well). MatMultDims GetMatMultDims() const; - bool EmitSmallGemmIfProfitable(const MatMultDims& mat_mult_dims); + // Lowers the dot operation as a tiled Matrix*Vector loop. + void EmitTiledLlvmIrGemv(); + + // Lowers the dot operation as a tiled Matrix*Matrix loop. + void EmitTiledLlvmIrGemm(); + + // Lowers the dot operation as a naive nested loop that computes the result + // one element at a time. + void EmitNaiveLlvmIrGemm(); // When doing a tiled GEMV in LLVM IR, a "tile" consists of this many vector // registers. @@ -142,17 +149,6 @@ class DotOpEmitter { .value_or(kDefaultTileSize); } - // Returns true if we should use an experimental implementation of GEMM - // (general matrix matrix multiplication) if possible. - bool EnableExperimentalLlvmIrGemm() const { - return options::EnableExperimentalLlvmIrGemm(hlo_module_config_); - } - - // Returns true if we should call into multi-threaded Eigen routines. - bool ShouldUseMultiThreadedEigen() { - return hlo_module_config_.debug_options().xla_cpu_multi_thread_eigen(); - } - const HloInstruction& dot_; const llvm_ir::IrArray& target_array_; const llvm_ir::IrArray& lhs_array_; diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h b/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h new file mode 100644 index 0000000000..cc28918ed6 --- /dev/null +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h @@ -0,0 +1,88 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_COMPILER_XLA_SERVICE_CPU_DOT_OP_EMITTER_INTERNAL_H_ +#define TENSORFLOW_COMPILER_XLA_SERVICE_CPU_DOT_OP_EMITTER_INTERNAL_H_ + +#include "tensorflow/compiler/xla/service/cpu/target_machine_features.h" +#include "tensorflow/compiler/xla/service/hlo_instruction.h" +#include "tensorflow/compiler/xla/service/hlo_module.h" + +// ----------------------------------------------------------------------------- +// INTERNAL HEADER. +// +// This file exposes internal implementation details from dot_op_emitter.cc for +// unit tests. Please do not depend on this! +// +// ----------------------------------------------------------------------------- + +namespace xla { +namespace cpu { +namespace internal { + +// Represents a dot operation. We use this in lieu of an `HloInstruction` +// because we want to be able to create this for the "inner" dot operation in a +// batch dot, for which there is no separate HLO instruction. +struct DotInfo { + Shape lhs_shape; + Shape rhs_shape; + Shape result_shape; + DotDimensionNumbers dim_nums; + + explicit DotInfo(const HloInstruction& instr) { + CHECK_EQ(instr.opcode(), HloOpcode::kDot); + lhs_shape = instr.operand(0)->shape(); + rhs_shape = instr.operand(1)->shape(); + result_shape = instr.shape(); + dim_nums = instr.dot_dimension_numbers(); + } +}; + +// Dictates how a dot operation is implemented. +enum class DotImplementationStrategy { + // The dot operation is lowered into LLVM IR that implements a naive nested + // loop that computes the result one element at a time. This is our + // "fallback"; we don't really want this to kick in for any non-trival dot + // operation. + kNaiveLlvmIr, + + // The dot operation is lowered into LLVM IR that implements a tiled + // Matrix*Vector operation. This strategy also allows fusing in a bias add + // into the dot. The matrix can be row major or column major, both are + // supported. + kTiledLlvmIrGemv, + + // The dot operation is lowered into LLVM IR that implemetns a tiled + // Matrix*Matrix operation. No fusions are supported. The two inputs + // and the output have to be row major. + kTiledLlvmIrGemm, + + // The dot operation is lowered into a call into an Eigen routine. No fusions + // are supported today. The two inputs and the output have to be row major. + // However, we do allow transposing either the LHS or the RHS as part of the + // GEMM -- we expose this flexibility as flexibility in the contraction + // dimensions, but we can also see this as flexibility in the input layouts. + kEigen, +}; + +// Returns the implementation strategy for a dot with the configuration +// `dot_info`. +DotImplementationStrategy GetDotImplementationStrategy( + const HloModuleConfig& config, const DotInfo& dot_info, + const TargetMachineFeatures& target_machine_features); +} // namespace internal +} // namespace cpu +} // namespace xla + +#endif // TENSORFLOW_COMPILER_XLA_SERVICE_CPU_DOT_OP_EMITTER_INTERNAL_H_ diff --git a/tensorflow/compiler/xla/service/cpu/parallel_task_assignment.cc b/tensorflow/compiler/xla/service/cpu/parallel_task_assignment.cc index 3b423f6391..6121d1ca9a 100644 --- a/tensorflow/compiler/xla/service/cpu/parallel_task_assignment.cc +++ b/tensorflow/compiler/xla/service/cpu/parallel_task_assignment.cc @@ -146,8 +146,6 @@ int64 ParallelTaskAssignment::GetTargetParallelTaskCount( (opcode == HloOpcode::kConvolution && PotentiallyImplementedAsEigenConvolution(*instruction, target_machine_features_)) || - PotentiallyImplementedAsEigenDot(*instruction, - target_machine_features_) || (opcode == HloOpcode::kFusion && instruction->fusion_kind() != HloInstruction::FusionKind::kLoop) || instruction->shape().IsTuple()) { -- GitLab From 8c91712adfb586da675aeaa59730fd4c6e8c5ded Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 8 Jan 2019 10:02:21 +0530 Subject: [PATCH 0315/2345] LOG the result in case of error. --- tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc index 77737a76b6..2879b00f55 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc @@ -46,7 +46,7 @@ bool CheckMask(se::StreamExecutor* exec, void* ptr, int64* mask) { Status result = exec->SynchronousMemcpyD2H(gpu_ptr, MASK_BYTES, tmp); if (!result.ok()) { - LOG(FATAL) << "Could not copy debug mask"; + LOG(FATAL) << "Could not copy debug mask, " << result.error_message(); } bool ok = true; @@ -66,7 +66,7 @@ void InitMask(se::StreamExecutor* exec, void* ptr, int64* mask) { se::DeviceMemory gpu_ptr{se::DeviceMemoryBase{ptr, MASK_BYTES}}; Status result = exec->SynchronousMemcpyH2D(mask, MASK_BYTES, &gpu_ptr); if (!result.ok()) { - LOG(FATAL) << "Could not copy debug mask"; + LOG(FATAL) << "Could not copy debug mask, " << result.error_message(); } } @@ -176,7 +176,7 @@ void* GPUNanResetAllocator::AllocateRaw(size_t alignment, size_t num_bytes) { Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, &nan_ptr); if (!result.ok()) { - LOG(ERROR) << "Could not initialize to NaNs"; + LOG(ERROR) << "Could not initialize to NaNs, " << result.error_message(); } return allocated_ptr; @@ -192,7 +192,7 @@ void GPUNanResetAllocator::DeallocateRaw(void* ptr) { Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, &nan_ptr); if (!result.ok()) { - LOG(ERROR) << "Could not initialize to NaNs"; + LOG(ERROR) << "Could not initialize to NaNs, " << result.error_message(); } } -- GitLab From 8808014ef840f98aafd93f1e54a069601a184586 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 8 Jan 2019 04:50:04 +0000 Subject: [PATCH 0316/2345] Update protobuf to 3.6.1 in install_proto3.sh This fix updates protobuf to 3.6.1 in install_proto3.sh. The protobuf library in workspace.bzl has been updated to 3.6.1 for some time, though in CI build protoc still uses 3.6.0. When build a custom op (in docker image `tensorflow/tensorflow:custom-op`) and using grpc's `grpc_defs()` (`grpc/bazel/grpc_deps.bzl`): https://github.com/grpc/grpc/blob/ac6795a57e05523b8fa220bc5cef26abb876aae5/bazel/grpc_deps.bzl#L5-L7 The issue arise because grpc's dependency is 3.6.1 (and an immediate earlier version is 3.5) so there is no match to fit grpc+protobuf no matter which version of grpc is used. This may not a problem for tensorflow itself as tensorflow does not call `grpc_defs()`. Tensorflow links to protobuf 3.6.1 explicitly. Still, it would be good to match the version of installed protoc (3.6.0) with protobuf library (3.6.1). Signed-off-by: Yong Tang --- tensorflow/tools/ci_build/install/install_proto3.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/install/install_proto3.sh b/tensorflow/tools/ci_build/install/install_proto3.sh index 821d50baff..3cb1008567 100755 --- a/tensorflow/tools/ci_build/install/install_proto3.sh +++ b/tensorflow/tools/ci_build/install/install_proto3.sh @@ -17,7 +17,7 @@ # Install protobuf3. # Select protobuf version. -PROTOBUF_VERSION="3.6.0" +PROTOBUF_VERSION="3.6.1" protobuf_ver_flat=$(echo $PROTOBUF_VERSION | sed 's/\.//g' | sed 's/^0*//g') local_protobuf_ver=$(protoc --version) local_protobuf_ver_flat=$(echo $local_protobuf_ver | sed 's/\.//g' | sed 's/^0*//g') -- GitLab From e7b6486adb822bb83a9ff669dc4d2d477d911b3c Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 8 Jan 2019 04:57:32 +0000 Subject: [PATCH 0317/2345] Update install_pip_packages.sh to protobuf 3.6.1 Signed-off-by: Yong Tang --- tensorflow/tools/ci_build/install/install_pip_packages.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/ci_build/install/install_pip_packages.sh b/tensorflow/tools/ci_build/install/install_pip_packages.sh index eeadabaa73..5ae840cfa0 100755 --- a/tensorflow/tools/ci_build/install/install_pip_packages.sh +++ b/tensorflow/tools/ci_build/install/install_pip_packages.sh @@ -60,8 +60,8 @@ pip2 install --upgrade markdown==2.6.8 pip3 install --upgrade markdown==2.6.8 # Install protobuf. -pip2 install --upgrade protobuf==3.6.0 -pip3 install --upgrade protobuf==3.6.0 +pip2 install --upgrade protobuf==3.6.1 +pip3 install --upgrade protobuf==3.6.1 # Remove obsolete version of six, which can sometimes confuse virtualenv. rm -rf /usr/lib/python3/dist-packages/six* -- GitLab From bb447c4db4c1c5990f476b7b5fdf1d3c5b3afacc Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 8 Jan 2019 04:58:19 +0000 Subject: [PATCH 0318/2345] Update protobuf to 3.6.1 Signed-off-by: Yong Tang --- .../tools/ci_build/install/install_python3.5_pip_packages.sh | 2 +- .../tools/ci_build/install/install_python3.6_pip_packages.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/ci_build/install/install_python3.5_pip_packages.sh b/tensorflow/tools/ci_build/install/install_python3.5_pip_packages.sh index 0654fb81b3..a58f49af28 100755 --- a/tensorflow/tools/ci_build/install/install_python3.5_pip_packages.sh +++ b/tensorflow/tools/ci_build/install/install_python3.5_pip_packages.sh @@ -52,7 +52,7 @@ pip3.5 install --upgrade absl-py pip3.5 install --upgrade six==1.10.0 # Install protobuf. -pip3.5 install --upgrade protobuf==3.6.0 +pip3.5 install --upgrade protobuf==3.6.1 # Remove obsolete version of six, which can sometimes confuse virtualenv. rm -rf /usr/lib/python3/dist-packages/six* diff --git a/tensorflow/tools/ci_build/install/install_python3.6_pip_packages.sh b/tensorflow/tools/ci_build/install/install_python3.6_pip_packages.sh index 3edf4571fb..b1c2a0ab00 100755 --- a/tensorflow/tools/ci_build/install/install_python3.6_pip_packages.sh +++ b/tensorflow/tools/ci_build/install/install_python3.6_pip_packages.sh @@ -64,7 +64,7 @@ pip3 install --upgrade absl-py pip3 install --upgrade six==1.10.0 # Install protobuf. -pip3 install --upgrade protobuf==3.6.0 +pip3 install --upgrade protobuf==3.6.1 # Remove obsolete version of six, which can sometimes confuse virtualenv. rm -rf /usr/lib/python3/dist-packages/six* -- GitLab From 246780be77509fa4bea195d05fa73faa99467c58 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Mon, 7 Jan 2019 21:32:32 -0800 Subject: [PATCH 0319/2345] Hide the DotOpEmitter class and make it use a DotInfo instead of a HloInstr; NFC - Hiding it in the .cc is good encapsulation. - Making it depend on a DotInfo as opposed to an HLO instruction will make implementing batch dot (in terms of non-batch dot) easier. PiperOrigin-RevId: 228281578 --- .../xla/service/cpu/dot_op_emitter.cc | 159 ++++++++++++++---- .../compiler/xla/service/cpu/dot_op_emitter.h | 130 ++------------ .../compiler/xla/service/cpu/ir_emitter.cc | 16 +- 3 files changed, 156 insertions(+), 149 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc index 70d8fee265..e8a84ebe6b 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc @@ -53,9 +53,107 @@ namespace { bool ShouldUseMultiThreadedEigen(const HloModuleConfig& config) { return config.debug_options().xla_cpu_multi_thread_eigen(); } + +// Helper class for emitting LLVM IR to perform the dot operation. +class DotOpEmitter { + public: + explicit DotOpEmitter(DotInfo dot_info, string dot_hlo_name, + const llvm_ir::IrArray& target_array, + const llvm_ir::IrArray& lhs_array, + const llvm_ir::IrArray& rhs_array, + const llvm_ir::IrArray* addend_array, + llvm::Value* executable_run_options_value, + llvm::IRBuilder<>* b, + const HloModuleConfig& hlo_module_config, + const TargetMachineFeatures& target_machine_features); + + // Emits the IR to perform the dot operation. + Status Emit(); + + private: + // Emits instructions to perform a scalar dot product (a multiply of the + // LHS and RHS) and store the results in the target. + Status EmitScalarDot(); + + // Emits a call to the CPU runtime to perform the matrix multiply. + Status EmitCallToRuntime(); + + // Represents the dimensions of a matrix-matrix multiply operation. + struct MatMultDims { + // The number of rows in the LHS. + int64 m; + + // The number of columns in the LHS, which is also must be equal to the + // number of rows in the RHS. + int64 k; + + // The number of columns on the RHS. + int64 n; + + // True if the LHS matrix is column major. + bool lhs_column_major; + + // True if the LHS contraction dimension is not 1. + bool lhs_non_canonical; + + // True if the RHS matrix is column major. + bool rhs_column_major; + + // True if the RHS contraction dimension is not 0. + bool rhs_non_canonical; + + // True if the result matrix is column major. + bool target_column_major; + }; + + // Get the MatMultDims instance for the dot product this DotOpEmitter + // represents. Precondition: the dot is of rank 2 (and thus its operands are + // of rank 2 as well). + MatMultDims GetMatMultDims() const; + + // Lowers the dot operation as a tiled Matrix*Vector loop. + void EmitTiledLlvmIrGemv(); + + // Lowers the dot operation as a tiled Matrix*Matrix loop. + void EmitTiledLlvmIrGemm(); + + // Lowers the dot operation as a naive nested loop that computes the result + // one element at a time. + void EmitNaiveLlvmIrGemm(); + + // When doing a tiled GEMV in LLVM IR, a "tile" consists of this many vector + // registers. + int64 GetGemvTilingFactor() const { + const int64 kDefaultTilingFactor = 8; + return options::LlvmIrGemvTilingFactor(hlo_module_config_) + .value_or(kDefaultTilingFactor); + } + + std::tuple GetGemmTileSize() const { + // Tuned for broadwell - Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz + // + // TODO(b/80093688): Tune for other architectures and centralize this + // information in one place. + const std::tuple kDefaultTileSize = + std::tuple(11, 9, 1); + return options::LlvmIrGemmTileSize(hlo_module_config_) + .value_or(kDefaultTileSize); + } + + DotInfo dot_info_; + string dot_hlo_name_; + const llvm_ir::IrArray& target_array_; + const llvm_ir::IrArray& lhs_array_; + const llvm_ir::IrArray& rhs_array_; + const llvm_ir::IrArray* addend_array_; + llvm::Value* executable_run_options_value_; + llvm::IRBuilder<>* b_; + const HloModuleConfig& hlo_module_config_; + const TargetMachineFeatures& target_machine_features_; +}; } // namespace -DotOpEmitter::DotOpEmitter(const HloInstruction& dot, +DotOpEmitter::DotOpEmitter(DotInfo dot_info, string dot_hlo_name, const llvm_ir::IrArray& target_array, const llvm_ir::IrArray& lhs_array, const llvm_ir::IrArray& rhs_array, @@ -64,7 +162,8 @@ DotOpEmitter::DotOpEmitter(const HloInstruction& dot, llvm::IRBuilder<>* b, const HloModuleConfig& hlo_module_config, const TargetMachineFeatures& target_machine_features) - : dot_(dot), + : dot_info_(std::move(dot_info)), + dot_hlo_name_(std::move(dot_hlo_name)), target_array_(target_array), lhs_array_(lhs_array), rhs_array_(rhs_array), @@ -74,23 +173,8 @@ DotOpEmitter::DotOpEmitter(const HloInstruction& dot, hlo_module_config_(hlo_module_config), target_machine_features_(target_machine_features) {} -/* static */ Status DotOpEmitter::EmitDotOperation( - const HloInstruction& dot, const llvm_ir::IrArray& target_array, - const llvm_ir::IrArray& lhs_array, const llvm_ir::IrArray& rhs_array, - const llvm_ir::IrArray* addend_array, - llvm::Value* executable_run_options_value, llvm::IRBuilder<>* b, - const HloModuleConfig& hlo_module_config, - const TargetMachineFeatures& target_machine_features) { - PrimitiveType type = target_array.GetShape().element_type(); - TF_RET_CHECK(F16 == type || F32 == type || F64 == type || C64 == type); - DotOpEmitter dot_emitter(dot, target_array, lhs_array, rhs_array, - addend_array, executable_run_options_value, b, - hlo_module_config, target_machine_features); - return dot_emitter.Emit(); -} - void DotOpEmitter::EmitTiledLlvmIrGemm() { - PrimitiveType primitive_type = dot_.shape().element_type(); + PrimitiveType primitive_type = dot_info_.result_shape.element_type(); MatMultDims mat_mult_dims = GetMatMultDims(); llvm::Value* lhs = lhs_array_.GetBasePointer(); @@ -136,7 +220,7 @@ void DotOpEmitter::EmitTiledLlvmIrGemm() { } void DotOpEmitter::EmitTiledLlvmIrGemv() { - PrimitiveType primitive_type = dot_.shape().element_type(); + PrimitiveType primitive_type = dot_info_.result_shape.element_type(); CHECK(primitive_util::IsFloatingPointType(primitive_type) || primitive_util::IsIntegralType(primitive_type)); @@ -258,11 +342,6 @@ Status DotOpEmitter::Emit() { // which performs the sum-of-products (the reduction loop) before storing // the result in the output buffer. - // This routine assumes that the dot operation is not in a parallelized - // enclosing computation. - CHECK( - dot_.parent()->root_instruction()->outer_dimension_partitions().empty()); - const Shape& lhs_shape = lhs_array_.GetShape(); const Shape& rhs_shape = rhs_array_.GetShape(); @@ -273,7 +352,7 @@ Status DotOpEmitter::Emit() { return EmitScalarDot(); } - switch (GetDotImplementationStrategy(hlo_module_config_, DotInfo(dot_), + switch (GetDotImplementationStrategy(hlo_module_config_, dot_info_, target_machine_features_)) { case DotImplementationStrategy::kNaiveLlvmIr: EmitNaiveLlvmIrGemm(); @@ -297,7 +376,7 @@ void DotOpEmitter::EmitNaiveLlvmIrGemm() { const Shape& lhs_shape = lhs_array_.GetShape(); const Shape& rhs_shape = rhs_array_.GetShape(); - const DotDimensionNumbers& dim_nums = dot_.dot_dimension_numbers(); + const DotDimensionNumbers& dim_nums = dot_info_.dim_nums; // Reduce along dimension 0 of the LHS and 1 of the RHS. Vectors are a special // case where the reduction dimension is 0 for both LHS and RHS. This results @@ -317,7 +396,7 @@ void DotOpEmitter::EmitNaiveLlvmIrGemm() { // Create loop nests which loop through the LHS operand dimensions and the RHS // operand dimensions. The reduction dimension of the LHS and RHS are handled // in a separate innermost loop which performs the sum of products. - llvm_ir::ForLoopNest loop_nest(llvm_ir::IrName(&dot_), b_); + llvm_ir::ForLoopNest loop_nest(llvm_ir::IrName(dot_hlo_name_), b_); llvm_ir::IrArray::Index lhs_index = loop_nest.EmitOperandArrayLoopNest( lhs_array_, /*dimension_to_skip=*/lhs_reduction_dimension, "lhs"); llvm_ir::IrArray::Index rhs_index = loop_nest.EmitOperandArrayLoopNest( @@ -561,11 +640,11 @@ Status DotOpEmitter::EmitCallToRuntime() { } DotOpEmitter::MatMultDims DotOpEmitter::GetMatMultDims() const { - CHECK_EQ(dot_.shape().dimensions_size(), 2); + CHECK_EQ(dot_info_.result_shape.dimensions_size(), 2); const Shape& lhs_shape = lhs_array_.GetShape(); const Shape& rhs_shape = rhs_array_.GetShape(); - const DotDimensionNumbers& dim_nums = dot_.dot_dimension_numbers(); + const DotDimensionNumbers& dim_nums = dot_info_.dim_nums; return { /*m=*/lhs_shape.dimensions(1 - dim_nums.lhs_contracting_dimensions(0)), @@ -754,5 +833,27 @@ bool DotOperandsAndResultMustHaveRowMajorLayout( return impl_strategy == DotImplementationStrategy::kTiledLlvmIrGemm || impl_strategy == DotImplementationStrategy::kEigen; } + +Status EmitDotOperation(const HloInstruction& dot, + const llvm_ir::IrArray& target_array, + const llvm_ir::IrArray& lhs_array, + const llvm_ir::IrArray& rhs_array, + const llvm_ir::IrArray* addend_array, + llvm::Value* executable_run_options_value, + llvm::IRBuilder<>* b, + const HloModuleConfig& hlo_module_config, + const TargetMachineFeatures& target_machine_features) { + // This routine assumes that the dot operation is not in a parallelized + // enclosing computation. + CHECK(dot.parent()->root_instruction()->outer_dimension_partitions().empty()); + + PrimitiveType type = target_array.GetShape().element_type(); + TF_RET_CHECK(F16 == type || F32 == type || F64 == type || C64 == type); + DotOpEmitter dot_emitter(DotInfo(dot), dot.name(), target_array, lhs_array, + rhs_array, addend_array, + executable_run_options_value, b, hlo_module_config, + target_machine_features); + return dot_emitter.Emit(); +} } // namespace cpu } // namespace xla diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h index eaa226a4c2..105bd3005c 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h @@ -48,118 +48,24 @@ bool DotImplementationCanHandleTranspose( absl::optional ProfitableToMakeDotOperandColumnMajor( const HloInstruction& hlo); -// Helper class for emitting LLVM IR to perform the dot operation. -class DotOpEmitter { - public: - // Emit LLVM IR to perform the dot operation on lhs_array and rhs_array and - // place the result in target_array. IR is emitted at current insert point of - // the builder. Upon completion of the method, the insert point is set to the - // end of all instructions emitted for this operation. - // - // If `addend_array` is not nullptr then it must be an array of the same - // dimensions as the result, and the result is computed as `addend_array` + - // dot(`lhs_array`, `rhs_array`). A non-null `addend_array` is only supported - // for Matrix-vector products. - static Status EmitDotOperation( - const HloInstruction& dot, const llvm_ir::IrArray& target_array, - const llvm_ir::IrArray& lhs_array, const llvm_ir::IrArray& rhs_array, - const llvm_ir::IrArray* addend_array, - llvm::Value* executable_run_options_value, llvm::IRBuilder<>* b, - const HloModuleConfig& hlo_module_config, - const TargetMachineFeatures& target_machine_features); - - private: - DotOpEmitter(const HloInstruction& dot, const llvm_ir::IrArray& target_array, - const llvm_ir::IrArray& lhs_array, - const llvm_ir::IrArray& rhs_array, - const llvm_ir::IrArray* addend_array, - llvm::Value* executable_run_options_value, llvm::IRBuilder<>* b, - const HloModuleConfig& hlo_module_config, - const TargetMachineFeatures& target_machine_features); - - // Emits the IR to perform the dot operation. - Status Emit(); - - // Emits instructions to perform a scalar dot product (a multiply of the - // LHS and RHS) and store the results in the target. - Status EmitScalarDot(); - - // Emits a call to the CPU runtime to perform the matrix multiply. - Status EmitCallToRuntime(); - - // Represents the dimensions of a matrix-matrix multiply operation. - struct MatMultDims { - // The number of rows in the LHS. - int64 m; - - // The number of columns in the LHS, which is also must be equal to the - // number of rows in the RHS. - int64 k; - - // The number of columns on the RHS. - int64 n; - - // True if the LHS matrix is column major. - bool lhs_column_major; - - // True if the LHS contraction dimension is not 1. - bool lhs_non_canonical; - - // True if the RHS matrix is column major. - bool rhs_column_major; - - // True if the RHS contraction dimension is not 0. - bool rhs_non_canonical; - - // True if the result matrix is column major. - bool target_column_major; - }; - - // Get the MatMultDims instance for the dot product this DotOpEmitter - // represents. Precondition: the dot is of rank 2 (and thus its operands are - // of rank 2 as well). - MatMultDims GetMatMultDims() const; - - // Lowers the dot operation as a tiled Matrix*Vector loop. - void EmitTiledLlvmIrGemv(); - - // Lowers the dot operation as a tiled Matrix*Matrix loop. - void EmitTiledLlvmIrGemm(); - - // Lowers the dot operation as a naive nested loop that computes the result - // one element at a time. - void EmitNaiveLlvmIrGemm(); - - // When doing a tiled GEMV in LLVM IR, a "tile" consists of this many vector - // registers. - int64 GetGemvTilingFactor() const { - const int64 kDefaultTilingFactor = 8; - return options::LlvmIrGemvTilingFactor(hlo_module_config_) - .value_or(kDefaultTilingFactor); - } - - std::tuple GetGemmTileSize() const { - // Tuned for broadwell - Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz - // - // TODO(b/80093688): Tune for other architectures and centralize this - // information in one place. - const std::tuple kDefaultTileSize = - std::tuple(11, 9, 1); - return options::LlvmIrGemmTileSize(hlo_module_config_) - .value_or(kDefaultTileSize); - } - - const HloInstruction& dot_; - const llvm_ir::IrArray& target_array_; - const llvm_ir::IrArray& lhs_array_; - const llvm_ir::IrArray& rhs_array_; - const llvm_ir::IrArray* addend_array_; - llvm::Value* executable_run_options_value_; - llvm::IRBuilder<>* b_; - const HloModuleConfig& hlo_module_config_; - const TargetMachineFeatures& target_machine_features_; -}; - +// Emit LLVM IR to perform the dot operation on lhs_array and rhs_array and +// place the result in target_array. IR is emitted at current insert point of +// the builder. Upon completion of the method, the insert point is set to the +// end of all instructions emitted for this operation. +// +// If `addend_array` is not nullptr then it must be an array of the same +// dimensions as the result, and the result is computed as `addend_array` + +// dot(`lhs_array`, `rhs_array`). A non-null `addend_array` is only supported +// for Matrix-vector products. +Status EmitDotOperation(const HloInstruction& dot, + const llvm_ir::IrArray& target_array, + const llvm_ir::IrArray& lhs_array, + const llvm_ir::IrArray& rhs_array, + const llvm_ir::IrArray* addend_array, + llvm::Value* executable_run_options_value, + llvm::IRBuilder<>* b, + const HloModuleConfig& hlo_module_config, + const TargetMachineFeatures& target_machine_features); } // namespace cpu } // namespace xla diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc index b352848824..91e3693354 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc @@ -970,10 +970,10 @@ Status IrEmitter::HandleDot(HloInstruction* dot) { << llvm_ir::DumpToString(*target_array.GetBasePointer()); // Dot operation is complicated so we delegate to a helper class. - return DotOpEmitter::EmitDotOperation( - *dot, target_array, lhs_array, rhs_array, /*addend_array=*/nullptr, - GetExecutableRunOptionsArgument(), &b_, hlo_module_config_, - target_machine_features_); + return EmitDotOperation(*dot, target_array, lhs_array, rhs_array, + /*addend_array=*/nullptr, + GetExecutableRunOptionsArgument(), &b_, + hlo_module_config_, target_machine_features_); } StatusOr IrEmitter::EmitTargetElementLoopBodyForConvolution( @@ -2203,10 +2203,10 @@ Status IrEmitter::HandleFusion(HloInstruction* fusion) { llvm_ir::IrArray addend_array( GetIrArrayFor(fusion->operand(addend_param_number))); - TF_RETURN_IF_ERROR(DotOpEmitter::EmitDotOperation( - *dot, target_array, lhs_array, rhs_array, &addend_array, - GetExecutableRunOptionsArgument(), &b_, hlo_module_config_, - target_machine_features_)); + TF_RETURN_IF_ERROR( + EmitDotOperation(*dot, target_array, lhs_array, rhs_array, + &addend_array, GetExecutableRunOptionsArgument(), &b_, + hlo_module_config_, target_machine_features_)); return Status::OK(); } else { return Unimplemented("Fusion kind not implemented on CPU"); -- GitLab From 2ca0bf7cf4e16cbed6affd7dd844fea4ed3a62df Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Mon, 7 Jan 2019 22:40:38 -0800 Subject: [PATCH 0320/2345] This CL checks StepStats, records the output slot for each Switch Op execution, and adds the output slot vector to GraphDef Switch node. When VirtualScheduler schedules the Switch Op, it will take the actual output branch. PiperOrigin-RevId: 228285499 --- .../core/grappler/costs/virtual_scheduler.cc | 113 +++- .../core/grappler/costs/virtual_scheduler.h | 12 + .../grappler/costs/virtual_scheduler_test.cc | 516 ++++++++++++++++++ 3 files changed, 623 insertions(+), 18 deletions(-) diff --git a/tensorflow/core/grappler/costs/virtual_scheduler.cc b/tensorflow/core/grappler/costs/virtual_scheduler.cc index 7071c679dc..bb8425a5a1 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler.cc +++ b/tensorflow/core/grappler/costs/virtual_scheduler.cc @@ -15,8 +15,6 @@ limitations under the License. #include "tensorflow/core/grappler/costs/virtual_scheduler.h" -#include - #include "tensorflow/core/framework/allocation_description.pb.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" @@ -38,6 +36,12 @@ namespace tensorflow { namespace grappler { namespace { +// Optional attribute name for Switch op as a vector of int that tells +// which branch the Switch output is taken on every round of execution. +// We use this side information, if provided, for scheduling ops after Switch +// correctly (e.g., While loop). +constexpr char kOutputSlots[] = "_output_slot_vector"; + Costs CombineCosts(const Costs& left, const Costs& right) { CHECK_NE(left.max_memory, kMemoryUnknown); CHECK_NE(left.max_per_op_buffers, kMemoryUnknown); @@ -402,6 +406,8 @@ Status VirtualScheduler::Init() { name_to_node[node->name()] = node; } + // Traverse the graph to check if the graph is annotated with Switch outputs. + // Also record _Send nodes. // TODO(dyoon): Instead of identifying _Send node here manually, add _Send // to _Recv as control dependency when creating GrapplerItem. std::unordered_map name_to_send; @@ -410,6 +416,11 @@ Status VirtualScheduler::Init() { const auto& attr = node.attr(); name_to_send[attr.at("tensor_name").s()] = &node; } + + if (IsSwitch(node)) { + const auto& attr = node.attr(); + if (attr.count(kOutputSlots) > 0) switch_outputs_annotated_ = true; + } } // To reuse _Recv ops. @@ -771,6 +782,82 @@ Costs& VirtualScheduler::FindOrCreateZero(const string& op_name, return it->second; } +// Check Switch outputs in updated MetaGraphDef, add corresponding nodes to +// ready queue. +// Fallback to add all outputs if fail to find the actual output. +bool VirtualScheduler::AddSwitchOutputsToReadyQueue( + const NodeDef* node, int curr_iter, const Costs::Duration& curr_time) { + if (node->attr().count(kOutputSlots) == 0) return false; + + auto& node_state = node_map_[node]; + const auto& slot_vector = node->attr().at(kOutputSlots); + if (slot_vector.list().i_size() <= curr_iter) { + // Sometimes we encounter infinite loop. Fall back to add all outputs. + return false; + } + + int slot = slot_vector.list().i(curr_iter); + for (const auto& port_num_output_pair : node_state.outputs) { + if (port_num_output_pair.first != slot) continue; + + for (auto* output_node : port_num_output_pair.second) { + auto& output_state = node_map_[output_node]; + output_state.num_inputs_ready++; + // Execute a node as soon as all its inputs are ready. Merge nodes + // are special since they run as soon as one of their inputs becomes + // available. + if (output_state.num_inputs_ready == output_state.inputs.size() || + IsMerge(*output_node)) { + // This output node is now ready. + output_state.time_ready = curr_time; + ready_nodes_->AddNode(output_node); + VLOG(3) << "Node " << node->name() << " iter " << curr_iter << "/" + << slot_vector.list().i_size() << " Add Switch output " << slot + << ": " << output_node->name(); + } + } + return true; + } + + return false; +} + +void VirtualScheduler::AddOutputNodesToReadyQueue( + const NodeDef* node, const Costs::Duration& curr_time) { + auto& node_state = node_map_[node]; + int curr_iter = node_state.num_executed_times; + ++node_state.num_executed_times; + + if (switch_outputs_annotated_) { + // If the graph is annotated with StepStats, reset num_inputs_ready so we + // can schedule the node multiple times. + node_state.num_inputs_ready = 0; + + // For Switch node, get output branch from updated MetaGraphDef. + if (IsSwitch(*node) && + AddSwitchOutputsToReadyQueue(node, curr_iter, curr_time)) + return; + } + + // Increment num_inputs_ready of the output nodes and maybe add to ready + // nodes. + for (const auto& port_num_output_pair : node_state.outputs) { + for (auto* output_node : port_num_output_pair.second) { + auto& output_state = node_map_[output_node]; + output_state.num_inputs_ready++; + // Execute a node as soon as all its inputs are ready. Merge nodes are + // special since they run as soon as one of their inputs becomes + // available. + if (output_state.num_inputs_ready == output_state.inputs.size() || + IsMerge(*output_node)) { + // This output node is now ready. + output_state.time_ready = curr_time; + ready_nodes_->AddNode(output_node); + } + } + } +} + bool VirtualScheduler::MarkCurrNodeExecuted(const Costs& node_costs) { // Update graph_costs_ and per-op costs. graph_costs_ = CombineCosts(graph_costs_, node_costs); @@ -798,6 +885,10 @@ bool VirtualScheduler::MarkCurrNodeExecuted(const Costs& node_costs) { // Node is scheduled when the device is available AND all the inputs are // ready; hence, time_scheduled is time_ready if time_ready > device curr // time. + // TODO(andiryxu): Current node_state result only records the last execution. + // With annotated MetaGraph we can schedule a node for multiple times. + // Refine NodeState structure accordingly, e.g. record time_scheduled in a + // vector. node_state.time_scheduled = std::max(device.GetCurrTime(), node_state.time_ready); // Override device curr time with the time_scheduled. @@ -831,22 +922,8 @@ bool VirtualScheduler::MarkCurrNodeExecuted(const Costs& node_costs) { << ", scheduled: " << node_state.time_scheduled.count() << ", finished: " << node_state.time_finished.count(); - // Increment num_inputs_ready of the output nodes and maybe add to ready nodes - for (const auto& port_num_output_pair : node_state.outputs) { - for (auto* output_node : port_num_output_pair.second) { - auto& output_state = node_map_[output_node]; - output_state.num_inputs_ready++; - // Execute a node as soon as all its inputs are ready. Merge nodes are - // special since they run as soon as one of their inputs becomes - // available. - if (output_state.num_inputs_ready == output_state.inputs.size() || - IsMerge(*output_node)) { - // This output node is now ready. - output_state.time_ready = curr_time; - ready_nodes_->AddNode(output_node); - } - } - } + // Check outputs, add ready nodes to queue. + AddOutputNodesToReadyQueue(node, curr_time); // Increment num_outputs_executed of the input nodes and maybe update memory. for (const auto& input_port : node_state.inputs) { diff --git a/tensorflow/core/grappler/costs/virtual_scheduler.h b/tensorflow/core/grappler/costs/virtual_scheduler.h index f500e4ab51..ffc5e9187c 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler.h +++ b/tensorflow/core/grappler/costs/virtual_scheduler.h @@ -70,11 +70,15 @@ struct NodeState { // Each output port uses up memory space from time_scheduled to its // time_no_references. + // How many times this node has been executed, e.g. in a while loop. + int num_executed_times; + NodeState() { num_inputs_ready = 0; time_ready = Costs::Duration::max(); time_scheduled = Costs::Duration::max(); time_finished = Costs::Duration::max(); + num_executed_times = 0; // Note that num_outputs_executed and time_no_references are not initialized // here, since we don't know the size (i.e., # outputs for this node). } @@ -330,6 +334,10 @@ class VirtualScheduler { std::map* op_cost); float Round2(const float x) const; bool IsPersistentNode(const NodeDef* node) const; + bool AddSwitchOutputsToReadyQueue(const NodeDef* node, int curr_iter, + const Costs::Duration& curr_time); + void AddOutputNodesToReadyQueue(const NodeDef* node, + const Costs::Duration& curr_time); // Scheduler states: ReadyNodeManager* ready_nodes_; // Not owned. @@ -360,6 +368,10 @@ class VirtualScheduler { bool initialized_; bool track_mem_usage_snapshot_; + // Whether the input graph includes Switch nodes annotated with output slots + // information. + bool switch_outputs_annotated_ = false; + VirtualPlacer placer_; // owned. }; diff --git a/tensorflow/core/grappler/costs/virtual_scheduler_test.cc b/tensorflow/core/grappler/costs/virtual_scheduler_test.cc index c5651715f4..5e044b2037 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler_test.cc +++ b/tensorflow/core/grappler/costs/virtual_scheduler_test.cc @@ -869,6 +869,439 @@ versions { grappler_item_->fetch = {"while/Exit", "while/Exit_1"}; } + // A simple while loop strengthened with Switch outputs. + void CreateGrapplerItemWithLoopSwitchOutputs() { + // Test graph produced in python using: + /* + with tf.Graph().as_default(): + i0 = tf.constant(0) + m0 = tf.ones([2, 2]) + c = lambda i, m: i < 10 + b = lambda i, m: [i+1, tf.concat([m, m], axis=0)] + r = tf.while_loop( + c, b, loop_vars=[i0, m0], + shape_invariants=[i0.get_shape(), tf.TensorShape([None, 2])]) + with open('/tmp/graph.pbtxt', 'w') as f: + f.write(str(tf.get_default_graph().as_graph_def())) + */ + const string gdef_ascii = R"EOF( +node { + name: "Const" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 0 + } + } + } +} +node { + name: "ones" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + dim { + size: 2 + } + dim { + size: 2 + } + } + float_val: 1.0 + } + } + } +} +node { + name: "while/Enter" + op: "Enter" + input: "Const" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "frame_name" + value { + s: "while/while/" + } + } + attr { + key: "is_constant" + value { + b: false + } + } + attr { + key: "parallel_iterations" + value { + i: 10 + } + } +} +node { + name: "while/Enter_1" + op: "Enter" + input: "ones" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "frame_name" + value { + s: "while/while/" + } + } + attr { + key: "is_constant" + value { + b: false + } + } + attr { + key: "parallel_iterations" + value { + i: 10 + } + } +} +node { + name: "while/Merge" + op: "Merge" + input: "while/Enter" + input: "while/NextIteration" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_INT32 + } + } +} +node { + name: "while/Merge_1" + op: "Merge" + input: "while/Enter_1" + input: "while/NextIteration_1" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "while/Less/y" + op: "Const" + input: "^while/Merge" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 10 + } + } + } +} +node { + name: "while/Less" + op: "Less" + input: "while/Merge" + input: "while/Less/y" + attr { + key: "T" + value { + type: DT_INT32 + } + } +} +node { + name: "while/LoopCond" + op: "LoopCond" + input: "while/Less" +} +node { + name: "while/Switch" + op: "Switch" + input: "while/Merge" + input: "while/LoopCond" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "_class" + value { + list { + s: "loc:@while/Merge" + } + } + } + attr { + key: "_output_slot_vector" + value { + list { + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 0 + } + } + } +} +node { + name: "while/Switch_1" + op: "Switch" + input: "while/Merge_1" + input: "while/LoopCond" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@while/Merge_1" + } + } + } + attr { + key: "_output_slot_vector" + value { + list { + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 1 + i: 0 + } + } + } +} +node { + name: "while/Identity" + op: "Identity" + input: "while/Switch:1" + attr { + key: "T" + value { + type: DT_INT32 + } + } +} +node { + name: "while/Identity_1" + op: "Identity" + input: "while/Switch_1:1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "while/add/y" + op: "Const" + input: "^while/Identity" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 1 + } + } + } +} +node { + name: "while/add" + op: "Add" + input: "while/Identity" + input: "while/add/y" + attr { + key: "T" + value { + type: DT_INT32 + } + } +} +node { + name: "while/concat/axis" + op: "Const" + input: "^while/Identity" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 0 + } + } + } +} +node { + name: "while/concat" + op: "ConcatV2" + input: "while/Identity_1" + input: "while/Identity_1" + input: "while/concat/axis" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } +} +node { + name: "while/NextIteration" + op: "NextIteration" + input: "while/add" + attr { + key: "T" + value { + type: DT_INT32 + } + } +} +node { + name: "while/NextIteration_1" + op: "NextIteration" + input: "while/concat" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "while/Exit" + op: "Exit" + input: "while/Switch" + attr { + key: "T" + value { + type: DT_INT32 + } + } +} +node { + name: "while/Exit_1" + op: "Exit" + input: "while/Switch_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +versions { + producer: 21 +} + )EOF"; + + grappler_item_.reset(new GrapplerItem); + CHECK(protobuf::TextFormat::ParseFromString(gdef_ascii, + &grappler_item_->graph)); + grappler_item_->id = "test_graph"; + grappler_item_->fetch = {"while/Exit", "while/Exit_1"}; + } + + // Create a FusedBatchNorm op that has multiple output ports. void CreateGrapplerItemWithInterDeviceTransfers() { tensorflow::Scope s = tensorflow::Scope::NewRootScope().WithDevice(kCPU0); @@ -1942,6 +2375,89 @@ TEST_F(VirtualSchedulerTest, WhileLoop) { ValidateDependencyChain(start_times, {"while/Switch_1", "while/Exit_1"}); } +TEST_F(VirtualSchedulerTest, WhileLoopWithSwitchOutputs) { + // Init. + CreateGrapplerItemWithLoopSwitchOutputs(); + InitScheduler(); + + // Runs the scheduler. + RunScheduler(""); + + RunMetadata metadata; + scheduler_->Summary(&metadata); + + // Nodes in topological order: + // * const, ones + // * while/Enter, while/Enter_1 + // * while/Merge, while/Merge_1 + // * while/Less/y + // * while/Less + // * while/LoopCond + // * while/Switch, while/Switch_1 + // * while/Identity, while/Identity_1, while/Exit, while/Exit_1 + // * while/add/y, while/concat/axis + // * while/add, while/concat + // * while/NextIteration, while/NextIteration_1 + + int num_next_iteration = 0; + int num_next_iteration_1 = 0; + int num_exit = 0; + int num_exit_1 = 0; + int64 next_iter_start_micro; + int64 next_iter_1_start_micro; + int64 exit_start_micro; + int64 exit_1_start_micro; + + std::unordered_map start_times; + for (const auto& device_step_stats : metadata.step_stats().dev_stats()) { + for (const auto& stats : device_step_stats.node_stats()) { + start_times[stats.node_name()] = stats.all_start_micros(); + if (stats.node_name() == "while/NextIteration") { + ++num_next_iteration; + next_iter_start_micro = stats.all_start_micros(); + } else if (stats.node_name() == "while/NextIteration_1") { + ++num_next_iteration_1; + next_iter_1_start_micro = stats.all_start_micros(); + } else if (stats.node_name() == "while/Exit") { + ++num_exit; + exit_start_micro = stats.all_start_micros(); + } else if (stats.node_name() == "while/Exit_1") { + ++num_exit_1; + exit_1_start_micro = stats.all_start_micros(); + } + } + } + + // Makes sure we run the loop body for ten times. + EXPECT_EQ(10, num_next_iteration); + EXPECT_EQ(10, num_next_iteration_1); + EXPECT_EQ(1, num_exit); + EXPECT_EQ(1, num_exit_1); + + // Start times of while/NextIteration and while/NextIteration_1 should be + // different, so should be those of while/Exit and while/Exit_1. + EXPECT_NE(next_iter_start_micro, next_iter_1_start_micro); + EXPECT_NE(exit_start_micro, exit_1_start_micro); + + // Checks dependency among the nodes; no matter what scheduling mechanism we + // use, the scheduled ops should follow these dependency chains. + // We have to break the loop into two parts, identified by Switch outputs. + ValidateDependencyChain( + start_times, + {"Const", "while/Enter", "while/Merge", "while/Less/y", "while/Less", + "while/LoopCond", "while/Switch", "while/Exit"}); + ValidateDependencyChain(start_times, {"while/Identity", "while/add/y", + "while/add", "while/NextIteration"}); + ValidateDependencyChain( + start_times, {"ones", "while/Enter_1", "while/Merge_1", "while/Switch_1", + "while/Exit_1"}); + ValidateDependencyChain(start_times, {"while/Identity_1", "while/concat", + "while/NextIteration_1"}); + ValidateDependencyChain( + start_times, {"while/Identity", "while/concat/axis", "while/concat"}); + ValidateDependencyChain(start_times, {"while/Identity", "while/add"}); +} + TEST_F(VirtualSchedulerTest, InterDeviceTransfer) { // Init. CreateGrapplerItemWithInterDeviceTransfers(); -- GitLab From 34d2f298f78d009b5ad2732e436cdae640509a33 Mon Sep 17 00:00:00 2001 From: "Li, Guizi" Date: Tue, 8 Jan 2019 16:11:08 +0800 Subject: [PATCH 0321/2345] update based on review comments --- tensorflow/core/graph/mkl_layout_pass.cc | 100 ++++---- tensorflow/core/graph/mkl_layout_pass_test.cc | 160 +++++++------ tensorflow/core/kernels/mkl_conv_ops.cc | 10 +- tensorflow/core/kernels/mkl_fused_ops_test.cc | 213 +++++++----------- 4 files changed, 212 insertions(+), 271 deletions(-) diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc index 23de237df4..dc799e7819 100644 --- a/tensorflow/core/graph/mkl_layout_pass.cc +++ b/tensorflow/core/graph/mkl_layout_pass.cc @@ -538,10 +538,10 @@ class MklLayoutRewritePass : public GraphOptimizationPass { minfo_.push_back({csinfo_.conv2d_grad_filter, csinfo_.bias_add_grad, csinfo_.conv2d_grad_filter_with_bias, GetConv2DBackpropFilterOrBiasAddGrad}); - minfo_.push_back( - {csinfo_.pad, csinfo_.conv2d, csinfo_.pad_with_conv2d, GetPadOrConv2D}); // Merge Pad and Conv2d, only if the pad op is "Pad" // Doesn't merge if pad op is "PadV2" or "MirrorPad" + minfo_.push_back( + {csinfo_.pad, csinfo_.conv2d, csinfo_.pad_with_conv2d, GetPadOrConv2D}); minfo_.push_back({csinfo_.pad, csinfo_.fused_conv2d, csinfo_.pad_with_fused_conv2d, GetPadOrFusedConv2D}); @@ -930,8 +930,8 @@ class MklLayoutRewritePass : public GraphOptimizationPass { // Find Pad or _FusedConv2D node that can be merged with input node 'm'. // If input 'm' is Pad, then check if there exists _FusedConv2D node that can - // be merged with 'm'. If input 'm' is _FusedConv2D, then check if there - // exists Pad node that can be merged with 'm'. + // be merged with 'm'. If input 'm' is _FusedConv2D, then check if there + // exists Pad node that can be merged with 'm'. static Node* GetPadOrFusedConv2D(const Node* m) { DCHECK(m); Node* n = nullptr; @@ -950,7 +950,7 @@ class MklLayoutRewritePass : public GraphOptimizationPass { } else { DCHECK_EQ(m->type_string(), csinfo_.fused_conv2d); // If m is _FusedConv2D, Go over all input edges - // and search for Pad Node. + // and search for Pad node. for (const Edge* e : m->in_edges()) { if (!e->IsControlEdge() && e->src()->type_string() == csinfo_.pad) { n = e->src(); @@ -959,8 +959,7 @@ class MklLayoutRewritePass : public GraphOptimizationPass { } } } - // Check if only VALID type of padding is used - // or not. + // Check if only VALID type of padding is used or not. if (n != nullptr) { string padding; TF_CHECK_OK(GetNodeAttr(conv_node->def(), "padding", &padding)); @@ -968,8 +967,9 @@ class MklLayoutRewritePass : public GraphOptimizationPass { // Then do not merge. n = nullptr; VLOG(1) << "MklLayoutRewritePass: Could match Pad and _FusedConv2D " - << "node for merging. Only VALID type of padding in conv op " - << "can be merged with Pad op Input node: " << m->DebugString(); + << "nodes but cannot merge them. Only conv ops with padding " + << "type VALID can be merged with Pad op Input node: " + << m->DebugString(); } } else { VLOG(1) << "MklLayoutRewritePass: Could not find matching " @@ -2090,7 +2090,6 @@ void MklLayoutRewritePass::CopyAttrsPadWithFusedConv2D(const Node* orig_node, NodeBuilder* nb, bool change_format) { DataType Tpaddings; - bool is_filter_const; CopyAttrsFusedConv2D(orig_node, nb, change_format); @@ -2099,11 +2098,10 @@ void MklLayoutRewritePass::CopyAttrsPadWithFusedConv2D(const Node* orig_node, // Check if filter is a constant. Node* filter_node = nullptr; orig_node->input_node(1, &filter_node); - is_filter_const = filter_node->IsConstant(); // Add attributes to new node. nb->Attr("Tpaddings", Tpaddings); - nb->Attr("is_filter_const", is_filter_const); + nb->Attr("is_filter_const", filter_node->IsConstant()); } // Used with MergePadWithConv2D @@ -2141,7 +2139,7 @@ void MklLayoutRewritePass::CopyAttrsFromPadAndConv2D(const Node* orig_node1, } void MklLayoutRewritePass::CopyAttrsFromPadAndFusedConv2D( - const Node* orig_node1, const Node* orig_node2, NodeBuilder* nb, + const Node* fused_conv2d, const Node* pad, NodeBuilder* nb, bool change_format) { DataType T; int num_args; @@ -2154,15 +2152,15 @@ void MklLayoutRewritePass::CopyAttrsFromPadAndFusedConv2D( DataType Tpaddings; // Get all attributes from old node. - TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "T", &T)); - TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "num_args", &num_args)); - TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "strides", &strides)); - TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "padding", &padding)); - TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "data_format", &data_format)); - TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "dilations", &dilations)); - TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "fused_ops", &fused_ops)); - TF_CHECK_OK(GetNodeAttr(orig_node1->def(), "epsilon", &epsilon)); - TF_CHECK_OK(GetNodeAttr(orig_node2->def(), "Tpaddings", &Tpaddings)); + TF_CHECK_OK(GetNodeAttr(fused_conv2d->def(), "T", &T)); + TF_CHECK_OK(GetNodeAttr(fused_conv2d->def(), "num_args", &num_args)); + TF_CHECK_OK(GetNodeAttr(fused_conv2d->def(), "strides", &strides)); + TF_CHECK_OK(GetNodeAttr(fused_conv2d->def(), "padding", &padding)); + TF_CHECK_OK(GetNodeAttr(fused_conv2d->def(), "data_format", &data_format)); + TF_CHECK_OK(GetNodeAttr(fused_conv2d->def(), "dilations", &dilations)); + TF_CHECK_OK(GetNodeAttr(fused_conv2d->def(), "fused_ops", &fused_ops)); + TF_CHECK_OK(GetNodeAttr(fused_conv2d->def(), "epsilon", &epsilon)); + TF_CHECK_OK(GetNodeAttr(pad->def(), "Tpaddings", &Tpaddings)); // Add attributes to new node. nb->Attr("T", T); @@ -2701,12 +2699,12 @@ Status MklLayoutRewritePass::MergeConv2DWithBiasAdd(std::unique_ptr* g, Status MklLayoutRewritePass::MergePadWithConv2D(std::unique_ptr* g, Node* m, Node* n) { - DCHECK(((m->type_string() == csinfo_.pad && - (n->type_string() == csinfo_.conv2d || - n->type_string() == csinfo_.fused_conv2d))) || - ((n->type_string() == csinfo_.pad && - (m->type_string() == csinfo_.conv2d || - m->type_string() == csinfo_.fused_conv2d)))); + DCHECK((m->type_string() == csinfo_.pad && + (n->type_string() == csinfo_.conv2d || + n->type_string() == csinfo_.fused_conv2d)) || + (n->type_string() == csinfo_.pad && + (m->type_string() == csinfo_.conv2d || + m->type_string() == csinfo_.fused_conv2d))); bool is_fused_conv2d = n->type_string() == csinfo_.fused_conv2d || m->type_string() == csinfo_.fused_conv2d; @@ -2726,10 +2724,8 @@ Status MklLayoutRewritePass::MergePadWithConv2D(std::unique_ptr* g, TF_CHECK_OK(GetNodeAttr(succ->def(), "padding", &padding)); TF_CHECK_OK(GetNodeAttr(succ->def(), "strides", &strides)); TF_CHECK_OK(GetNodeAttr(succ->def(), "dilations", &dilations)); - // Data format for pad is not available and not necessary, thus - // dont need to match data format for Pad - // Check if the data types and devices of both succ and pred are the same. - // Assert is not used, because it can be too strict. + // Check if the devices of both succ and pred are the same. + // Assert is not used because it can be too strict. // Don't need to check for data formats because it is not available in Pad. if (T_pred != T_succ || pred->assigned_device_name() != succ->assigned_device_name() || @@ -2782,41 +2778,32 @@ Status MklLayoutRewritePass::MergePadWithConv2D(std::unique_ptr* g, } } - if (is_fused_conv2d) { - DCHECK_EQ(ConvDataInputEdges, 3); - } else { - DCHECK_EQ(ConvDataInputEdges, 2); - } + DCHECK_EQ(ConvDataInputEdges, is_fused_conv2d ? 3 : 2); // We will use the node name of Conv2D as the name of new node // Build new node. We use same name as original node, but change the op // name. - auto fused_op_name = csinfo_.pad_with_conv2d; - if (is_fused_conv2d) { - fused_op_name = csinfo_.pad_with_fused_conv2d; - } - NodeBuilder nb(succ->name(), fused_op_name); + NodeBuilder nb(succ->name(), is_fused_conv2d ? csinfo_.pad_with_fused_conv2d + : csinfo_.pad_with_conv2d); nb.Input(pred_in[0].first, pred_in[0].second); // In1 (input data) of Pad // pred_in[1] will be 2nd Tensorflow tensor for Conv2D. nb.Input(succ_in[1].first, succ_in[1].second); // In2 (filter) of conv2d // In1 of Conv2D is same as output of Pad. // Thus, only need to add In2 of Conv2D - // FusedConv2D has one additional input, args if (is_fused_conv2d) { + // FusedConv2D has one additional input, args std::vector args; args.emplace_back(succ_in[2].first, succ_in[2].second); nb.Input(gtl::ArraySlice{ - args}); // In3 (args) of FusedConv2D - } - nb.Input(pred_in[1].first, pred_in[1].second); // In2 (paddings) of Pad - - if (is_fused_conv2d) { + args}); // In3 (args) of FusedConv2D + nb.Input(pred_in[1].first, pred_in[1].second); // In2 (paddings) of Pad // Copy attributes from Pad and FusedConv2D to PadWithFusedConv2D. CopyAttrsFromPadAndFusedConv2D(const_cast(succ), const_cast(pred), &nb); } else { + nb.Input(pred_in[1].first, pred_in[1].second); // In2 (paddings) of Pad // Copy attributes from Pad and conv2D to PadWithConv2D. CopyAttrsFromPadAndConv2D(const_cast(succ), const_cast(pred), &nb); @@ -3018,17 +3005,12 @@ Status MklLayoutRewritePass::MergeNode(std::unique_ptr* g, Node* m, m->type_string() == csinfo_.conv2d))) { return this->MergeConv2DWithBiasAdd(g, m, n); } - if (((m->type_string() == csinfo_.pad && - n->type_string() == csinfo_.conv2d)) || - ((n->type_string() == csinfo_.pad && - m->type_string() == csinfo_.conv2d))) { - return this->MergePadWithConv2D(g, m, n); - } - - if (((m->type_string() == csinfo_.pad && - n->type_string() == csinfo_.fused_conv2d && FusedConv2DRewrite(n))) || - ((n->type_string() == csinfo_.pad && - m->type_string() == csinfo_.fused_conv2d && FusedConv2DRewrite(m)))) { + if ((m->type_string() == csinfo_.pad && + (n->type_string() == csinfo_.conv2d || + (n->type_string() == csinfo_.fused_conv2d && FusedConv2DRewrite(n)))) || + (n->type_string() == csinfo_.pad && + (m->type_string() == csinfo_.conv2d || + (m->type_string() == csinfo_.fused_conv2d && FusedConv2DRewrite(m))))) { return this->MergePadWithConv2D(g, m, n); } diff --git a/tensorflow/core/graph/mkl_layout_pass_test.cc b/tensorflow/core/graph/mkl_layout_pass_test.cc index 5544e2bc36..3ac25cdf02 100644 --- a/tensorflow/core/graph/mkl_layout_pass_test.cc +++ b/tensorflow/core/graph/mkl_layout_pass_test.cc @@ -1245,9 +1245,9 @@ TEST_F(MklLayoutPassTest, NodeRewrite_FusedConv2D_Negative2) { // Merge test for PadWithFusedConv2D Op with BiasAdd fusion // padding is VALID type -// A = input(image), B = input(paddings), C= Pad = input of conv2D, -// D=input(filter), E = input(bias), F = _FusedConv2D -// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// A = input(image), B = input(paddings), C = Pad(A, B) = input of conv2D, +// D = input(filter), E = input(bias), F = _FusedConv2D(C, D, E) +// G = Zeta(F, E) // After layout pass // _MklPadWithFusedConv2D(A, D, E, B, DMT/_0, DMT/_1, DMT/_2, DMT/_3) TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Positive1) { @@ -1283,9 +1283,9 @@ TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Positive1) { // Merge test for PadWithFusedConv2D Op with BiasAdd+Relu fusion // padding is VALID type -// A = input(image), B = input(paddings), C= Pad = input of conv2D, -// D=input(filter), E = input(bias), F = _FusedConv2D(With relu) -// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// A = input(image), B = input(paddings), C = Pad(A, B) = input of conv2D, +// D = input(filter), E = input(bias), F = _FusedConv2D(C, D, E) (With relu) +// G = Zeta(F, E) // After layout pass // _MklPadWithFusedConv2D(A, D, E, B, DMT/_0, DMT/_1, DMT/_2, DMT/_3) TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Positive2) { @@ -1322,9 +1322,9 @@ TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Positive2) { // Merge test for PadWithFusedConv2D Op with unsupported fusion // padding is VALID type -// A = input(image), B = input(paddings), C= Pad = input of conv2D, -// D=input(filter), E = input(bias), F = _FusedConv2D(With Unsupported) -// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// A = input(image), B = input(paddings), C = Pad(A, B) = input of conv2D, +// D = input(filter), E = input(bias), +// F = _FusedConv2D(C, D, E) (With Unsupported), G = Zeta(F, E) // After layout pass - No merging TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Negative1) { InitGraph( @@ -1355,9 +1355,9 @@ TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Negative1) { // Merge test for PadWithFusedConv2D Op with BiasAdd fusion // padding is SAME type -// A = input(image), B = input(paddings), C= Pad = input of conv2D, -// D=input(filter), E = input(bias), F = _FusedConv2D -// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// A = input(image), B = input(paddings), C = Pad(A, B) = input of conv2D, +// D = input(filter), E = input(bias), F = _FusedConv2D(C,D,E) +// G = Zeta(F,E) // After layout pass - No merging TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Negative2) { InitGraph( @@ -1391,9 +1391,9 @@ TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Negative2) { // Merge test for PadWithFusedConv2D Op with BiasAdd+Relu fusion // padding is SAME type -// A = input(image), B = input(paddings), C= Pad = input of conv2D, -// D=input(filter), E = input(bias), F = _FusedConv2D(With relu) -// G = Zeta C=Pad(A,B); F=_FusedConv2D(C,D,E); G=Zeta(F,E) +// A = input(image), B = input(paddings), C = Pad(A, B) = input of conv2D, +// D = input(filter), E = input(bias), F = _FusedConv2D(C,D,E)(With relu) +// G = Zeta(F,E) // After layout pass - No merging TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Negative3) { InitGraph( @@ -1426,24 +1426,25 @@ TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Negative3) { "_2->F:5;E->F:2;E->G:1;F->G"); } -// Test if input control edges do not duplicate after merge. -// If both the merging ops have input control edge from a common op +// Tests that there are no duplicate input control edges after merge. +// If both the merging ops have input control edges from a common op // then, the merged op will have only one control edge from that -// common op. +// common op. This test only add additional input control edge check +// based on the previous test NodeMerge_PadWithFusedConv2D_Positive1 // padding is VALID type -// A = input(image), A1 = input, B = input(paddings), -// C= Pad = input of conv2D, -// D=input(filter), F=input(bias), E = _FusedConv2D, Z = Zeta -// C=Pad(A,B); E=_FusedConv2D(C,D,F); Z=Zeta(E,Y) -// A1:control->C:control -// A1:control->E:control +// A = input(image), X = input, B = input(paddings), +// C = Pad(A, B) = input of conv2D, +// D = input(filter), E = input(bias), F = _FusedConv2D(C, D, E) +// G = Zeta(F, E) +// X:control->C:control +// X:control->F:control // After layout pass: // _MklPadWithFusedConv2D(A, D, B, F, DMT/_0, DMT/_1, DMT/_2, DMT/_3) -// A1:control->E:control (only one control edge) +// X:control->E:control (only one control edge) TEST_F(MklLayoutPassTest, Input_ControlEdge_PadWithFusedConv2D_Positive) { DCHECK_EQ(kTensorOrdering, MklTfTensorOrdering::TENSORS_CONTIGUOUS); InitGraph( - "node { name: 'A1' op: 'Input'}" + "node { name: 'X' op: 'Input'}" "node { name: 'A' op: 'Input'}" "node { name: 'B' op: 'Int32Input'}" "node { name: 'C' op: 'Pad'" @@ -1451,8 +1452,8 @@ TEST_F(MklLayoutPassTest, Input_ControlEdge_PadWithFusedConv2D_Positive) { " attr { key: 'Tpaddings' value { type: DT_INT32 } }" " input: ['A', 'B']}" "node { name: 'D' op: 'Input'}" - "node { name: 'F' op: 'Input'}" - "node { name: 'E' op: '_FusedConv2D'" + "node { name: 'E' op: 'Input'}" + "node { name: 'F' op: '_FusedConv2D'" " attr { key: 'T' value { type: DT_FLOAT } }" " attr { key: 'num_args' value { i: 1 } }" " attr { key: 'data_format' value { s: 'NCHW' } }" @@ -1461,45 +1462,45 @@ TEST_F(MklLayoutPassTest, Input_ControlEdge_PadWithFusedConv2D_Positive) { " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" " attr { key: 'epsilon' value { f: 0.001 }}" - " input: ['C', 'D', 'F']}" - "node { name: 'Y' op: 'Input'}" - "node { name: 'Z' op: 'Zeta'" + " input: ['C', 'D', 'E']}" + "node { name: 'G' op: 'Zeta'" " attr {key: 'T' value { type: DT_FLOAT } }" - " input: ['E', 'Y']}"); - Node* a1 = FindNode("A1"); + " input: ['F', 'E']}"); + Node* x = FindNode("X"); Node* c = FindNode("C"); - Node* e = FindNode("E"); - const Edge* edge = graph_.AddControlEdge(a1, c); - const Edge* edge_1 = graph_.AddControlEdge(a1, e); + Node* f = FindNode("F"); + const Edge* edge = graph_.AddControlEdge(x, c); + const Edge* edge_1 = graph_.AddControlEdge(x, f); ASSERT_NE(edge, nullptr); ASSERT_NE(edge_1, nullptr); EXPECT_EQ(DoMklLayoutOptimizationPass(), - "A(Input);A1(Input);B(Int32Input);D(Input);DMT/_0(Const);DMT/" - "_1(Const);DMT/_2(Const);DMT/" - "_3(Const);E(_MklPadWithFusedConv2D);F(Input);Y(Input);Z(Zeta)|A->" - "E;A1:control->E:control;A:control->DMT/_0:control;A:control->DMT/" - "_1:control;A:control->DMT/_2:control;A:control->DMT/" - "_3:control;B->E:3;D->E:1;DMT/_0->E:4;DMT/_1->E:5;DMT/_2->E:6;DMT/" - "_3->E:7;E->Z;F->E:2;Y->Z:1"); + "A(Input);B(Int32Input);D(Input);DMT/_0(Const);DMT/_1(Const);" + "DMT/_2(Const);DMT/_3(Const);E(Input);F(_MklPadWithFusedConv2D);" + "G(Zeta);X(Input)|A->F;A:control->DMT/_0:control;" + "A:control->DMT/_1:control;A:control->DMT/_2:control;" + "A:control->DMT/_3:control;B->F:3;D->F:1;DMT/_0->F:4;" + "DMT/_1->F:5;DMT/_2->F:6;DMT/_3->F:7;E->F:2;E->G:1;F->G;" + "X:control->F:control"); } -// Test if output control edges does not duplicate after merge. +// ts that there are no duplicate output control edges after merge. // If both the merging ops have output control edge to a common op, // then after merge, the merged op will have only one control edge -// to that commom op. +// to that commom op. This test only add additional output control edge check +// based on the previous test NodeMerge_PadWithFusedConv2D_Positive1 // padding is VALID type -// A = input(image), B = input(paddings), C= Pad = input of conv2D, -// D=input(filter), F=input(bias), E = _FusedConv2D, Z = Zeta -// C=Pad(A,B); E=_FusedConv2D(C,D,F); Z=Zeta(E,Y) -// C:control->A1:control -// E:control->A1:control +// A = input(image), B = input(paddings), C = Pad(A, B) = input of conv2D, +// D = input(filter), E = input(bias), F = _FusedConv2D(C, D, E) +// G = Zeta(F, E), X = input +// C:control->X:control +// F:control->X:control // After layout pass: // _MklPadWithFusedConv2D(A, D, B, F, DMT/_0, DMT/_1, DMT/_2, DMT/_2) -// E:control->A1:control (only one control edge) +// F:control->X:control (only one control edge) TEST_F(MklLayoutPassTest, Output_ControlEdge_PadWithFusedConv2D_Positive) { DCHECK_EQ(kTensorOrdering, MklTfTensorOrdering::TENSORS_CONTIGUOUS); InitGraph( - "node { name: 'A1' op: 'Input'}" + "node { name: 'X' op: 'Input'}" "node { name: 'A' op: 'Input'}" "node { name: 'B' op: 'Int32Input'}" "node { name: 'C' op: 'Pad'" @@ -1507,8 +1508,8 @@ TEST_F(MklLayoutPassTest, Output_ControlEdge_PadWithFusedConv2D_Positive) { " attr { key: 'Tpaddings' value { type: DT_INT32 } }" " input: ['A', 'B']}" "node { name: 'D' op: 'Input'}" - "node { name: 'F' op: 'Input'}" - "node { name: 'E' op: '_FusedConv2D'" + "node { name: 'E' op: 'Input'}" + "node { name: 'F' op: '_FusedConv2D'" " attr { key: 'T' value { type: DT_FLOAT } }" " attr { key: 'num_args' value { i: 1 } }" " attr { key: 'data_format' value { s: 'NCHW' } }" @@ -1517,34 +1518,31 @@ TEST_F(MklLayoutPassTest, Output_ControlEdge_PadWithFusedConv2D_Positive) { " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" " attr { key: 'epsilon' value { f: 0.001 }}" - " input: ['C', 'D', 'F']}" - "node { name: 'Y' op: 'Input'}" - "node { name: 'Z' op: 'Zeta'" + " input: ['C', 'D', 'E']}" + "node { name: 'G' op: 'Zeta'" " attr {key: 'T' value { type: DT_FLOAT } }" - " input: ['E', 'Y']}"); - Node* a1 = FindNode("A1"); + " input: ['F', 'E']}"); + Node* x = FindNode("X"); Node* c = FindNode("C"); - Node* e = FindNode("E"); - const Edge* edge = graph_.AddControlEdge(c, a1); - const Edge* edge_1 = graph_.AddControlEdge(e, a1); + Node* f = FindNode("F"); + const Edge* edge = graph_.AddControlEdge(c, x); + const Edge* edge_1 = graph_.AddControlEdge(f, x); ASSERT_NE(edge, nullptr); ASSERT_NE(edge_1, nullptr); EXPECT_EQ(DoMklLayoutOptimizationPass(), - "A(Input);A1(Input);B(Int32Input);D(Input);DMT/_0(Const);DMT/" - "_1(Const);DMT/_2(Const);DMT/" - "_3(Const);E(_MklPadWithFusedConv2D);F(Input);Y(Input);Z(Zeta)|A->" - "E;A:control->DMT/_0:control;A:control->DMT/" + "A(Input);B(Int32Input);D(Input);DMT/_0(Const);DMT/_1(Const);DMT/" + "_2(Const);DMT/_3(Const);E(Input);F(_MklPadWithFusedConv2D);" + "G(Zeta);X(Input)|A->F;A:control->DMT/_0:control;A:control->DMT/" "_1:control;A:control->DMT/_2:control;A:control->DMT/" - "_3:control;B->E:3;D->E:1;DMT/_0->E:4;DMT/_1->E:5;DMT/_2->E:6;DMT/" - "_3->E:7;E->Z;E:control->A1:control;F->E:2;Y->Z:1"); + "_3:control;B->F:3;D->F:1;DMT/_0->F:4;DMT/_1->F:5;DMT/_2->F:6;DMT/" + "_3->F:7;E->F:2;E->G:1;F->G;F:control->X:control"); } // Pad + _FusedConv2D with padding is VALID, // Input node pointing to both Pad and _FusedConv2D -// Output of both Pad and _FusedConv2D feeds one node (Z as Output2) -// A = input(as image), B = input(as paddings), C= Pad -// F = input(as bias), E = _FusedConv2D, Z = Output2 -// C=Pad(A,B); E=_FusedConv2D(C,A,F); Z=Output(C,E) +// Output of both Pad and _FusedConv2D feeds one node (G as Output2) +// A = input(as image), B = input(as paddings), C = Pad(A, B) +// E = input(as bias), F = _FusedConv2D(C, A, E), G=Output(C, F) // After layout pass - No merging, since Pad and _FusedConv2D both // feed to the same node (Z) TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Common_InOutput) { @@ -1556,8 +1554,8 @@ TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Common_InOutput) { " attr { key: 'T' value { type: DT_FLOAT } }" " attr { key: 'Tpaddings' value { type: DT_INT32 } }" " input: ['A', 'B']}" - "node { name: 'F' op: 'Input'}" - "node { name: 'E' op: '_FusedConv2D'" + "node { name: 'E' op: 'Input'}" + "node { name: 'F' op: '_FusedConv2D'" " attr { key: 'T' value { type: DT_FLOAT } }" " attr { key: 'num_args' value { i: 1 } }" " attr { key: 'data_format' value { s: 'NCHW' } }" @@ -1566,15 +1564,15 @@ TEST_F(MklLayoutPassTest, NodeMerge_PadWithFusedConv2D_Common_InOutput) { " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" " attr { key: 'fused_ops' value { list: {s: 'BiasAdd'} } }" " attr { key: 'epsilon' value { f: 0.001 }}" - " input: ['C', 'A', 'F']}" - "node { name: 'Z' op: 'Output2'" - " input: ['C', 'E']}"); + " input: ['C', 'A', 'E']}" + "node { name: 'G' op: 'Output2'" + " input: ['C', 'F']}"); EXPECT_EQ(DoMklLayoutOptimizationPass(), "A(Input);B(Int32Input);C(Pad);DMT/_0(Const);DMT/_1(Const);DMT/" - "_2(Const);E(_MklFusedConv2D);F(Input);Z(Output2)|A->C;A->E:1;B->C:" - "1;C->E;C->Z;C:control->DMT/_0:control;C:control->DMT/" - "_1:control;C:control->DMT/_2:control;DMT/_0->E:3;DMT/_1->E:4;DMT/" - "_2->E:5;E->Z:1;F->E:2"); + "_2(Const);E(Input);F(_MklFusedConv2D);G(Output2)|A->C;A->F:1;B->C:" + "1;C->F;C->G;C:control->DMT/_0:control;C:control->DMT/" + "_1:control;C:control->DMT/_2:control;DMT/_0->F:3;DMT/_1->F:4;DMT/" + "_2->F:5;E->F:2;F->G:1"); } TEST_F(MklLayoutPassTest, NodeRewrite_Conv2DGradFilter_Positive) { diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc index ead2f1e09e..f75a0e5768 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_ops.cc @@ -1126,7 +1126,7 @@ class MklConvOp : public OpKernel { void PadWithConvFusion(OpKernelContext* context, memory::dims& padding_left, memory::dims& padding_right) { - const Tensor& paddings_tf = MklGetInput(context, input_index_pad); + 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())); @@ -1168,7 +1168,7 @@ class MklConvOp : public OpKernel { void set_fuse_pad(bool fuse_pad) { fuse_pad_ = fuse_pad; // In PadwithFusedConv OP, pad is the fourth index. - input_index_pad = 3; + input_index_pad_ = 3; } // This method is for the base class MklConvOp, which handles the @@ -1245,7 +1245,7 @@ class MklConvOp : public OpKernel { bool fuse_relu_ = false; bool fuse_pad_ = pad_enabled; - int input_index_pad = 2; + int input_index_pad_ = 2; const int kInputIndex_Src = 0, kInputIndex_Filter = 1, kInputIndex_Bias = 2; const int kOutputIndex_Dst = 0, kOutputIndex_Filter = 1; @@ -1972,6 +1972,8 @@ TF_CALL_float(REGISTER_MKL_CPU_2D); TF_CALL_float(REGISTER_MKL_CPU_2D_DEPTHWISE); +// Note we are registering _MklFusedConv2D. +// We check the fused_ops attributes to decide if bias is enabled or not. #define REGISTER_MKL_CPU_2D_FUSED(T) \ REGISTER_KERNEL_BUILDER( \ Name("_MklFusedConv2D") \ @@ -1999,8 +2001,6 @@ TF_CALL_float(REGISTER_MKL_CPU_2D_DEPTHWISE); .TypeConstraint("Tpaddings") \ .Label(mkl_op_registry::kMklOpLabel), \ MklDummyOp); -// Note we are registering _MklFusedConv2D. -// We check the fused_ops attributes to decide if bias is enabled or not. TF_CALL_float(REGISTER_MKL_CPU_2D_FUSED); diff --git a/tensorflow/core/kernels/mkl_fused_ops_test.cc b/tensorflow/core/kernels/mkl_fused_ops_test.cc index f1243c5b68..f2527a675d 100644 --- a/tensorflow/core/kernels/mkl_fused_ops_test.cc +++ b/tensorflow/core/kernels/mkl_fused_ops_test.cc @@ -38,8 +38,12 @@ namespace tensorflow { static const uint8 dummy_tensor[] = {0, 0, 0, 0, 0, 0, 0, 0}; static const TensorShape dummy_shape({8}); +using BiasAddGraphRunner = + std::function; + template -class ConvMklToTF : public OpsTestBase { +class CommonTestUtilities : public OpsTestBase { public: void PerformConversion(DataType dtype, const Tensor& tensor, const Tensor& mkl_meta_tensor, Tensor* output) { @@ -61,8 +65,8 @@ class ConvMklToTF : public OpsTestBase { // Runs a Tensorflow graph defined by the root scope, and fetches the result // of 'fetch' node into the output Tensor. - void RunAndFetch(const tensorflow::Scope& root, const string& fetch, - Tensor* output) { + static void RunAndFetch(const tensorflow::Scope& root, const string& fetch, + Tensor* output) { tensorflow::GraphDef graph; TF_ASSERT_OK(root.ToGraphDef(&graph)); @@ -84,6 +88,35 @@ class ConvMklToTF : public OpsTestBase { test::ExpectTensorNear(expected, output, 1e-5); } void TestBody() {} + + static void VerifyBiasAddTensorsClose(int depth, int image_width, + int image_height, int image_batch_count, + int filter_size, int filter_count, + const BiasAddGraphRunner& run_default, + const BiasAddGraphRunner& run_fused) { + DataType dtype = DataTypeToEnum::v(); + + Tensor image(dtype, {image_batch_count, image_height, image_width, depth}); + image.flat() = image.flat().setRandom(); + + Tensor filter(dtype, {filter_size, filter_size, depth, filter_count}); + filter.flat() = filter.flat().setRandom(); + + const int bias_size = filter_count; + Tensor bias(dtype, {bias_size}); + bias.flat() = bias.flat().setRandom(); + + Tensor conv_2d; + Tensor fused_conv_2d; + + run_default(image, filter, bias, &conv_2d); + run_fused(image, filter, bias, &fused_conv_2d); + + ASSERT_EQ(conv_2d.dtype(), fused_conv_2d.dtype()); + ASSERT_EQ(conv_2d.shape(), fused_conv_2d.shape()); + + test::ExpectClose(conv_2d, fused_conv_2d); + } }; // Testing MKL's fused convolution ops @@ -96,10 +129,6 @@ class MklFusedConv2DOpTest : public OpsTestBase { static constexpr int kImageHeight = 32; static constexpr int kImageBatchCount = 8; - using BiasAddGraphRunner = - std::function; - void RunConv2DWithBias(const Tensor& input_data, const Tensor& filter_data, const Tensor& bias_data, Tensor* output, int stride = 1) { @@ -115,8 +144,7 @@ class MklFusedConv2DOpTest : public OpsTestBase { root.WithOpName("with_bias"), conv, ops::Const(root.WithOpName("bias"), Input::Initializer(bias_data))); - ConvMklToTF conv_comp; - conv_comp.RunAndFetch(root, "with_bias", output); + CommonTestUtilities::RunAndFetch(root, "with_bias", output); } void RunConv2DWithBiasAndRelu(const Tensor& input_data, @@ -137,8 +165,7 @@ class MklFusedConv2DOpTest : public OpsTestBase { auto with_relu = ops::Relu(root.WithOpName("with_relu"), with_bias); - ConvMklToTF conv_comp; - conv_comp.RunAndFetch(root, "with_relu", output); + CommonTestUtilities::RunAndFetch(root, "with_relu", output); } void RunMklFusedConv2DOp(const Tensor& image, const Tensor& filter, @@ -149,6 +176,7 @@ class MklFusedConv2DOpTest : public OpsTestBase { int num_args = static_cast(args.size()); TF_EXPECT_OK(NodeDefBuilder("fused_conv_op", "_MklFusedConv2D") + .Attr("T", dtype) .Input(FakeInput(dtype)) .Input(FakeInput(dtype)) .Attr("num_args", num_args) @@ -156,7 +184,6 @@ class MklFusedConv2DOpTest : public OpsTestBase { .Input(FakeInput(DT_UINT8)) .Input(FakeInput(DT_UINT8)) .Input(FakeInput(num_args, DT_UINT8)) - .Attr("T", dtype) .Attr("strides", {1, stride, stride, 1}) .Attr("padding", "SAME") .Attr("fused_ops", fused_ops) @@ -180,40 +207,11 @@ class MklFusedConv2DOpTest : public OpsTestBase { // Index 2 will need to be changed if the number of outputs produced // by MklConv2D change. const Tensor& output_meta_tensor = *GetOutput(2); - ConvMklToTF conv_comp; - conv_comp.PerformConversion(dtype, output_tensor, output_meta_tensor, + CommonTestUtilities test_util; + test_util.PerformConversion(dtype, output_tensor, output_meta_tensor, output); } - void VerifyBiasAddTensorsNear(int depth, int image_width, int image_height, - int image_batch_count, int filter_size, - int filter_count, - const BiasAddGraphRunner& run_default, - const BiasAddGraphRunner& run_fused) { - DataType dtype = DataTypeToEnum::v(); - - Tensor image(dtype, {image_batch_count, image_height, image_width, depth}); - image.flat() = image.flat().setRandom(); - - Tensor filter(dtype, {filter_size, filter_size, depth, filter_count}); - filter.flat() = filter.flat().setRandom(); - - const int bias_size = filter_count; - Tensor bias(dtype, {bias_size}); - bias.flat() = bias.flat().setRandom(); - - Tensor conv_2d; - Tensor fused_conv_2d; - - run_default(image, filter, bias, &conv_2d); - run_fused(image, filter, bias, &fused_conv_2d); - - ASSERT_EQ(conv_2d.dtype(), fused_conv_2d.dtype()); - ASSERT_EQ(conv_2d.shape(), fused_conv_2d.shape()); - - test::ExpectClose(conv_2d, fused_conv_2d); - } - // Verifies that computing Conv2D+BiasAdd in a graph is identical to // FusedConv2D. void VerifyConv2DWithBias(int filter_size, int filter_count, @@ -233,9 +231,9 @@ class MklFusedConv2DOpTest : public OpsTestBase { out); }; - VerifyBiasAddTensorsNear(depth, image_width, image_height, - image_batch_count, filter_size, filter_count, - run_default, run_fused); + CommonTestUtilities::VerifyBiasAddTensorsClose( + depth, image_width, image_height, image_batch_count, filter_size, + filter_count, run_default, run_fused); } // Verifies that computing Conv2D+BiasAdd+Relu in a graph is identical to @@ -258,9 +256,9 @@ class MklFusedConv2DOpTest : public OpsTestBase { {"BiasAdd", "Relu"}, out); }; - VerifyBiasAddTensorsNear(depth, image_width, image_height, - image_batch_count, filter_size, filter_count, - run_default, run_fused); + CommonTestUtilities::VerifyBiasAddTensorsClose( + depth, image_width, image_height, image_batch_count, filter_size, + filter_count, run_default, run_fused); } }; @@ -343,8 +341,8 @@ class FusedPadConvOpTest : public OpsTestBase { // Compare output to expected results const Tensor& first = *GetOutput(0); const Tensor& second = *GetOutput(2); - ConvMklToTF conv_comp; - conv_comp.ConvertAndCompare(dtype, first, second, expected); + CommonTestUtilities test_util; + test_util.ConvertAndCompare(dtype, first, second, expected); } }; @@ -405,7 +403,6 @@ TEST_F(FusedPadConvOpTest, PaddingConvTestNchw) { } // Testing fusion of pad and fusedconv2d - template class MklPadWithFusedConv2DOpTest : public OpsTestBase { protected: @@ -414,45 +411,12 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { static constexpr int kImageHeight = 28; static constexpr int kImageBatchCount = 8; - // 0: top pad, 1 bottom pad, 2 left pad, 3 right pad - int padding_list[4]; - - using BiasAddGraphRunner = - std::function; - - void VerifyBiasAddTensorsNear(int depth, int image_width, int image_height, - int image_batch_count, int filter_size, - int filter_count, - const BiasAddGraphRunner& run_default, - const BiasAddGraphRunner& run_fused) { - DataType dtype = DataTypeToEnum::v(); - - Tensor image(dtype, {image_batch_count, image_height, image_width, depth}); - image.flat() = image.flat().setRandom(); - - Tensor filter(dtype, {filter_size, filter_size, depth, filter_count}); - filter.flat() = filter.flat().setRandom(); - - const int bias_size = filter_count; - Tensor bias(dtype, {bias_size}); - bias.flat() = bias.flat().setRandom(); - - Tensor conv_2d; - Tensor fused_conv_2d; - - run_default(image, filter, bias, &conv_2d); - run_fused(image, filter, bias, &fused_conv_2d); - - ASSERT_EQ(conv_2d.dtype(), fused_conv_2d.dtype()); - ASSERT_EQ(conv_2d.shape(), fused_conv_2d.shape()); - - test::ExpectClose(conv_2d, fused_conv_2d); - } + // 0: top pad, 1: bottom pad, 2: left pad, 3: right pad + int padding_list_[4]; // Verifies that computing Pad+Conv2D+BiasAdd in a graph is identical to // FusedConv2D. - void VerifyConv2DWithBiasAndPad(int filter_size, int filter_count, + void VerifyPadAndConv2DWithBias(int filter_size, int filter_count, int depth = kDepth, int image_width = kImageWidth, int image_height = kImageHeight, @@ -460,7 +424,7 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { const BiasAddGraphRunner run_default = [this]( const Tensor& input_data, const Tensor& filter_data, const Tensor& bias_data, Tensor* out) { - RunMklFusedConv2DWithPadAndBias(input_data, filter_data, bias_data, out); + RunMklPadWithFusedConv2DAndBias(input_data, filter_data, bias_data, out); }; const BiasAddGraphRunner run_fused = [this]( @@ -470,21 +434,21 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { {"BiasAdd"}, out); }; - VerifyBiasAddTensorsNear(depth, image_width, image_height, - image_batch_count, filter_size, filter_count, - run_default, run_fused); + CommonTestUtilities::VerifyBiasAddTensorsClose( + depth, image_width, image_height, image_batch_count, filter_size, + filter_count, run_default, run_fused); } // Verifies that computing Pad+Conv2D+BiasAdd+Relu in a graph is identical to // FusedConv2D. - void VerifyConv2DWithBiasReluAndPad( + void VerifyPadAndConv2DWithBiasRelu( int filter_size, int filter_count, int depth = kDepth, int image_width = kImageWidth, int image_height = kImageHeight, int image_batch_count = kImageBatchCount) { const BiasAddGraphRunner run_default = [this]( const Tensor& input_data, const Tensor& filter_data, const Tensor& bias_data, Tensor* out) { - RunMklFusedConv2DWithPadAndBiasRelu(input_data, filter_data, bias_data, + RunMklPadWithFusedConv2DAndBiasRelu(input_data, filter_data, bias_data, out); }; @@ -495,21 +459,21 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { {"BiasAdd", "Relu"}, out); }; - VerifyBiasAddTensorsNear(depth, image_width, image_height, - image_batch_count, filter_size, filter_count, - run_default, run_fused); + CommonTestUtilities::VerifyBiasAddTensorsClose( + depth, image_width, image_height, image_batch_count, filter_size, + filter_count, run_default, run_fused); } - void RunMklFusedConv2DWithPadAndBias(const Tensor& input_data, + void RunMklPadWithFusedConv2DAndBias(const Tensor& input_data, const Tensor& filter_data, const Tensor& bias_data, Tensor* output, int stride = 1) { auto root = tensorflow::Scope::NewRootScope(); - // As FusedConv2D only support NHWC format, so here use NHWC format. + // FusedConv2D only supports NHWC format so we use NHWC here. auto padding = ops::Const(root.WithOpName("padding"), - {0, 0, padding_list[0], padding_list[1], - padding_list[2], padding_list[3], 0, 0}, + {0, 0, padding_list_[0], padding_list_[1], + padding_list_[2], padding_list_[3], 0, 0}, {4, 2}); auto pad = ops::Pad( root.WithOpName("pad"), @@ -525,20 +489,19 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { root.WithOpName("with_bias"), conv, ops::Const(root.WithOpName("bias"), Input::Initializer(bias_data))); - ConvMklToTF conv_comp; - conv_comp.RunAndFetch(root, "with_bias", output); + CommonTestUtilities::RunAndFetch(root, "with_bias", output); } - void RunMklFusedConv2DWithPadAndBiasRelu(const Tensor& input_data, + void RunMklPadWithFusedConv2DAndBiasRelu(const Tensor& input_data, const Tensor& filter_data, const Tensor& bias_data, Tensor* output, int stride = 1) { auto root = tensorflow::Scope::NewRootScope(); - // As FusedConv2D only support NHWC format, so here use NHWC format. + // FusedConv2D only supports NHWC format so we use NHWC here. auto padding = ops::Const(root.WithOpName("padding"), - {0, 0, padding_list[0], padding_list[1], - padding_list[2], padding_list[3], 0, 0}, + {0, 0, padding_list_[0], padding_list_[1], + padding_list_[2], padding_list_[3], 0, 0}, {4, 2}); auto pad = ops::Pad( root.WithOpName("pad"), @@ -556,8 +519,7 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { auto with_relu = ops::Relu(root.WithOpName("with_relu"), with_bias); - ConvMklToTF conv_comp; - conv_comp.RunAndFetch(root, "with_relu", output); + CommonTestUtilities::RunAndFetch(root, "with_relu", output); } void RunMklFusedConv2DWithPadOp(const Tensor& image, const Tensor& filter, @@ -567,10 +529,12 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { DataType dtype = DataTypeToEnum::v(); const int num_args = static_cast(args.size()); Tensor padding(DT_INT32, {4, 2}); - test::FillValues(&padding, {0, 0, padding_list[0], padding_list[1], - padding_list[2], padding_list[3], 0, 0}); + test::FillValues( + &padding, {0, 0, padding_list_[0], padding_list_[1], padding_list_[2], + padding_list_[3], 0, 0}); TF_EXPECT_OK(NodeDefBuilder("pad_fused_conv_op", "_MklPadWithFusedConv2D") + .Attr("T", dtype) .Input(FakeInput(dtype)) .Input(FakeInput(dtype)) .Attr("num_args", num_args) @@ -580,7 +544,6 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { .Input(FakeInput(DT_UINT8)) .Input(FakeInput(num_args, DT_UINT8)) .Input(FakeInput(DT_UINT8)) - .Attr("T", dtype) .Attr("strides", {1, stride, stride, 1}) .Attr("padding", "VALID") .Attr("fused_ops", fused_ops) @@ -594,10 +557,8 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { for (const Tensor& arg : args) AddInputFromArray(arg.shape(), arg.flat()); AddInputFromArray(padding.shape(), padding.flat()); - AddInputFromArray(dummy_shape, dummy_tensor); - AddInputFromArray(dummy_shape, dummy_tensor); - AddInputFromArray(dummy_shape, dummy_tensor); - for (const Tensor& arg : args) + // Add MKL meta input for input, filter, pad and agrs. + for (int i = 0; i < args.size() + 3; i++) AddInputFromArray(dummy_shape, dummy_tensor); TF_ASSERT_OK(RunOpKernel()); @@ -606,17 +567,17 @@ class MklPadWithFusedConv2DOpTest : public OpsTestBase { // Index 2 will need to be changed if the number of outputs produced // by MklConv2D change. const Tensor& output_meta_tensor = *GetOutput(2); - ConvMklToTF conv_comp; - conv_comp.PerformConversion(dtype, output_tensor, output_meta_tensor, + CommonTestUtilities test_util; + test_util.PerformConversion(dtype, output_tensor, output_meta_tensor, output); } public: void SetPaddingList(int top, int bottom, int left, int right) { - padding_list[0] = top; - padding_list[1] = bottom; - padding_list[2] = left; - padding_list[3] = right; + padding_list_[0] = top; + padding_list_[1] = bottom; + padding_list_[2] = left; + padding_list_[3] = right; } }; @@ -626,28 +587,28 @@ TYPED_TEST_P(MklPadWithFusedConv2DOpTest, WithBiasAndRoundPad) { const int filter_size = 1; const int filter_count = 12; this->SetPaddingList(2, 2, 1, 1); - this->VerifyConv2DWithBiasAndPad(filter_size, filter_count); + this->VerifyPadAndConv2DWithBias(filter_size, filter_count); } TYPED_TEST_P(MklPadWithFusedConv2DOpTest, WithBiasAndPartialPad) { const int filter_size = 1; const int filter_count = 12; this->SetPaddingList(4, 0, 2, 0); - this->VerifyConv2DWithBiasAndPad(filter_size, filter_count); + this->VerifyPadAndConv2DWithBias(filter_size, filter_count); } TYPED_TEST_P(MklPadWithFusedConv2DOpTest, WithBiasReluAndRoundPad) { const int filter_size = 1; const int filter_count = 12; this->SetPaddingList(2, 2, 1, 1); - this->VerifyConv2DWithBiasReluAndPad(filter_size, filter_count); + this->VerifyPadAndConv2DWithBiasRelu(filter_size, filter_count); } TYPED_TEST_P(MklPadWithFusedConv2DOpTest, WithBiasReluAndPartialPad) { const int filter_size = 1; const int filter_count = 12; this->SetPaddingList(4, 0, 2, 0); - this->VerifyConv2DWithBiasReluAndPad(filter_size, filter_count); + this->VerifyPadAndConv2DWithBiasRelu(filter_size, filter_count); } REGISTER_TYPED_TEST_CASE_P(MklPadWithFusedConv2DOpTest, // -- GitLab From 94c604bdd72717b0c18a1648927529b27aeea320 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 00:15:37 -0800 Subject: [PATCH 0322/2345] Documentation fix for the unsorted_segment_max op RELNOTES: n/a PiperOrigin-RevId: 228290885 --- .../core/api_def/base_api/api_def_UnsortedSegmentMax.pbtxt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/api_def/base_api/api_def_UnsortedSegmentMax.pbtxt b/tensorflow/core/api_def/base_api/api_def_UnsortedSegmentMax.pbtxt index 7a60e4387a..ed4a2bd558 100644 --- a/tensorflow/core/api_def/base_api/api_def_UnsortedSegmentMax.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_UnsortedSegmentMax.pbtxt @@ -3,7 +3,8 @@ op { in_arg { name: "segment_ids" description: < Date: Tue, 8 Jan 2019 01:03:21 -0800 Subject: [PATCH 0323/2345] compat: Update forward compatibility horizon to 2019-01-08 PiperOrigin-RevId: 228294335 --- 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 dbad494e40..5008741d55 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, 1, 7) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 8) @tf_export("compat.forward_compatible") -- GitLab From 84cf1da2b9552f0af564519e8b200ac8859a17b4 Mon Sep 17 00:00:00 2001 From: James Keeling Date: Tue, 8 Jan 2019 01:34:31 -0800 Subject: [PATCH 0324/2345] Split tile functor GPU code into multiple files This file was a bottleneck during compilation, often taking many minutes to compile. In local testing this change reduces the wall-clock build time for the tile functor GPU kernels from 217s to 71s. PiperOrigin-RevId: 228296771 --- tensorflow/core/kernels/BUILD | 11 ++++++- ...e_functor_gpu.cu.cc => tile_functor_gpu.h} | 28 +++-------------- .../core/kernels/tile_functor_gpu_bool.cu.cc | 31 +++++++++++++++++++ .../kernels/tile_functor_gpu_complex128.cu.cc | 31 +++++++++++++++++++ .../kernels/tile_functor_gpu_complex64.cu.cc | 31 +++++++++++++++++++ .../kernels/tile_functor_gpu_double.cu.cc | 31 +++++++++++++++++++ .../core/kernels/tile_functor_gpu_float.cu.cc | 31 +++++++++++++++++++ .../core/kernels/tile_functor_gpu_half.cu.cc | 31 +++++++++++++++++++ .../core/kernels/tile_functor_gpu_int16.cu.cc | 31 +++++++++++++++++++ .../core/kernels/tile_functor_gpu_int32.cu.cc | 31 +++++++++++++++++++ .../core/kernels/tile_functor_gpu_int64.cu.cc | 31 +++++++++++++++++++ 11 files changed, 294 insertions(+), 24 deletions(-) rename tensorflow/core/kernels/{tile_functor_gpu.cu.cc => tile_functor_gpu.h} (85%) create mode 100644 tensorflow/core/kernels/tile_functor_gpu_bool.cu.cc create mode 100644 tensorflow/core/kernels/tile_functor_gpu_complex128.cu.cc create mode 100644 tensorflow/core/kernels/tile_functor_gpu_complex64.cu.cc create mode 100644 tensorflow/core/kernels/tile_functor_gpu_double.cu.cc create mode 100644 tensorflow/core/kernels/tile_functor_gpu_float.cu.cc create mode 100644 tensorflow/core/kernels/tile_functor_gpu_half.cu.cc create mode 100644 tensorflow/core/kernels/tile_functor_gpu_int16.cu.cc create mode 100644 tensorflow/core/kernels/tile_functor_gpu_int32.cu.cc create mode 100644 tensorflow/core/kernels/tile_functor_gpu_int64.cu.cc diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 701bda7ef6..fee30fa6c3 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -1012,7 +1012,16 @@ tf_kernel_library( hdrs = ["tile_functor.h"], gpu_srcs = [ "tile_functor.h", - "tile_functor_gpu.cu.cc", + "tile_functor_gpu.h", + "tile_functor_gpu_bool.cu.cc", + "tile_functor_gpu_complex64.cu.cc", + "tile_functor_gpu_complex128.cu.cc", + "tile_functor_gpu_double.cu.cc", + "tile_functor_gpu_float.cu.cc", + "tile_functor_gpu_half.cu.cc", + "tile_functor_gpu_int16.cu.cc", + "tile_functor_gpu_int32.cu.cc", + "tile_functor_gpu_int64.cu.cc", ], prefix = "tile_ops", deps = ARRAY_DEPS, diff --git a/tensorflow/core/kernels/tile_functor_gpu.cu.cc b/tensorflow/core/kernels/tile_functor_gpu.h similarity index 85% rename from tensorflow/core/kernels/tile_functor_gpu.cu.cc rename to tensorflow/core/kernels/tile_functor_gpu.h index 84a5060fc3..0de32e730e 100644 --- a/tensorflow/core/kernels/tile_functor_gpu.cu.cc +++ b/tensorflow/core/kernels/tile_functor_gpu.h @@ -13,6 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#ifndef TENSORFLOW_CORE_KERNELS_TILE_FUNCTOR_GPU_H_ +#define TENSORFLOW_CORE_KERNELS_TILE_FUNCTOR_GPU_H_ + #if GOOGLE_CUDA #define EIGEN_USE_GPU @@ -80,28 +83,7 @@ void TileSimple(const Device& d, Tensor* out, const Tensor& in) { } } // end namespace internal - -namespace functor { - -typedef Eigen::GpuDevice GPUDevice; - -// Register functors used for Tile functor. -#define DEFINE_TYPE(T) \ - template struct Tile; \ - template struct Tile; - -TF_CALL_bool(DEFINE_TYPE); -TF_CALL_int16(DEFINE_TYPE); -TF_CALL_int32(DEFINE_TYPE); -TF_CALL_int64(DEFINE_TYPE); -TF_CALL_float(DEFINE_TYPE); -TF_CALL_double(DEFINE_TYPE); -TF_CALL_half(DEFINE_TYPE); -TF_CALL_complex64(DEFINE_TYPE); -TF_CALL_complex128(DEFINE_TYPE); - -#undef DEFINE_TYPE - -} // end namespace functor } // namespace tensorflow #endif // GOOGLE_CUDA + +#endif // TENSORFLOW_CORE_KERNELS_TILE_FUNCTOR_GPU_H_ diff --git a/tensorflow/core/kernels/tile_functor_gpu_bool.cu.cc b/tensorflow/core/kernels/tile_functor_gpu_bool.cu.cc new file mode 100644 index 0000000000..c7a814c7a2 --- /dev/null +++ b/tensorflow/core/kernels/tile_functor_gpu_bool.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/tile_functor.h" +#include "tensorflow/core/kernels/tile_functor_gpu.h" + +namespace tensorflow { +namespace functor { +using Eigen::GpuDevice; + +template struct Tile; +template struct Tile; +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/tile_functor_gpu_complex128.cu.cc b/tensorflow/core/kernels/tile_functor_gpu_complex128.cu.cc new file mode 100644 index 0000000000..4dfa4bac1b --- /dev/null +++ b/tensorflow/core/kernels/tile_functor_gpu_complex128.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/tile_functor.h" +#include "tensorflow/core/kernels/tile_functor_gpu.h" + +namespace tensorflow { +namespace functor { +using Eigen::GpuDevice; + +template struct Tile; +template struct Tile; +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/tile_functor_gpu_complex64.cu.cc b/tensorflow/core/kernels/tile_functor_gpu_complex64.cu.cc new file mode 100644 index 0000000000..525ede938f --- /dev/null +++ b/tensorflow/core/kernels/tile_functor_gpu_complex64.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/tile_functor.h" +#include "tensorflow/core/kernels/tile_functor_gpu.h" + +namespace tensorflow { +namespace functor { +using Eigen::GpuDevice; + +template struct Tile; +template struct Tile; +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/tile_functor_gpu_double.cu.cc b/tensorflow/core/kernels/tile_functor_gpu_double.cu.cc new file mode 100644 index 0000000000..25e024083e --- /dev/null +++ b/tensorflow/core/kernels/tile_functor_gpu_double.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/tile_functor.h" +#include "tensorflow/core/kernels/tile_functor_gpu.h" + +namespace tensorflow { +namespace functor { +using Eigen::GpuDevice; + +template struct Tile; +template struct Tile; +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/tile_functor_gpu_float.cu.cc b/tensorflow/core/kernels/tile_functor_gpu_float.cu.cc new file mode 100644 index 0000000000..f0f31370e4 --- /dev/null +++ b/tensorflow/core/kernels/tile_functor_gpu_float.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/tile_functor.h" +#include "tensorflow/core/kernels/tile_functor_gpu.h" + +namespace tensorflow { +namespace functor { +using Eigen::GpuDevice; + +template struct Tile; +template struct Tile; +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/tile_functor_gpu_half.cu.cc b/tensorflow/core/kernels/tile_functor_gpu_half.cu.cc new file mode 100644 index 0000000000..6c3810a0bc --- /dev/null +++ b/tensorflow/core/kernels/tile_functor_gpu_half.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/tile_functor.h" +#include "tensorflow/core/kernels/tile_functor_gpu.h" + +namespace tensorflow { +namespace functor { +using Eigen::GpuDevice; + +template struct Tile; +template struct Tile; +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/tile_functor_gpu_int16.cu.cc b/tensorflow/core/kernels/tile_functor_gpu_int16.cu.cc new file mode 100644 index 0000000000..2280dcbc82 --- /dev/null +++ b/tensorflow/core/kernels/tile_functor_gpu_int16.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/tile_functor.h" +#include "tensorflow/core/kernels/tile_functor_gpu.h" + +namespace tensorflow { +namespace functor { +using Eigen::GpuDevice; + +template struct Tile; +template struct Tile; +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/tile_functor_gpu_int32.cu.cc b/tensorflow/core/kernels/tile_functor_gpu_int32.cu.cc new file mode 100644 index 0000000000..b05403bada --- /dev/null +++ b/tensorflow/core/kernels/tile_functor_gpu_int32.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/tile_functor.h" +#include "tensorflow/core/kernels/tile_functor_gpu.h" + +namespace tensorflow { +namespace functor { +using Eigen::GpuDevice; + +template struct Tile; +template struct Tile; +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/tile_functor_gpu_int64.cu.cc b/tensorflow/core/kernels/tile_functor_gpu_int64.cu.cc new file mode 100644 index 0000000000..2d83c6b3a1 --- /dev/null +++ b/tensorflow/core/kernels/tile_functor_gpu_int64.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/tile_functor.h" +#include "tensorflow/core/kernels/tile_functor_gpu.h" + +namespace tensorflow { +namespace functor { +using Eigen::GpuDevice; + +template struct Tile; +template struct Tile; +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA -- GitLab From f09a6224bac04fd8b45d72b5babee08212dcf836 Mon Sep 17 00:00:00 2001 From: Gaurav Jain Date: Tue, 8 Jan 2019 02:23:41 -0800 Subject: [PATCH 0325/2345] Make DebugString() in ResourceBase constant PiperOrigin-RevId: 228300469 --- .../compiler/jit/xla_compilation_cache.cc | 2 +- .../compiler/jit/xla_compilation_cache.h | 2 +- .../compiler/tf2xla/xla_compiler_test.cc | 2 +- tensorflow/compiler/tf2xla/xla_context.cc | 2 +- tensorflow/compiler/tf2xla/xla_context.h | 2 +- .../compiler/xrt/xrt_compilation_cache.cc | 4 +++- .../compiler/xrt/xrt_compilation_cache.h | 2 +- tensorflow/compiler/xrt/xrt_state.h | 2 +- .../contrib/bigtable/kernels/bigtable_lib.h | 4 ++-- .../kernels/stats_accumulator_ops.cc | 2 +- .../decision_tree_ensemble_resource.h | 2 +- .../resources/quantile_stream_resource.h | 2 +- .../kernels/v4/decision-tree-resource.h | 2 +- .../kernels/v4/fertile-stats-resource.h | 2 +- .../contrib/tensorrt/resources/trt_resources.h | 2 +- tensorflow/core/framework/lookup_interface.h | 2 +- tensorflow/core/framework/queue_interface.h | 4 ++-- tensorflow/core/framework/reader_interface.h | 2 +- tensorflow/core/framework/resource_mgr.h | 2 +- tensorflow/core/framework/resource_mgr_test.cc | 8 ++++---- .../core/framework/resource_op_kernel_test.cc | 2 +- tensorflow/core/framework/resource_var.h | 2 +- tensorflow/core/framework/stats_aggregator.h | 2 +- tensorflow/core/kernels/barrier_ops.cc | 2 +- tensorflow/core/kernels/batch_kernels.cc | 6 +++--- .../quantiles/quantile_stream_resource.h | 18 +++++++++--------- .../core/kernels/boosted_trees/resources.cc | 2 +- .../core/kernels/boosted_trees/resources.h | 2 +- .../kernels/conditional_accumulator_base.h | 2 +- tensorflow/core/kernels/conv_ops.h | 2 +- .../core/kernels/data/cache_dataset_ops.cc | 4 +++- .../data/experimental/indexed_dataset_op.cc | 2 +- .../data/experimental/threadpool_dataset_op.cc | 2 +- tensorflow/core/kernels/data/iterator_ops.cc | 2 +- .../kernels/data/multi_device_iterator_ops.cc | 2 +- .../core/kernels/data/shuffle_dataset_op.cc | 2 +- tensorflow/core/kernels/fifo_queue.h | 2 +- tensorflow/core/kernels/map_stage_op.cc | 2 +- tensorflow/core/kernels/meta_support.cc | 2 +- tensorflow/core/kernels/mutex_ops.cc | 4 +++- tensorflow/core/kernels/priority_queue.h | 2 +- .../core/kernels/random_shuffle_queue_op.cc | 2 +- .../core/kernels/sparse_tensors_map_ops.cc | 2 +- tensorflow/core/kernels/stack.cc | 2 +- tensorflow/core/kernels/stage_op.cc | 4 ++-- tensorflow/core/kernels/summary_op.cc | 4 +++- tensorflow/core/kernels/tensor_array.h | 4 ++-- .../core/kernels/tensor_forest/resources.h | 2 +- tensorflow/core/kernels/variable_ops.cc | 4 ++-- tensorflow/core/summary/summary_db_writer.cc | 2 +- tensorflow/core/summary/summary_file_writer.cc | 2 +- tensorflow/python/framework/test_ops.cc | 2 +- 52 files changed, 78 insertions(+), 70 deletions(-) diff --git a/tensorflow/compiler/jit/xla_compilation_cache.cc b/tensorflow/compiler/jit/xla_compilation_cache.cc index d5a1ab0850..bff4cc57ee 100644 --- a/tensorflow/compiler/jit/xla_compilation_cache.cc +++ b/tensorflow/compiler/jit/xla_compilation_cache.cc @@ -62,7 +62,7 @@ XlaCompilationCache::~XlaCompilationCache() { // about? } -string XlaCompilationCache::DebugString() { +string XlaCompilationCache::DebugString() const { return "XLA JIT compilation cache"; } diff --git a/tensorflow/compiler/jit/xla_compilation_cache.h b/tensorflow/compiler/jit/xla_compilation_cache.h index 846d0c963d..02aa8f8839 100644 --- a/tensorflow/compiler/jit/xla_compilation_cache.h +++ b/tensorflow/compiler/jit/xla_compilation_cache.h @@ -88,7 +88,7 @@ class XlaCompilationCache : public ResourceBase { xla::LocalClient* client() const { return client_; } const DeviceType& device_type() const { return device_type_; } - string DebugString() override; + string DebugString() const override; // Describes the types, shapes and any compile-time constant arguments // to a kernel. Key that uniquely identifies a compilation output. diff --git a/tensorflow/compiler/tf2xla/xla_compiler_test.cc b/tensorflow/compiler/tf2xla/xla_compiler_test.cc index 32a4301840..492010f731 100644 --- a/tensorflow/compiler/tf2xla/xla_compiler_test.cc +++ b/tensorflow/compiler/tf2xla/xla_compiler_test.cc @@ -82,7 +82,7 @@ namespace { // compiled kernels. class DummyResourceForTest : public ResourceBase { public: - string DebugString() override { return "dummy"; } + string DebugString() const override { return "dummy"; } void Increment() { ++value_; } int Get() { return value_; } diff --git a/tensorflow/compiler/tf2xla/xla_context.cc b/tensorflow/compiler/tf2xla/xla_context.cc index a69af70503..6139bf3cea 100644 --- a/tensorflow/compiler/tf2xla/xla_context.cc +++ b/tensorflow/compiler/tf2xla/xla_context.cc @@ -61,7 +61,7 @@ void XlaContext::set_args(std::vector args) { XlaContext::XlaContext(XlaCompiler* compiler, xla::XlaBuilder* builder) : compiler_(compiler), builder_(builder) {} -string XlaContext::DebugString() { return "XLA JIT context"; } +string XlaContext::DebugString() const { return "XLA JIT context"; } void XlaContext::SetRetval(int index, const XlaExpression& expression) { if (retvals_.size() <= index) { diff --git a/tensorflow/compiler/tf2xla/xla_context.h b/tensorflow/compiler/tf2xla/xla_context.h index 0767d1faac..eb4ad3fe6a 100644 --- a/tensorflow/compiler/tf2xla/xla_context.h +++ b/tensorflow/compiler/tf2xla/xla_context.h @@ -47,7 +47,7 @@ class XlaContext : public ResourceBase { XlaContext(XlaCompiler* compiler, xla::XlaBuilder* builder); // Virtual method defined by ResourceBase. - string DebugString() override; + string DebugString() const override; XlaCompiler* compiler() const { return compiler_; } diff --git a/tensorflow/compiler/xrt/xrt_compilation_cache.cc b/tensorflow/compiler/xrt/xrt_compilation_cache.cc index d1405eae46..8bf0f28d22 100644 --- a/tensorflow/compiler/xrt/xrt_compilation_cache.cc +++ b/tensorflow/compiler/xrt/xrt_compilation_cache.cc @@ -273,6 +273,8 @@ Status XRTCompilationCache::Lookup( return Status::OK(); } -string XRTCompilationCache::DebugString() { return "XRTCompilationCache"; } +string XRTCompilationCache::DebugString() const { + return "XRTCompilationCache"; +} } // namespace tensorflow diff --git a/tensorflow/compiler/xrt/xrt_compilation_cache.h b/tensorflow/compiler/xrt/xrt_compilation_cache.h index c43d0fc478..7398e847d8 100644 --- a/tensorflow/compiler/xrt/xrt_compilation_cache.h +++ b/tensorflow/compiler/xrt/xrt_compilation_cache.h @@ -118,7 +118,7 @@ class XRTCompilationCache : public ResourceBase { // EntryRef holding the program is returned in entry. Status Lookup(int64 uid, std::unique_ptr* entry); - string DebugString() override; + string DebugString() const override; private: // An entry in the compilation cache. The entry is deleted once it has been diff --git a/tensorflow/compiler/xrt/xrt_state.h b/tensorflow/compiler/xrt/xrt_state.h index 3e3d502412..4aac37737e 100644 --- a/tensorflow/compiler/xrt/xrt_state.h +++ b/tensorflow/compiler/xrt/xrt_state.h @@ -172,7 +172,7 @@ class XRTTupleAllocation : public ResourceBase { // ownership of the device memory is transferred to the result. xla::ShapeTree ToDeviceMemoryTree(bool release); - string DebugString() override { return "XLA allocation handle"; } + string DebugString() const override { return "XLA allocation handle"; } private: // Creates a new handle with (tuple) shape. diff --git a/tensorflow/contrib/bigtable/kernels/bigtable_lib.h b/tensorflow/contrib/bigtable/kernels/bigtable_lib.h index 4652021fec..e3b4535bac 100644 --- a/tensorflow/contrib/bigtable/kernels/bigtable_lib.h +++ b/tensorflow/contrib/bigtable/kernels/bigtable_lib.h @@ -42,7 +42,7 @@ class BigtableClientResource : public ResourceBase { return client_; } - string DebugString() override { + string DebugString() const override { return strings::StrCat("BigtableClientResource(project_id: ", project_id_, ", instance_id: ", instance_id_, ")"); } @@ -67,7 +67,7 @@ class BigtableTableResource : public ResourceBase { ::google::cloud::bigtable::noex::Table& table() { return table_; } - string DebugString() override { + string DebugString() const override { return strings::StrCat( "BigtableTableResource(client: ", client_->DebugString(), ", table: ", table_name_, ")"); diff --git a/tensorflow/contrib/boosted_trees/kernels/stats_accumulator_ops.cc b/tensorflow/contrib/boosted_trees/kernels/stats_accumulator_ops.cc index e446c411a8..6faf696301 100644 --- a/tensorflow/contrib/boosted_trees/kernels/stats_accumulator_ops.cc +++ b/tensorflow/contrib/boosted_trees/kernels/stats_accumulator_ops.cc @@ -96,7 +96,7 @@ class StatsAccumulatorResource : public boosted_trees::StampedResource { TensorShapeUtils::IsScalar(hessian_shape)); } - string DebugString() override { + string DebugString() const override { return strings::StrCat("StatsAccumulatorResource[size=", values_.size(), "]"); } diff --git a/tensorflow/contrib/boosted_trees/resources/decision_tree_ensemble_resource.h b/tensorflow/contrib/boosted_trees/resources/decision_tree_ensemble_resource.h index 94aeb2c7bb..0fe57c0a4e 100644 --- a/tensorflow/contrib/boosted_trees/resources/decision_tree_ensemble_resource.h +++ b/tensorflow/contrib/boosted_trees/resources/decision_tree_ensemble_resource.h @@ -34,7 +34,7 @@ class DecisionTreeEnsembleResource : public StampedResource { protobuf::Arena::CreateMessage< boosted_trees::trees::DecisionTreeEnsembleConfig>(&arena_)) {} - string DebugString() override { + string DebugString() const override { return strings::StrCat("GTFlowDecisionTreeEnsemble[size=", decision_tree_ensemble_->trees_size(), "]"); } diff --git a/tensorflow/contrib/boosted_trees/resources/quantile_stream_resource.h b/tensorflow/contrib/boosted_trees/resources/quantile_stream_resource.h index fdaaae7f47..574e3065e7 100644 --- a/tensorflow/contrib/boosted_trees/resources/quantile_stream_resource.h +++ b/tensorflow/contrib/boosted_trees/resources/quantile_stream_resource.h @@ -43,7 +43,7 @@ class QuantileStreamResource : public StampedResource { set_stamp(stamp_token); } - string DebugString() override { return "QuantileStreamResource"; } + string DebugString() const override { return "QuantileStreamResource"; } tensorflow::mutex* mutex() { return &mu_; } diff --git a/tensorflow/contrib/tensor_forest/kernels/v4/decision-tree-resource.h b/tensorflow/contrib/tensor_forest/kernels/v4/decision-tree-resource.h index d3edb43733..3100a5a0e5 100644 --- a/tensorflow/contrib/tensor_forest/kernels/v4/decision-tree-resource.h +++ b/tensorflow/contrib/tensor_forest/kernels/v4/decision-tree-resource.h @@ -32,7 +32,7 @@ class DecisionTreeResource : public ResourceBase { // Constructor. explicit DecisionTreeResource(const TensorForestParams& params); - string DebugString() override { + string DebugString() const override { return strings::StrCat("DecisionTree[size=", decision_tree_->decision_tree().nodes_size(), "]"); } diff --git a/tensorflow/contrib/tensor_forest/kernels/v4/fertile-stats-resource.h b/tensorflow/contrib/tensor_forest/kernels/v4/fertile-stats-resource.h index eea0be27ca..44f2b3f473 100644 --- a/tensorflow/contrib/tensor_forest/kernels/v4/fertile-stats-resource.h +++ b/tensorflow/contrib/tensor_forest/kernels/v4/fertile-stats-resource.h @@ -40,7 +40,7 @@ class FertileStatsResource : public ResourceBase { model_op_ = LeafModelOperatorFactory::CreateLeafModelOperator(params_); } - string DebugString() override { return "FertileStats"; } + string DebugString() const override { return "FertileStats"; } void ExtractFromProto(const FertileStats& stats); diff --git a/tensorflow/contrib/tensorrt/resources/trt_resources.h b/tensorflow/contrib/tensorrt/resources/trt_resources.h index aac9e5c7bd..8d877b392f 100644 --- a/tensorflow/contrib/tensorrt/resources/trt_resources.h +++ b/tensorflow/contrib/tensorrt/resources/trt_resources.h @@ -48,7 +48,7 @@ class TRTCalibrationResource : public tensorflow::ResourceBase { allocator_.reset(); } - string DebugString() override { + string DebugString() const override { std::stringstream oss; using std::dec; using std::endl; diff --git a/tensorflow/core/framework/lookup_interface.h b/tensorflow/core/framework/lookup_interface.h index d33945fd1b..7e5dbe5632 100644 --- a/tensorflow/core/framework/lookup_interface.h +++ b/tensorflow/core/framework/lookup_interface.h @@ -131,7 +131,7 @@ class LookupInterface : public ResourceBase { // - the default_value tensor shape matches the table's value shape. Status CheckFindArguments(const Tensor& keys, const Tensor& default_value); - string DebugString() override { + string DebugString() const override { return strings::StrCat("A lookup table of size: ", size()); } diff --git a/tensorflow/core/framework/queue_interface.h b/tensorflow/core/framework/queue_interface.h index 4ca4416c5a..9395cce164 100644 --- a/tensorflow/core/framework/queue_interface.h +++ b/tensorflow/core/framework/queue_interface.h @@ -85,11 +85,11 @@ class QueueInterface : public ResourceBase { virtual Status MatchesNodeDef(const NodeDef& node_def) = 0; // Returns the number of elements in the queue. - virtual int32 size() = 0; + virtual int32 size() const = 0; virtual const DataTypeVector& component_dtypes() const = 0; - string DebugString() override { + string DebugString() const override { return strings::StrCat("A Queue of size: ", size()); } diff --git a/tensorflow/core/framework/reader_interface.h b/tensorflow/core/framework/reader_interface.h index f894acbe1d..e47644cb8f 100644 --- a/tensorflow/core/framework/reader_interface.h +++ b/tensorflow/core/framework/reader_interface.h @@ -76,7 +76,7 @@ class ReaderInterface : public ResourceBase { // Note: Must Reset on error. virtual Status RestoreState(const string& state) = 0; - string DebugString() override { return "a reader"; } + string DebugString() const override { return "a reader"; } protected: virtual ~ReaderInterface() {} diff --git a/tensorflow/core/framework/resource_mgr.h b/tensorflow/core/framework/resource_mgr.h index 8a7c25da92..9c381e7d6b 100644 --- a/tensorflow/core/framework/resource_mgr.h +++ b/tensorflow/core/framework/resource_mgr.h @@ -77,7 +77,7 @@ namespace tensorflow { class ResourceBase : public core::RefCounted { public: // Returns a debug string for *this. - virtual string DebugString() = 0; + virtual string DebugString() const = 0; // Returns memory used by this resource. virtual int64 MemoryUsed() const { return 0; } diff --git a/tensorflow/core/framework/resource_mgr_test.cc b/tensorflow/core/framework/resource_mgr_test.cc index 7c7f0af0ce..1c785736e6 100644 --- a/tensorflow/core/framework/resource_mgr_test.cc +++ b/tensorflow/core/framework/resource_mgr_test.cc @@ -32,7 +32,7 @@ class Resource : public ResourceBase { explicit Resource(const string& label) : label_(label) {} ~Resource() override {} - string DebugString() override { return strings::StrCat("R/", label_); } + string DebugString() const override { return strings::StrCat("R/", label_); } private: string label_; @@ -43,7 +43,7 @@ class Other : public ResourceBase { explicit Other(const string& label) : label_(label) {} ~Other() override {} - string DebugString() override { return strings::StrCat("O/", label_); } + string DebugString() const override { return strings::StrCat("O/", label_); } private: string label_; @@ -245,7 +245,7 @@ class StubDevice : public DeviceBase { // Empty stub resource for testing resource handles. class StubResource : public ResourceBase { public: - string DebugString() override { return ""; } + string DebugString() const override { return ""; } int value_{0}; }; @@ -305,7 +305,7 @@ TEST(ResourceHandleTest, DifferentDevice) { // Other stub resource to test type-checking of resource handles. class OtherStubResource : public ResourceBase { public: - string DebugString() override { return ""; } + string DebugString() const override { return ""; } }; TEST(ResourceHandleTest, DifferentType) { diff --git a/tensorflow/core/framework/resource_op_kernel_test.cc b/tensorflow/core/framework/resource_op_kernel_test.cc index c1e503dc57..7a2a87045b 100644 --- a/tensorflow/core/framework/resource_op_kernel_test.cc +++ b/tensorflow/core/framework/resource_op_kernel_test.cc @@ -46,7 +46,7 @@ class StubDevice : public DeviceBase { // Stub resource for testing resource op kernel. class StubResource : public ResourceBase { public: - string DebugString() override { return ""; } + string DebugString() const override { return ""; } int code; }; diff --git a/tensorflow/core/framework/resource_var.h b/tensorflow/core/framework/resource_var.h index f5de5dba88..9387b6c23c 100644 --- a/tensorflow/core/framework/resource_var.h +++ b/tensorflow/core/framework/resource_var.h @@ -67,7 +67,7 @@ class Var : public ResourceBase { mutex* mu() { return &mu_; } Tensor* tensor() { return &tensor_; } - string DebugString() override { + string DebugString() const override { return strings::StrCat(DataTypeString(tensor_.dtype()), "/", tensor_.shape().DebugString()); } diff --git a/tensorflow/core/framework/stats_aggregator.h b/tensorflow/core/framework/stats_aggregator.h index af53ed0a3c..7c960840d7 100644 --- a/tensorflow/core/framework/stats_aggregator.h +++ b/tensorflow/core/framework/stats_aggregator.h @@ -83,7 +83,7 @@ class StatsAggregatorResource : public ResourceBase { return stats_aggregator_; } - string DebugString() { return "StatsAggregatorResource"; } + string DebugString() const override { return "StatsAggregatorResource"; } private: const std::shared_ptr stats_aggregator_; diff --git a/tensorflow/core/kernels/barrier_ops.cc b/tensorflow/core/kernels/barrier_ops.cc index aa91235822..d5bd36b4ce 100644 --- a/tensorflow/core/kernels/barrier_ops.cc +++ b/tensorflow/core/kernels/barrier_ops.cc @@ -300,7 +300,7 @@ class Barrier : public ResourceBase { ready_queue_->Unref(); } - string DebugString() override { return "A barrier"; } + string DebugString() const override { return "A barrier"; } protected: template diff --git a/tensorflow/core/kernels/batch_kernels.cc b/tensorflow/core/kernels/batch_kernels.cc index 35ddda0ec0..5ba461aa9d 100644 --- a/tensorflow/core/kernels/batch_kernels.cc +++ b/tensorflow/core/kernels/batch_kernels.cc @@ -233,7 +233,7 @@ class BatchResource : public ResourceBase { return Status::OK(); } - string DebugString() final { return "BatchResource"; } + string DebugString() const final { return "BatchResource"; } // Ingests data from one invocation of the batch op. The data is enqueued to // be combined with others into a batch, asynchronously. @@ -878,7 +878,7 @@ class UnbatchResource : public ResourceBase { timeout_enforcer_ = nullptr; } - string DebugString() final { return "UnbatchResource"; } + string DebugString() const final { return "UnbatchResource"; } Status Compute(OpKernelContext* context, AsyncOpKernel::DoneCallback done) { const Tensor& data_t = context->input(0); @@ -1094,7 +1094,7 @@ class UnbatchGradResource : public ResourceBase { public: UnbatchGradResource() {} - string DebugString() final { return "UnbatchGradResource"; } + string DebugString() const final { return "UnbatchGradResource"; } // Flushes the information for one batch, given its context and done // callback. Clears all information about it from the available_tensors_. diff --git a/tensorflow/core/kernels/boosted_trees/quantiles/quantile_stream_resource.h b/tensorflow/core/kernels/boosted_trees/quantiles/quantile_stream_resource.h index 1c31724272..965bf2c924 100644 --- a/tensorflow/core/kernels/boosted_trees/quantiles/quantile_stream_resource.h +++ b/tensorflow/core/kernels/boosted_trees/quantiles/quantile_stream_resource.h @@ -37,15 +37,15 @@ class BoostedTreesQuantileStreamResource : public ResourceBase { epsilon_(epsilon), num_streams_(num_streams), max_elements_(max_elements) { - streams_.reserve(num_streams_); - boundaries_.reserve(num_streams_); - for (int64 idx = 0; idx < num_streams; ++idx) { - streams_.push_back(QuantileStream(epsilon, max_elements)); - boundaries_.push_back(std::vector()); - } - } - - string DebugString() override { return "QuantileStreamResource"; } + streams_.reserve(num_streams_); + boundaries_.reserve(num_streams_); + for (int64 idx = 0; idx < num_streams; ++idx) { + streams_.push_back(QuantileStream(epsilon, max_elements)); + boundaries_.push_back(std::vector()); + } + } + + string DebugString() const override { return "QuantileStreamResource"; } tensorflow::mutex* mutex() { return &mu_; } diff --git a/tensorflow/core/kernels/boosted_trees/resources.cc b/tensorflow/core/kernels/boosted_trees/resources.cc index 2798722536..42df484881 100644 --- a/tensorflow/core/kernels/boosted_trees/resources.cc +++ b/tensorflow/core/kernels/boosted_trees/resources.cc @@ -31,7 +31,7 @@ BoostedTreesEnsembleResource::BoostedTreesEnsembleResource() protobuf::Arena::CreateMessage( &arena_)) {} -string BoostedTreesEnsembleResource::DebugString() { +string BoostedTreesEnsembleResource::DebugString() const { return strings::StrCat("TreeEnsemble[size=", tree_ensemble_->trees_size(), "]"); } diff --git a/tensorflow/core/kernels/boosted_trees/resources.h b/tensorflow/core/kernels/boosted_trees/resources.h index f961ed3814..3c7b2df9b0 100644 --- a/tensorflow/core/kernels/boosted_trees/resources.h +++ b/tensorflow/core/kernels/boosted_trees/resources.h @@ -48,7 +48,7 @@ class BoostedTreesEnsembleResource : public StampedResource { public: BoostedTreesEnsembleResource(); - string DebugString() override; + string DebugString() const override; bool InitFromSerialized(const string& serialized, const int64 stamp_token); diff --git a/tensorflow/core/kernels/conditional_accumulator_base.h b/tensorflow/core/kernels/conditional_accumulator_base.h index 4a5ec6f0fb..2618ffbb09 100644 --- a/tensorflow/core/kernels/conditional_accumulator_base.h +++ b/tensorflow/core/kernels/conditional_accumulator_base.h @@ -68,7 +68,7 @@ class ConditionalAccumulatorBase : public ResourceBase { const DataType& dtype() const { return dtype_; } - string DebugString() override { return "A conditional accumulator"; } + string DebugString() const override { return "A conditional accumulator"; } // SetGlobalStep is a modifier method for current_global_step. // It returns an InvalidArgument error if the new_global_step is less than diff --git a/tensorflow/core/kernels/conv_ops.h b/tensorflow/core/kernels/conv_ops.h index 7ec878e0b2..7ccbaf4bf2 100644 --- a/tensorflow/core/kernels/conv_ops.h +++ b/tensorflow/core/kernels/conv_ops.h @@ -63,7 +63,7 @@ struct Im2ColBufferResource : public ResourceBase { // the buffer memory held by this resource. mutex mu; T* data; - string DebugString() { return "Im2ColBufferResource"; } + string DebugString() const { return "Im2ColBufferResource"; } }; // Convolution parameters specified by Op attributes. diff --git a/tensorflow/core/kernels/data/cache_dataset_ops.cc b/tensorflow/core/kernels/data/cache_dataset_ops.cc index f00b38e732..535f49cff8 100644 --- a/tensorflow/core/kernels/data/cache_dataset_ops.cc +++ b/tensorflow/core/kernels/data/cache_dataset_ops.cc @@ -614,7 +614,9 @@ class CacheDatasetOp : public UnaryDatasetOpKernel { public: MemoryCache() = default; - string DebugString() override { return "CacheDataset::MemoryCache"; } + string DebugString() const override { + return "CacheDataset::MemoryCache"; + } // Marks the cache as completed. void Complete() { diff --git a/tensorflow/core/kernels/data/experimental/indexed_dataset_op.cc b/tensorflow/core/kernels/data/experimental/indexed_dataset_op.cc index a07eaebdf9..83eeed7892 100644 --- a/tensorflow/core/kernels/data/experimental/indexed_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/indexed_dataset_op.cc @@ -106,7 +106,7 @@ class MaterializedDatasetResource : public ResourceBase { const std::vector& output_shapes) : output_dtypes_(output_dtypes), output_shapes_(output_shapes) {} - string DebugString() override { + string DebugString() const override { return "Materialized IndexedDataset resource"; } diff --git a/tensorflow/core/kernels/data/experimental/threadpool_dataset_op.cc b/tensorflow/core/kernels/data/experimental/threadpool_dataset_op.cc index 8ae45ed5c9..fab3cab7da 100644 --- a/tensorflow/core/kernels/data/experimental/threadpool_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/threadpool_dataset_op.cc @@ -51,7 +51,7 @@ class ThreadPoolResource : public ResourceBase { int32 NumThreads() { return thread_pool_.NumThreads(); } - string DebugString() override { return "ThreadPoolResource"; } + string DebugString() const override { return "ThreadPoolResource"; } private: thread::ThreadPool thread_pool_; diff --git a/tensorflow/core/kernels/data/iterator_ops.cc b/tensorflow/core/kernels/data/iterator_ops.cc index 9f5881563b..81e26d35c0 100644 --- a/tensorflow/core/kernels/data/iterator_ops.cc +++ b/tensorflow/core/kernels/data/iterator_ops.cc @@ -231,7 +231,7 @@ class IteratorResource : public ResourceBase { return Status::OK(); } - string DebugString() override { return "Iterator resource"; } + string DebugString() const override { return "Iterator resource"; } const DataTypeVector& output_dtypes() const { return output_dtypes_; } diff --git a/tensorflow/core/kernels/data/multi_device_iterator_ops.cc b/tensorflow/core/kernels/data/multi_device_iterator_ops.cc index ba2125a66e..05528e0ee0 100644 --- a/tensorflow/core/kernels/data/multi_device_iterator_ops.cc +++ b/tensorflow/core/kernels/data/multi_device_iterator_ops.cc @@ -59,7 +59,7 @@ class MultiDeviceIterator : public ResourceBase { DCHECK(lib_ != nullptr); } - string DebugString() override { + string DebugString() const override { return strings::StrCat("MultiDeviceIterator for ", devices_.size(), " devices"); } diff --git a/tensorflow/core/kernels/data/shuffle_dataset_op.cc b/tensorflow/core/kernels/data/shuffle_dataset_op.cc index db0cc6fa4d..4c380c1fa2 100644 --- a/tensorflow/core/kernels/data/shuffle_dataset_op.cc +++ b/tensorflow/core/kernels/data/shuffle_dataset_op.cc @@ -412,7 +412,7 @@ class ShuffleDatasetOp : public ShuffleDatasetOpBase { parent_generator_(seed, seed2), generator_(&parent_generator_) {} - string DebugString() override { + string DebugString() const override { return "ReshufflingDataset::RandomSeedGenerator"; } diff --git a/tensorflow/core/kernels/fifo_queue.h b/tensorflow/core/kernels/fifo_queue.h index 697ee81c39..4d3a7c1971 100644 --- a/tensorflow/core/kernels/fifo_queue.h +++ b/tensorflow/core/kernels/fifo_queue.h @@ -49,7 +49,7 @@ class FIFOQueue : public TypedQueue > { CallbackWithTuple callback) override; Status MatchesNodeDef(const NodeDef& node_def) override; - int32 size() override { + int32 size() const override { mutex_lock lock(mu_); return queues_[0].size(); } diff --git a/tensorflow/core/kernels/map_stage_op.cc b/tensorflow/core/kernels/map_stage_op.cc index dd89597369..27a8696e54 100644 --- a/tensorflow/core/kernels/map_stage_op.cc +++ b/tensorflow/core/kernels/map_stage_op.cc @@ -480,7 +480,7 @@ class StagingMap : public ResourceBase { return map_.size(); } - string DebugString() override { return "StagingMap"; } + string DebugString() const override { return "StagingMap"; } }; template diff --git a/tensorflow/core/kernels/meta_support.cc b/tensorflow/core/kernels/meta_support.cc index 39e60c9fce..44f2997e18 100644 --- a/tensorflow/core/kernels/meta_support.cc +++ b/tensorflow/core/kernels/meta_support.cc @@ -54,7 +54,7 @@ class Scratch : public ResourceBase { uint8_t* buffer() { return scratch_32_aligned_; } - string DebugString() { return "MetaGemmScratchResource"; } + string DebugString() const override { return "MetaGemmScratchResource"; } private: std::unique_ptr scratch_; diff --git a/tensorflow/core/kernels/mutex_ops.cc b/tensorflow/core/kernels/mutex_ops.cc index ddb7a606c1..1603a2aa86 100644 --- a/tensorflow/core/kernels/mutex_ops.cc +++ b/tensorflow/core/kernels/mutex_ops.cc @@ -45,7 +45,9 @@ class Mutex : public ResourceBase { VLOG(2) << "Creating mutex with name " << name << ": " << this; } - string DebugString() override { return strings::StrCat("Mutex ", name_); } + string DebugString() const override { + return strings::StrCat("Mutex ", name_); + } class LockReleaser { public: diff --git a/tensorflow/core/kernels/priority_queue.h b/tensorflow/core/kernels/priority_queue.h index 8e69b5b699..a719c518c3 100644 --- a/tensorflow/core/kernels/priority_queue.h +++ b/tensorflow/core/kernels/priority_queue.h @@ -68,7 +68,7 @@ class PriorityQueue Status MatchesPriorityNodeDefTypes(const NodeDef& node_def) const; Status MatchesPriorityNodeDefShapes(const NodeDef& node_def) const; - int32 size() override { + int32 size() const override { mutex_lock lock(mu_); return queues_[0].size(); } diff --git a/tensorflow/core/kernels/random_shuffle_queue_op.cc b/tensorflow/core/kernels/random_shuffle_queue_op.cc index 31e8ce944f..02b9b022fd 100644 --- a/tensorflow/core/kernels/random_shuffle_queue_op.cc +++ b/tensorflow/core/kernels/random_shuffle_queue_op.cc @@ -59,7 +59,7 @@ class RandomShuffleQueue : public TypedQueue > { CallbackWithTuple callback) override; Status MatchesNodeDef(const NodeDef& node_def) override; - int32 size() override { + int32 size() const override { mutex_lock lock(mu_); return queues_[0].size(); } diff --git a/tensorflow/core/kernels/sparse_tensors_map_ops.cc b/tensorflow/core/kernels/sparse_tensors_map_ops.cc index 74fa3a15f0..939638b370 100644 --- a/tensorflow/core/kernels/sparse_tensors_map_ops.cc +++ b/tensorflow/core/kernels/sparse_tensors_map_ops.cc @@ -43,7 +43,7 @@ class SparseTensorsMap : public ResourceBase { public: explicit SparseTensorsMap(const string& name) : name_(name), counter_(0) {} - string DebugString() override { return "A SparseTensorsMap"; } + string DebugString() const override { return "A SparseTensorsMap"; } typedef struct { PersistentTensor indices; diff --git a/tensorflow/core/kernels/stack.cc b/tensorflow/core/kernels/stack.cc index 5c70a2d62d..2af6b4b814 100644 --- a/tensorflow/core/kernels/stack.cc +++ b/tensorflow/core/kernels/stack.cc @@ -96,7 +96,7 @@ class Stack : public ResourceBase { DataType ElemType() { return elem_type_; } - string DebugString() override { + string DebugString() const override { mutex_lock l(mu_); return strings::StrCat("Stack[", stack_name_, "]"); } diff --git a/tensorflow/core/kernels/stage_op.cc b/tensorflow/core/kernels/stage_op.cc index c91bdc43cf..65174e163c 100644 --- a/tensorflow/core/kernels/stage_op.cc +++ b/tensorflow/core/kernels/stage_op.cc @@ -132,7 +132,7 @@ class Buffer : public ResourceBase { notify_inserters_if_bounded(&lock); } - string DebugString() override { + string DebugString() const override { std::unique_lock lock(mu_); return strings::StrCat("Staging size: ", buf_.size()); } @@ -170,7 +170,7 @@ class Buffer : public ResourceBase { std::size_t capacity_; std::size_t memory_limit_; std::size_t current_bytes_; - std::mutex mu_; + mutable std::mutex mu_; std::condition_variable non_empty_cond_var_; std::condition_variable full_cond_var_; std::deque buf_; diff --git a/tensorflow/core/kernels/summary_op.cc b/tensorflow/core/kernels/summary_op.cc index 1f4e3418f4..1053aa7d53 100644 --- a/tensorflow/core/kernels/summary_op.cc +++ b/tensorflow/core/kernels/summary_op.cc @@ -124,7 +124,9 @@ TF_CALL_REAL_NUMBER_TYPES(REGISTER) struct HistogramResource : public ResourceBase { histogram::ThreadSafeHistogram histogram; - string DebugString() override { return "A histogram summary. Stats ..."; } + string DebugString() const override { + return "A histogram summary. Stats ..."; + } }; class SummaryMergeOp : public OpKernel { diff --git a/tensorflow/core/kernels/tensor_array.h b/tensorflow/core/kernels/tensor_array.h index 384a63e945..507ab459ca 100644 --- a/tensorflow/core/kernels/tensor_array.h +++ b/tensorflow/core/kernels/tensor_array.h @@ -261,7 +261,7 @@ class TensorArray : public ResourceBase { return Status::OK(); } - string DebugString() override { + string DebugString() const override { mutex_lock l(mu_); CHECK(!closed_); return strings::StrCat("TensorArray[", tensors_.size(), "]"); @@ -376,7 +376,7 @@ class TensorArray : public ResourceBase { const DataType dtype_; Tensor handle_; - mutex mu_; + mutable mutex mu_; // Marks that the tensor_array_ has been cleared. bool closed_ GUARDED_BY(mu_); diff --git a/tensorflow/core/kernels/tensor_forest/resources.h b/tensorflow/core/kernels/tensor_forest/resources.h index da258e5017..f0a78f9726 100644 --- a/tensorflow/core/kernels/tensor_forest/resources.h +++ b/tensorflow/core/kernels/tensor_forest/resources.h @@ -34,7 +34,7 @@ class TensorForestTreeResource : public ResourceBase { public: TensorForestTreeResource(); - string DebugString() override { + string DebugString() const override { return strings::StrCat("TensorForestTree[size=", get_size(), "]"); } diff --git a/tensorflow/core/kernels/variable_ops.cc b/tensorflow/core/kernels/variable_ops.cc index eadea18f76..00994bbe8e 100644 --- a/tensorflow/core/kernels/variable_ops.cc +++ b/tensorflow/core/kernels/variable_ops.cc @@ -35,7 +35,7 @@ class LegacyVar : public ResourceBase { mutex* mu() { return &mu_; } Tensor* tensor() { return &tensor_; } - string DebugString() override { + string DebugString() const override { return strings::StrCat(DataTypeString(tensor_.dtype()), "/", tensor_.shape().DebugString()); } @@ -116,7 +116,7 @@ class TemporaryVariableOp : public OpKernel { mutex mu; Tensor val; string name; - string DebugString() override { return name; } + string DebugString() const override { return name; } ~TmpVar() override { VLOG(3) << "TmpVar " << name << " deleted"; } }; diff --git a/tensorflow/core/summary/summary_db_writer.cc b/tensorflow/core/summary/summary_db_writer.cc index 7a5d796821..b203d439cc 100644 --- a/tensorflow/core/summary/summary_db_writer.cc +++ b/tensorflow/core/summary/summary_db_writer.cc @@ -972,7 +972,7 @@ class SummaryDbWriter : public SummaryWriterInterface { return MigrateEvent(std::move(e)); } - string DebugString() override { return "SummaryDbWriter"; } + string DebugString() const override { return "SummaryDbWriter"; } private: Status Write(int64 step, const Tensor& t, const string& tag, diff --git a/tensorflow/core/summary/summary_file_writer.cc b/tensorflow/core/summary/summary_file_writer.cc index 593ccdd684..711a7d3d10 100644 --- a/tensorflow/core/summary/summary_file_writer.cc +++ b/tensorflow/core/summary/summary_file_writer.cc @@ -148,7 +148,7 @@ class SummaryFileWriter : public SummaryWriterInterface { return Status::OK(); } - string DebugString() override { return "SummaryFileWriter"; } + string DebugString() const override { return "SummaryFileWriter"; } private: double GetWallTime() { diff --git a/tensorflow/python/framework/test_ops.cc b/tensorflow/python/framework/test_ops.cc index 99e184a8ac..1d0145f61c 100644 --- a/tensorflow/python/framework/test_ops.cc +++ b/tensorflow/python/framework/test_ops.cc @@ -157,7 +157,7 @@ REGISTER_KERNEL_BUILDER(Name("Old").Device(DEVICE_CPU), OldOp); // Stubbed-out resource to test resource handle ops. class StubResource : public ResourceBase { public: - string DebugString() override { return ""; } + string DebugString() const override { return ""; } }; REGISTER_RESOURCE_HANDLE_KERNEL(StubResource); -- GitLab From 8624a703ebd914e9d91bb7992570b52946fad970 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Tue, 8 Jan 2019 02:47:46 -0800 Subject: [PATCH 0326/2345] Try to make resize bilinear test more deterministic. PiperOrigin-RevId: 228301953 --- tensorflow/lite/kernels/internal/resize_bilinear_test.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tensorflow/lite/kernels/internal/resize_bilinear_test.cc b/tensorflow/lite/kernels/internal/resize_bilinear_test.cc index 1c5ac1992f..4a19b69a7c 100644 --- a/tensorflow/lite/kernels/internal/resize_bilinear_test.cc +++ b/tensorflow/lite/kernels/internal/resize_bilinear_test.cc @@ -76,6 +76,7 @@ void TestOneResizeBilinear(int batch, int depth, int input_width, } TEST(ResizeBilinear, TestResizeBilinear8Bit) { + RandomEngine().seed(38291); const int kTestsToRun = 100 * 1000; for (int i = 0; i < kTestsToRun; i++) { const int batch = ExponentialRandomPositiveInt(0.9f, 3, 20); @@ -91,6 +92,7 @@ TEST(ResizeBilinear, TestResizeBilinear8Bit) { } TEST(ResizeBilinear2x2, TestResizeBilinear8Bit) { + RandomEngine().seed(38291); const int kTestsToRun = 100 * 1000; for (int i = 0; i < kTestsToRun; i++) { const int batch = ExponentialRandomPositiveInt(0.9f, 3, 20); @@ -106,6 +108,7 @@ TEST(ResizeBilinear2x2, TestResizeBilinear8Bit) { } TEST(ResizeBilinear, TestResizeBilinear) { + RandomEngine().seed(38291); const int kTestsToRun = 100 * 1000; for (int i = 0; i < kTestsToRun; i++) { const int batch = ExponentialRandomPositiveInt(0.9f, 3, 20); @@ -121,6 +124,7 @@ TEST(ResizeBilinear, TestResizeBilinear) { } TEST(ResizeBilinear2x2, TestResizeBilinear) { + RandomEngine().seed(38291); const int kTestsToRun = 100 * 1000; for (int i = 0; i < kTestsToRun; i++) { const int batch = ExponentialRandomPositiveInt(0.9f, 3, 20); -- GitLab From 07c6aa1a40f4515506d53262d7da2e8663c049cb Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Tue, 8 Jan 2019 02:48:31 -0800 Subject: [PATCH 0327/2345] Use int32_t rather than int32 for compatibility. PiperOrigin-RevId: 228302002 --- tensorflow/lite/kernels/sparse_output_fully_connected_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/sparse_output_fully_connected_test.cc b/tensorflow/lite/kernels/sparse_output_fully_connected_test.cc index 8152ae6685..7d5fec192c 100644 --- a/tensorflow/lite/kernels/sparse_output_fully_connected_test.cc +++ b/tensorflow/lite/kernels/sparse_output_fully_connected_test.cc @@ -62,7 +62,7 @@ class BaseSparseOutputFullyConnectedOpModel : public SingleOpModel { PopulateTensor(input_, data); } - void SetLookup(const std::vector& f) { PopulateTensor(lookup_, f); } + void SetLookup(const std::vector& f) { PopulateTensor(lookup_, f); } void SetBias(const std::vector& f) { PopulateTensor(bias_, f); } -- GitLab From 81492c074aa134d8756b361557ec2dd9aad3cdc1 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 8 Jan 2019 02:50:54 -0800 Subject: [PATCH 0328/2345] Add DistStrat `ReplicaContext.all_reduce`. PiperOrigin-RevId: 228302155 --- .../collective_all_reduce_strategy_test.py | 44 ++++- .../python/mirrored_strategy_multigpu_test.py | 47 ++++- .../python/one_device_strategy_test.py | 22 ++- .../python/parameter_server_strategy_test.py | 38 +++- .../distribute/python/strategy_test_lib.py | 162 ++++++++++++++++++ .../python/distribute/distribute_lib.py | 54 ++++++ ...nsorflow.distribute.-replica-context.pbtxt | 4 + ...nsorflow.distribute.-replica-context.pbtxt | 4 + 8 files changed, 363 insertions(+), 12 deletions(-) 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 0fb672dded..4c8c01a216 100644 --- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py +++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py @@ -397,9 +397,11 @@ class DistributedCollectiveAllReduceStrategyTestWithChief( self._test_complex_model, self._cluster_spec, num_gpus=num_gpus) -class LocalCollectiveAllReduceStrategy(CollectiveAllReduceStrategyTestBase, - strategy_test_lib.DistributionTestBase, - parameterized.TestCase): +class LocalCollectiveAllReduceStrategy( + CollectiveAllReduceStrategyTestBase, + strategy_test_lib.DistributionTestBase, + strategy_test_lib.TwoDeviceDistributionTestBase, + parameterized.TestCase): def testMinimizeLossGraph(self, num_gpus=2): # Collective ops doesn't support strategy with one device. @@ -428,6 +430,42 @@ class LocalCollectiveAllReduceStrategy(CollectiveAllReduceStrategyTestBase, self._test_input_fn_iterator(None, None, num_gpus, input_fn, expected_values) + def testAllReduceSum(self): + if context.num_gpus() < 2: self.skipTest('Not enough GPUs') + distribution, target, config = self._get_test_object(None, None, num_gpus=2) + with self.cached_session(config=config, target=target): + self._test_all_reduce_sum(distribution) + + def testAllReduceSumGradients(self): + if context.num_gpus() < 2: self.skipTest('Not enough GPUs') + distribution, target, config = self._get_test_object(None, None, num_gpus=2) + with self.cached_session(config=config, target=target): + self._test_all_reduce_sum_gradients(distribution) + + def testAllReduceSumGradientTape(self): + if context.num_gpus() < 2: self.skipTest('Not enough GPUs') + distribution, target, config = self._get_test_object(None, None, num_gpus=2) + with self.cached_session(config=config, target=target): + self._test_all_reduce_sum_gradient_tape(distribution) + + def testAllReduceMean(self): + if context.num_gpus() < 2: self.skipTest('Not enough GPUs') + distribution, target, config = self._get_test_object(None, None, num_gpus=2) + with self.cached_session(config=config, target=target): + self._test_all_reduce_mean(distribution) + + def testAllReduceMeanGradients(self): + if context.num_gpus() < 2: self.skipTest('Not enough GPUs') + distribution, target, config = self._get_test_object(None, None, num_gpus=2) + with self.cached_session(config=config, target=target): + self._test_all_reduce_mean_gradients(distribution) + + def testAllReduceMeanGradientTape(self): + if context.num_gpus() < 2: self.skipTest('Not enough GPUs') + distribution, target, config = self._get_test_object(None, None, num_gpus=2) + with self.cached_session(config=config, target=target): + self._test_all_reduce_mean_gradient_tape(distribution) + if __name__ == '__main__': test.main() diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py index 9cbc34412d..9821828b2d 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py @@ -66,8 +66,10 @@ GPU_TEST = "test_gpu" in sys.argv[0] combinations.core_mirrored_strategy_with_gpu_and_cpu, combinations.core_mirrored_strategy_with_two_gpus], mode=["graph", "eager"])) -class MirroredTwoDeviceDistributionTest(strategy_test_lib.DistributionTestBase, - parameterized.TestCase): +class MirroredTwoDeviceDistributionTest( + strategy_test_lib.DistributionTestBase, + strategy_test_lib.TwoDeviceDistributionTestBase, + parameterized.TestCase): def testMinimizeLoss(self, distribution): if context.executing_eagerly(): @@ -117,6 +119,24 @@ class MirroredTwoDeviceDistributionTest(strategy_test_lib.DistributionTestBase, def testGlobalStepUpdate(self, distribution): self._test_global_step_update(distribution) + def testAllReduceSum(self, distribution): + self._test_all_reduce_sum(distribution) + + def testAllReduceSumGradients(self, distribution): + self._test_all_reduce_sum_gradients(distribution) + + def testAllReduceSumGradientTape(self, distribution): + self._test_all_reduce_sum_gradient_tape(distribution) + + def testAllReduceMean(self, distribution): + self._test_all_reduce_mean(distribution) + + def testAllReduceMeanGradients(self, distribution): + self._test_all_reduce_mean_gradients(distribution) + + def testAllReduceMeanGradientTape(self, distribution): + self._test_all_reduce_mean_gradient_tape(distribution) + def one_device_combinations(): return combinations.combine( @@ -128,25 +148,42 @@ def one_device_combinations(): mode=["graph", "eager"]) +@combinations.generate(one_device_combinations()) class MirroredOneDeviceDistributionTest( strategy_test_lib.DistributionTestBase, + strategy_test_lib.OneDeviceDistributionTestBase, parameterized.TestCase): - @combinations.generate(one_device_combinations()) def testMinimizeLoss(self, distribution): if context.executing_eagerly(): self._test_minimize_loss_eager(distribution) else: self._test_minimize_loss_graph(distribution) - @combinations.generate(one_device_combinations()) def testReplicaId(self, distribution): self._test_replica_id(distribution) - @combinations.generate(one_device_combinations()) def testCallAndMergeExceptions(self, distribution): self._test_call_and_merge_exceptions(distribution) + def testAllReduceSum(self, distribution): + self._test_all_reduce_sum(distribution) + + def testAllReduceSumGradients(self, distribution): + self._test_all_reduce_sum_gradients(distribution) + + def testAllReduceSumGradientTape(self, distribution): + self._test_all_reduce_sum_gradient_tape(distribution) + + def testAllReduceMean(self, distribution): + self._test_all_reduce_mean(distribution) + + def testAllReduceMeanGradients(self, distribution): + self._test_all_reduce_mean_gradients(distribution) + + def testAllReduceMeanGradientTape(self, distribution): + self._test_all_reduce_mean_gradient_tape(distribution) + class MirroredStrategyVariableCreatorStackTest( test.TestCase, parameterized.TestCase): diff --git a/tensorflow/contrib/distribute/python/one_device_strategy_test.py b/tensorflow/contrib/distribute/python/one_device_strategy_test.py index d46cd6f529..2403dc8f12 100644 --- a/tensorflow/contrib/distribute/python/one_device_strategy_test.py +++ b/tensorflow/contrib/distribute/python/one_device_strategy_test.py @@ -25,7 +25,9 @@ from tensorflow.python.eager import test from tensorflow.python.framework import test_util -class OneDeviceStrategyTest(strategy_test_lib.DistributionTestBase): +class OneDeviceStrategyTest( + strategy_test_lib.DistributionTestBase, + strategy_test_lib.OneDeviceDistributionTestBase): def _get_distribution_strategy(self): return one_device_strategy.OneDeviceStrategy("/device:CPU:0") @@ -57,6 +59,24 @@ class OneDeviceStrategyTest(strategy_test_lib.DistributionTestBase): self._test_input_fn_iterator( iterator, d.extended.worker_devices, expected_values) + def testAllReduceSum(self): + self._test_all_reduce_sum(self._get_distribution_strategy()) + + def testAllReduceSumGradients(self): + self._test_all_reduce_sum_gradients(self._get_distribution_strategy()) + + def testAllReduceSumGradientTape(self): + self._test_all_reduce_sum_gradient_tape(self._get_distribution_strategy()) + + def testAllReduceMean(self): + self._test_all_reduce_mean(self._get_distribution_strategy()) + + def testAllReduceMeanGradients(self): + self._test_all_reduce_mean_gradients(self._get_distribution_strategy()) + + def testAllReduceMeanGradientTape(self): + self._test_all_reduce_mean_gradient_tape(self._get_distribution_strategy()) + if __name__ == "__main__": test.main() diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py index d8c6c50db2..7836687e7d 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py @@ -603,9 +603,11 @@ class ParameterServerStrategyTestBase( self.assertEqual(expected_value, computed_value) -class ParameterServerStrategyTest(ParameterServerStrategyTestBase, - strategy_test_lib.DistributionTestBase, - parameterized.TestCase): +class ParameterServerStrategyTest( + ParameterServerStrategyTestBase, + strategy_test_lib.DistributionTestBase, + strategy_test_lib.TwoDeviceDistributionTestBase, + parameterized.TestCase): @classmethod def setUpClass(cls): @@ -782,6 +784,36 @@ class ParameterServerStrategyTest(ParameterServerStrategyTestBase, # Verify isolate_session_state self.assertTrue(new_config.isolate_session_state) + def testAllReduceSum(self): + distribution = parameter_server_strategy.ParameterServerStrategy( + num_gpus_per_worker=2) + self._test_all_reduce_sum(distribution) + + def testAllReduceSumGradients(self): + distribution = parameter_server_strategy.ParameterServerStrategy( + num_gpus_per_worker=2) + self._test_all_reduce_sum_gradients(distribution) + + def testAllReduceSumGradientTape(self): + distribution = parameter_server_strategy.ParameterServerStrategy( + num_gpus_per_worker=2) + self._test_all_reduce_sum_gradient_tape(distribution) + + def testAllReduceMean(self): + distribution = parameter_server_strategy.ParameterServerStrategy( + num_gpus_per_worker=2) + self._test_all_reduce_mean(distribution) + + def testAllReduceMeanGradients(self): + distribution = parameter_server_strategy.ParameterServerStrategy( + num_gpus_per_worker=2) + self._test_all_reduce_mean_gradients(distribution) + + def testAllReduceMeanGradientTape(self): + distribution = parameter_server_strategy.ParameterServerStrategy( + num_gpus_per_worker=2) + self._test_all_reduce_mean_gradient_tape(distribution) + class ParameterServerStrategyWithChiefTest(ParameterServerStrategyTestBase, parameterized.TestCase): diff --git a/tensorflow/contrib/distribute/python/strategy_test_lib.py b/tensorflow/contrib/distribute/python/strategy_test_lib.py index 6e5280e356..4fbd630cf7 100644 --- a/tensorflow/contrib/distribute/python/strategy_test_lib.py +++ b/tensorflow/contrib/distribute/python/strategy_test_lib.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function from tensorflow.core.protobuf import config_pb2 +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.distribute import distribution_strategy_context as ds_context from tensorflow.python.distribute import reduce_util from tensorflow.python.distribute import values @@ -31,6 +32,7 @@ from tensorflow.python.framework import errors from tensorflow.python.framework import ops from tensorflow.python.layers import core from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gradients_impl from tensorflow.python.ops import init_ops from tensorflow.python.ops import variable_scope from tensorflow.python.ops import variables @@ -292,3 +294,163 @@ class DistributionTestBase(test.TestCase): global_step_tensors = strategy.unwrap(value) global_step_values = self.evaluate(global_step_tensors) self.assertEqual((1,) * len(global_step_tensors), global_step_values) + + +class OneDeviceDistributionTestBase(test.TestCase): + """Some tests that should work with any one-device DistributionStrategy.""" + + def _test_all_reduce_sum(self, strategy): + self._test_collective_comms( + strategy, _all_sum, inputs=(4., [42., 43.]), expected=(4., [42., 43.])) + + def _test_all_reduce_sum_gradients(self, strategy): + self._test_collective_comms_gradients( + strategy, _all_sum, inputs=[4.], expected_grads=[4.]) + + def _test_all_reduce_sum_gradient_tape(self, strategy): + self._test_collective_comms_gradient_tape( + strategy, _all_sum, inputs=[4.], expected_grads=[4.]) + + def _test_all_reduce_mean(self, strategy): + self._test_collective_comms( + strategy, _all_mean, inputs=(2., [21., 22.]), expected=(2., [21., 22.])) + + def _test_all_reduce_mean_gradients(self, strategy): + self._test_collective_comms_gradients( + strategy, _all_mean, inputs=[5.], expected_grads=[5.]) + + def _test_all_reduce_mean_gradient_tape(self, strategy): + self._test_collective_comms_gradient_tape( + strategy, _all_mean, inputs=[5.], expected_grads=[5.]) + + def _test_collective_comms(self, strategy, comm_fn, inputs, expected): + inputs = strategy.make_input_fn_iterator( + lambda _: dataset_ops.Dataset.from_tensors(inputs)) + + self.evaluate(inputs.initialize()) + outputs = self.evaluate( + list(map(strategy.unwrap, strategy.experimental_run(comm_fn, inputs)))) + self.assertAllEqual([expected[0]], outputs[0]) + self.assertAllEqual([expected[1]], outputs[1]) + + def _test_collective_comms_gradients( + self, strategy, comm_fn, inputs, expected_grads): + if context.executing_eagerly(): + self.skipTest("`tf.gradients` is not supported with eager execution.") + + def step(c): + x = constant_op.constant(42.) + y = comm_fn(x) * c + return gradients_impl.gradients(y, [x])[0] + + inputs = strategy.make_input_fn_iterator( + lambda _: dataset_ops.Dataset.from_tensors(inputs)) + + self.evaluate(inputs.initialize()) + self.assertAllEqual( + expected_grads, + self.evaluate(strategy.unwrap(strategy.experimental_run(step, inputs)))) + + def _test_collective_comms_gradient_tape( + self, strategy, comm_fn, inputs, expected_grads): + def step(c): + x = constant_op.constant(42.) + with backprop.GradientTape() as tape: + tape.watch(x) + y = comm_fn(x) * c + return tape.gradient(y, x) + + inputs = strategy.make_input_fn_iterator( + lambda _: dataset_ops.Dataset.from_tensors(inputs)) + + self.evaluate(inputs.initialize()) + self.assertAllEqual( + expected_grads, + self.evaluate(strategy.unwrap(strategy.experimental_run(step, inputs)))) + + +class TwoDeviceDistributionTestBase(test.TestCase): + """Some tests that should work with any two-device DistributionStrategy.""" + + def _test_all_reduce_sum(self, strategy): + self._test_collective_comms( + strategy, _all_sum, + inputs=([1., 3.], [[39., 2.], [3., 41.]]), + expected=(4., [42., 43.])) + + def _test_all_reduce_sum_gradients(self, strategy): + self._test_collective_comms_gradients( + strategy, _all_sum, inputs=[1., 3.], expected_grads=[4., 4.]) + + def _test_all_reduce_sum_gradient_tape(self, strategy): + self._test_collective_comms_gradient_tape( + strategy, _all_sum, inputs=[1., 3.], expected_grads=[4., 4.]) + + def _test_all_reduce_mean(self, strategy): + self._test_collective_comms( + strategy, _all_mean, + inputs=([1., 3.], [[39., 2.], [3., 41.]]), + expected=(2., [21., 21.5])) + + def _test_all_reduce_mean_gradients(self, strategy): + self._test_collective_comms_gradients( + strategy, _all_mean, inputs=[1., 3.], expected_grads=[2., 2.]) + + def _test_all_reduce_mean_gradient_tape(self, strategy): + self._test_collective_comms_gradient_tape( + strategy, _all_mean, inputs=[1., 3.], expected_grads=[2., 2.]) + + def _test_collective_comms(self, strategy, comm_fn, inputs, expected): + inputs = strategy.make_input_fn_iterator( + lambda _: dataset_ops.Dataset.from_tensor_slices(inputs)) + + self.evaluate(inputs.initialize()) + outputs = self.evaluate( + list(map(strategy.unwrap, strategy.experimental_run(comm_fn, inputs)))) + self.assertAllEqual([expected[0], expected[0]], outputs[0]) + self.assertAllEqual([expected[1], expected[1]], outputs[1]) + + def _test_collective_comms_gradients( + self, strategy, comm_fn, inputs, expected_grads): + if context.executing_eagerly(): + self.skipTest("`tf.gradients` is not supported with eager execution.") + + def step(c): + x = constant_op.constant(42.) + y = comm_fn(x) * c + return gradients_impl.gradients(y, [x])[0] + + inputs = strategy.make_input_fn_iterator( + lambda _: dataset_ops.Dataset.from_tensor_slices(inputs)) + + self.evaluate(inputs.initialize()) + self.assertAllEqual( + expected_grads, + self.evaluate(strategy.unwrap(strategy.experimental_run(step, inputs)))) + + def _test_collective_comms_gradient_tape( + self, strategy, comm_fn, inputs, expected_grads): + def step(c): + x = constant_op.constant(42.) + with backprop.GradientTape() as tape: + tape.watch(x) + y = comm_fn(x) * c + return tape.gradient(y, x) + + inputs = strategy.make_input_fn_iterator( + lambda _: dataset_ops.Dataset.from_tensor_slices(inputs)) + + self.evaluate(inputs.initialize()) + self.assertAllEqual( + expected_grads, + self.evaluate(strategy.unwrap(strategy.experimental_run(step, inputs)))) + + +def _all_sum(value): + ctx = ds_context.get_replica_context() + return ctx.all_reduce(reduce_util.ReduceOp.SUM, value) + + +def _all_mean(value): + ctx = ds_context.get_replica_context() + return ctx.all_reduce(reduce_util.ReduceOp.MEAN, value) diff --git a/tensorflow/python/distribute/distribute_lib.py b/tensorflow/python/distribute/distribute_lib.py index 76cbdd53d9..4ad8cc00b8 100644 --- a/tensorflow/python/distribute/distribute_lib.py +++ b/tensorflow/python/distribute/distribute_lib.py @@ -33,6 +33,7 @@ from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.ops import array_ops from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import custom_gradient from tensorflow.python.ops import resource_variable_ops from tensorflow.python.ops import variable_scope from tensorflow.python.ops.losses import losses_impl @@ -1554,6 +1555,50 @@ class ReplicaContext(object): require_replica_context(self) return (device_util.current(),) + def all_reduce(self, reduce_op, value): + """All-reduces the given `Tensor` nest across replicas. + + If `all_reduce` is called in any replica, it must be called in all replicas. + The nested structure and `Tensor` shapes must be identical in all replicas. + + IMPORTANT: The ordering of communications must be identical in all replicas. + + Example with two replicas: + Replica 0 `value`: {'a': 1, 'b': [40, 1]} + Replica 1 `value`: {'a': 3, 'b': [ 2, 98]} + + If `reduce_op` == `SUM`: + Result (on all replicas): {'a': 4, 'b': [42, 99]} + + If `reduce_op` == `MEAN`: + Result (on all replicas): {'a': 2, 'b': [21, 49.5]} + + Args: + reduce_op: Reduction type, an instance of `tf.distribute.ReduceOp` enum. + value: The nested structure of `Tensor`s to all-reduced. + The structure must be compatible with `tf.nest`. + + Returns: + A `Tensor` nest with the reduced `value`s from each replica. + """ + def batch_all_reduce(strategy, *value_flat): + return strategy.extended.batch_reduce_to( + reduce_op, [(v, _batch_reduce_destination(v)) for v in value_flat]) + + if reduce_op in [reduce_util.ReduceOp.SUM, reduce_util.ReduceOp.MEAN]: + # TODO(cjfj): Work out why `batch_reduce` doesn't return the correct grad. + @custom_gradient.custom_gradient + def grad_wrapper(*xs): + ys = self.merge_call(batch_all_reduce, args=xs) + # The gradient of an all-sum is itself an all-sum (all-mean, likewise). + return ys, lambda *dy_s: self.all_reduce(reduce_op, dy_s) + return nest.pack_sequence_as(value, grad_wrapper(*nest.flatten(value))) + else: + # TODO(cjfj): Implement gradients for other reductions. + reduced = nest.pack_sequence_as( + value, self.merge_call(batch_all_reduce, args=nest.flatten(value))) + return nest.map_structure(array_ops.prevent_gradient, reduced) + # TODO(josh11b): Implement `start_all_reduce(method, t)` for efficient # all-reduce. It would return a function returning the result of reducing `t` # across all replicas. The caller would wait to call this function until they @@ -1564,6 +1609,15 @@ class ReplicaContext(object): # to that point that the first result is needed. Most likely this can be # implemented in terms of `merge_call()` and `batch_reduce_to()`. + +def _batch_reduce_destination(x): + """Returns the destinations for batch all-reduce.""" + if isinstance(x, ops.Tensor): # One device strategies. + return x.device + else: + return x + + # ------------------------------------------------------------------------------ diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-replica-context.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-replica-context.pbtxt index df707e8920..22f8160c96 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-replica-context.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-replica-context.pbtxt @@ -26,6 +26,10 @@ tf_class { name: "__init__" argspec: "args=[\'self\', \'strategy\', \'replica_id_in_sync_group\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "all_reduce" + argspec: "args=[\'self\', \'reduce_op\', \'value\'], varargs=None, keywords=None, defaults=None" + } member_method { name: "merge_call" argspec: "args=[\'self\', \'merge_fn\', \'args\', \'kwargs\'], varargs=None, keywords=None, defaults=[\'()\', \'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-replica-context.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-replica-context.pbtxt index df707e8920..22f8160c96 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-replica-context.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-replica-context.pbtxt @@ -26,6 +26,10 @@ tf_class { name: "__init__" argspec: "args=[\'self\', \'strategy\', \'replica_id_in_sync_group\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "all_reduce" + argspec: "args=[\'self\', \'reduce_op\', \'value\'], varargs=None, keywords=None, defaults=None" + } member_method { name: "merge_call" argspec: "args=[\'self\', \'merge_fn\', \'args\', \'kwargs\'], varargs=None, keywords=None, defaults=[\'()\', \'None\'], " -- GitLab From 0ef4b190443a2f7b3b2330f053df5503af6b0191 Mon Sep 17 00:00:00 2001 From: James Keeling Date: Tue, 8 Jan 2019 02:53:00 -0800 Subject: [PATCH 0329/2345] Split topk GPU code into multiple files This file was a bottleneck during compilation, often taking many minutes to compile. In local testing this change reduces the wall-clock build time for the topk GPU kernels from 155s to 48s. PiperOrigin-RevId: 228302319 --- tensorflow/core/kernels/BUILD | 16 ++++++++++- .../{topk_op_gpu.cu.cc => topk_op_gpu.h} | 12 +++----- .../core/kernels/topk_op_gpu_double.cu.cc | 28 +++++++++++++++++++ .../core/kernels/topk_op_gpu_float.cu.cc | 28 +++++++++++++++++++ .../core/kernels/topk_op_gpu_half.cu.cc | 28 +++++++++++++++++++ .../core/kernels/topk_op_gpu_int16.cu.cc | 28 +++++++++++++++++++ .../core/kernels/topk_op_gpu_int32.cu.cc | 28 +++++++++++++++++++ .../core/kernels/topk_op_gpu_int64.cu.cc | 28 +++++++++++++++++++ .../core/kernels/topk_op_gpu_int8.cu.cc | 28 +++++++++++++++++++ .../core/kernels/topk_op_gpu_uint16.cu.cc | 28 +++++++++++++++++++ .../core/kernels/topk_op_gpu_uint8.cu.cc | 28 +++++++++++++++++++ 11 files changed, 271 insertions(+), 9 deletions(-) rename tensorflow/core/kernels/{topk_op_gpu.cu.cc => topk_op_gpu.h} (98%) create mode 100644 tensorflow/core/kernels/topk_op_gpu_double.cu.cc create mode 100644 tensorflow/core/kernels/topk_op_gpu_float.cu.cc create mode 100644 tensorflow/core/kernels/topk_op_gpu_half.cu.cc create mode 100644 tensorflow/core/kernels/topk_op_gpu_int16.cu.cc create mode 100644 tensorflow/core/kernels/topk_op_gpu_int32.cu.cc create mode 100644 tensorflow/core/kernels/topk_op_gpu_int64.cu.cc create mode 100644 tensorflow/core/kernels/topk_op_gpu_int8.cu.cc create mode 100644 tensorflow/core/kernels/topk_op_gpu_uint16.cu.cc create mode 100644 tensorflow/core/kernels/topk_op_gpu_uint8.cu.cc diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index fee30fa6c3..3df63d34e3 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -3862,7 +3862,21 @@ tf_kernel_library( tf_kernel_library( name = "topk_op", - prefix = "topk_op", + srcs = ["topk_op.cc"], + hdrs = ["topk_op.h"], + gpu_srcs = [ + "topk_op.h", + "topk_op_gpu.h", + "topk_op_gpu_double.cu.cc", + "topk_op_gpu_float.cu.cc", + "topk_op_gpu_half.cu.cc", + "topk_op_gpu_int64.cu.cc", + "topk_op_gpu_int32.cu.cc", + "topk_op_gpu_int16.cu.cc", + "topk_op_gpu_uint16.cu.cc", + "topk_op_gpu_int8.cu.cc", + "topk_op_gpu_uint8.cu.cc", + ], deps = NN_DEPS + if_cuda(["@cub_archive//:cub"]), ) diff --git a/tensorflow/core/kernels/topk_op_gpu.cu.cc b/tensorflow/core/kernels/topk_op_gpu.h similarity index 98% rename from tensorflow/core/kernels/topk_op_gpu.cu.cc rename to tensorflow/core/kernels/topk_op_gpu.h index 2fbe1fe7cb..6f3bec20f6 100644 --- a/tensorflow/core/kernels/topk_op_gpu.cu.cc +++ b/tensorflow/core/kernels/topk_op_gpu.h @@ -12,6 +12,8 @@ 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_TOPK_OP_GPU_H_ +#define TENSORFLOW_CORE_KERNELS_TOPK_OP_GPU_H_ #if GOOGLE_CUDA @@ -561,14 +563,8 @@ struct TopKFunctor { }; } // end namespace functor - -#define INSTANTIATE_TEMPLATE(type) \ - template struct functor::TopKFunctor; - -TF_CALL_GPU_NUMBER_TYPES(INSTANTIATE_TEMPLATE); -TF_CALL_INTEGRAL_TYPES(INSTANTIATE_TEMPLATE); -#undef INSTANTIATE_TEMPLATE - } // namespace tensorflow #endif // GOOGLE_CUDA + +#endif // TENSORFLOW_CORE_KERNELS_TOPK_OP_GPU_H_ diff --git a/tensorflow/core/kernels/topk_op_gpu_double.cu.cc b/tensorflow/core/kernels/topk_op_gpu_double.cu.cc new file mode 100644 index 0000000000..8a5a7e71b1 --- /dev/null +++ b/tensorflow/core/kernels/topk_op_gpu_double.cu.cc @@ -0,0 +1,28 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/topk_op.h" +#include "tensorflow/core/kernels/topk_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct functor::TopKFunctor; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/topk_op_gpu_float.cu.cc b/tensorflow/core/kernels/topk_op_gpu_float.cu.cc new file mode 100644 index 0000000000..0b69396bb1 --- /dev/null +++ b/tensorflow/core/kernels/topk_op_gpu_float.cu.cc @@ -0,0 +1,28 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/topk_op.h" +#include "tensorflow/core/kernels/topk_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct functor::TopKFunctor; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/topk_op_gpu_half.cu.cc b/tensorflow/core/kernels/topk_op_gpu_half.cu.cc new file mode 100644 index 0000000000..e53586aeca --- /dev/null +++ b/tensorflow/core/kernels/topk_op_gpu_half.cu.cc @@ -0,0 +1,28 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/topk_op.h" +#include "tensorflow/core/kernels/topk_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct functor::TopKFunctor; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/topk_op_gpu_int16.cu.cc b/tensorflow/core/kernels/topk_op_gpu_int16.cu.cc new file mode 100644 index 0000000000..5bd310523c --- /dev/null +++ b/tensorflow/core/kernels/topk_op_gpu_int16.cu.cc @@ -0,0 +1,28 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/topk_op.h" +#include "tensorflow/core/kernels/topk_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct functor::TopKFunctor; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/topk_op_gpu_int32.cu.cc b/tensorflow/core/kernels/topk_op_gpu_int32.cu.cc new file mode 100644 index 0000000000..55b393a0c0 --- /dev/null +++ b/tensorflow/core/kernels/topk_op_gpu_int32.cu.cc @@ -0,0 +1,28 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/topk_op.h" +#include "tensorflow/core/kernels/topk_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct functor::TopKFunctor; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/topk_op_gpu_int64.cu.cc b/tensorflow/core/kernels/topk_op_gpu_int64.cu.cc new file mode 100644 index 0000000000..3e4a775056 --- /dev/null +++ b/tensorflow/core/kernels/topk_op_gpu_int64.cu.cc @@ -0,0 +1,28 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/topk_op.h" +#include "tensorflow/core/kernels/topk_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct functor::TopKFunctor; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/topk_op_gpu_int8.cu.cc b/tensorflow/core/kernels/topk_op_gpu_int8.cu.cc new file mode 100644 index 0000000000..ac73cd170b --- /dev/null +++ b/tensorflow/core/kernels/topk_op_gpu_int8.cu.cc @@ -0,0 +1,28 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/topk_op.h" +#include "tensorflow/core/kernels/topk_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct functor::TopKFunctor; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/topk_op_gpu_uint16.cu.cc b/tensorflow/core/kernels/topk_op_gpu_uint16.cu.cc new file mode 100644 index 0000000000..8d5f8ceb06 --- /dev/null +++ b/tensorflow/core/kernels/topk_op_gpu_uint16.cu.cc @@ -0,0 +1,28 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/topk_op.h" +#include "tensorflow/core/kernels/topk_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct functor::TopKFunctor; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/topk_op_gpu_uint8.cu.cc b/tensorflow/core/kernels/topk_op_gpu_uint8.cu.cc new file mode 100644 index 0000000000..fc1a8a2c8c --- /dev/null +++ b/tensorflow/core/kernels/topk_op_gpu_uint8.cu.cc @@ -0,0 +1,28 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/topk_op.h" +#include "tensorflow/core/kernels/topk_op_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; + +template struct functor::TopKFunctor; +} // namespace tensorflow + +#endif // GOOGLE_CUDA -- GitLab From 6c74d3a5893ff6e3ba35b471841ccc349848587d Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 8 Jan 2019 17:01:58 +0530 Subject: [PATCH 0330/2345] Ensure variables are initialized 'ReadVarint32FromArray' & 'ReadVarint64FromArray' doesn't ensure output variable 'value' is filled always. Since 'unused_ok' is not checked for true/false initialize the temp variables, so that random values dont propogate. --- tensorflow/core/util/proto/decode.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tensorflow/core/util/proto/decode.h b/tensorflow/core/util/proto/decode.h index 8dde14dffc..188830cc1f 100644 --- a/tensorflow/core/util/proto/decode.h +++ b/tensorflow/core/util/proto/decode.h @@ -91,7 +91,7 @@ inline const uint8* ReadVarint64FromArray(const uint8* buffer, bool* ok, // the 64 bit version instead of copying the code. inline const uint8* ReadVarint32FromArray(const uint8* buffer, bool* ok, uint32* value) { - uint64 tmp; + uint64 tmp = 0; const uint8* buf = ReadVarint64FromArray(buffer, ok, &tmp); *value = tmp & 0xffffffff; return buf; @@ -106,7 +106,7 @@ const uint8* ReadFromArray(const uint8* buf, TensorType* value); template <> inline const uint8* ReadFromArray( const uint8* buf, int64* value) { - uint32 temp; + uint32 temp = 0; bool unused_ok; // The Counting pass would have failed if this were corrupt. buf = ReadVarint32FromArray(buf, &unused_ok, &temp); *value = static_cast(temp); @@ -116,7 +116,7 @@ inline const uint8* ReadFromArray( template <> inline const uint8* ReadFromArray( const uint8* buf, int32* value) { - uint32 temp; + uint32 temp = 0; bool unused_ok; // The Counting pass would have failed if this were corrupt. buf = ReadVarint32FromArray(buf, &unused_ok, &temp); *value = static_cast(temp); @@ -126,7 +126,7 @@ inline const uint8* ReadFromArray( template <> inline const uint8* ReadFromArray( const uint8* buf, int64* value) { - uint64 temp; + uint64 temp = 0; bool unused_ok; // The Counting pass would have failed if this were corrupt. buf = ReadVarint64FromArray(buf, &unused_ok, &temp); *value = WrapUnsignedAsSigned64(temp); @@ -136,7 +136,7 @@ inline const uint8* ReadFromArray( template <> inline const uint8* ReadFromArray( const uint8* buf, uint64* value) { - uint32 temp; + uint32 temp = 0; bool unused_ok; // The Counting pass would have failed if this were corrupt. buf = ReadVarint32FromArray(buf, &unused_ok, &temp); *value = temp; @@ -160,7 +160,7 @@ inline const uint8* ReadFromArray( template <> inline const uint8* ReadFromArray( const uint8* buf, int64* value) { - uint64 temp; + uint64 temp = 0; bool unused_ok; // The Counting pass would have failed if this were corrupt. buf = ReadVarint64FromArray(buf, &unused_ok, &temp); *value = WireFormatLite::ZigZagDecode32(temp); @@ -170,7 +170,7 @@ inline const uint8* ReadFromArray( template <> inline const uint8* ReadFromArray( const uint8* buf, int32* value) { - uint32 temp; + uint32 temp = 0; bool unused_ok; // The Counting pass would have failed if this were corrupt. buf = ReadVarint32FromArray(buf, &unused_ok, &temp); *value = WireFormatLite::ZigZagDecode32(temp); @@ -180,7 +180,7 @@ inline const uint8* ReadFromArray( template <> inline const uint8* ReadFromArray( const uint8* buf, int64* value) { - uint64 temp; + uint64 temp = 0; bool unused_ok; // The Counting pass would have failed if this were corrupt. buf = ReadVarint64FromArray(buf, &unused_ok, &temp); *value = WireFormatLite::ZigZagDecode64(temp); @@ -280,7 +280,7 @@ inline const uint8* ReadFromArray( template <> inline const uint8* ReadFromArray( const uint8* buf, bool* value) { - uint64 temp; + uint64 temp = 0; bool unused_ok; // The Counting pass would have failed if this were corrupt. buf = ReadVarint64FromArray(buf, &unused_ok, &temp); *value = temp != 0; @@ -290,7 +290,7 @@ inline const uint8* ReadFromArray( template <> inline const uint8* ReadFromArray( const uint8* buf, int* value) { - uint32 temp; + uint32 temp = 0; bool unused_ok; // The Counting pass would have failed if this were corrupt. buf = ReadVarint32FromArray(buf, &unused_ok, &temp); *value = static_cast(temp); -- GitLab From 3436665db2438dd339a0edddd0d0a3d49b5b2424 Mon Sep 17 00:00:00 2001 From: James Keeling Date: Tue, 8 Jan 2019 03:28:39 -0800 Subject: [PATCH 0331/2345] Split scan ops GPU code into multiple files This file was a bottleneck during compilation, often taking many minutes to compile. In local testing this change reduces the wall-clock build time for the scan ops GPU kernels from 107s to 96s. PiperOrigin-RevId: 228304727 --- tensorflow/core/kernels/BUILD | 10 +++++- .../{scan_ops_gpu.cu.cc => scan_ops_gpu.h} | 16 +++------- .../core/kernels/scan_ops_gpu_double.cu.cc | 31 +++++++++++++++++++ .../core/kernels/scan_ops_gpu_float.cu.cc | 31 +++++++++++++++++++ .../core/kernels/scan_ops_gpu_half.cu.cc | 31 +++++++++++++++++++ 5 files changed, 107 insertions(+), 12 deletions(-) rename tensorflow/core/kernels/{scan_ops_gpu.cu.cc => scan_ops_gpu.h} (97%) create mode 100644 tensorflow/core/kernels/scan_ops_gpu_double.cu.cc create mode 100644 tensorflow/core/kernels/scan_ops_gpu_float.cu.cc create mode 100644 tensorflow/core/kernels/scan_ops_gpu_half.cu.cc diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 3df63d34e3..b934c64bfc 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -3279,7 +3279,15 @@ tf_kernel_library( tf_kernel_library( name = "scan_ops", - prefix = "scan_ops", + srcs = ["scan_ops.cc"], + hdrs = ["scan_ops.h"], + gpu_srcs = [ + "scan_ops.h", + "scan_ops_gpu.h", + "scan_ops_gpu_double.cu.cc", + "scan_ops_gpu_float.cu.cc", + "scan_ops_gpu_half.cu.cc", + ], deps = MATH_DEPS + if_cuda(["@cub_archive//:cub"]), ) diff --git a/tensorflow/core/kernels/scan_ops_gpu.cu.cc b/tensorflow/core/kernels/scan_ops_gpu.h similarity index 97% rename from tensorflow/core/kernels/scan_ops_gpu.cu.cc rename to tensorflow/core/kernels/scan_ops_gpu.h index ed66c02dc5..976b221540 100644 --- a/tensorflow/core/kernels/scan_ops_gpu.cu.cc +++ b/tensorflow/core/kernels/scan_ops_gpu.h @@ -13,6 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#ifndef TENSORFLOW_CORE_KERNELS_SCAN_OPS_GPU_H_ +#define TENSORFLOW_CORE_KERNELS_SCAN_OPS_GPU_H_ + #if GOOGLE_CUDA #define EIGEN_USE_GPU @@ -290,17 +293,8 @@ struct Scan, T> { }; } // namespace functor - -#define DEFINE(REDUCER, T) template struct functor::Scan; - -#define DEFINE_FOR_ALL_REDUCERS(T) \ - DEFINE(Eigen::internal::SumReducer, T); \ - DEFINE(Eigen::internal::ProdReducer, T); - -TF_CALL_GPU_NUMBER_TYPES(DEFINE_FOR_ALL_REDUCERS); -#undef DEFINE_FOR_ALL_REDUCERS -#undef DEFINE - } // end namespace tensorflow #endif // GOOGLE_CUDA + +#endif // TENSORFLOW_CORE_KERNELS_SCAN_OPS_GPU_H_ diff --git a/tensorflow/core/kernels/scan_ops_gpu_double.cu.cc b/tensorflow/core/kernels/scan_ops_gpu_double.cu.cc new file mode 100644 index 0000000000..adce37e473 --- /dev/null +++ b/tensorflow/core/kernels/scan_ops_gpu_double.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA + +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/scan_ops.h" +#include "tensorflow/core/kernels/scan_ops_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; +template struct functor::Scan, + double>; +template struct functor::Scan, + double>; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/scan_ops_gpu_float.cu.cc b/tensorflow/core/kernels/scan_ops_gpu_float.cu.cc new file mode 100644 index 0000000000..b72415822d --- /dev/null +++ b/tensorflow/core/kernels/scan_ops_gpu_float.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA + +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/scan_ops.h" +#include "tensorflow/core/kernels/scan_ops_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; +template struct functor::Scan, + float>; +template struct functor::Scan, + float>; +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/scan_ops_gpu_half.cu.cc b/tensorflow/core/kernels/scan_ops_gpu_half.cu.cc new file mode 100644 index 0000000000..f9fb528be9 --- /dev/null +++ b/tensorflow/core/kernels/scan_ops_gpu_half.cu.cc @@ -0,0 +1,31 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA + +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/scan_ops.h" +#include "tensorflow/core/kernels/scan_ops_gpu.h" + +namespace tensorflow { +using Eigen::GpuDevice; +template struct functor::Scan< + GpuDevice, Eigen::internal::SumReducer, Eigen::half>; +template struct functor::Scan< + GpuDevice, Eigen::internal::ProdReducer, Eigen::half>; +} // namespace tensorflow + +#endif // GOOGLE_CUDA -- GitLab From 137c142341c9f10a1e105403fbfb1123a1802eb9 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Tue, 8 Jan 2019 03:57:48 -0800 Subject: [PATCH 0332/2345] Make toco_port trivial initialize-able for test cases. Problem is toco_port indirects a way of initializing google. One impl has a static way of finding out if it is initialized (internal). One impl has a non-static way to find out. In the non-static way, test cases won't work since we initialize the tensorflow google emulation elsewhere (gtest override) PiperOrigin-RevId: 228306710 --- tensorflow/lite/toco/BUILD | 2 ++ tensorflow/lite/toco/import_tensorflow_test.cc | 2 ++ tensorflow/lite/toco/toco_convert_test.cc | 2 ++ tensorflow/lite/toco/toco_port.cc | 7 +++++++ tensorflow/lite/toco/toco_port.h | 4 ++++ tensorflow/lite/toco/toco_port_test.cc | 1 + tensorflow/lite/toco/tooling_util_test.cc | 2 ++ 7 files changed, 20 insertions(+) diff --git a/tensorflow/lite/toco/BUILD b/tensorflow/lite/toco/BUILD index d77234c80a..40bceedd6a 100644 --- a/tensorflow/lite/toco/BUILD +++ b/tensorflow/lite/toco/BUILD @@ -342,6 +342,7 @@ tf_cc_test( name = "import_tensorflow_test", srcs = ["import_tensorflow_test.cc"], deps = [ + ":toco_port", ":toco_tooling", "//tensorflow/core:framework", "//tensorflow/core:graph", @@ -386,6 +387,7 @@ tf_cc_test( srcs = ["tooling_util_test.cc"], deps = [ ":model", + ":toco_port", ":tooling_util", "//tensorflow/core:lib", "//tensorflow/lite/testing:util", diff --git a/tensorflow/lite/toco/import_tensorflow_test.cc b/tensorflow/lite/toco/import_tensorflow_test.cc index 70382f546b..de7f4cdb7e 100644 --- a/tensorflow/lite/toco/import_tensorflow_test.cc +++ b/tensorflow/lite/toco/import_tensorflow_test.cc @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "tensorflow/lite/toco/import_tensorflow.h" +#include "tensorflow/lite/toco/toco_port.h" #include #include @@ -569,5 +570,6 @@ TEST(ImportTest, UnsupportedOpWithMultipleOutputs) { int main(int argc, char** argv) { ::tflite::LogToStderr(); ::testing::InitGoogleTest(&argc, argv); + ::toco::port::InitGoogleWasDoneElsewhere(); return RUN_ALL_TESTS(); } diff --git a/tensorflow/lite/toco/toco_convert_test.cc b/tensorflow/lite/toco/toco_convert_test.cc index 3730c53ae1..739b924607 100644 --- a/tensorflow/lite/toco/toco_convert_test.cc +++ b/tensorflow/lite/toco/toco_convert_test.cc @@ -16,6 +16,7 @@ limitations under the License. #include #include #include "tensorflow/lite/testing/util.h" +#include "tensorflow/lite/toco/toco_port.h" namespace toco { namespace { @@ -176,5 +177,6 @@ TEST(TocoTest, TransientStringTensors) { int main(int argc, char** argv) { ::tflite::LogToStderr(); ::testing::InitGoogleTest(&argc, argv); + ::toco::port::InitGoogleWasDoneElsewhere(); return RUN_ALL_TESTS(); } diff --git a/tensorflow/lite/toco/toco_port.cc b/tensorflow/lite/toco/toco_port.cc index fb8c1b8337..b222032e61 100644 --- a/tensorflow/lite/toco/toco_port.cc +++ b/tensorflow/lite/toco/toco_port.cc @@ -57,6 +57,11 @@ void InitGoogle(const char* usage, int* argc, char*** argv, bool remove_flags) { ::InitGoogle(usage, argc, argv, remove_flags); } +void InitGoogleWasDoneElsewhere() { + // Nothing need be done since ::CheckInitGoogleIsDone() is aware of other + // possible initialization entry points. +} + void CheckInitGoogleIsDone(const char* message) { ::CheckInitGoogleIsDone(message); } @@ -152,6 +157,8 @@ constexpr int kFileWriteFlags = O_CREAT | O_WRONLY; static bool port_initialized = false; +void InitGoogleWasDoneElsewhere() { port_initialized = true; } + void InitGoogle(const char* usage, int* argc, char*** argv, bool remove_flags) { if (!port_initialized) { #if defined(PLATFORM_GOOGLE) diff --git a/tensorflow/lite/toco/toco_port.h b/tensorflow/lite/toco/toco_port.h index 2f39e3d6d5..231612ecd4 100644 --- a/tensorflow/lite/toco/toco_port.h +++ b/tensorflow/lite/toco/toco_port.h @@ -55,6 +55,10 @@ double round(double x); namespace toco { namespace port { +// Things like tests use other initialization routines that need control +// of flags. However, for testing we still want to use toco_port.h facilities. +// This function sets initialized flag trivially. +void InitGoogleWasDoneElsewhere(); void InitGoogle(const char* usage, int* argc, char*** argv, bool remove_flags); void CheckInitGoogleIsDone(const char* message); diff --git a/tensorflow/lite/toco/toco_port_test.cc b/tensorflow/lite/toco/toco_port_test.cc index d80d423ed7..997da58b8f 100644 --- a/tensorflow/lite/toco/toco_port_test.cc +++ b/tensorflow/lite/toco/toco_port_test.cc @@ -61,5 +61,6 @@ TEST(TocoPortTest, JoinPath) { int main(int argc, char** argv) { ::tflite::LogToStderr(); ::testing::InitGoogleTest(&argc, argv); + ::toco::port::InitGoogleWasDoneElsewhere(); return RUN_ALL_TESTS(); } diff --git a/tensorflow/lite/toco/tooling_util_test.cc b/tensorflow/lite/toco/tooling_util_test.cc index e44b94b712..faa6fe412e 100644 --- a/tensorflow/lite/toco/tooling_util_test.cc +++ b/tensorflow/lite/toco/tooling_util_test.cc @@ -19,6 +19,7 @@ limitations under the License. #include "tensorflow/core/lib/core/status.h" #include "tensorflow/lite/testing/util.h" #include "tensorflow/lite/toco/model.h" +#include "tensorflow/lite/toco/toco_port.h" #include "tensorflow/lite/toco/tooling_util.h" namespace toco { @@ -208,5 +209,6 @@ TEST(FusedActivationTest, DefaultsToUnfused) { int main(int argc, char** argv) { ::tflite::LogToStderr(); ::testing::InitGoogleTest(&argc, argv); + ::toco::port::InitGoogleWasDoneElsewhere(); return RUN_ALL_TESTS(); } -- GitLab From c503c6254608a622b06340f1f7ddd8f31565ab5e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 04:37:51 -0800 Subject: [PATCH 0333/2345] Small clean up in in saved_model/load_test.py Temporary `function` in checkpointable under test is not needed in most examples as `cycle` is passing signatures explicitly. PiperOrigin-RevId: 228309220 --- tensorflow/python/saved_model/load_test.py | 34 +++++++++------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/tensorflow/python/saved_model/load_test.py b/tensorflow/python/saved_model/load_test.py index 9eac3e6555..a1c81d3f14 100644 --- a/tensorflow/python/saved_model/load_test.py +++ b/tensorflow/python/saved_model/load_test.py @@ -42,9 +42,6 @@ class LoadTest(test.TestCase): def test_structure_import(self): root = tracking.Checkpointable() - root.f = def_function.function( - lambda x: 2. * x, - input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) root.dep_one = tracking.Checkpointable() root.dep_two = tracking.Checkpointable() root.dep_two.dep = tracking.Checkpointable() @@ -52,19 +49,25 @@ class LoadTest(test.TestCase): imported = self.cycle(root) self.assertIs(imported.dep_three, imported.dep_two.dep) self.assertIsNot(imported.dep_one, imported.dep_two) - self.assertEqual(4., imported.f(constant_op.constant(2.)).numpy()) def test_variables(self): root = tracking.Checkpointable() root.v1 = variables.Variable(1.) root.v2 = variables.Variable(2.) - root.f = def_function.function( - lambda x: root.v2 * x, - input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) imported = self.cycle(root) self.assertEquals(imported.v1.numpy(), 1.0) self.assertEquals(imported.v2.numpy(), 2.0) + + def test_capture_variables(self): + root = tracking.Checkpointable() + root.weights = variables.Variable(2.) + root.f = def_function.function( + lambda x: root.weights * x, + input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) + imported = self.cycle(root) self.assertEqual(4., imported.f(constant_op.constant(2.)).numpy()) + imported.weights.assign(4.0) + self.assertEqual(8., imported.f(constant_op.constant(2.)).numpy()) def _make_asset(self, contents): filename = tempfile.mktemp(prefix=self.get_temp_dir()) @@ -72,19 +75,16 @@ class LoadTest(test.TestCase): f.write(contents) return filename - def test_assets_import(self): + def test_assets(self): file1 = self._make_asset("contents 1") file2 = self._make_asset("contents 2") root = tracking.Checkpointable() - root.f = def_function.function( - lambda x: 2. * x, - input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) root.asset1 = tracking.TrackableAsset(file1) root.asset2 = tracking.TrackableAsset(file2) save_dir = os.path.join(self.get_temp_dir(), "save_dir") - save.save(root, save_dir) + save.save(root, save_dir, signatures={}) file_io.delete_file(file1) file_io.delete_file(file2) @@ -110,18 +110,12 @@ class LoadTest(test.TestCase): with open(imported_output, "r") as f: self.assertEquals("contents", f.read()) - def test_assets_dedup(self): + def test_dedup_assets(self): vocab = self._make_asset("contents") root = tracking.Checkpointable() - root.f = def_function.function( - lambda x: 2. * x, - input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) - root.asset1 = tracking.TrackableAsset(vocab) root.asset2 = tracking.TrackableAsset(vocab) - imported = self.cycle(root) - self.assertEqual(imported.asset1.asset_path.numpy(), imported.asset2.asset_path.numpy()) @@ -154,7 +148,7 @@ class LoadTest(test.TestCase): imported = self.cycle(root) self.assertEqual(4., imported.f(constant_op.constant(2.0)).numpy()) - def test_nested_func(self): + def test_nested_functions(self): f = def_function.function( lambda x: x*2.0, input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) -- GitLab From 114f0d81404e7f9a93297f11ebc2fbceee8dbf7c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 05:20:40 -0800 Subject: [PATCH 0334/2345] Add fine-tune capabilities to object based saved model save/load. - Keep "trainable" bit of variables. - Wire variables in restored FuncGraphs so they are automaticaly captured by GradientTapes. PiperOrigin-RevId: 228311974 --- tensorflow/python/eager/function.py | 2 +- tensorflow/python/saved_model/load.py | 8 +++++- tensorflow/python/saved_model/load_test.py | 25 +++++++++++++++++-- tensorflow/python/saved_model/save.py | 1 + .../saved_model/saved_object_graph.proto | 3 ++- 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 58d1f6b886..05fccfbcd9 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -105,7 +105,7 @@ def _parse_func_attrs(attributes): attrs[key] = attr_value_pb2.AttrValue(i=value) elif isinstance(value, float): attrs[key] = attr_value_pb2.AttrValue(f=value) - elif isinstance(value, (str, bytes)): + elif isinstance(value, (str, bytes, six.text_type)): attrs[key] = attr_value_pb2.AttrValue(s=compat.as_bytes(value)) else: raise ValueError("Unsupported attribute type for %s with type %s" % diff --git a/tensorflow/python/saved_model/load.py b/tensorflow/python/saved_model/load.py index 3e85fade50..1ee1b69b03 100644 --- a/tensorflow/python/saved_model/load.py +++ b/tensorflow/python/saved_model/load.py @@ -58,6 +58,11 @@ class _Loader(object): bound_inputs = [ self._get_tensor_from_node(node_id) for node_id in monomorphic_function.bound_inputs] + bound_variables = [ + self._nodes[node_id] + for node_id in monomorphic_function.bound_inputs + if self._proto.nodes[node_id].WhichOneof("kind") == "variable" + ] if name in seen_functions: if self._functions[name]._captured_inputs != bound_inputs: # pylint: disable=protected-access raise NotImplementedError( @@ -69,6 +74,7 @@ class _Loader(object): # concrete function, note that we did not modify the FuncGraph # itself. self._functions[name]._captured_inputs = bound_inputs # pylint: disable=protected-access + self._functions[name]._func_graph.variables = bound_variables # pylint: disable=protected-access def _get_tensor_from_node(self, node_id): obj = self._nodes[node_id] @@ -123,7 +129,7 @@ class _Loader(object): def _recreate_variable(self, proto): # TODO(andresp): Can we use the checkpointed value as initializer? dummy_value = init_ops.Zeros(dtype=proto.dtype)(shape=proto.shape) - return variables.Variable(dummy_value) + return variables.Variable(dummy_value, trainable=proto.trainable) def _load_saved_object_graph_proto(filename): diff --git a/tensorflow/python/saved_model/load_test.py b/tensorflow/python/saved_model/load_test.py index a1c81d3f14..35e384dbfc 100644 --- a/tensorflow/python/saved_model/load_test.py +++ b/tensorflow/python/saved_model/load_test.py @@ -21,6 +21,7 @@ from __future__ import print_function import os import tempfile +from tensorflow.python.eager import backprop from tensorflow.python.eager import def_function from tensorflow.python.eager import test from tensorflow.python.framework import constant_op @@ -52,11 +53,13 @@ class LoadTest(test.TestCase): def test_variables(self): root = tracking.Checkpointable() - root.v1 = variables.Variable(1.) - root.v2 = variables.Variable(2.) + root.v1 = variables.Variable(1., trainable=True) + root.v2 = variables.Variable(2., trainable=False) imported = self.cycle(root) self.assertEquals(imported.v1.numpy(), 1.0) + self.assertTrue(imported.v1.trainable) self.assertEquals(imported.v2.numpy(), 2.0) + self.assertFalse(imported.v2.trainable) def test_capture_variables(self): root = tracking.Checkpointable() @@ -248,6 +251,24 @@ class LoadTest(test.TestCase): self.cycle(m) self.assertEquals(4.0, m.f(constant_op.constant(2.0)).numpy()) + def test_basic_backprop(self): + weight = variables.Variable(1., trainable=True) + bias = variables.Variable(0., trainable=True) + g = def_function.function( + lambda x: x*weight + bias, + input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) + + root = tracking.Checkpointable() + root.weight = weight + root.bias = bias + root.g = g + imported = self.cycle(root) + with backprop.GradientTape(watch_accessed_variables=True) as t: + x = constant_op.constant([3.5]) + loss = imported.g(x) + grad = t.gradient(loss, [imported.weight, imported.bias]) + self.assertAllClose(grad, [3.5, 1.0]) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/saved_model/save.py b/tensorflow/python/saved_model/save.py index a76b370565..d79b5fe3a6 100644 --- a/tensorflow/python/saved_model/save.py +++ b/tensorflow/python/saved_model/save.py @@ -610,6 +610,7 @@ def _write_object_proto(obj, proto, asset_file_def_index, node_ids): proto.asset.asset_file_def_index = asset_file_def_index[obj] elif resource_variable_ops.is_resource_variable(obj): proto.variable.SetInParent() + proto.variable.trainable = obj.trainable proto.variable.dtype = obj.dtype.as_datatype_enum proto.variable.shape.CopyFrom(obj.shape.as_proto()) elif isinstance(obj, def_function.PolymorphicFunction): diff --git a/tensorflow/python/saved_model/saved_object_graph.proto b/tensorflow/python/saved_model/saved_object_graph.proto index f46927d6e8..1e2514b7f7 100644 --- a/tensorflow/python/saved_model/saved_object_graph.proto +++ b/tensorflow/python/saved_model/saved_object_graph.proto @@ -104,6 +104,7 @@ message SavedMonomorphicFunction { message SavedVariable { DataType dtype = 1; TensorShapeProto shape = 2; + bool trainable = 3; - // TODO(andresp): Add "trainable" and save_slice_info_def. + // TODO(andresp): Add save_slice_info_def? } -- GitLab From 0ad1d56cd974c1feef19ebf81b202b9bd4066247 Mon Sep 17 00:00:00 2001 From: Grzegorz Pawelczak Date: Tue, 8 Jan 2019 13:44:32 +0000 Subject: [PATCH 0335/2345] Make RemoveUnusedOperandFromSort a seperate pass --- tensorflow/compiler/xla/service/BUILD | 32 +++++ .../xla/service/algebraic_simplifier.cc | 75 ----------- .../xla/service/algebraic_simplifier_test.cc | 68 ---------- .../compiler/xla/service/sort_simplifier.cc | 125 ++++++++++++++++++ .../compiler/xla/service/sort_simplifier.h | 35 +++++ .../xla/service/sort_simplifier_test.cc | 102 ++++++++++++++ 6 files changed, 294 insertions(+), 143 deletions(-) create mode 100644 tensorflow/compiler/xla/service/sort_simplifier.cc create mode 100644 tensorflow/compiler/xla/service/sort_simplifier.h create mode 100644 tensorflow/compiler/xla/service/sort_simplifier_test.cc diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index ec63ae1ac6..7be180c624 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -3462,6 +3462,38 @@ cc_library( ], ) +cc_library( + name = "sort_simplifier", + srcs = ["sort_simplifier.cc"], + hdrs = ["sort_simplifier.h"], + deps = [ + ":hlo", + ":hlo_pass", + ":while_util", + "//tensorflow/compiler/xla:statusor", + "//tensorflow/compiler/xla:util", + "//tensorflow/core:lib", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:inlined_vector", + ], +) + +tf_cc_test( + name = "sort_simplifier_test", + srcs = ["sort_simplifier_test.cc"], + deps = [ + ":hlo_matchers", + ":sort_simplifier", + ":pattern_matcher", + ":pattern_matcher_gmock", + "//tensorflow/compiler/xla:test", + "//tensorflow/compiler/xla/service:hlo_parser", + "//tensorflow/compiler/xla/tests:hlo_test_base", + "//tensorflow/core:test", + ], +) + cc_library( name = "tuple_util", srcs = ["tuple_util.cc"], diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc index 5ac746c9f3..b76f2febc7 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc @@ -371,11 +371,6 @@ class AlgebraicSimplifierVisitor : public DfsHloVisitorWithDefault { // Tries to convert slice(reshape(X)) into reshape(slice(X)) StatusOr TryToReorderSliceAndReshape(HloInstruction* slice); - // If the sort instruction has a tuple shape then looks for unused output - // values and removes them from the sort instruction. Returns true if the - // graph have been modified. - StatusOr RemoveUnusedOperandFromSort(HloInstruction* sort); - // Current HloComputation instance the AlgebraicSimplifierVisitor is // traversing. HloComputation* computation_; @@ -2819,69 +2814,6 @@ Status AlgebraicSimplifierVisitor::HandleSelect(HloInstruction* select) { return Status::OK(); } -StatusOr AlgebraicSimplifierVisitor::RemoveUnusedOperandFromSort( - HloInstruction* sort) { - if (!sort->shape().IsTuple()) { - return false; - } - - if (sort->parent()->root_instruction() == sort) { - // Can't analyse users of the root instruction. - return false; - } - - // Index 0 is the sorting key used by the sort HLO itself. - absl::flat_hash_set used_indices{0}; - for (const HloInstruction* user : sort->users()) { - if (user->opcode() != HloOpcode::kGetTupleElement) { - // Can't analyse users other then get-tuple-element. - return false; - } - used_indices.insert(user->tuple_index()); - } - - if (used_indices.size() == sort->operand_count()) { - // All operands are used. - return false; - } - - std::vector operands{sort->mutable_operand(0)}; - std::vector new_shapes{sort->operand(0)->shape()}; - for (int64 i = 1; i < sort->operand_count(); ++i) { - if (used_indices.count(i)) { - operands.push_back(sort->mutable_operand(i)); - new_shapes.push_back(sort->operand(i)->shape()); - } - } - Shape new_sort_shape = new_shapes.size() == 1 - ? new_shapes[0] - : ShapeUtil::MakeTupleShape(new_shapes); - HloInstruction* new_sort = computation_->AddInstruction( - sort->CloneWithNewOperands(new_sort_shape, operands)); - - // Map from original get-tuple-element tuple index to new HLO instruction - absl::flat_hash_map result_map; - if (new_sort->shape().IsTuple()) { - // Old sort key maps to new sort key. - int64 new_index = 0; - for (int64 i = 0; i < sort->operand_count(); ++i) { - if (used_indices.count(i)) { - result_map[i] = - computation_->AddInstruction(HloInstruction::CreateGetTupleElement( - new_shapes[new_index], new_sort, new_index)); - ++new_index; - } - } - } else { - result_map[0] = new_sort; - } - for (HloInstruction* user : sort->users()) { - TF_RETURN_IF_ERROR( - user->ReplaceAllUsesWith(result_map.at(user->tuple_index()))); - } - return true; -} - Status AlgebraicSimplifierVisitor::HandleSort(HloInstruction* sort) { auto operand = sort->mutable_operand(0); int64 dimension_to_sort = sort->dimensions(0); @@ -2895,13 +2827,6 @@ Status AlgebraicSimplifierVisitor::HandleSort(HloInstruction* sort) { sort, HloInstruction::CreateTuple(sort->operands())); } - // Remove the unused values from a key-value sort. - TF_ASSIGN_OR_RETURN(bool removed_operand, RemoveUnusedOperandFromSort(sort)); - if (removed_operand) { - changed_ = true; - return Status::OK(); - } - if (!options_.enable_permutation_sort_replacement()) { return Status::OK(); } diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc index e39321f1f3..a2bbc4861f 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc @@ -2709,74 +2709,6 @@ TEST_F(AlgebraicSimplifierTest, DontReplacePermutationSortWrongDimensions) { EXPECT_FALSE(simplifier.Run(module.get()).ValueOrDie()); } -TEST_F(AlgebraicSimplifierTest, RemoveUnusedSortOperandArrayResult) { - const char* hlo_string = R"( - HloModule permutation_sort - - ENTRY sort_computation { - keys = f32[64,8732]{1,0} parameter(0) - values = s32[64,8732]{1,0} parameter(1) - sort = (f32[64,8732]{1,0}, s32[64,8732]{1,0}) sort(keys, values), - dimensions={1} - ROOT gte = f32[64,8732]{1,0} get-tuple-element(sort), index=0 - })"; - TF_ASSERT_OK_AND_ASSIGN(auto module, - ParseAndReturnVerifiedModule(hlo_string)); - - AlgebraicSimplifierOptions options(bitcasting_callback()); - AlgebraicSimplifier simplifier(options); - EXPECT_TRUE(simplifier.Run(module.get()).ValueOrDie()); - auto root = module->entry_computation()->root_instruction(); - EXPECT_THAT(root, GmockMatch(m::Sort(m::Parameter(0)))); -} - -TEST_F(AlgebraicSimplifierTest, RemoveUnusedSortOperandTuple) { - const char* hlo_string = R"( - HloModule permutation_sort - - ENTRY sort_computation { - keys = f32[64,87] parameter(0) - values.0 = s32[64,87] parameter(1) - values.1 = u32[64,87] parameter(2) - sort = (f32[64,87], s32[64,87], u32[64,87]) sort( - keys, values.0, values.1), - dimensions={1} - gte.0 = f32[64,87] get-tuple-element(sort), index=0 - gte.1 = u32[64,87] get-tuple-element(sort), index=2 - ROOT tuple = (f32[64,87], u32[64,87]) tuple(gte.0, gte.1) - })"; - TF_ASSERT_OK_AND_ASSIGN(auto module, - ParseAndReturnVerifiedModule(hlo_string)); - - AlgebraicSimplifierOptions options(bitcasting_callback()); - AlgebraicSimplifier simplifier(options); - EXPECT_TRUE(simplifier.Run(module.get()).ValueOrDie()); - auto root = module->entry_computation()->root_instruction(); - EXPECT_THAT( - root, - GmockMatch(m::Tuple( - m::GetTupleElement(m::Sort(m::Parameter(0), m::Parameter(2)), 0), - m::GetTupleElement(m::Sort(m::Parameter(0), m::Parameter(2)), 1)))); -} - -TEST_F(AlgebraicSimplifierTest, DontRemoveUnusedSortKey) { - const char* hlo_string = R"( - HloModule permutation_sort - - ENTRY sort_computation { - keys = f32[64,8732]{1,0} parameter(0) - values = s32[64,8732]{1,0} parameter(1) - sort = (f32[64,8732]{1,0}, s32[64,8732]{1,0}) sort(keys, values), dimensions={1} - ROOT gte = s32[64,8732]{1,0} get-tuple-element(sort), index=1 - })"; - TF_ASSERT_OK_AND_ASSIGN(auto module, - ParseAndReturnVerifiedModule(hlo_string)); - - AlgebraicSimplifierOptions options(bitcasting_callback()); - AlgebraicSimplifier simplifier(options); - EXPECT_FALSE(simplifier.Run(module.get()).ValueOrDie()); -} - TEST_F(AlgebraicSimplifierTest, ReplaceEffectiveScalarKeyValueSortWithTuple) { auto builder = HloComputation::Builder(TestName()); diff --git a/tensorflow/compiler/xla/service/sort_simplifier.cc b/tensorflow/compiler/xla/service/sort_simplifier.cc new file mode 100644 index 0000000000..3280672574 --- /dev/null +++ b/tensorflow/compiler/xla/service/sort_simplifier.cc @@ -0,0 +1,125 @@ +/* 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/sort_simplifier.h" +#include "tensorflow/compiler/xla/service/hlo_computation.h" +#include "tensorflow/compiler/xla/service/hlo_instruction.h" +#include "tensorflow/compiler/xla/statusor.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" + +namespace xla { +namespace { + +// If the sort instruction has a tuple shape then looks for unused output +// values and removes them from the sort instruction. Returns true if the +// graph have been modified. +StatusOr RemoveUnusedOperandFromSort(HloInstruction* sort) { + if (!sort->shape().IsTuple()) { + return false; + } + + if (sort->parent()->root_instruction() == sort) { + // Can't analyse users of the root instruction. + return false; + } + + // Index 0 is the sorting key used by the sort HLO itself. + absl::flat_hash_set used_indices{0}; + for (const HloInstruction* user : sort->users()) { + if (user->opcode() != HloOpcode::kGetTupleElement) { + // Can't analyse users other then get-tuple-element. + return false; + } + used_indices.insert(user->tuple_index()); + } + + if (used_indices.size() == sort->operand_count()) { + // All operands are used. + return false; + } + + std::vector operands{sort->mutable_operand(0)}; + std::vector new_shapes{sort->operand(0)->shape()}; + for (int64 i = 1; i < sort->operand_count(); ++i) { + if (used_indices.count(i)) { + operands.push_back(sort->mutable_operand(i)); + new_shapes.push_back(sort->operand(i)->shape()); + } + } + HloComputation* computation = sort->parent(); + + Shape new_sort_shape = new_shapes.size() == 1 + ? new_shapes[0] + : ShapeUtil::MakeTupleShape(new_shapes); + HloInstruction* new_sort = computation->AddInstruction( + sort->CloneWithNewOperands(new_sort_shape, operands)); + + // Map from original get-tuple-element tuple index to new HLO instruction + absl::flat_hash_map result_map; + if (new_sort->shape().IsTuple()) { + // Old sort key maps to new sort key. + int64 new_index = 0; + for (int64 i = 0; i < sort->operand_count(); ++i) { + if (used_indices.count(i)) { + result_map[i] = + computation->AddInstruction(HloInstruction::CreateGetTupleElement( + new_shapes[new_index], new_sort, new_index)); + ++new_index; + } + } + } else { + result_map[0] = new_sort; + } + for (HloInstruction* user : sort->users()) { + TF_RETURN_IF_ERROR( + user->ReplaceAllUsesWith(result_map.at(user->tuple_index()))); + TF_RETURN_IF_ERROR(computation->RemoveInstructionAndUnusedOperands(user)); + } + return true; +} + +} // namespace + +StatusOr SortSimplifier::Run(HloModule* module) { + VLOG(2) << "HLO module before SortSimplifier:"; + XLA_VLOG_LINES(2, module->ToString()); + + bool changed = false; + std::vector sort_instrs; + for (auto* comp : module->MakeNonfusionComputations()) { + absl::c_copy_if(comp->instructions(), std::back_inserter(sort_instrs), + [](const HloInstruction* instr) { + return instr->opcode() == HloOpcode::kSort; + }); + } + + for (HloInstruction* sort_instr : sort_instrs) { + TF_ASSIGN_OR_RETURN(bool result, + RemoveUnusedOperandFromSort(sort_instr)); + changed |= result; + } + + if (changed) { + VLOG(2) << "HLO module after SortSimplifier:"; + XLA_VLOG_LINES(2, module->ToString()); + } else { + VLOG(2) << "HLO module unchanged after SortSimplifier"; + } + + return changed; +} +} // namespace xla diff --git a/tensorflow/compiler/xla/service/sort_simplifier.h b/tensorflow/compiler/xla/service/sort_simplifier.h new file mode 100644 index 0000000000..8c6f313aa0 --- /dev/null +++ b/tensorflow/compiler/xla/service/sort_simplifier.h @@ -0,0 +1,35 @@ +/* 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_SORT_SIMPLIFIER_H_ +#define TENSORFLOW_COMPILER_XLA_SERVICE_SORT_SIMPLIFIER_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 { + +// HLO pass which removes unused operands from sort, where an unused operand is +// defined as an operand at some index 'x' at which the output is not used. +class SortSimplifier : public HloModulePass { + public: + absl::string_view name() const override { return "simplify-sorts"; } + StatusOr Run(HloModule* module) override; +}; + +} // namespace xla + +#endif // TENSORFLOW_COMPILER_XLA_SERVICE_SORT_SIMPLIFIER_H_ diff --git a/tensorflow/compiler/xla/service/sort_simplifier_test.cc b/tensorflow/compiler/xla/service/sort_simplifier_test.cc new file mode 100644 index 0000000000..5a81e537de --- /dev/null +++ b/tensorflow/compiler/xla/service/sort_simplifier_test.cc @@ -0,0 +1,102 @@ +/* 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/compiler/xla/service/sort_simplifier.h" + +#include "tensorflow/compiler/xla/service/hlo_matchers.h" +#include "tensorflow/compiler/xla/service/hlo_parser.h" +#include "tensorflow/compiler/xla/service/pattern_matcher.h" +#include "tensorflow/compiler/xla/service/pattern_matcher_gmock.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 { + +namespace m = match; + +using SortSimplifierTest = HloTestBase; + +TEST_F(SortSimplifierTest, RemoveUnusedSortOperandArrayResult) { + const char* hlo_string = R"( + HloModule permutation_sort + + ENTRY sort_computation { + keys = f32[64,8732]{1,0} parameter(0) + values = s32[64,8732]{1,0} parameter(1) + sort = (f32[64,8732]{1,0}, s32[64,8732]{1,0}) sort(keys, values), + dimensions={1} + ROOT gte = f32[64,8732]{1,0} get-tuple-element(sort), index=0 + })"; + TF_ASSERT_OK_AND_ASSIGN(auto module, + ParseAndReturnVerifiedModule(hlo_string)); + + SortSimplifier simplifier; + uint64 num_executions = 0; + do { + num_executions++; + } while (simplifier.Run(module.get()).ValueOrDie()); + EXPECT_TRUE(num_executions == 2); + auto root = module->entry_computation()->root_instruction(); + EXPECT_THAT(root, GmockMatch(m::Sort(m::Parameter(0)))); +} + +TEST_F(SortSimplifierTest, RemoveUnusedSortOperandTuple) { + const char* hlo_string = R"( + HloModule permutation_sort + + ENTRY sort_computation { + keys = f32[64,87] parameter(0) + values.0 = s32[64,87] parameter(1) + values.1 = u32[64,87] parameter(2) + sort = (f32[64,87], s32[64,87], u32[64,87]) sort( + keys, values.0, values.1), + dimensions={1} + gte.0 = f32[64,87] get-tuple-element(sort), index=0 + gte.1 = u32[64,87] get-tuple-element(sort), index=2 + ROOT tuple = (f32[64,87], u32[64,87]) tuple(gte.0, gte.1) + })"; + TF_ASSERT_OK_AND_ASSIGN(auto module, + ParseAndReturnVerifiedModule(hlo_string)); + + SortSimplifier simplifier; + EXPECT_TRUE(simplifier.Run(module.get()).ValueOrDie()); + auto root = module->entry_computation()->root_instruction(); + EXPECT_THAT( + root, + GmockMatch(m::Tuple( + m::GetTupleElement(m::Sort(m::Parameter(0), m::Parameter(2)), 0), + m::GetTupleElement(m::Sort(m::Parameter(0), m::Parameter(2)), 1)))); +} + +TEST_F(SortSimplifierTest, DontRemoveUnusedSortKey) { + const char* hlo_string = R"( + HloModule permutation_sort + + ENTRY sort_computation { + keys = f32[64,8732]{1,0} parameter(0) + values = s32[64,8732]{1,0} parameter(1) + sort = (f32[64,8732]{1,0}, s32[64,8732]{1,0}) sort(keys, values), dimensions={1} + ROOT gte = s32[64,8732]{1,0} get-tuple-element(sort), index=1 + })"; + TF_ASSERT_OK_AND_ASSIGN(auto module, + ParseAndReturnVerifiedModule(hlo_string)); + + SortSimplifier simplifier; + EXPECT_FALSE(simplifier.Run(module.get()).ValueOrDie()); +} +} // namespace +} // namespace xla -- GitLab From 0f9478c3c2a71f26fd26083b2e2ed76fb69d1030 Mon Sep 17 00:00:00 2001 From: ktaebum Date: Tue, 8 Jan 2019 23:19:38 +0900 Subject: [PATCH 0336/2345] resolved incompatible dimension reshape before feed into final dense layer --- tensorflow/contrib/eager/python/examples/rnn_ptb/rnn_ptb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/eager/python/examples/rnn_ptb/rnn_ptb.py b/tensorflow/contrib/eager/python/examples/rnn_ptb/rnn_ptb.py index 15776c694e..9b5a2c947b 100644 --- a/tensorflow/contrib/eager/python/examples/rnn_ptb/rnn_ptb.py +++ b/tensorflow/contrib/eager/python/examples/rnn_ptb/rnn_ptb.py @@ -128,7 +128,7 @@ class PTBModel(tf.keras.Model): self.linear = layers.Dense( vocab_size, kernel_initializer=tf.random_uniform_initializer(-0.1, 0.1)) - self._output_shape = [-1, embedding_dim] + self._output_shape = [-1, hidden_dim] def call(self, input_seq, training): """Run the forward pass of PTBModel. -- GitLab From 7fa90cddf07c6aac532306dac15e02216a83b9fa Mon Sep 17 00:00:00 2001 From: Asim Shankar Date: Tue, 8 Jan 2019 07:29:42 -0800 Subject: [PATCH 0337/2345] [eager]: Disable benchmark that is currently timing out. This timeout seems to have started after https://github.com/tensorflow/tensorflow/commit/0445684a64d1bea8490a99eb9ce278176133df75 PiperOrigin-RevId: 228326334 --- tensorflow/python/eager/benchmarks_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tensorflow/python/eager/benchmarks_test.py b/tensorflow/python/eager/benchmarks_test.py index 31a7efca82..9191c8e689 100644 --- a/tensorflow/python/eager/benchmarks_test.py +++ b/tensorflow/python/eager/benchmarks_test.py @@ -867,6 +867,10 @@ class MicroBenchmarks(test.Benchmark): self._run(scan, 100) def benchmarkScanDefun(self): + if context.num_gpus(): + # TODO(b/122081934): Re-enable this after figuring out why this became + # really slow with control flow V2 + return elems = math_ops.range(1600) @function.defun -- GitLab From 107b7c4de6727ad02a0b4fc24aa1ac17bd697776 Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Tue, 8 Jan 2019 11:37:37 -0500 Subject: [PATCH 0338/2345] Fixes for rnn_keras_estimator.ipynb * wrapping `range(batch_size)` into a `list` call so that it's convertible to `Tensor` in Python 3, where `range` alone returns just a generator * specifying a `dtype` argument to `LSTMBlockCell`'s constructor These should fix the breakages in #24701 --- .../autograph/examples/notebooks/rnn_keras_estimator.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/autograph/examples/notebooks/rnn_keras_estimator.ipynb b/tensorflow/contrib/autograph/examples/notebooks/rnn_keras_estimator.ipynb index 44532cb078..831c613f2c 100644 --- a/tensorflow/contrib/autograph/examples/notebooks/rnn_keras_estimator.ipynb +++ b/tensorflow/contrib/autograph/examples/notebooks/rnn_keras_estimator.ipynb @@ -186,8 +186,8 @@ "\n", " def __init__(self):\n", " super(RnnColorbot, self).__init__()\n", - " self.lower_cell = tf.contrib.rnn.LSTMBlockCell(256)\n", - " self.upper_cell = tf.contrib.rnn.LSTMBlockCell(128)\n", + " self.lower_cell = tf.contrib.rnn.LSTMBlockCell(256, dtype=tf.float32)\n", + " self.upper_cell = tf.contrib.rnn.LSTMBlockCell(128, dtype=tf.float32)\n", " self.relu_layer = tf.layers.Dense(3, activation=tf.nn.relu)\n", "\n", " def _rnn_layer(self, chars, cell, batch_size, training):\n", @@ -241,7 +241,7 @@ " seq = self._rnn_layer(seq, self.upper_cell, batch_size, training)\n", "\n", " # Grab just the end-of-sequence from each output.\n", - " indices = (length - 1, range(batch_size))\n", + " indices = (length - 1, list(range(batch_size)))\n", " indices = tf.stack(indices, 1)\n", " sequence_ends = tf.gather_nd(seq, indices)\n", " return self.relu_layer(sequence_ends)\n", -- GitLab From 02d4333dfc5a9fe2c3929ba6288e9a679725da85 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Tue, 8 Jan 2019 09:18:09 -0800 Subject: [PATCH 0339/2345] Give bytes constructor the appropriate utf-8 argument. PiperOrigin-RevId: 228342070 --- tensorflow/tools/git/gen_git_source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/git/gen_git_source.py b/tensorflow/tools/git/gen_git_source.py index 0c37508650..c2449da923 100755 --- a/tensorflow/tools/git/gen_git_source.py +++ b/tensorflow/tools/git/gen_git_source.py @@ -29,8 +29,8 @@ from __future__ import print_function import argparse import json import os -import subprocess import shutil +import subprocess def parse_branch_ref(filename): @@ -175,7 +175,7 @@ def get_git_version(git_base_path, git_tag_override): # two "-" are those inserted by the git describe command. abbrev_commit = split_val[-1] val = version_separator.join( - [bytes(git_tag_override), b"0", abbrev_commit]) + [bytes(git_tag_override, "utf-8"), b"0", abbrev_commit]) return val if val else unknown_label except (subprocess.CalledProcessError, OSError): return unknown_label -- GitLab From 3ba26026545be700c4aed9bc609b25fad9c1531e Mon Sep 17 00:00:00 2001 From: Sergei Lebedev Date: Tue, 8 Jan 2019 09:25:59 -0800 Subject: [PATCH 0340/2345] Removed ResourceVariable._cached_shape_as_list _shape_as_list is no longer on the hotpath of EagerLinearRegressionBenchmark, the change does not seem to affect throughput estimates. Note that both estimates have high variance. Before: entry { name: "EagerLinearRegressionBenchmark.eager_train_cpu" iters: 2000 wall_time: 1.24479007721 extras { key: "examples_per_sec" value { double_value: 102828.583183 } } } After: entry { name: "EagerLinearRegressionBenchmark.eager_train_cpu" iters: 2000 wall_time: 1.25307798386 extras { key: "examples_per_sec" value { double_value: 102148.470924 } } } PiperOrigin-RevId: 228343127 --- tensorflow/python/ops/resource_variable_ops.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tensorflow/python/ops/resource_variable_ops.py b/tensorflow/python/ops/resource_variable_ops.py index 1d98fb2c89..d27e38c735 100644 --- a/tensorflow/python/ops/resource_variable_ops.py +++ b/tensorflow/python/ops/resource_variable_ops.py @@ -504,7 +504,6 @@ class ResourceVariable(variables.VariableV1): # all in graph mode. self._handle_deleter = EagerResourceDeleter( handle=self._handle, handle_device=self._handle.device) - self._cached_shape_as_list = None def _init_from_proto(self, variable_def, import_scope=None): """Initializes from `VariableDef` proto.""" @@ -562,7 +561,6 @@ class ResourceVariable(variables.VariableV1): self._caching_device = None self._dtype = dtypes.as_dtype(self._handle.op.get_attr("dtype")) self._constraint = None - self._cached_shape_as_list = None @contextlib.contextmanager def _assign_dependencies(self): @@ -632,12 +630,9 @@ class ResourceVariable(variables.VariableV1): return self._distribute_strategy def _shape_as_list(self): - if self._cached_shape_as_list: - return self._cached_shape_as_list if self.shape.ndims is None: return None - self._cached_shape_as_list = [dim.value for dim in self.shape.dims] - return self._cached_shape_as_list + return [dim.value for dim in self.shape.dims] def _shape_tuple(self): shape = self._shape_as_list() -- GitLab From aa5e17a2dc94ba47d41a74021d6701a8c10de4a5 Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Tue, 8 Jan 2019 09:37:02 -0800 Subject: [PATCH 0341/2345] Fix linkage for several lite/testing tests Use the tf_cc_test rule for targets that have TensorFlow deps. PiperOrigin-RevId: 228344897 --- tensorflow/lite/testing/BUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/testing/BUILD b/tensorflow/lite/testing/BUILD index fa25cfaa69..19c950b4f8 100644 --- a/tensorflow/lite/testing/BUILD +++ b/tensorflow/lite/testing/BUILD @@ -257,7 +257,7 @@ cc_library( ], ) -cc_test( +tf_cc_test( name = "tf_driver_test", size = "small", srcs = ["tf_driver_test.cc"], @@ -286,7 +286,7 @@ cc_library( ], ) -cc_test( +tf_cc_test( name = "generate_testspec_test", size = "small", srcs = ["generate_testspec_test.cc"], -- GitLab From f0fba8b59b3e3fa6b068c0c2715104153983c554 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Tue, 8 Jan 2019 09:54:11 -0800 Subject: [PATCH 0342/2345] Use correct _t integer types to fix mac build. PiperOrigin-RevId: 228347592 --- tensorflow/lite/kernels/dequantize_test.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/kernels/dequantize_test.cc b/tensorflow/lite/kernels/dequantize_test.cc index 6343745eef..be7caa3189 100644 --- a/tensorflow/lite/kernels/dequantize_test.cc +++ b/tensorflow/lite/kernels/dequantize_test.cc @@ -12,6 +12,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#include + #include #include "tensorflow/lite/interpreter.h" #include "tensorflow/lite/kernels/internal/types.h" @@ -59,7 +61,7 @@ TEST(DequantizeOpTest, UINT8) { // [-63.5, 64] -> scale=0.5 zero_point=127 for UINT8 DequantizeOpModel m(TensorType_UINT8, {2, 5}, 0.5, 127); - m.SetInput({0, 1, 2, 3, 4, 251, 252, 253, 254, 255}); + m.SetInput({0, 1, 2, 3, 4, 251, 252, 253, 254, 255}); m.Invoke(); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear( @@ -70,7 +72,7 @@ TEST(DequantizeOpTest, INT8) { // [-63.5, 64] -> scale=0.5, zero_point=1 for INT8 DequantizeOpModel m(TensorType_INT8, {2, 5}, 0.5, -1); - m.SetInput({-128, -127, -126, -125, -124, 123, 124, 125, 126, 127}); + m.SetInput({-128, -127, -126, -125, -124, 123, 124, 125, 126, 127}); m.Invoke(); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear( -- GitLab From 0ad4e9ea2b41ad1d9c8c59844fd814b01820bab2 Mon Sep 17 00:00:00 2001 From: Jian Li Date: Tue, 8 Jan 2019 09:57:31 -0800 Subject: [PATCH 0343/2345] Update unidirectional lstm test case. PiperOrigin-RevId: 228348125 --- tensorflow/lite/kernels/unidirectional_sequence_lstm_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/unidirectional_sequence_lstm_test.cc b/tensorflow/lite/kernels/unidirectional_sequence_lstm_test.cc index 274f907076..bc35d90773 100644 --- a/tensorflow/lite/kernels/unidirectional_sequence_lstm_test.cc +++ b/tensorflow/lite/kernels/unidirectional_sequence_lstm_test.cc @@ -252,7 +252,7 @@ class HybridUnidirectionalLSTMOpModel : public UnidirectionalLSTMOpModel { tensor_type_ = tensor_type; } - void SetWeights(int weights_idx, std::vector f) { + void SetWeights(int weights_idx, const std::vector& f) { if (tensor_type_ == TensorType_UINT8) { SymmetricQuantizeAndPopulate(weights_idx, f); } else { -- GitLab From 011a8094c182101a65fb84e98a60a152e0f58f0d Mon Sep 17 00:00:00 2001 From: Sergei Lebedev Date: Tue, 8 Jan 2019 10:06:30 -0800 Subject: [PATCH 0344/2345] Aligned ResourceVariable.__reduce__ with ResourceVariable.__deepcopy__ PiperOrigin-RevId: 228350078 --- .../kernel_tests/resource_variable_ops_test.py | 13 ++++++++++--- tensorflow/python/ops/resource_variable_ops.py | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/kernel_tests/resource_variable_ops_test.py b/tensorflow/python/kernel_tests/resource_variable_ops_test.py index 67fa350317..2594055119 100644 --- a/tensorflow/python/kernel_tests/resource_variable_ops_test.py +++ b/tensorflow/python/kernel_tests/resource_variable_ops_test.py @@ -286,12 +286,19 @@ class ResourceVariableOpsTest(test_util.TensorFlowTestCase): tmp_dir = self.get_temp_dir() fname = os.path.join(tmp_dir, "var.pickle") with open(fname, "wb") as f: - v = resource_variable_ops.ResourceVariable(10.0) + v = resource_variable_ops.ResourceVariable( + 10.0, + dtype=dtypes.float16, + name="v") pickle.dump(v, f) with open(fname, "rb") as f: - v = pickle.load(f) - self.assertAllEqual(v.numpy(), 10.0) + new_v = pickle.load(f) + self.assertEqual(new_v.name, v.name) + self.assertEqual(new_v.shape, v.shape) + self.assertEqual(new_v.dtype, v.dtype) + self.assertEqual(new_v.trainable, v.trainable) + self.assertAllEqual(new_v.numpy(), v.numpy()) @test_util.run_in_graph_and_eager_modes def testScatterDiv(self): diff --git a/tensorflow/python/ops/resource_variable_ops.py b/tensorflow/python/ops/resource_variable_ops.py index d27e38c735..2d46e21074 100644 --- a/tensorflow/python/ops/resource_variable_ops.py +++ b/tensorflow/python/ops/resource_variable_ops.py @@ -20,6 +20,7 @@ from __future__ import division from __future__ import print_function import contextlib +import functools from tensorflow.core.framework import attr_value_pb2 from tensorflow.core.framework import variable_pb2 @@ -595,7 +596,8 @@ class ResourceVariable(variables.VariableV1): trainable=self._trainable, constraint=self._constraint, dtype=self._dtype, - name=self._shared_name + "_copy") + name=self._shared_name + "_copy", + distribute_strategy=self.distribute_strategy) memo[self._unique_id] = copied_variable return copied_variable @@ -938,7 +940,15 @@ class ResourceVariable(variables.VariableV1): return assign_op def __reduce__(self): - return (ResourceVariable, (self.numpy(),)) + # The implementation mirrors that of __deepcopy__. + return functools.partial( + ResourceVariable, + initial_value=self.numpy(), + trainable=self.trainable, + name=self._shared_name, + dtype=self.dtype, + constraint=self.constraint, + distribute_strategy=self.distribute_strategy), () def scatter_sub(self, sparse_delta, use_locking=False, name=None): """Subtracts `IndexedSlices` from this variable. -- GitLab From 35641fa70182fb9ee4c7e015b4ed38d0148cf340 Mon Sep 17 00:00:00 2001 From: Francois Chollet Date: Tue, 8 Jan 2019 10:15:50 -0800 Subject: [PATCH 0345/2345] Make dataset instances passed to `fit`/etc propagate to the `model_iteration` loop. This is a necessary step in order to start resetting a dataset of known cardinality as part of the loop. PiperOrigin-RevId: 228351978 --- tensorflow/python/keras/engine/training.py | 185 ++++++++---------- .../python/keras/engine/training_arrays.py | 23 ++- .../keras/engine/training_dataset_test.py | 55 +++--- .../python/keras/engine/training_utils.py | 67 ++++++- 4 files changed, 192 insertions(+), 138 deletions(-) diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index bd06751428..1eda8cf797 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -19,14 +19,12 @@ from __future__ import division from __future__ import print_function import collections -import weakref import numpy as np from tensorflow.python import tf2 from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.ops import iterator_ops from tensorflow.python.eager import context -from tensorflow.python.framework import errors from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import tensor_util @@ -122,10 +120,6 @@ class Model(Network): def __init__(self, *args, **kwargs): super(Model, self).__init__(*args, **kwargs) - # Create a cache for iterator get_next op. - self._iterator_get_next = weakref.WeakKeyDictionary() - # Create a cache for dataset - uninitialized iterators - self._dataset_iterator_cache = weakref.WeakKeyDictionary() # initializing _distribution_strategy here since it is possible to call # predict on a model without compiling it. self._distribution_strategy = None @@ -1247,7 +1241,8 @@ class Model(Network): 'compiled with DistributionStrategy.') # Validate and standardize user data. x, y, sample_weights = self._standardize_user_data( - x, y, sample_weight=sample_weight, class_weight=class_weight) + x, y, sample_weight=sample_weight, class_weight=class_weight, + extract_tensors_from_dataset=True) if self.run_eagerly: outputs = training_eager.train_on_batch( @@ -1316,7 +1311,7 @@ class Model(Network): 'compiled with DistributionStrategy.') # Validate and standardize user data. x, y, sample_weights = self._standardize_user_data( - x, y, sample_weight=sample_weight) + x, y, sample_weight=sample_weight, extract_tensors_from_dataset=True) if self.run_eagerly: outputs = training_eager.test_on_batch( @@ -1359,7 +1354,8 @@ class Model(Network): raise NotImplementedError('`predict_on_batch` is not supported for ' 'models compiled with DistributionStrategy.') # Validate and standardize user data. - inputs, _, _ = self._standardize_user_data(x) + inputs, _, _ = self._standardize_user_data( + x, extract_tensors_from_dataset=True) if self.run_eagerly: if (isinstance(inputs, iterator_ops.EagerIterator) or (isinstance(inputs, dataset_ops.DatasetV2))): @@ -2103,13 +2099,6 @@ class Model(Network): self._make_predict_function() return self.predict_function - def _get_iterator_get_next_tensors(self, iterator): - get_next_op = self._iterator_get_next.get(iterator, None) - if get_next_op is None: - get_next_op = iterator.get_next() - self._iterator_get_next[iterator] = get_next_op - return get_next_op - def _distribution_standardize_user_data(self, x, y=None, @@ -2231,7 +2220,8 @@ class Model(Network): steps_name='steps', steps=None, validation_split=0, - shuffle=False): + shuffle=False, + extract_tensors_from_dataset=False): """Runs validation checks on input and target data passed by the user. Also standardizes the data to lists of arrays, in order. @@ -2275,6 +2265,10 @@ class Model(Network): validation_split: Float between 0 and 1. Fraction of the training data to be used as validation data. shuffle: Boolean whether to shuffle the training data before each epoch. + extract_tensors_from_dataset: Boolean. When `x` is a dataset instance, + this indicates whether to extract actual tensors from the dataset or + instead output the dataset instance itself. + Set to True when calling from `train_on_batch`/etc. Returns: A tuple of 3: inputs (arrays or dicts, depending on whether `x` was a dict @@ -2287,60 +2281,30 @@ class Model(Network): ValueError: In case of invalid user-provided data. RuntimeError: If the model was never compiled. """ - if isinstance(x, dataset_ops.DatasetV2): - if context.executing_eagerly(): - x = iter(x) - else: - if x in self._dataset_iterator_cache: - x = self._dataset_iterator_cache[x] - else: - iterator = dataset_ops.make_initializable_iterator(x) - self._dataset_iterator_cache[x] = iterator - x = iterator - K.get_session().run(x.initializer) + if isinstance(x, (dataset_ops.DatasetV2, dataset_ops.DatasetV1)): + # Graph mode dataset. We'll pass the dataset as-is (unless + # `extract_tensors_from_dataset` is True, in which case we extract + # the tensors from the dataset and we output them. + training_utils.validate_dataset_input(x, y, sample_weight, + validation_split) + is_dataset = True + if extract_tensors_from_dataset: + # We do this for `train_on_batch`/etc. + x, y, sample_weight = training_utils.extract_tensors_from_dataset(x) + elif isinstance(x, iterator_ops.Iterator): + # Graph mode iterator. We extract the symbolic tensors. + training_utils.validate_dataset_input(x, y, sample_weight, + validation_split) + iterator = x + x, y, sample_weight = training_utils.unpack_iterator_input(iterator) + is_dataset = True + else: + is_dataset = False # Validates `steps` argument based on x's type. if check_steps: training_utils.check_steps_argument(x, steps, steps_name) - is_x_eager_iterator = isinstance(x, iterator_ops.EagerIterator) - is_x_iterator = isinstance(x, iterator_ops.Iterator) - - # Validate user inputs when data is given as a dataset or dataset iterator. - if is_x_iterator or is_x_eager_iterator: - training_utils.validate_dataset_input(x, y, sample_weight, - validation_split) - - # For eager iterators, when we have to process multiple batches of samples, - # we will standardize the data when we actually loop over iterator and get - # the batches. For now, we just return the iterator as is. - if is_x_eager_iterator: - return x, y, sample_weight - - # If input data is a dataset iterator in graph mode or if it is an eager - # iterator and only one batch of samples is required, we fetch the data - # tensors from the iterator and then standardize them. - if is_x_iterator: - try: - next_element = self._get_iterator_get_next_tensors(x) - except errors.OutOfRangeError: - raise RuntimeError('Your dataset iterator ran out of data; ' - 'Make sure that your dataset can generate ' - 'required number of samples.') - - if isinstance(next_element, (list, tuple)): - if len(next_element) not in [2, 3]: - raise ValueError( - 'Please provide model inputs as a list or tuple of 2 or 3' - 'elements: (input, target) or (input, target, sample_weights)' - 'Received %s' % next_element) - if len(next_element) == 2: - x, y = next_element - else: - x, y, sample_weight = next_element - else: - x = next_element - # First, we build/compile the model on the fly if necessary. all_inputs = [] is_build_called = False @@ -2349,40 +2313,51 @@ class Model(Network): # rather than list inputs (e.g. FeatureColumn-based models). dict_inputs = False if not self.inputs: - # We need to use `x` to set the model inputs. - # We type-check that `x` and `y` are either single arrays + # We need to use `x_input` to set the model inputs. + + # If input data is a dataset iterator in graph mode or if it is an eager + # iterator and only one batch of samples is required, we fetch the data + # tensors from the iterator and then standardize them. + if isinstance(x, (dataset_ops.DatasetV2, dataset_ops.DatasetV1)): + x_input, y_input, _ = training_utils.extract_tensors_from_dataset(x) + else: + x_input = x + y_input = y + # We type-check that `x_input` and `y_input` are either single arrays # or lists of arrays. - if isinstance(x, (list, tuple)): + if isinstance(x_input, (list, tuple)): if not all(isinstance(v, np.ndarray) or - tensor_util.is_tensor(v) for v in x): + tensor_util.is_tensor(v) for v in x_input): raise ValueError('Please provide as model inputs either a single ' 'array or a list of arrays. You passed: x=' + str(x)) - all_inputs += list(x) - elif isinstance(x, dict): + all_inputs += list(x_input) + elif isinstance(x_input, dict): dict_inputs = True - keys = sorted(x.keys()) - all_inputs = [x[k] for k in keys] + keys = sorted(x_input.keys()) + all_inputs = [x_input[k] for k in keys] else: - if not isinstance(x, np.ndarray) and not tensor_util.is_tensor(x): + if (not isinstance(x_input, np.ndarray) and + not tensor_util.is_tensor(x_input)): raise ValueError('Please provide as model inputs either a single ' 'array or a list of arrays. You passed: x=' + str(x)) - all_inputs.append(x) + all_inputs.append(x_input) # Build the model using the retrieved inputs (value or symbolic). # If values or generated from a dataset, then in symbolic-mode # placeholders will be created to match the value shapes. is_build_called = True - if is_x_iterator: - cast_inputs = nest.map_structure(lambda v: v.shape, x) - elif training_utils.has_tensors(x): - cast_inputs = training_utils.cast_if_floating_dtype(x) + if is_dataset: + cast_inputs = nest.map_structure(lambda v: v.shape, x_input) + elif training_utils.has_tensors(x_input): + cast_inputs = training_utils.cast_if_floating_dtype(x_input) else: - cast_inputs = x + cast_inputs = x_input self._set_inputs(cast_inputs) else: + y_input = y dict_inputs = isinstance(self.inputs, dict) - if y is not None: + if y_input is not None: if not self.optimizer: raise RuntimeError('You must compile a model before ' 'training/testing. ' @@ -2390,23 +2365,24 @@ class Model(Network): if not self._is_compiled: # On-the-fly compilation of the model. # We need to use `y` to set the model targets. - if training_utils.has_tensors(y): - y = training_utils.cast_if_floating_dtype(y) - if isinstance(y, (list, tuple)): + if training_utils.has_tensors(y_input): + y_input = training_utils.cast_if_floating_dtype(y_input) + if isinstance(y_input, (list, tuple)): if not all(isinstance(v, np.ndarray) or - tensor_util.is_tensor(v) for v in y): + tensor_util.is_tensor(v) for v in y_input): raise ValueError('Please provide as model targets either a single ' 'array or a list of arrays. ' 'You passed: y=' + str(y)) - all_inputs += list(y) - elif isinstance(y, dict): - raise ValueError('Please do not pass a dictionary as model targets.') + all_inputs += list(y_input) + elif isinstance(y_input, dict): + raise ValueError('You cannot pass a dictionary as model targets.') else: - if not isinstance(y, np.ndarray) and not tensor_util.is_tensor(y): + if (not isinstance(y_input, np.ndarray) and + not tensor_util.is_tensor(y_input)): raise ValueError('Please provide as model targets either a single ' 'array or a list of arrays. ' 'You passed: y=' + str(y)) - all_inputs.append(y) + all_inputs.append(y_input) # Typecheck that all inputs are *either* value *or* symbolic. # TODO(fchollet): this check could be removed in Eager mode? @@ -2416,13 +2392,13 @@ class Model(Network): 'TensorFlow tensors. ' 'You passed: x=' + str(x) + '; y=' + str(y)) - if self.run_eagerly or is_x_iterator: + if is_dataset or context.executing_eagerly(): target_tensors = None else: # Handle target tensors if any passed. - if not isinstance(y, (list, tuple)): - y = [y] - target_tensors = [v for v in y if _is_symbolic_tensor(v)] + if not isinstance(y_input, (list, tuple)): + y_input = [y_input] + target_tensors = [v for v in y_input if _is_symbolic_tensor(v)] is_compile_called = True self.compile( optimizer=self.optimizer, @@ -2440,7 +2416,7 @@ class Model(Network): # Note: in this case, `any` and `all` are equivalent since we disallow # mixed symbolic/value inputs. if (not self.run_eagerly and is_build_called and is_compile_called and - not is_x_iterator and any(_is_symbolic_tensor(v) for v in all_inputs)): + not is_dataset and any(_is_symbolic_tensor(v) for v in all_inputs)): return [], [], [] # What follows is input validation and standardization to list format, @@ -2462,12 +2438,14 @@ class Model(Network): feed_input_shapes = self._feed_input_shapes # Standardize the inputs. - x = training_utils.standardize_input_data( - x, - feed_input_names, - feed_input_shapes, - check_batch_axis=False, # Don't enforce the batch size. - exception_prefix='input') + if not isinstance(x, (dataset_ops.DatasetV2, dataset_ops.DatasetV1)): + # TODO(fchollet): run static checks with dataset output shape(s). + x = training_utils.standardize_input_data( + x, + feed_input_names, + feed_input_shapes, + check_batch_axis=False, # Don't enforce the batch size. + exception_prefix='input') if y is not None: if not self._is_graph_network: @@ -2541,7 +2519,8 @@ class Model(Network): str(x[0].shape[0]) + ' samples') # If dictionary inputs were provided, we return a dictionary as well. - if dict_inputs: + if dict_inputs and not isinstance(x, (dataset_ops.DatasetV2, + dataset_ops.DatasetV1)): x = dict(zip(feed_input_names, x)) return x, y, sample_weights diff --git a/tensorflow/python/keras/engine/training_arrays.py b/tensorflow/python/keras/engine/training_arrays.py index 9887fe34a8..ab7d455bfa 100644 --- a/tensorflow/python/keras/engine/training_arrays.py +++ b/tensorflow/python/keras/engine/training_arrays.py @@ -23,6 +23,7 @@ import functools import numpy as np +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.eager import context from tensorflow.python.framework import errors from tensorflow.python.keras import backend as K @@ -146,15 +147,15 @@ def model_iteration(model, Arguments: model: Keras Model instance. - inputs: Either a list of arrays or a dictionary. - targets: List of target arrays. + inputs: Either a list or dictionary of arrays, or a dataset instance. + targets: List/dictionary of input arrays. sample_weights: Optional list of sample weight arrays. batch_size: Integer batch size or None if unknown. epochs: Number of times to iterate over the data verbose: Verbosity mode, 0, 1 or 2 callbacks: List of callbacks to be called during training - val_inputs: List of input arrays. - val_targets: List of target arrays. + val_inputs: Either a list or dictionary of arrays, or a dataset instance. + val_targets: List/dictionary of target arrays. val_sample_weights: Optional list of sample weight arrays. shuffle: Whether to shuffle the data at the beginning of each epoch concatenation of list the display names of the outputs of `f` and the @@ -186,6 +187,20 @@ def model_iteration(model, if 'steps' in kwargs: steps_per_epoch = kwargs['steps'] + # In case we are passed datasets, we extract symbolic tensors from them. + if isinstance(inputs, (dataset_ops.DatasetV2, dataset_ops.DatasetV1)): + inputs, targets, sample_weights = model._standardize_user_data( + inputs, + steps_name='steps_per_epoch', + steps=steps_per_epoch, + extract_tensors_from_dataset=True) + if isinstance(val_inputs, (dataset_ops.DatasetV2, dataset_ops.DatasetV1)): + val_inputs, val_targets, val_sample_weights = model._standardize_user_data( + val_inputs, + steps_name='validation_steps', + steps=validation_steps, + extract_tensors_from_dataset=True) + _validate_arguments(steps_per_epoch, validation_steps, kwargs) if mode == ModeKeys.TRAIN: _print_train_info(inputs, val_inputs, steps_per_epoch, verbose) diff --git a/tensorflow/python/keras/engine/training_dataset_test.py b/tensorflow/python/keras/engine/training_dataset_test.py index 646ce31909..40d42c13f0 100644 --- a/tensorflow/python/keras/engine/training_dataset_test.py +++ b/tensorflow/python/keras/engine/training_dataset_test.py @@ -25,7 +25,6 @@ import numpy as np from tensorflow.python import keras from tensorflow.python.data.ops import dataset_ops from tensorflow.python.eager import context -from tensorflow.python.framework import ops from tensorflow.python.framework import test_util as tf_test_util from tensorflow.python.keras import keras_parameterized from tensorflow.python.keras import metrics as metrics_module @@ -99,29 +98,6 @@ class TestTrainingWithDatasetIterators(keras_parameterized.TestCase): 'the `steps` argument'): model.predict(iterator, verbose=0) - @keras_parameterized.run_with_all_model_types - @keras_parameterized.run_all_keras_modes - def test_get_next_op_created_once(self): - model = testing_utils.get_small_mlp(1, 4, input_dim=3) - optimizer = RMSPropOptimizer(learning_rate=0.001) - loss = 'mse' - metrics = ['mae'] - model.compile(optimizer, loss, metrics=metrics, - run_eagerly=testing_utils.should_run_eagerly()) - - inputs = np.zeros((10, 3), np.float32) - targets = np.zeros((10, 4), np.float32) - dataset = dataset_ops.Dataset.from_tensor_slices((inputs, targets)) - dataset = dataset.repeat(100) - dataset = dataset.batch(10) - iterator = dataset_ops.make_one_shot_iterator(dataset) - - model.fit(iterator, epochs=1, steps_per_epoch=2, verbose=1) - # Finalize graph to make sure we are not appending another iterator - # get_next op in the graph. - ops.get_default_graph().finalize() - model.fit(iterator, epochs=1, steps_per_epoch=2, verbose=1) - @keras_parameterized.run_with_all_model_types @keras_parameterized.run_all_keras_modes def test_iterators_running_out_of_data(self): @@ -172,9 +148,6 @@ class TestTrainingWithDataset(keras_parameterized.TestCase): # Call fit with validation data model.fit(dataset, epochs=1, steps_per_epoch=2, verbose=0, validation_data=dataset, validation_steps=2) - # Finalize the graph to make sure new ops aren't added when calling on the - # same dataset - ops.get_default_graph().finalize() model.fit(dataset, epochs=1, steps_per_epoch=2, verbose=0, validation_data=dataset, validation_steps=2) @@ -292,6 +265,34 @@ class TestTrainingWithDataset(keras_parameterized.TestCase): model.fit(dataset, epochs=1, steps_per_epoch=2, verbose=1) + @keras_parameterized.run_all_keras_modes + def test_dataset_fit_correctness(self): + + class SumLayer(keras.layers.Layer): + + def build(self, _): + self.w = self.add_weight('w', ()) + + def call(self, inputs): + return keras.backend.sum(inputs) + self.w * 0 + + model = keras.Sequential([SumLayer(input_shape=(2,))]) + model.compile(RMSPropOptimizer(learning_rate=0.001), + loss='mae', + run_eagerly=testing_utils.should_run_eagerly()) + + inputs = np.zeros((40, 2), dtype=np.float32) + inputs[10:20, :] = 2 + inputs[20:30, :] = 1 + inputs[30:, :] = 4 + targets = np.zeros((40, 1), dtype=np.float32) + dataset = dataset_ops.Dataset.from_tensor_slices((inputs, targets)) + dataset = dataset.batch(10) + history = model.fit(dataset, + epochs=2, steps_per_epoch=2, verbose=1, shuffle=False) + self.assertListEqual(history.history['loss'], + [inputs[:20].sum() / 2, inputs[20:].sum() / 2]) + @tf_test_util.run_deprecated_v1 def test_dataset_input_shape_validation(self): with self.cached_session(): diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py index 5edf1de1b0..949f1e400d 100644 --- a/tensorflow/python/keras/engine/training_utils.py +++ b/tensorflow/python/keras/engine/training_utils.py @@ -31,6 +31,7 @@ from tensorflow.python.data.ops import readers 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 errors from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import tensor_spec @@ -1081,10 +1082,10 @@ def is_feature_layer(layer): def is_eager_dataset_or_iterator(data): - if context.executing_eagerly(): - if isinstance(data, (dataset_ops.DatasetV2, iterator_ops.EagerIterator)): - return True - return False + return context.executing_eagerly() and isinstance( + data, (dataset_ops.DatasetV1, + dataset_ops.DatasetV2, + iterator_ops.EagerIterator)) # pylint: disable=protected-access @@ -1199,6 +1200,64 @@ def assert_not_shuffled(dataset): raise ValueError('Could not assert that dataset is not shuffled.') +def is_dataset_or_iterator(data): + return isinstance(data, (dataset_ops.DatasetV1, + dataset_ops.DatasetV2, + iterator_ops.EagerIterator, + iterator_ops.Iterator)) + + +def extract_tensors_from_dataset(dataset): + """Extract a tuple of tensors `inputs, targets, sample_weight` from a dataset. + + Works only for graph mode. + + Arguments: + dataset: Dataset instance. + + Returns: + Tuple of tensors `x, y, weights`. `y` and `weights` entry may be None. + """ + iterator = dataset_ops.make_initializable_iterator(dataset) + K.get_session().run(iterator.initializer) + inputs, targets, sample_weight = unpack_iterator_input(iterator) + return inputs, targets, sample_weight + + +def unpack_iterator_input(iterator): + """Convert a dataset iterator to a tuple of tensors `x, y, sample_weights`. + + Arguments: + iterator: Instance of a dataset iterator. + + Returns: + Tuple of tensors `x, y, weights`. `y` and `weights` entry may be None. + """ + try: + next_element = iterator.get_next() + except errors.OutOfRangeError: + raise RuntimeError('Your dataset iterator ran out of data; ' + 'Make sure that your dataset can generate ' + 'required number of samples.') + + if isinstance(next_element, (list, tuple)): + if len(next_element) not in [2, 3]: + raise ValueError( + 'Please provide model inputs as a list or tuple of 2 or 3 ' + 'elements: (input, target) or (input, target, sample_weights) ' + 'Received %s' % next_element) + if len(next_element) == 2: + x, y = next_element + weights = None + else: + x, y, weights = next_element + else: + x = next_element + y = None + weights = None + return x, y, weights + + class ModelInputs(object): """Encapsulates model inputs. -- GitLab From e5319d26bb5b3fed5e3c9c865193db41f75cf958 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Tue, 8 Jan 2019 10:17:23 -0800 Subject: [PATCH 0346/2345] Add mean relative error v2 metric. PiperOrigin-RevId: 228352265 --- tensorflow/python/keras/metrics.py | 80 +++++++++++++++++++++++++ tensorflow/python/keras/metrics_test.py | 56 +++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index 9a2e057780..2c2700ffa3 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -51,6 +51,7 @@ from tensorflow.python.keras.utils.generic_utils import serialize_keras_object from tensorflow.python.keras.utils.generic_utils import to_list from tensorflow.python.keras.utils.losses_utils import squeeze_or_expand_dimensions from tensorflow.python.ops import array_ops +from tensorflow.python.ops import confusion_matrix from tensorflow.python.ops import init_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import nn @@ -346,6 +347,85 @@ class Mean(Metric): return math_ops.div_no_nan(self.total, self.count) +class MeanRelativeError(Mean): + """Computes the mean relative error by normalizing with the given values. + + This metric creates two local variables, `total` and `count` that are used to + compute the mean relative absolute error. This average is weighted by + `sample_weight`, and it is ultimately returned as `mean_relative_error`: + an idempotent operation that simply divides `total` by `count`. + + If `sample_weight` is `None`, weights default to 1. + Use `sample_weight` of 0 to mask values. + + Usage: + + ```python + m = tf.keras.metrics.MeanRelativeError(normalizer=[1, 3, 2, 3]) + m.update_state([1, 3, 2, 3], [2, 4, 6, 8]) + + # metric = mean(|y_pred - y_true| / normalizer) + # = mean([1, 1, 4, 5] / [1, 3, 2, 3]) = mean([1, 1/3, 2, 5/3]) + # = 5/4 = 1.25 + print('Final result: ', m.result().numpy()) # Final result: 1.25 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile( + 'sgd', + loss='mse', + metrics=[tf.keras.metrics.MeanRelativeError(normalizer=[1, 3])]) + ``` + """ + + def __init__(self, normalizer, name=None, dtype=None): + """Creates a `MeanRelativeError` instance. + + Args: + normalizer: The normalizer values with same shape as predictions. + name: (Optional) string name of the metric instance. + dtype: (Optional) data type of the metric result. + """ + super(MeanRelativeError, self).__init__(name=name, dtype=dtype) + normalizer = math_ops.cast(normalizer, self._dtype) + self.normalizer = normalizer + + def update_state(self, y_true, y_pred, sample_weight=None): + """Accumulates metric statistics. + + Args: + y_true: The ground truth values. + y_pred: The predicted values. + sample_weight: Optional weighting of each example. Defaults to 1. Can be a + `Tensor` whose rank is either 0, or the same rank as `y_true`, and must + be broadcastable to `y_true`. + + Returns: + Update op. + """ + y_true = math_ops.cast(y_true, self._dtype) + y_pred = math_ops.cast(y_pred, self._dtype) + y_pred, y_true, sample_weight = squeeze_or_expand_dimensions( + y_pred, y_true, sample_weight) + + y_pred, self.normalizer = confusion_matrix.remove_squeezable_dimensions( + y_pred, self.normalizer) + y_pred.shape.assert_is_compatible_with(y_pred.shape) + relative_errors = math_ops.div_no_nan( + math_ops.abs(y_true - y_pred), self.normalizer) + + return super(MeanRelativeError, self).update_state( + relative_errors, sample_weight=sample_weight) + + def get_config(self): + config = {'normalizer': self.normalizer} + base_config = super(MeanRelativeError, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + class MeanMetricWrapper(Mean): """Wraps a stateless metric function with the Mean metric.""" diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index a02b95bb46..4f8406f0e3 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -1603,6 +1603,62 @@ class KullbackLeiblerDivergenceTest(test.TestCase): self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) +@test_util.run_all_in_graph_and_eager_modes +class MeanRelativeErrorTest(test.TestCase): + + def test_config(self): + normalizer = constant_op.constant([1, 3], dtype=dtypes.float32) + mre_obj = metrics.MeanRelativeError(normalizer=normalizer, name='mre') + self.assertEqual(mre_obj.name, 'mre') + self.assertArrayNear(self.evaluate(mre_obj.normalizer), [1, 3], 1e-1) + + mre_obj2 = metrics.MeanRelativeError.from_config(mre_obj.get_config()) + self.assertEqual(mre_obj2.name, 'mre') + self.assertArrayNear(self.evaluate(mre_obj2.normalizer), [1, 3], 1e-1) + + def test_unweighted(self): + np_y_pred = np.asarray([2, 4, 6, 8], dtype=np.float32) + np_y_true = np.asarray([1, 3, 2, 3], dtype=np.float32) + expected_error = np.mean( + np.divide(np.absolute(np_y_pred - np_y_true), np_y_true)) + + y_pred = constant_op.constant(np_y_pred, shape=(1, 4), dtype=dtypes.float32) + y_true = constant_op.constant(np_y_true, shape=(1, 4)) + + mre_obj = metrics.MeanRelativeError(normalizer=y_true) + self.evaluate(variables.variables_initializer(mre_obj.variables)) + + result = mre_obj(y_true, y_pred) + self.assertAllClose(self.evaluate(result), expected_error, atol=1e-3) + + def test_weighted(self): + np_y_pred = np.asarray([2, 4, 6, 8], dtype=np.float32) + np_y_true = np.asarray([1, 3, 2, 3], dtype=np.float32) + sample_weight = np.asarray([0.2, 0.3, 0.5, 0], dtype=np.float32) + rel_errors = np.divide(np.absolute(np_y_pred - np_y_true), np_y_true) + expected_error = np.sum(rel_errors * sample_weight) + + y_pred = constant_op.constant(np_y_pred, dtype=dtypes.float32) + y_true = constant_op.constant(np_y_true) + + mre_obj = metrics.MeanRelativeError(normalizer=y_true) + self.evaluate(variables.variables_initializer(mre_obj.variables)) + + result = mre_obj( + y_true, y_pred, sample_weight=constant_op.constant(sample_weight)) + self.assertAllClose(self.evaluate(result), expected_error, atol=1e-3) + + def test_zero_normalizer(self): + y_pred = constant_op.constant([2, 4], dtype=dtypes.float32) + y_true = constant_op.constant([1, 3]) + + mre_obj = metrics.MeanRelativeError(normalizer=array_ops.zeros_like(y_true)) + self.evaluate(variables.variables_initializer(mre_obj.variables)) + + result = mre_obj(y_true, y_pred) + self.assertEqual(self.evaluate(result), 0) + + def _get_model(compile_metrics): model_layers = [ layers.Dense(3, activation='relu', kernel_initializer='ones'), -- GitLab From dff89fe60f5bbf1aa6c124fe295c9af3d9e3a868 Mon Sep 17 00:00:00 2001 From: Tong Shen Date: Tue, 8 Jan 2019 10:18:15 -0800 Subject: [PATCH 0347/2345] Support outside compilation in function call. PiperOrigin-RevId: 228352466 --- tensorflow/compiler/jit/BUILD | 1 + .../jit/encapsulate_subgraphs_pass_test.cc | 98 ++++++-- .../jit/extract_outside_compilation_pass.cc | 196 +++++++++++++--- .../jit/extract_outside_compilation_pass.h | 4 +- .../extract_outside_compilation_pass_test.cc | 216 +++++++++++++++++- tensorflow/compiler/tf2xla/graph_compiler.cc | 28 ++- .../compiler/tf2xla/side_effect_util.cc | 4 + 7 files changed, 475 insertions(+), 72 deletions(-) diff --git a/tensorflow/compiler/jit/BUILD b/tensorflow/compiler/jit/BUILD index b9a87ba296..ba9fa93654 100644 --- a/tensorflow/compiler/jit/BUILD +++ b/tensorflow/compiler/jit/BUILD @@ -634,6 +634,7 @@ tf_cc_test( "//tensorflow/core:framework_internal", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", + "//tensorflow/core:session_options", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", diff --git a/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc b/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc index 8617beec00..1f8ec09e19 100644 --- a/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc +++ b/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc @@ -25,6 +25,8 @@ limitations under the License. #include "tensorflow/compiler/jit/encapsulate_util.h" #include "tensorflow/compiler/jit/extract_outside_compilation_pass.h" #include "tensorflow/compiler/tf2xla/side_effect_util.h" +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/function.h" #include "tensorflow/core/framework/function_testlib.h" #include "tensorflow/core/framework/graph_to_functiondef.h" #include "tensorflow/core/graph/graph_constructor.h" @@ -32,6 +34,8 @@ limitations under the License. #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/platform/test.h" +#include "tensorflow/core/public/session_options.h" +#include "tensorflow/core/public/version.h" #include "tensorflow/core/util/equal_graph_def.h" namespace tensorflow { @@ -513,6 +517,18 @@ Status Encapsulate(GraphDef* graphdef, FunctionDefLibrary* library, s = PerformStaticShapeInferenceBeforeEncapsulation(graph.get()); if (!s.ok()) return s; + // Create FunctionLibraryRuntime. + SessionOptions session_options; + std::vector> devices; + TF_CHECK_OK(DeviceFactory::AddDevices( + session_options, "/job:localhost/replica:0/task:0", &devices)); + OptimizerOptions opts; + auto device_mgr = absl::make_unique(std::move(devices)); + auto pflr = absl::make_unique( + device_mgr.get(), Env::Default(), TF_GRAPH_DEF_VERSION, lib_def.get(), + opts, /*default_thread_pool=*/nullptr, /*cluster_flr=*/nullptr); + auto flr = pflr->GetFLR("/job:localhost/replica:0/task:0/cpu:0"); + std::unique_ptr graph_out; s = EncapsulateSubgraphsInFunctions( "_encapsulate", /*outside_compilation_attribute=*/"", *graph, @@ -538,7 +554,7 @@ Status Encapsulate(GraphDef* graphdef, FunctionDefLibrary* library, std::map{}}); } s = ExtractOutsideCompilation("_encapsulate", "_outside", clusters, - graph_out.get(), lib_def.get()); + graph_out.get(), flr, lib_def.get()); if (!s.ok()) return s; GraphDef graphdef_out; @@ -941,7 +957,9 @@ TEST(EncapsulateSubgraphsTest, OneFunctionOneOutside) { {"key", "host_compute_channel_F1_O1"}, {"shape_inference_graph", shape_inference_graph}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O1"}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}, {"c"}}, }, {{"f_0_retval_retval", "F:o:0"}}); @@ -1101,7 +1119,9 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { {"key", "host_compute_channel_F1_O2"}, {"shape_inference_graph", shape_inference_graph2}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O2"}}, + {"_outside_compilation_subgraph", "O2"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}, {"F"}}, {{"outside_compilation_O1_host_compute"}, "XlaHostCompute", @@ -1112,7 +1132,9 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { {"key", "host_compute_channel_F1_O1"}, {"shape_inference_graph", shape_inference_graph1}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O1"}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}, {"D"}}, }, {{"g_0_retval_retval", "outside_compilation_O2_host_compute:outputs:0"}, @@ -1244,7 +1266,9 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutside) { {"shape_inference_graph", NameAttrList()}, {"shapes", absl::Span({shape_proto_expected})}, - {"_outside_compilation_subgraph", "O1"}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}, {"D"}}, }, {{"e_0_retval_retval", "outside_compilation_O1_host_compute:outputs:0"}, @@ -1269,7 +1293,9 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutside) { {"shape_inference_graph", NameAttrList()}, {"shapes", absl::Span({shape_proto_expected})}, - {"_outside_compilation_subgraph", "O1"}}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, }, {{"g_0_retval_retval", "G:o:0"}, {"i_0_retval_retval", "I:o:0"}}); @@ -1397,7 +1423,9 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutsideDependencyFromOutside) { {"shape_inference_graph", NameAttrList()}, {"shapes", absl::Span({shape_proto_expected})}, - {"_outside_compilation_subgraph", "O1"}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}, {"D"}}, }, {{"f_0_retval_retval", "F:o:0"}}); @@ -1419,7 +1447,9 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutsideDependencyFromOutside) { {"shape_inference_graph", NameAttrList()}, {"shapes", absl::Span({shape_proto_expected})}, - {"_outside_compilation_subgraph", "O1"}}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, }, {{"i_0_retval_retval", "I:o:0"}}); @@ -1527,7 +1557,9 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationNoInputs) { {"shape_inference_graph", NameAttrList()}, {"shapes", absl::Span({shape_proto_expected})}, - {"_outside_compilation_subgraph", "O1"}}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, }, {{"f_0_retval_retval", "F:o:0"}}); @@ -1615,7 +1647,9 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationControlInput) { {"shape_inference_graph", NameAttrList()}, {"shapes", absl::Span({shape_proto_expected})}, - {"_outside_compilation_subgraph", "O1"}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}, {"D"}}, }, {{"f_0_retval_retval", "F:o:0"}}); @@ -1716,7 +1750,9 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationNoOutputs) { {"key", "host_compute_channel_F1_O1"}, {"shape_inference_graph", shape_inference_graph}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O1"}}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, }, {{"e_0_retval_retval", "outside_compilation_O1_host_compute:outputs:0"}, {"f_0_retval_retval", "F:o:0"}}); @@ -1821,7 +1857,9 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationControlOutput) { {"key", "host_compute_channel_F1_O1"}, {"shape_inference_graph", shape_inference_graph}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O1"}}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, }, {{"e_0_retval_retval", "outside_compilation_O1_host_compute:outputs:0"}, {"f_0_retval_retval", "F:o:0"}}); @@ -1949,7 +1987,9 @@ TEST(EncapsulateSubgraphsTest, {"key", "host_compute_channel_F1_O1"}, {"shape_inference_graph", shape_inference_graph1}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O1"}}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, {{"outside_compilation_O2_host_compute"}, "XlaHostCompute", {"F:o:0"}, @@ -1959,7 +1999,9 @@ TEST(EncapsulateSubgraphsTest, {"key", "host_compute_channel_F1_O2"}, {"shape_inference_graph", shape_inference_graph2}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O2"}}}, + {"_outside_compilation_subgraph", "O2"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, }, {{"e_0_retval_retval", "outside_compilation_O1_host_compute:outputs:0"}, {"h_0_retval_retval", "H:o:0"}}); @@ -2082,7 +2124,9 @@ TEST(EncapsulateSubgraphsTest, {"key", "host_compute_channel_F1_O2"}, {"shape_inference_graph", NameAttrList()}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O2"}}}, + {"_outside_compilation_subgraph", "O2"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, {{"outside_compilation_O1_host_compute"}, "XlaHostCompute", {"D:o:0"}, @@ -2092,7 +2136,9 @@ TEST(EncapsulateSubgraphsTest, {"key", "host_compute_channel_F1_O1"}, {"shape_inference_graph", shape_inference_graph}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O1"}}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, }, {{"e_0_retval_retval", "outside_compilation_O1_host_compute:outputs:0"}, {"h_0_retval_retval", "H:o:0"}}); @@ -2214,7 +2260,9 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationClusterDependency) { {"key", "host_compute_channel_F1_O1"}, {"shape_inference_graph", shape_inference_graph}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O1"}}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, {{"outside_compilation_O2_host_compute"}, "XlaHostCompute", {"D:o:0"}, @@ -2224,7 +2272,9 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationClusterDependency) { {"key", "host_compute_channel_F1_O2"}, {"shape_inference_graph", NameAttrList()}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O2"}}, + {"_outside_compilation_subgraph", "O2"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}, {}}, {{"outside_compilation_O3_host_compute"}, "XlaHostCompute", @@ -2235,7 +2285,9 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationClusterDependency) { {"key", "host_compute_channel_F1_O3"}, {"shape_inference_graph", NameAttrList()}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O3"}}, + {"_outside_compilation_subgraph", "O3"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}, {}}}, {{"e_0_retval_retval", "outside_compilation_O1_host_compute:outputs:0"}, {"h_0_retval_retval", "H:o:0"}}); @@ -2354,7 +2406,9 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationNoInputsOrOutputs) { {"key", "host_compute_channel_F1_O1"}, {"shape_inference_graph", shape_inference_graph}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O1"}}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}}, }, {{"e_0_retval_retval", "outside_compilation_O1_host_compute:outputs:0"}, {"f_0_retval_retval", "F:o:0"}}); @@ -2465,7 +2519,9 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationShapeInference) { {"key", "host_compute_channel_F1_O1"}, {"shape_inference_graph", shape_inference_graph}, {"shapes", absl::Span({})}, - {"_outside_compilation_subgraph", "O1"}}, + {"_outside_compilation_subgraph", "O1"}, + {"_xla_token_input_nodes", + absl::Span({"_xla_token_arg_node"})}}, {"c"}}, }, {{"f_0_retval_retval", "F:o:0"}}); diff --git a/tensorflow/compiler/jit/extract_outside_compilation_pass.cc b/tensorflow/compiler/jit/extract_outside_compilation_pass.cc index 8b01768c49..2a770c527b 100644 --- a/tensorflow/compiler/jit/extract_outside_compilation_pass.cc +++ b/tensorflow/compiler/jit/extract_outside_compilation_pass.cc @@ -30,6 +30,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor_shape.pb.h" #include "tensorflow/core/graph/algorithm.h" #include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/lib/gtl/cleanup.h" namespace tensorflow { @@ -308,6 +309,10 @@ xla::StatusOr BuildXlaHostComputeNodeDef( host_compute_builder.Attr("tpu_core", core); } + // Set input tokens. + host_compute_builder.Attr(kXlaTokenInputNodesAttrName, + std::vector{kXlaTokenArgNodeName}); + // Populate inputs. std::vector input_dtypes; TF_RETURN_IF_ERROR(GetNodeAttr(call_node->attrs(), "Tinputs", &input_dtypes)); @@ -398,8 +403,8 @@ Status ReplaceOrRemoveOutsideCompilationCallNode( } // Resets "device_ordinal" attr to placeholder value for related nodes -// (XlaRecvAtHost nodes; XlaSendFromHost nodes; If nodes containing -// XlaRecvAtHost/XlaSendFromHost). +// (XlaRecvAtHost nodes; XlaSendFromHost nodes; If/While/FuncCall nodes +// containing XlaRecvAtHost/XlaSendFromHost). Status ResetDeviceOrdinalToPlaceholderValue(Graph* g) { AttrValue device_ordinal_value; device_ordinal_value.set_placeholder("device_ordinal"); @@ -429,6 +434,10 @@ Status ResetDeviceOrdinalToPlaceholderValue(Graph* g) { n->ClearAttr(attr_name); n->AddAttr(attr_name, branch_func); } + } else if (HasNodeAttr(n->def(), "device_ordinal")) { + // Function call node containing outside compilation. + n->ClearAttr("device_ordinal"); + n->AddAttr("device_ordinal", device_ordinal_value); } else { return errors::Internal("Unknown node marked with ", kXlaHasHostTransferAttrName, ": ", @@ -1217,20 +1226,129 @@ Status BuildHostGraphForWhileNode( return Status::OK(); } +// Builds host graph for func call nodes. +Status BuildHostGraphForFuncCallNode(const string& func_call_node_name, + const string& xla_cluster_name, + const string& func_call_host_func_name, + const string& host_graph_func_name, + FunctionLibraryDefinition* fld) { + Graph host_graph(fld); + AttrValue device_ordinal_value; + device_ordinal_value.set_placeholder("device_ordinal"); + + // Step 1: add key placeholder node. + TF_ASSIGN_OR_RETURN( + Node * key_placeholder, + AddHostComputeKeyPlaceholder(xla_cluster_name, &host_graph)); + + // Step 2: rewrite `host_func_name`, replace key placeholder with an _Arg + // node. + TF_RETURN_IF_ERROR(ReplaceKeyPlaceholderWithArgNode( + xla_cluster_name, func_call_host_func_name, fld)); + + // Step 3: build a function call node with `host_func_name`, with + // `key_placeholder` as input. + NodeDefBuilder call_builder(absl::StrCat("oc_call_", func_call_node_name), + func_call_host_func_name, fld); + call_builder.Input(key_placeholder->name(), 0, DT_STRING); + call_builder.Attr("device_ordinal", device_ordinal_value); + call_builder.Attr(kXlaHasHostTransferAttrName, true); + NodeDef call_def; + TF_RETURN_IF_ERROR(call_builder.Finalize(&call_def)); + Status s; + Node* call_node = host_graph.AddNode(call_def, &s); + TF_RETURN_IF_ERROR(s); + host_graph.AddEdge(key_placeholder, 0, call_node, 0); + + // Convert `host_graph` to function, and add a "device_ordinal" attr. + FunctionDef oc_host_graph_fdef; + TF_RETURN_IF_ERROR(GraphToFunctionDef(host_graph, host_graph_func_name, + &oc_host_graph_fdef)); + if (fld->Find(host_graph_func_name)) { + TF_RETURN_IF_ERROR( + fld->ReplaceFunction(host_graph_func_name, oc_host_graph_fdef)); + } else { + TF_RETURN_IF_ERROR(fld->AddFunctionDef(oc_host_graph_fdef)); + } + + return Status::OK(); +} + Status ExtractOutsideCompilationForNodesWithAssociatedFunctions( Graph* g, const string& xla_cluster_attr_name, const string& outside_compilation_attr_name, const string& xla_cluster_name, - const std::map& host_compute_core, + const std::map& host_compute_core, FunctionLibraryRuntime* flr, FunctionLibraryDefinition* fld, std::vector* host_graphs, std::vector* shape_inference_graphs, bool* has_outside_compilation) { - std::vector if_nodes, while_nodes; + std::vector if_nodes, while_nodes, func_call_nodes; for (Node* n : g->nodes()) { if (n->type_string() == "If") { if_nodes.push_back(n); } else if (n->type_string() == "While") { while_nodes.push_back(n); + } else if (fld->Contains(n->type_string())) { + func_call_nodes.push_back(n); + } else if (n->type_string() == FunctionLibraryDefinition::kGradientOp) { + // Only gradient for user-defined function should be considered as + // function call node. + NameAttrList original_func; + TF_RETURN_IF_ERROR(GetNodeAttr( + n->def(), FunctionLibraryDefinition::kFuncAttr, &original_func)); + if (fld->Contains(original_func.name())) { + func_call_nodes.push_back(n); + } + } + } + + for (Node* n : func_call_nodes) { + // Extract outside compilation for the function call. + bool func_has_outside_compilation = false; + NameAttrList func; + func.set_name(n->type_string()); + typedef protobuf::Map AttrMap; + *func.mutable_attr() = AttrMap(n->attrs().begin(), n->attrs().end()); + string new_func_name = absl::StrCat(n->name(), "_oc"); + string host_func_name = absl::StrCat("oc_func_call_host_", n->name()); + TF_RETURN_IF_ERROR(ExtractOutsideCompilationForFunction( + xla_cluster_attr_name, outside_compilation_attr_name, xla_cluster_name, + func, new_func_name, host_func_name, host_compute_core, flr, fld, + shape_inference_graphs, &func_has_outside_compilation)); + + // If the function call does not have outside compilation, nothing to do. + if (!func_has_outside_compilation) { + continue; } + + *has_outside_compilation = true; + + // Change `n` to call the new function directly. + NodeDefBuilder replace_builder(n->name(), new_func_name, fld); + for (const Edge* e : n->in_edges()) { + if (e->IsControlEdge()) { + continue; + } + replace_builder.Input(e->src()->name(), e->src_output(), + e->src()->output_type(e->src_output())); + } + for (const auto& attr : n->attrs()) { + replace_builder.Attr(attr.first, attr.second); + } + NodeDef replace_def; + TF_RETURN_IF_ERROR(replace_builder.Finalize(&replace_def)); + TF_ASSIGN_OR_RETURN(Node * replace, ReplaceNode(g, n, replace_def)); + replace->AddAttr(kXlaTokenInputNodesAttrName, + std::vector{kXlaTokenArgNodeName}); + + // Build host side graph for the function call. + string oc_host_graph_name = + absl::StrCat("oc_func_host_graph_", replace->name()); + TF_RETURN_IF_ERROR( + BuildHostGraphForFuncCallNode(replace->name(), xla_cluster_name, + host_func_name, oc_host_graph_name, fld)); + + // Record the host graph. + host_graphs->push_back(oc_host_graph_name); } for (Node* n : if_nodes) { @@ -1251,12 +1369,12 @@ Status ExtractOutsideCompilationForNodesWithAssociatedFunctions( TF_RETURN_IF_ERROR(ExtractOutsideCompilationForFunction( xla_cluster_attr_name, outside_compilation_attr_name, xla_cluster_name, then_branch, then_branch_xla_func_name, then_branch_host_func_name, - host_compute_core, fld, shape_inference_graphs, + host_compute_core, flr, fld, shape_inference_graphs, &then_branch_has_outside_compilation)); TF_RETURN_IF_ERROR(ExtractOutsideCompilationForFunction( xla_cluster_attr_name, outside_compilation_attr_name, xla_cluster_name, else_branch, else_branch_xla_func_name, else_branch_host_func_name, - host_compute_core, fld, shape_inference_graphs, + host_compute_core, flr, fld, shape_inference_graphs, &else_branch_has_outside_compilation)); // If then/else branch do not have outside compilation, nothing to do. @@ -1316,12 +1434,12 @@ Status ExtractOutsideCompilationForNodesWithAssociatedFunctions( body_xla_func_name = absl::StrCat(body.name(), "_oc"); TF_RETURN_IF_ERROR(ExtractOutsideCompilationForFunction( xla_cluster_attr_name, outside_compilation_attr_name, xla_cluster_name, - cond, cond_xla_func_name, cond_host_func_name, host_compute_core, fld, - shape_inference_graphs, &cond_has_outside_compilation)); + cond, cond_xla_func_name, cond_host_func_name, host_compute_core, flr, + fld, shape_inference_graphs, &cond_has_outside_compilation)); TF_RETURN_IF_ERROR(ExtractOutsideCompilationForFunction( xla_cluster_attr_name, outside_compilation_attr_name, xla_cluster_name, - body, body_xla_func_name, body_host_func_name, host_compute_core, fld, - shape_inference_graphs, &body_has_outside_compilation)); + body, body_xla_func_name, body_host_func_name, host_compute_core, flr, + fld, shape_inference_graphs, &body_has_outside_compilation)); // If cond/body do not have outside compilation, nothing to do. if (!cond_has_outside_compilation && !body_has_outside_compilation) { @@ -1469,17 +1587,27 @@ Status ExtractOutsideCompilationForFunction( const string& outside_compilation_attr_name, const string& xla_cluster_name, const NameAttrList& func_name_attrs, const string& new_func_name, const string& host_graph_func_name, - const std::map& host_compute_core, + const std::map& host_compute_core, FunctionLibraryRuntime* flr, FunctionLibraryDefinition* fld, std::vector* shape_inference_graphs, bool* has_outside_compilation) { + // Convert the function to graph. const string& func_name = func_name_attrs.name(); - const FunctionDef* fdef = fld->Find(func_name); - if (!fdef) { - return errors::Internal("Cannot find function ", func_name); - } + FunctionLibraryRuntime::Handle handle; + TF_RETURN_IF_ERROR( + flr->Instantiate(func_name, AttrSlice(&func_name_attrs.attr()), &handle)); + Status ret_status = Status::OK(); + auto cleanup_handle = gtl::MakeCleanup([&]() { + auto s = flr->ReleaseHandle(handle); + if (!s.ok()) { + ret_status.Update(s); + } + }); + const FunctionBody* fbody = flr->GetFunctionBody(handle); + + // Check if we have outside compilation nodes. *has_outside_compilation = false; - for (auto& node_def : fdef->node_def()) { - if (HasNodeAttr(node_def, outside_compilation_attr_name)) { + for (Node* n : fbody->graph->nodes()) { + if (HasNodeAttr(n->def(), outside_compilation_attr_name)) { *has_outside_compilation = true; break; } @@ -1487,16 +1615,6 @@ Status ExtractOutsideCompilationForFunction( // We cannot early return here, because we might have outside compilation in // If/While function body. - // Convert the function to graph. - FunctionBody* fbody = nullptr; - TF_RETURN_IF_ERROR(FunctionDefToBodyHelper( - *fld->Find(func_name), AttrSlice(&func_name_attrs.attr()), fld, - [&](const string& op, const OpDef** sig) { - return fld->LookUpOpDef(op, sig); - }, - &fbody)); - std::unique_ptr fbody_deleter(fbody); - // Preprocess edges between different outside compilations. They will be // restored in `ConstructHostGraph()`. TF_RETURN_IF_ERROR(PreprocessEdgesBetweenOutsideCompilations( @@ -1553,16 +1671,11 @@ Status ExtractOutsideCompilationForFunction( TF_RETURN_IF_ERROR(ReplaceOrRemoveOutsideCompilationCallNode( graph_out.get(), n, host_compute_core)); } - if (VLOG_IS_ON(4)) { - dump_graph::DumpGraphToFile( - absl::StrCat("extract_outside_compilation_for_func_after_", func_name), - *graph_out, fld); - } // Handle nodes with associated functions. TF_RETURN_IF_ERROR(ExtractOutsideCompilationForNodesWithAssociatedFunctions( graph_out.get(), xla_cluster_attr_name, outside_compilation_attr_name, - xla_cluster_name, host_compute_core, fld, + xla_cluster_name, host_compute_core, flr, fld, &outside_compilation_host_graphs, shape_inference_graphs, has_outside_compilation)); @@ -1580,20 +1693,31 @@ Status ExtractOutsideCompilationForFunction( FunctionDef updated_fdef; TF_RETURN_IF_ERROR( GraphToFunctionDef(*graph_out, new_func_name, &updated_fdef)); + const FunctionDef* original_fdef = fld->Find(func_name); + if (original_fdef) { + for (const auto& attr : original_fdef->attr()) { + (*updated_fdef.mutable_attr())[attr.first] = attr.second; + } + } if (fld->Find(new_func_name)) { TF_RETURN_IF_ERROR(fld->ReplaceFunction(new_func_name, updated_fdef)); } else { TF_RETURN_IF_ERROR(fld->AddFunctionDef(updated_fdef)); } + if (VLOG_IS_ON(4)) { + dump_graph::DumpGraphToFile( + absl::StrCat("extract_outside_compilation_for_func_after_", func_name), + *graph_out, fld); + } - return Status::OK(); + return ret_status; } Status ExtractOutsideCompilation( const string& xla_cluster_attr_name, const string& outside_compilation_attr_name, const std::unordered_map& clusters, Graph* g, - FunctionLibraryDefinition* fld) { + FunctionLibraryRuntime* flr, FunctionLibraryDefinition* fld) { if (VLOG_IS_ON(4)) { dump_graph::DumpGraphToFile("extract_outside_compilation_before", *g, fld); } @@ -1610,7 +1734,7 @@ Status ExtractOutsideCompilation( TF_RETURN_IF_ERROR(ExtractOutsideCompilationForFunction( xla_cluster_attr_name, outside_compilation_attr_name, xla_cluster_name, func_name_attrs, func_name_attrs.name(), host_graph_func_name, - host_compute_core, fld, &shape_inference_graphs, + host_compute_core, flr, fld, &shape_inference_graphs, &has_outside_compilation)); TF_RETURN_IF_ERROR( ExpandHostGraphIntoMainGraph(g, fld, host_graph_func_name, n)); diff --git a/tensorflow/compiler/jit/extract_outside_compilation_pass.h b/tensorflow/compiler/jit/extract_outside_compilation_pass.h index e07e7c5dd0..d64cc2a103 100644 --- a/tensorflow/compiler/jit/extract_outside_compilation_pass.h +++ b/tensorflow/compiler/jit/extract_outside_compilation_pass.h @@ -89,7 +89,7 @@ Status ExtractOutsideCompilationForFunction( const string& outside_compilation_attr_name, const string& xla_cluster_name, const NameAttrList& func_name_attrs, const string& new_func_name, const string& host_graph_func_name, - const std::map& host_compute_core, + const std::map& host_compute_core, FunctionLibraryRuntime* flr, FunctionLibraryDefinition* fld, std::vector* shape_inference_graphs, bool* has_outside_compilation); @@ -101,7 +101,7 @@ Status ExtractOutsideCompilation( const string& xla_cluster_attr_name, const string& outside_compilation_attr_name, const std::unordered_map& clusters, Graph* g, - FunctionLibraryDefinition* fld); + FunctionLibraryRuntime* flr, FunctionLibraryDefinition* fld); } // namespace tensorflow diff --git a/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc b/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc index e9a89e34e0..7c3a24feff 100644 --- a/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc +++ b/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc @@ -23,6 +23,7 @@ limitations under the License. #include "tensorflow/cc/ops/standard_ops.h" #include "tensorflow/compiler/jit/encapsulate_util.h" #include "tensorflow/compiler/xla/test.h" +#include "tensorflow/core/common_runtime/device_factory.h" #include "tensorflow/core/common_runtime/function.h" #include "tensorflow/core/framework/common_shape_fns.h" #include "tensorflow/core/framework/function.h" @@ -31,6 +32,8 @@ limitations under the License. #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/framework/tensor_shape.pb.h" #include "tensorflow/core/platform/test.h" +#include "tensorflow/core/public/session_options.h" +#include "tensorflow/core/public/version.h" namespace tensorflow { @@ -222,7 +225,42 @@ TEST(RewriteOutsideCompilationSubgraphFnTest, ShapesInferred) { EXPECT_EQ(shapes[0].dim_size(), 1); } -TEST(ExtractOutsideCompilationForFunctionTest, Basic) { +class ExtractOutsideCompilationForFunctionTest : public ::testing::Test { + public: + void SetUp() override { + SessionOptions session_options; + std::vector> devices; + TF_CHECK_OK(DeviceFactory::AddDevices( + session_options, "/job:localhost/replica:0/task:0", &devices)); + device_mgr_ = absl::make_unique(std::move(devices)); + } + + Status ExtractOutsideCompilationTest( + const string &xla_cluster_attr_name, + const string &outside_compilation_attr_name, + const string &xla_cluster_name, const NameAttrList &func_name_attrs, + const string &new_func_name, const string &host_graph_func_name, + const std::map &host_compute_core, + FunctionLibraryDefinition *fld, + std::vector *shape_inference_graphs, + bool *has_outside_compilation) { + OptimizerOptions opts; + pflr_ = absl::make_unique( + device_mgr_.get(), Env::Default(), TF_GRAPH_DEF_VERSION, fld, opts, + /*default_thread_pool=*/nullptr, /*cluster_flr=*/nullptr); + auto flr = pflr_->GetFLR("/job:localhost/replica:0/task:0/cpu:0"); + return ExtractOutsideCompilationForFunction( + xla_cluster_attr_name, outside_compilation_attr_name, xla_cluster_name, + func_name_attrs, new_func_name, host_graph_func_name, host_compute_core, + flr, fld, shape_inference_graphs, has_outside_compilation); + } + + private: + std::unique_ptr device_mgr_; + std::unique_ptr pflr_; +}; + +TEST_F(ExtractOutsideCompilationForFunctionTest, Basic) { // Build the XLA computation func. // "const0" // "identity0" = "const0" (outside compilation cluster "0") @@ -256,7 +294,7 @@ TEST(ExtractOutsideCompilationForFunctionTest, Basic) { NameAttrList name_attrs; name_attrs.set_name("cluster"); *name_attrs.mutable_attr() = attrs; - TF_CHECK_OK(ExtractOutsideCompilationForFunction( + TF_CHECK_OK(ExtractOutsideCompilationTest( "_xla", "_oc", "cluster", name_attrs, "cluster_rewritten", "host_graph", host_compute_core, &fld, &shape_inference_graphs, &has_outside_compilation)); @@ -362,7 +400,7 @@ TEST(ExtractOutsideCompilationForFunctionTest, Basic) { } } -TEST(ExtractOutsideCompilationForFunctionTest, NoHostGraph) { +TEST_F(ExtractOutsideCompilationForFunctionTest, NoHostGraph) { // Build the XLA computation func. // "const0" FunctionDefLibrary fdl; @@ -384,7 +422,7 @@ TEST(ExtractOutsideCompilationForFunctionTest, NoHostGraph) { NameAttrList name_attrs; name_attrs.set_name("cluster"); *name_attrs.mutable_attr() = attrs; - TF_CHECK_OK(ExtractOutsideCompilationForFunction( + TF_CHECK_OK(ExtractOutsideCompilationTest( "_xla", "_oc", "cluster", name_attrs, "cluster_rewritten", "host_graph", host_compute_core, &fld, &shape_inference_graphs, &has_outside_compilation)); @@ -406,7 +444,7 @@ TEST(ExtractOutsideCompilationForFunctionTest, NoHostGraph) { EXPECT_EQ(host_graph->num_nodes(), 2); } -TEST(ExtractOutsideCompilationForFunctionTest, XlaHostComputeRemoved) { +TEST_F(ExtractOutsideCompilationForFunctionTest, XlaHostComputeRemoved) { // Build the XLA computation func. // "const0" // "const1" (outside compilation cluster "0") @@ -432,7 +470,7 @@ TEST(ExtractOutsideCompilationForFunctionTest, XlaHostComputeRemoved) { NameAttrList name_attrs; name_attrs.set_name("cluster"); *name_attrs.mutable_attr() = attrs; - TF_CHECK_OK(ExtractOutsideCompilationForFunction( + TF_CHECK_OK(ExtractOutsideCompilationTest( "_xla", "_oc", "cluster", name_attrs, "cluster_rewritten", "host_graph", host_compute_core, &fld, &shape_inference_graphs, &has_outside_compilation)); @@ -489,7 +527,7 @@ REGISTER_OP("XlaRecvFromHost") .Attr("key: string") .SetIsStateful(); -TEST(ExtractOutsideCompilationForFunctionTest, OutsideCompilationInIf) { +TEST_F(ExtractOutsideCompilationForFunctionTest, OutsideCompilationInIf) { // Build the XLA computation func. // "const0" (bool) // "const1" (int32) @@ -555,7 +593,7 @@ TEST(ExtractOutsideCompilationForFunctionTest, OutsideCompilationInIf) { NameAttrList name_attrs; name_attrs.set_name("cluster"); *name_attrs.mutable_attr() = attrs; - TF_CHECK_OK(ExtractOutsideCompilationForFunction( + TF_CHECK_OK(ExtractOutsideCompilationTest( "_xla", "_oc", "cluster", name_attrs, "cluster_rewritten", "host_graph", host_compute_core, &fld, &shape_inference_graphs, &has_outside_compilation)); @@ -651,7 +689,7 @@ TEST(ExtractOutsideCompilationForFunctionTest, OutsideCompilationInIf) { } } -TEST(ExtractOutsideCompilationForFunctionTest, OutsideCompilationInWhile) { +TEST_F(ExtractOutsideCompilationForFunctionTest, OutsideCompilationInWhile) { // Build the XLA computation func. // "const0" (bool) // "while0" (input = "const0", cond = "cond_fn", body = "body_fn") @@ -714,7 +752,7 @@ TEST(ExtractOutsideCompilationForFunctionTest, OutsideCompilationInWhile) { NameAttrList name_attrs; name_attrs.set_name("cluster"); *name_attrs.mutable_attr() = attrs; - TF_CHECK_OK(ExtractOutsideCompilationForFunction( + TF_CHECK_OK(ExtractOutsideCompilationTest( "_xla", "_oc", "cluster", name_attrs, "cluster_rewritten", "host_graph", host_compute_core, &fld, &shape_inference_graphs, &has_outside_compilation)); @@ -782,4 +820,162 @@ TEST(ExtractOutsideCompilationForFunctionTest, OutsideCompilationInWhile) { } } +TEST_F(ExtractOutsideCompilationForFunctionTest, OutsideCompilationInFunction) { + // Build the XLA computation func. + // "const0" (int32) + // "fn" (input = "const0") + FunctionDefLibrary fdl; + { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output arg = ops::_Arg(s.WithOpName("arg"), DT_INT32, 0); + Output identity = ops::Identity(s.WithOpName("identity"), arg); + ops::_Retval retval(s.WithOpName("retval"), identity, 0); + std::unique_ptr g(new Graph(OpRegistry::Global())); + TF_CHECK_OK(s.ToGraph(g.get())); + auto node_name_image = g->BuildNodeNameIndex(); + node_name_image["identity"]->AddAttr("_oc", "0"); + PartialTensorShape shape({2}); + node_name_image["identity"]->AddAttr( + kXlaInferredShapesAttrName, std::vector{shape}); + + FunctionDef *true_fn_fdef = fdl.add_function(); + TF_CHECK_OK(GraphToFunctionDef(*g, "fn", true_fn_fdef)); + } + FunctionLibraryDefinition fld(OpRegistry::Global(), fdl); + { + std::unique_ptr g(new Graph(&fld)); + + tensorflow::TensorProto tensor_proto; + tensor_proto.set_dtype(tensorflow::DT_INT32); + tensorflow::TensorShapeProto shape; + shape.add_dim()->set_size(2); + *tensor_proto.mutable_tensor_shape() = shape; + for (int i = 0; i < 2; ++i) { + tensor_proto.add_int_val(1); + } + NodeDef const_def; + TF_CHECK_OK(NodeDefBuilder("const", "Const") + .Attr("dtype", DT_INT32) + .Attr("value", tensor_proto) + .Finalize(&const_def)); + Status s; + Node *const_node = g->AddNode(const_def, &s); + TF_CHECK_OK(s); + + NodeDef fn_def; + TF_CHECK_OK(NodeDefBuilder("fn", "fn", &fld) + .Input("const", 0, DT_INT32) + .Finalize(&fn_def)); + Node *fn_node = g->AddNode(fn_def, &s); + TF_CHECK_OK(s); + g->AddEdge(const_node, 0, fn_node, 0); + + NodeDef ret_def; + TF_CHECK_OK(NodeDefBuilder("ret", "_Retval") + .Attr("index", 0) + .Attr("T", DT_INT32) + .Input("fn", 0, DT_INT32) + .Finalize(&ret_def)); + Node *ret_node = g->AddNode(ret_def, &s); + TF_CHECK_OK(s); + g->AddEdge(fn_node, 0, ret_node, 0); + + FunctionDef *xla_fdef = fdl.add_function(); + TF_CHECK_OK(GraphToFunctionDef(*g, "cluster", xla_fdef)); + TF_CHECK_OK(fld.AddFunctionDef(*xla_fdef)); + } + + protobuf::Map attrs; + std::map host_compute_core; + std::vector shape_inference_graphs; + bool has_outside_compilation; + NameAttrList name_attrs; + name_attrs.set_name("cluster"); + *name_attrs.mutable_attr() = attrs; + TF_CHECK_OK(ExtractOutsideCompilationTest( + "_xla", "_oc", "cluster", name_attrs, "cluster_rewritten", "host_graph", + host_compute_core, &fld, &shape_inference_graphs, + &has_outside_compilation)); + + // Check host graph. + { + FunctionBody *host_fbody = nullptr; + AttrValue device_ordinal_temp_value; + device_ordinal_temp_value.set_i(0); + protobuf::Map host_func_attrs; + host_func_attrs["device_ordinal"] = device_ordinal_temp_value; + TF_CHECK_OK(FunctionDefToBodyHelper( + *fld.Find("host_graph"), AttrSlice(&host_func_attrs), &fld, + [&](const string &op, const OpDef **sig) { + return fld.LookUpOpDef(op, sig); + }, + &host_fbody)); + std::unique_ptr host_fbody_deleter(host_fbody); + Graph *host_graph = host_fbody->graph; + auto node_name_index = host_graph->BuildNodeNameIndex(); + + // Verify we have call node for outside compilation in `fn`. + Node *call_node = node_name_index["oc_call_fn"]; + EXPECT_NE(call_node, nullptr); + + FunctionBody *call_fbody = nullptr; + TF_CHECK_OK(FunctionDefToBodyHelper( + *fld.Find("oc_func_call_host_fn"), AttrSlice(&host_func_attrs), &fld, + [&](const string &op, const OpDef **sig) { + return fld.LookUpOpDef(op, sig); + }, + &call_fbody)); + std::unique_ptr call_fbody_deleter(call_fbody); + + // Verify we have _XlaRecvAtHost and _XlaSendFromHost nodes. + bool has_recv = false, has_send = false; + for (Node *n : call_fbody->graph->nodes()) { + if (n->type_string() == "_XlaRecvAtHost") { + has_recv = true; + } else if (n->type_string() == "_XlaSendFromHost") { + has_send = true; + } + } + EXPECT_TRUE(has_recv); + EXPECT_TRUE(has_send); + } + + // Check XLA graph. + { + FunctionBody *xla_fbody = nullptr; + TF_CHECK_OK(FunctionDefToBodyHelper( + *fld.Find("cluster_rewritten"), AttrSlice(), &fld, + [&](const string &op, const OpDef **sig) { + return fld.LookUpOpDef(op, sig); + }, + &xla_fbody)); + std::unique_ptr xla_fbody_deleter(xla_fbody); + Graph *xla_graph = xla_fbody->graph; + auto node_name_index = xla_graph->BuildNodeNameIndex(); + + // Check that we have call node. + Node *fn_node = node_name_index["fn"]; + EXPECT_NE(fn_node, nullptr); + EXPECT_EQ(fn_node->type_string(), "fn_oc"); + + FunctionBody *call_fbody = nullptr; + TF_CHECK_OK(FunctionDefToBodyHelper( + *fld.Find("fn_oc"), AttrSlice(), &fld, + [&](const string &op, const OpDef **sig) { + return fld.LookUpOpDef(op, sig); + }, + &call_fbody)); + std::unique_ptr call_fbody_deleter(call_fbody); + + // Verify we have XlaHostCompute nodes. + bool has_hc = false; + for (Node *n : call_fbody->graph->nodes()) { + if (n->type_string() == "XlaHostCompute") { + has_hc = true; + } + } + EXPECT_TRUE(has_hc); + } +} + } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/graph_compiler.cc b/tensorflow/compiler/tf2xla/graph_compiler.cc index efb7574972..0c2bb02239 100644 --- a/tensorflow/compiler/tf2xla/graph_compiler.cc +++ b/tensorflow/compiler/tf2xla/graph_compiler.cc @@ -22,6 +22,7 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/dump_graph.h" #include "tensorflow/compiler/tf2xla/literal_util.h" #include "tensorflow/compiler/tf2xla/shape_util.h" +#include "tensorflow/compiler/tf2xla/side_effect_util.h" #include "tensorflow/compiler/tf2xla/type_util.h" #include "tensorflow/compiler/tf2xla/xla_compiler.h" #include "tensorflow/compiler/tf2xla/xla_context.h" @@ -191,6 +192,9 @@ Status GraphCompiler::CompileFunctionalNode(Node* n, // into the functions. XlaOpKernelContext xla_op_context(op_context); + XlaContext& context = XlaContext::Get(op_context); + auto* b = context.builder(); + XlaCompiler* compiler = xla_op_context.compiler(); NameAttrList func; @@ -219,8 +223,12 @@ Status GraphCompiler::CompileFunctionalNode(Node* n, TF_RETURN_IF_ERROR( PrepareArguments(&xla_op_context, graph.get(), expressions, &arguments)); + bool add_token_input_output = + HasNodeAttr(n->def(), kXlaTokenInputNodesAttrName); + XlaCompiler::CompileOptions compile_options; compile_options.is_entry_computation = false; + compile_options.add_token_input_output = add_token_input_output; XlaCompiler::CompilationResult result; TF_RETURN_IF_ERROR( compiler->CompileFunction(compile_options, func, arguments, &result)); @@ -234,9 +242,19 @@ Status GraphCompiler::CompileFunctionalNode(Node* n, } handles.push_back(expressions[i]->handle()); } - - XlaContext& context = XlaContext::Get(op_context); - auto* b = context.builder(); + if (add_token_input_output) { + std::vector token_input_nodes; + TF_RETURN_IF_ERROR( + GetNodeAttr(n->def(), kXlaTokenInputNodesAttrName, &token_input_nodes)); + std::vector token_inputs; + for (const string& node_name : token_input_nodes) { + auto token_or = compiler->GetNodeToken(node_name); + TF_RETURN_IF_ERROR(token_or.status()); + token_inputs.push_back(token_or.ConsumeValueOrDie()); + } + xla::XlaOp token_input = xla::AfterAll(b, token_inputs); + handles.push_back(token_input); + } auto output_handle = xla::Call(b, *result.computation, handles); // The output handle of `Call` computation is a tuple type. Unzip it so @@ -251,6 +269,10 @@ Status GraphCompiler::CompileFunctionalNode(Node* n, ++computation_output; } } + if (add_token_input_output) { + TF_RETURN_IF_ERROR(compiler->SetNodeToken( + n->name(), xla::GetTupleElement(output_handle, computation_output))); + } return b->first_error(); } diff --git a/tensorflow/compiler/tf2xla/side_effect_util.cc b/tensorflow/compiler/tf2xla/side_effect_util.cc index a9144cfc4f..412f31adbb 100644 --- a/tensorflow/compiler/tf2xla/side_effect_util.cc +++ b/tensorflow/compiler/tf2xla/side_effect_util.cc @@ -58,6 +58,10 @@ Status SetDeviceOrdinalAttributeForNode(Node* node, int device_ordinal) { node->ClearAttr(attr_name); node->AddAttr(attr_name, branch_func); } + } else if (HasNodeAttr(node->def(), "device_ordinal")) { + // Function call node containing outside compilation. + node->ClearAttr("device_ordinal"); + node->AddAttr("device_ordinal", device_ordinal); } else { return errors::Internal("Unknown node type to set 'device_ordinal': ", node->DebugString()); -- GitLab From 1b2e900f3cadc1a61d0c0a65c239e53afba7a7ec Mon Sep 17 00:00:00 2001 From: Michael Kuperstein Date: Tue, 8 Jan 2019 10:19:40 -0800 Subject: [PATCH 0348/2345] [XLA] Start moving over the DynamicSlice / DynamicUpdateSlice API * Updated operation semantics document for new syntax. * Added XlaBuilder APIs. * Marked some of the old APIs as deprecated. * Moved over a couple of slice tests to the new API. Other uses in XLA tests and in TF2XLA will be moved over incrementally. PiperOrigin-RevId: 228352804 --- tensorflow/compiler/xla/client/xla_builder.cc | 62 +++++++++++++++++++ tensorflow/compiler/xla/client/xla_builder.h | 30 +++++++-- .../compiler/xla/g3doc/operation_semantics.md | 59 +++++++++--------- .../compiler/xla/service/hlo_instruction.h | 2 + 4 files changed, 121 insertions(+), 32 deletions(-) diff --git a/tensorflow/compiler/xla/client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_builder.cc index 433033cae6..328abbc7b9 100644 --- a/tensorflow/compiler/xla/client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_builder.cc @@ -705,6 +705,34 @@ XlaOp XlaBuilder::DynamicSlice(const XlaOp& operand, const XlaOp& start_indices, }); } +XlaOp XlaBuilder::DynamicSlice(const XlaOp& operand, + absl::Span start_indices, + absl::Span slice_sizes) { + return ReportErrorOrReturn([&]() -> StatusOr { + HloInstructionProto instr; + + TF_ASSIGN_OR_RETURN(const Shape& operand_shape, GetShape(operand)); + std::vector start_indices_shape_ptrs; + TF_ASSIGN_OR_RETURN(const auto& start_indices_shapes, + GetOperandShapes(start_indices)); + absl::c_transform(start_indices_shapes, + std::back_inserter(start_indices_shape_ptrs), + [](const Shape& shape) { return &shape; }); + TF_ASSIGN_OR_RETURN(Shape shape, + ShapeInference::InferDynamicSliceShape( + operand_shape, start_indices_shapes, slice_sizes)); + *instr.mutable_shape() = shape.ToProto(); + + for (int64 size : slice_sizes) { + instr.add_dynamic_slice_sizes(size); + } + + std::vector operands = {operand}; + operands.insert(operands.end(), start_indices.begin(), start_indices.end()); + return AddInstruction(std::move(instr), HloOpcode::kDynamicSlice, operands); + }); +} + XlaOp XlaBuilder::DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, const XlaOp& start_indices) { return ReportErrorOrReturn([&]() -> StatusOr { @@ -724,6 +752,31 @@ XlaOp XlaBuilder::DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, }); } +XlaOp XlaBuilder::DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, + absl::Span start_indices) { + return ReportErrorOrReturn([&]() -> StatusOr { + HloInstructionProto instr; + + TF_ASSIGN_OR_RETURN(const Shape& operand_shape, GetShape(operand)); + TF_ASSIGN_OR_RETURN(const Shape& update_shape, GetShape(update)); + std::vector start_indices_shape_ptrs; + TF_ASSIGN_OR_RETURN(const auto& start_indices_shapes, + GetOperandShapes(start_indices)); + absl::c_transform(start_indices_shapes, + std::back_inserter(start_indices_shape_ptrs), + [](const Shape& shape) { return &shape; }); + TF_ASSIGN_OR_RETURN(Shape shape, + ShapeInference::InferDynamicUpdateSliceShape( + operand_shape, update_shape, start_indices_shapes)); + *instr.mutable_shape() = shape.ToProto(); + + std::vector operands = {operand, update}; + operands.insert(operands.end(), start_indices.begin(), start_indices.end()); + return AddInstruction(std::move(instr), HloOpcode::kDynamicUpdateSlice, + operands); + }); +} + XlaOp XlaBuilder::ConcatInDim(absl::Span operands, int64 dimension) { return ReportErrorOrReturn([&]() -> StatusOr { @@ -2751,12 +2804,21 @@ XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices, absl::Span slice_sizes) { return operand.builder()->DynamicSlice(operand, start_indices, slice_sizes); } +XlaOp DynamicSlice(const XlaOp& operand, absl::Span start_indices, + absl::Span slice_sizes) { + return operand.builder()->DynamicSlice(operand, start_indices, slice_sizes); +} XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, const XlaOp& start_indices) { return operand.builder()->DynamicUpdateSlice(operand, update, start_indices); } +XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, + absl::Span start_indices) { + return operand.builder()->DynamicUpdateSlice(operand, update, start_indices); +} + XlaOp ConcatInDim(XlaBuilder* builder, absl::Span operands, int64 dimension) { return builder->ConcatInDim(operands, dimension); diff --git a/tensorflow/compiler/xla/client/xla_builder.h b/tensorflow/compiler/xla/client/xla_builder.h index ebef8e0879..8908d172fa 100644 --- a/tensorflow/compiler/xla/client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_builder.h @@ -359,11 +359,18 @@ class XlaBuilder { XlaOp SliceInDim(const XlaOp& operand, int64 start_index, int64 limit_index, int64 stride, int64 dimno); + ABSL_DEPRECATED("Use span-of-indices form instead") XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices, absl::Span slice_sizes); + XlaOp DynamicSlice(const XlaOp& operand, + absl::Span start_indices, + absl::Span slice_sizes); + ABSL_DEPRECATED("Use span-of-indices form instead") XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, const XlaOp& start_indices); + XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, + absl::Span start_indices); XlaOp ConcatInDim(absl::Span operands, int64 dimension); @@ -875,9 +882,14 @@ class XlaBuilder { friend XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices, absl::Span slice_sizes); + friend XlaOp DynamicSlice(const XlaOp& operand, + absl::Span start_indices, + absl::Span slice_sizes); friend XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, const XlaOp& start_indices); + friend XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, + absl::Span start_indices); friend XlaOp ConcatInDim(XlaBuilder* builder, absl::Span operands, int64 dimension); @@ -1319,10 +1331,15 @@ XlaOp SliceInDim(const XlaOp& operand, int64 start_index, int64 limit_index, // The size of the slice in each dimension is passed in 'slice_sizes', // which specify the end point of exclusive slice intervals in each // dimension [start, start + size). -// The shape of 'start_indices' must be rank == 1, with dimension size -// equal to the rank of the 'operand'. +// The shape of each element of 'start_indices' must be scalar, with the span +// size equal to the rank of the 'operand'. All elements of 'start_indices' must +// have the same shape. // Slice index calculations are computed modulo input dimension sizes to // prevent dynamic start indices from generating out-of-bound array accesses. +XlaOp DynamicSlice(const XlaOp& operand, absl::Span start_indices, + absl::Span slice_sizes); + +ABSL_DEPRECATED("Use span-of-indices form instead") XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices, absl::Span slice_sizes); @@ -1338,10 +1355,15 @@ XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices, // [4 5 6] => DynamicUpdateslice(data, update, start) => [4 10 11] // [7 8 9] [7 8 9 ] // -// The shape of 'start_indices' must be rank == 1, with dimension size -// equal to the rank of the 'operand'. +// The shape of each element of 'start_indices' must be scalar, with the span +// size equal to the rank of the 'operand'. All elements of 'start_indices' must +// have the same shape. // Slice index calculations are computed modulo update dimension sizes to // prevent dynamic start indices from generating out-of-bound array accesses. +XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, + absl::Span start_indices); + +ABSL_DEPRECATED("Use span-of-indices form instead") XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update, const XlaOp& start_indices); diff --git a/tensorflow/compiler/xla/g3doc/operation_semantics.md b/tensorflow/compiler/xla/g3doc/operation_semantics.md index 78d4617017..7377ed729b 100644 --- a/tensorflow/compiler/xla/g3doc/operation_semantics.md +++ b/tensorflow/compiler/xla/g3doc/operation_semantics.md @@ -944,21 +944,21 @@ dimension: [start, start + size). The shape of `start_indices` must be rank == `DynamicSlice(operand, start_indices, size_indices)` -| Arguments | Type | Semantics | -| --------------- | ------------------- | ----------------------------------- | -| `operand` | `XlaOp` | N dimensional array of type T | -| `start_indices` | `XlaOp` | Rank 1 array of N integers | -: : : containing the starting indices of : -: : : the slice for each dimension. Value : -: : : must be greater than or equal to : -: : : zero. : -| `size_indices` | `ArraySlice` | List of N integers containing the | -: : : slice size for each dimension. Each : -: : : value must be strictly greater than : -: : : zero, and start + size must be less : -: : : than or equal to the size of the : -: : : dimension to avoid wrapping modulo : -: : : dimension size. : +| Arguments | Type | Semantics | +| --------------- | --------------------- | ---------------------------------- | +| `operand` | `XlaOp` | N dimensional array of type T | +| `start_indices` | sequence of N `XlaOp` | List of N scalar integers | +: : : containing the starting indices of : +: : : the slice for each dimension. : +: : : Value must be greater than or : +: : : equal to zero. : +| `size_indices` | `ArraySlice` | List of N integers containing the | +: : : slice size for each dimension. : +: : : Each value must be strictly : +: : : greater than zero, and start + : +: : : size must be less than or equal to : +: : : the size of the dimension to avoid : +: : : wrapping modulo dimension size. : The effective slice indices are computed by applying the following transformation for each index `i` in `[1, N)` before performing the slice: @@ -1009,19 +1009,22 @@ the rank of `operand`. `DynamicUpdateSlice(operand, update, start_indices)` -| Arguments | Type | Semantics | -| --------------- | ------- | ------------------------------------------------ | -| `operand` | `XlaOp` | N dimensional array of type T | -| `update` | `XlaOp` | N dimensional array of type T containing the | -: : : slice update. Each dimension of update shape : -: : : must be strictly greater than zero, and start + : -: : : update must be less than or equal to the operand : -: : : size for each dimension to avoid generating : -: : : out-of-bounds update indices. : -| `start_indices` | `XlaOp` | Rank 1 array of N integers containing the | -: : : starting indices of the slice for each : -: : : dimension. Value must be greater than or equal : -: : : to zero. : +| Arguments | Type | Semantics | +| --------------- | --------------------- | ---------------------------------- | +| `operand` | `XlaOp` | N dimensional array of type T | +| `update` | `XlaOp` | N dimensional array of type T | +: : : containing the slice update. Each : +: : : dimension of update shape must be : +: : : strictly greater than zero, and : +: : : start + update must be less than : +: : : or equal to the operand size for : +: : : each dimension to avoid generating : +: : : out-of-bounds update indices. : +| `start_indices` | sequence of N `XlaOp` | List of N scalar integers | +: : : containing the starting indices of : +: : : the slice for each dimension. : +: : : Value must be greater than or : +: : : equal to zero. : The effective slice indices are computed by applying the following transformation for each index `i` in `[1, N)` before performing the slice: diff --git a/tensorflow/compiler/xla/service/hlo_instruction.h b/tensorflow/compiler/xla/service/hlo_instruction.h index 2827eed0df..789ace6a26 100644 --- a/tensorflow/compiler/xla/service/hlo_instruction.h +++ b/tensorflow/compiler/xla/service/hlo_instruction.h @@ -556,6 +556,7 @@ class HloInstruction { // Creates a slice instruction, where the first operand is sliced by // start indices specified in the second operand, and by size specified in // 'slice_sizes'. + ABSL_DEPRECATED("Use span-of-indices form instead") static std::unique_ptr CreateDynamicSlice( const Shape& shape, HloInstruction* operand, HloInstruction* start_indices, absl::Span slice_sizes); @@ -567,6 +568,7 @@ class HloInstruction { // Creates a dynamic update slice instruction, which updates a slice // of 'operand' with 'update' and 'start_indices'. + ABSL_DEPRECATED("Use span-of-indices form instead") static std::unique_ptr CreateDynamicUpdateSlice( const Shape& shape, HloInstruction* operand, HloInstruction* update, HloInstruction* start_indices); -- GitLab From 3b6f11930a3c6fe91c43fc92aeb303e8cefffc5d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 10:22:53 -0800 Subject: [PATCH 0349/2345] Automated rollback of commit ec569e692435fb68191553fd7ffa5b633fdf1ef2 PiperOrigin-RevId: 228353495 --- tensorflow/core/grappler/optimizers/BUILD | 4 - .../optimizers/dependency_optimizer.cc | 456 +++++++++++------- .../optimizers/dependency_optimizer.h | 24 +- .../optimizers/dependency_optimizer_test.cc | 9 +- 4 files changed, 291 insertions(+), 202 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 5a328a91c5..7e29cee86a 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -303,18 +303,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", ], ) diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc index 9bceb6b906..7fee3ae9d5 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc @@ -18,14 +18,10 @@ limitations under the License. #include #include -#include "absl/container/flat_hash_map.h" -#include "absl/container/flat_hash_set.h" #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" @@ -42,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 @@ -67,10 +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 @@ -81,23 +79,20 @@ bool DependencyOptimizer::SafeToRemoveIdentity(const NodeDef& node) const { // Don't turn Identity nodes following Switch into NoOp or remove them // if it requires anchoring a control dependencies the Switch node, which // is not valid. - MutableGraphView::OutputPort control_port(const_cast(&node), - Graph::kControlSlot); - if (!graph_view_->GetFanout(control_port).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; - for (auto consumer : - graph_view_->GetFanouts(node, /*include_controlled_nodes=*/true)) { - if (node_has_multiple_inputs && IsMerge(*consumer.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(*consumer.node, - {node.name(), Graph::kControlSlot})) { - return false; + for (const string& consumer_input : consumer->input()) { + if (consumer_input == AsControlDependency(node.name())) { + return false; + } } } } @@ -131,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; } @@ -139,56 +134,61 @@ bool DependencyOptimizer::SafeToConvertToNoOp(const NodeDef& node) const { } int DependencyOptimizer::NumEdgesIfBypassed( - const NodeDef& node, int num_fanins, - const absl::flat_hash_set& fanout_edges) const { + const NodeDef& node, const std::vector& output_nodes) const { const bool is_multi_input_identity_n = IsIdentityN(node) && !IsIdentityNSingleInput(node); - const int num_fanouts = fanout_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 = - graph_view_->NumFanins(node, /*include_controlling_nodes=*/false); - num_edges_if_bypassed += num_non_controlling_fanins; - num_edges_if_bypassed += - (num_fanins - num_non_controlling_fanins) * num_fanouts; - - for (const auto& fanout : fanout_edges) { - if (fanout.dst.port_id == Graph::kControlSlot) { - num_edges_if_bypassed += num_fanins; + 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_fanouts; + return num_inputs * num_outputs; } } bool DependencyOptimizer::BypassingNodeIsBeneficial( - const NodeDef& node, - const absl::flat_hash_set& fanins, - 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_outputs = fanout_edges.size(); - const int num_inputs = fanins.size(); + const int num_outputs = output_nodes.size(); + const int num_inputs = node.input_size(); - if (NumEdgesIfBypassed(node, num_inputs, fanout_edges) > - num_inputs + num_outputs) { + 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_inputs == 1 && num_outputs > 1 && - fanins.begin()->node->device() != node.device()) || + input_nodes[0]->device() != node.device()) || (num_inputs > 1 && num_outputs == 1 && - fanout_edges.begin()->dst.node->device() != node.device())) { + output_nodes[0]->device() != node.device())) { return false; } @@ -197,12 +197,12 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( // cost before and after. const string& node_dev = node.device(); int num_cross_in = 0; - for (const auto& fanin : fanins) { - num_cross_in += static_cast(fanin.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 : fanout_edges) { - num_cross_out += static_cast(fanout.dst.node->device() != node_dev); + for (NodeDef* output_node : output_nodes) { + num_cross_out += static_cast(output_node->device() != node_dev); } if ((is_identity || is_multi_input_identity_n) && num_cross_in > 0 && @@ -216,10 +216,10 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( // 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 : fanins) { - for (const auto& fanout : fanout_edges) { + for (NodeDef* input_node : input_nodes) { + for (NodeDef* output_node : output_nodes) { num_cross_after += - static_cast(fanin.node->device() != fanout.dst.node->device()); + static_cast(input_node->device() != output_node->device()); } } if (num_cross_after > num_cross_before) { @@ -228,32 +228,45 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( return true; } -void DependencyOptimizer::OptimizeNode(NodeDef* node, - SetVector* nodes_to_simplify, - std::set* nodes_to_delete) { - const string node_name = 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); + 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) && - graph_view_->NumFanins(*node, /*include_controlling_nodes=*/true) == 0) { - for (const auto& fanout : - graph_view_->GetFanouts(*node, /*include_controlled_nodes=*/true)) { - if (graph_view_->RemoveFanin(fanout.node->name(), - {node_name, Graph::kControlSlot})) { - nodes_to_simplify->PushBack(fanout.node); + 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; } @@ -264,23 +277,33 @@ void DependencyOptimizer::OptimizeNode(NodeDef* node, << ") 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. - int num_non_controlling_fanins = - graph_view_->NumFanins(*node, /*include_controlling_nodes=*/false); - if (num_non_controlling_fanins > 0) { - absl::flat_hash_set non_controlling_fanins( - node->input().begin(), - node->input().begin() + num_non_controlling_fanins); - graph_view_->RemoveAllFanins(node_name, - /*keep_controlling_fanins=*/true); - for (const string& fanin : non_controlling_fanins) { - TensorId tensor_id = ParseTensorName(fanin); - graph_view_->AddControllingFanin(node_name, tensor_id); - nodes_to_simplify->PushBack(graph_view_->GetNode(tensor_id.node())); + 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; } node->set_op("NoOp"); node->clear_attr(); - nodes_to_simplify->PushBack(node); + nodes_to_simplify->PushBack(node_to_idx_[node]); return; } @@ -334,80 +357,110 @@ void DependencyOptimizer::OptimizeNode(NodeDef* node, if (is_noop || ((is_identity || is_multi_input_identity) && SafeToRemoveIdentity(*node))) { - auto fanins = - graph_view_->GetFanins(*node, /*include_controlling_nodes=*/true); - auto fanout_edges = - graph_view_->GetFanoutEdges(*node, /*include_controlled_edges=*/true); + 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); + } - if (!BypassingNodeIsBeneficial(*node, fanins, fanout_edges)) { + if (!BypassingNodeIsBeneficial(*node, input_nodes, output_nodes)) { return; } - int num_non_controlling_fanins = - graph_view_->NumFanins(*node, /*include_controlling_nodes=*/false); VLOG(1) << "***** Rerouting input around\n" << node->DebugString(); // Now remove the node and re-wire its inputs to its outputs. - for (auto fanout_edge : fanout_edges) { + for (auto consumer : output_nodes) { bool updated_consumer = false; - NodeDef* consumer = fanout_edge.dst.node; VLOG(1) << "consumer before:\n" << consumer->DebugString(); - if (fanout_edge.src.port_id == Graph::kControlSlot) { - updated_consumer = graph_view_->RemoveFanin( - consumer->name(), {node_name, Graph::kControlSlot}); - if (updated_consumer) { - // Add all non controlling fanins of node as controlling fanins to - // consumer. - for (int i = 0; i < num_non_controlling_fanins; ++i) { - TensorId input = ParseTensorName(node->input(i)); - if (graph_view_->AddControllingFanin(consumer->name(), input)) { - nodes_to_simplify->PushBack(graph_view_->GetNode(input.node())); + 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; + } } - } else { - updated_consumer = graph_view_->UpdateFanin( - consumer->name(), {node_name, fanout_edge.src.port_id}, - ParseTensorName(node->input(fanout_edge.src.port_id))); } + // Remove dependency on node from consumer. + updated_consumer |= RemoveInput(consumer, AsControlDependency(node_name), + node_map_.get()); if (updated_consumer) { - // Forward all controlling fanins of node to consumer. - for (int i = num_non_controlling_fanins; i < node->input_size(); ++i) { - TensorId input = ParseTensorName(node->input(i)); - if (graph_view_->AddFanin(consumer->name(), input)) { - nodes_to_simplify->PushBack(graph_view_->GetNode(input.node())); - } - } - nodes_to_simplify->PushBack(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()) { // 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. - graph_view_->RemoveAllFanins(node_name, - /*keep_controlling_fanins=*/false); + node_map_->RemoveInputs(node_name); + node->clear_input(); } } } +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; - std::set nodes_to_delete; - for (int i = 0; i < graph_view_->graph()->node_size(); ++i) { - NodeDef* node = graph_view_->graph()->mutable_node(i); - if (IsNoOp(*node) || IsIdentity(*node) || IsIdentityN(*node) || - IsConstant(*node) || SafeToConvertToNoOp(*node)) { - nodes_to_simplify.PushBack(node); + 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(i); } } while (!nodes_to_simplify.Empty()) { - NodeDef* 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->name()) != - nodes_to_delete.end()) { + while (nodes_to_delete.find(node_to_simplify) != nodes_to_delete.end()) { node_to_simplify = nodes_to_simplify.PopBack(); } OptimizeNode(node_to_simplify, &nodes_to_simplify, &nodes_to_delete); @@ -415,17 +468,17 @@ Status DependencyOptimizer::OptimizeDependencies() { if (fetch_nodes_known_) { VLOG(1) << "Deleted " << nodes_to_delete.size() << " out of " - << graph_view_->graph()->node_size() << " nodes."; - 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. @@ -434,20 +487,20 @@ Status DependencyOptimizer::TransitiveReduction() { 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 (int input_slot = 0; input_slot < node.input_size(); ++input_slot) { const string& input = node.input(input_slot); - const NodeDef* input_node = graph_view_->GetNode(NodeName(input)); + 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; @@ -513,12 +566,16 @@ Status DependencyOptimizer::TransitiveReduction() { 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(); + 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 TensorId tensor_id = ParseTensorName(target_node.input(input_slot)); - graph_view_->RemoveFanin(target_node_name, tensor_id); + 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; } } @@ -527,17 +584,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 +// A->C', B->C', C' -> C void DependencyOptimizer::GroupCrossDeviceControlEdges() { - const int num_nodes = graph_view_->graph()->node_size(); + 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 @@ -548,50 +614,66 @@ void 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) { - TensorId tensor_id(controlling_fanin.node->name(), Graph::kControlSlot); - graph_view_->RemoveFanin(node->name(), tensor_id); - graph_view_->AddFanin(it->second->name(), tensor_id); + 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) { - graph_view_->AddFanin(node->name(), - {entry.second->name(), Graph::kControlSlot}); + node->add_input(AsControlDependency(*entry.second)); + node_map_->AddOutput(entry.second->name(), node->name()); } } } @@ -599,17 +681,22 @@ void DependencyOptimizer::GroupCrossDeviceControlEdges() { 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. TF_RETURN_IF_ERROR(TransitiveReduction()); @@ -622,6 +709,9 @@ Status DependencyOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, // nodes. TF_RETURN_IF_ERROR(OptimizeDependencies()); + // Dedup control inputs. + CleanControlInputs(); + GroupCrossDeviceControlEdges(); } diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer.h b/tensorflow/core/grappler/optimizers/dependency_optimizer.h index eb7fc89080..7b032673fb 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer.h +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer.h @@ -17,8 +17,6 @@ limitations under the License. #define TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DEPENDENCY_OPTIMIZER_H_ #include - -#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" @@ -48,22 +46,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, - const absl::flat_hash_set& fanins, - const absl::flat_hash_set& fanout_edges) const; - int NumEdgesIfBypassed( - const NodeDef& node, int num_fanins, - const absl::flat_hash_set& fanout_edges) 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; + // 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. - void OptimizeNode(NodeDef* node, SetVector* nodes_to_simplify, - std::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(); @@ -76,7 +76,9 @@ class DependencyOptimizer : public GraphOptimizer { RewriterConfig::Toggle opt_level_; 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 673fc987a8..8d70d9d5c7 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc @@ -91,7 +91,7 @@ TEST_F(DependencyOptimizerTest, DependenciesDrivenByConstants) { // The 'z' node should have been optimized away leaving only 5 nodes. 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") { EXPECT_EQ(1, node.input_size()); EXPECT_EQ("add", node.input(0)); @@ -125,8 +125,8 @@ TEST_F(DependencyOptimizerTest, ChangeToNoop) { 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("add", node.name()); if (node.name() == "id1") { @@ -164,6 +164,7 @@ TEST_F(DependencyOptimizerTest, ChangeToNoop_RepeatedInput) { item.graph.Swap(&output); status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); + LOG(INFO) << output.DebugString(); EXPECT_EQ(item.graph.node_size(), output.node_size()); int found = 0; @@ -747,7 +748,7 @@ TEST_F(DependencyOptimizerTest, Identity_DeviceCrossing_ConsumerOnSameDevice) { GraphDef output; Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - + LOG(INFO) << output.DebugString(); EXPECT_EQ(3, output.node_size()); for (const auto& node : output.node()) { EXPECT_NE("x_on_2", node.name()); -- GitLab From 93f760a1d83404cadf2ce1bc8cd8268c47e2f4f8 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Tue, 8 Jan 2019 10:41:24 -0800 Subject: [PATCH 0350/2345] [XLA] Fix VLOG(0) usage in dynamic_dimension_inference that caused a lot of LOG spam. PiperOrigin-RevId: 228357053 --- tensorflow/compiler/xla/service/dynamic_dimension_inference.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/dynamic_dimension_inference.cc b/tensorflow/compiler/xla/service/dynamic_dimension_inference.cc index f2b7e9a186..2b158d7a6e 100644 --- a/tensorflow/compiler/xla/service/dynamic_dimension_inference.cc +++ b/tensorflow/compiler/xla/service/dynamic_dimension_inference.cc @@ -433,7 +433,7 @@ Status DynamicDimensionInferenceVisitor::ForEachOperandDynamicDimension( /* static */ StatusOr DynamicDimensionInference::Run( HloModule* module) { - VLOG(0) << "Param Config " << module->dynamic_parameter_binding().ToString(); + VLOG(2) << "Param Config " << module->dynamic_parameter_binding().ToString(); DynamicDimensionInference inference(module); TF_RETURN_IF_ERROR(inference.AnalyzeDynamicDimensions()); return inference; -- GitLab From 2ce3e3758d0fc1eea31b3e16c94321198c121953 Mon Sep 17 00:00:00 2001 From: Pete Warden Date: Tue, 8 Jan 2019 10:47:29 -0800 Subject: [PATCH 0351/2345] Fix for Raspberry Pi build, removing default dependency on Intel MKL PiperOrigin-RevId: 228358313 --- tensorflow/tools/ci_build/pi/build_raspberry_pi.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/tools/ci_build/pi/build_raspberry_pi.sh b/tensorflow/tools/ci_build/pi/build_raspberry_pi.sh index 864278c647..987f0769b2 100755 --- a/tensorflow/tools/ci_build/pi/build_raspberry_pi.sh +++ b/tensorflow/tools/ci_build/pi/build_raspberry_pi.sh @@ -107,6 +107,7 @@ bazel build -c opt ${PI_COPTS} \ --copt=-funsafe-math-optimizations --copt=-ftree-vectorize \ --copt=-fomit-frame-pointer --cpu=armeabi \ --crosstool_top=@local_config_arm_compiler//:toolchain \ + --define tensorflow_mkldnn_contraction_kernel=0 \ --verbose_failures \ //tensorflow:libtensorflow.so \ //tensorflow:libtensorflow_framework.so \ -- GitLab From 570767085d89516d145e2960ecc6edb23aa1456e Mon Sep 17 00:00:00 2001 From: "srinivasan.narayanamoorthy" Date: Tue, 8 Jan 2019 11:04:30 -0800 Subject: [PATCH 0352/2345] Mkl small size allocator fix for reducing lock overhead in critical path --- .../core/common_runtime/mkl_cpu_allocator.h | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/tensorflow/core/common_runtime/mkl_cpu_allocator.h b/tensorflow/core/common_runtime/mkl_cpu_allocator.h index 429b19599b..640300e523 100644 --- a/tensorflow/core/common_runtime/mkl_cpu_allocator.h +++ b/tensorflow/core/common_runtime/mkl_cpu_allocator.h @@ -39,6 +39,8 @@ typedef unsigned int uint; namespace tensorflow { +static bool mkl_small_allocator_collect_stats = false; + class MklSubAllocator : public BasicCPUAllocator { public: MklSubAllocator() : BasicCPUAllocator(port::kNUMANoAffinity, {}, {}) {} @@ -62,15 +64,8 @@ class MklSmallSizeAllocator : public Allocator { inline string Name() override { return name_; } void* AllocateRaw(size_t alignment, size_t num_bytes) override { - void* ptr = sub_allocator_->Alloc(alignment, num_bytes); - if (ptr != nullptr) { - std::pair map_val(ptr, num_bytes); - mutex_lock l(mutex_); - // Check that insertion in the hash map was successful. - CHECK(map_.insert(map_val).second); - // Increment statistics for small-size allocations. - IncrementStats(num_bytes); - } + void* ptr = port::AlignedMalloc(num_bytes, alignment); + if (mkl_small_allocator_collect_stats) IncrementStats(num_bytes); return ptr; } @@ -80,23 +75,11 @@ class MklSmallSizeAllocator : public Allocator { return; } - mutex_lock l(mutex_); - auto map_iter = map_.find(ptr); - if (map_iter != map_.end()) { - // Call free visitors. - size_t dealloc_bytes = map_iter->second; - sub_allocator_->Free(ptr, dealloc_bytes); - DecrementStats(dealloc_bytes); - map_.erase(map_iter); - } else { - LOG(ERROR) << "tried to deallocate invalid pointer"; - return; + if (mkl_small_allocator_collect_stats) { + const size_t alloc_size = port::MallocExtension_GetAllocatedSize(ptr); + DecrementStats(alloc_size); } - } - - inline bool IsSmallSizeAllocation(const void* ptr) const { - mutex_lock l(mutex_); - return map_.find(ptr) != map_.end(); + port::AlignedFree(ptr); } void GetStats(AllocatorStats* stats) override { @@ -135,10 +118,6 @@ class MklSmallSizeAllocator : public Allocator { // Allocator name string name_; - // Hash map to keep track of "small" allocations - // We do not use BFC allocator for small allocations. - std::unordered_map map_ GUARDED_BY(mutex_); - // Allocator stats for small allocs AllocatorStats stats_ GUARDED_BY(mutex_); }; @@ -215,23 +194,50 @@ class MklCPUAllocator : public Allocator { } inline string Name() override { return kName; } + inline bool IsSmallSizeAllocation(const void* ptr) const { + mutex_lock l(mutex_); + return large_allocations_map_.find(ptr) == large_allocations_map_.end(); + } + // AddLargeAllocMap and RemoveLargeAllocMap are always called with a lock held + inline void AddLargeAllocMap(void* ptr, size_t num_bytes) { + if (ptr != nullptr) { + std::pair map_val(ptr, num_bytes); + large_allocations_map_.insert(map_val).second; + } + } + inline void RemoveLargeAllocMap(void* ptr) { + auto map_iter = large_allocations_map_.find(ptr); + if (map_iter != large_allocations_map_.end()) { + large_allocations_map_.erase(map_iter); + } else { + LOG(ERROR) << "tried to deallocate invalid pointer"; + } + return; + } inline void* AllocateRaw(size_t alignment, size_t num_bytes) override { // If the allocation size is less than threshold, call small allocator, // otherwise call large-size allocator (BFC). We found that BFC allocator // does not deliver good performance for small allocations when // inter_op_parallelism_threads is high. - return (num_bytes < kSmallAllocationsThreshold) - ? small_size_allocator_->AllocateRaw(alignment, num_bytes) - : large_size_allocator_->AllocateRaw(alignment, num_bytes); + if (num_bytes < kSmallAllocationsThreshold) { + return small_size_allocator_->AllocateRaw(alignment, num_bytes); + } else { + mutex_lock l(mutex_); + void* ptr = large_size_allocator_->AllocateRaw(alignment, num_bytes); + AddLargeAllocMap(ptr, num_bytes); + return ptr; + } } inline void DeallocateRaw(void* ptr) override { // Check if ptr is for "small" allocation. If it is, then call Free // directly. Otherwise, call BFC to handle free. - if (small_size_allocator_->IsSmallSizeAllocation(ptr)) { + if (IsSmallSizeAllocation(ptr)) { small_size_allocator_->DeallocateRaw(ptr); } else { + mutex_lock l(mutex_); + RemoveLargeAllocMap(ptr); large_size_allocator_->DeallocateRaw(ptr); } } @@ -299,6 +305,12 @@ class MklCPUAllocator : public Allocator { MklSmallSizeAllocator* small_size_allocator_; // owned by this class. SubAllocator* sub_allocator_; // not owned by this class + mutable mutex mutex_; + + // Hash map to keep track of "BFC" allocations + // We do not use BFC allocator for small allocations. + std::unordered_map large_allocations_map_ + GUARDED_BY(mutex_); // Size in bytes that defines the upper-bound for "small" allocations. // Any allocation below this threshold is "small" allocation. -- GitLab From dd355d3b10a73474fb34972ec0ec708868bd0a24 Mon Sep 17 00:00:00 2001 From: Saurabh Saxena Date: Tue, 8 Jan 2019 11:13:29 -0800 Subject: [PATCH 0353/2345] Fill zeros for DT_INVALID tensors in TensorListStack. PiperOrigin-RevId: 228363806 --- tensorflow/core/kernels/BUILD | 1 + tensorflow/core/kernels/fill_functor.cc | 5 ++ tensorflow/core/kernels/list_kernels.h | 71 ++++++++++++++----- .../python/kernel_tests/list_ops_test.py | 41 +++++++++++ .../kernel_tests/tensor_array_ops_test.py | 13 ++-- 5 files changed, 106 insertions(+), 25 deletions(-) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index b934c64bfc..aaea09bdc4 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -2256,6 +2256,7 @@ tf_kernel_library( ], deps = [ ":concat_lib", + ":fill_functor", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:list_ops_op_lib", diff --git a/tensorflow/core/kernels/fill_functor.cc b/tensorflow/core/kernels/fill_functor.cc index 7090417dfd..9c4c0487f0 100644 --- a/tensorflow/core/kernels/fill_functor.cc +++ b/tensorflow/core/kernels/fill_functor.cc @@ -51,6 +51,11 @@ DEFINE_SETZERO_CPU(uint16); DEFINE_SETZERO_CPU(int16); DEFINE_SETZERO_CPU(int32); DEFINE_SETZERO_CPU(int64); +DEFINE_SETZERO_CPU(quint8); +DEFINE_SETZERO_CPU(qint8); +DEFINE_SETZERO_CPU(quint16); +DEFINE_SETZERO_CPU(qint16); +DEFINE_SETZERO_CPU(qint32); DEFINE_SETZERO_CPU(complex64); DEFINE_SETZERO_CPU(complex128); DEFINE_SETZERO_CPU(Variant); diff --git a/tensorflow/core/kernels/list_kernels.h b/tensorflow/core/kernels/list_kernels.h index 559e14b03c..fd1be80f11 100644 --- a/tensorflow/core/kernels/list_kernels.h +++ b/tensorflow/core/kernels/list_kernels.h @@ -28,6 +28,7 @@ limitations under the License. #include "tensorflow/core/framework/variant.h" #include "tensorflow/core/framework/variant_op_registry.h" #include "tensorflow/core/kernels/concat_lib.h" +#include "tensorflow/core/kernels/fill_functor.h" #include "tensorflow/core/lib/core/coding.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/gtl/array_slice.h" @@ -65,6 +66,15 @@ struct TensorList { Status TensorShapeFromTensor(const Tensor& t, PartialTensorShape* out); +// Allocates a Tensor of requested shape and dtype and fills it with zeros. +template +void BuildZerosTensor(OpKernelContext* c, DataType dtype, + const TensorShape& shape, Tensor* zeros) { + OP_REQUIRES_OK(c, c->allocate_temp(dtype, shape, zeros)); + functor::SetZeroFunctor f; + f(c->eigen_device(), zeros->flat()); +} + template class TensorListStack : public OpKernel { public: @@ -94,7 +104,7 @@ class TensorListStack : public OpKernel { !tensor_list->tensors.empty() || tensor_list->element_shape.IsFullyDefined(), errors::InvalidArgument("Tried to stack elements of a empty ", - "list with non-fully-defined shape: ", + "list with non-fully-defined element_shape: ", tensor_list->element_shape.DebugString())); if (num_elements_ != -1) { OP_REQUIRES(c, tensor_list->tensors.size() == num_elements_, @@ -106,34 +116,59 @@ class TensorListStack : public OpKernel { // Compute the shape of the output tensor. // If `element_shape` is fully-defined it gets used. It is assumed that all // element tensors have the same shape. - // If `element_shape` is not fully-defined the shape of the first element - // tensor is used and it is checked that all other tensors have the same - // shape. - TensorShape resulting_shape; - if (!tensor_list->element_shape.AsTensorShape(&resulting_shape)) { - const Tensor& t = tensor_list->tensors[0]; - resulting_shape = t.shape(); - for (int i = 1; i < tensor_list->tensors.size(); ++i) { + // If `element_shape` is not fully-defined the shape of the first + // initialized element tensor is used and it is checked that all other + // initialized tensors have the same shape. An error is thrown if the list + // only contains DT_INVALID type tensors. + TensorShape resulting_element_shape; + if (!tensor_list->element_shape.AsTensorShape(&resulting_element_shape)) { + bool resulting_element_shape_initialized = false; + for (int i = 0; i < tensor_list->tensors.size(); ++i) { const Tensor& t = tensor_list->tensors[i]; - OP_REQUIRES(c, t.shape() == resulting_shape, - errors::InvalidArgument( - "Tried to stack tensors with unequal shapes: ", - resulting_shape.DebugString(), " vs ", - t.shape().DebugString())); + if (!resulting_element_shape_initialized) { + if (t.dtype() == DT_INVALID) { + continue; + } + resulting_element_shape = t.shape(); + resulting_element_shape_initialized = true; + continue; + } + OP_REQUIRES( + c, t.dtype() == DT_INVALID || t.shape() == resulting_element_shape, + errors::InvalidArgument( + "Tried to stack tensors with unequal shapes: ", + resulting_element_shape.DebugString(), " vs ", + t.shape().DebugString())); } + OP_REQUIRES( + c, resulting_element_shape_initialized, + errors::InvalidArgument("Tried to stack list which only contains ", + "uninitialized tensors and has a ", + "non-fully-defined element_shape: ", + tensor_list->element_shape.DebugString())); } - resulting_shape.InsertDim(0, tensor_list->tensors.size()); + TensorShape output_tensor_shape = resulting_element_shape; + output_tensor_shape.InsertDim(0, tensor_list->tensors.size()); Tensor* output; - OP_REQUIRES_OK(c, c->allocate_output(0, resulting_shape, &output)); + OP_REQUIRES_OK(c, c->allocate_output(0, output_tensor_shape, &output)); if (output->NumElements() == 0) { return; } ConstMatrixVector inputs_flat; inputs_flat.reserve(tensor_list->tensors.size()); + Tensor zeros; + BuildZerosTensor(c, element_dtype_, resulting_element_shape, + &zeros); for (const auto& t : tensor_list->tensors) { - inputs_flat.emplace_back(new typename TTypes::ConstMatrix( - t.shaped({1, t.NumElements()}))); + if (t.dtype() != DT_INVALID) { + inputs_flat.emplace_back(new typename TTypes::ConstMatrix( + t.shaped({1, t.NumElements()}))); + } else { + inputs_flat.emplace_back(new typename TTypes::ConstMatrix( + const_cast(zeros).shaped( + {1, zeros.NumElements()}))); + } } auto output_flat = output->shaped({1, output->NumElements()}); diff --git a/tensorflow/python/kernel_tests/list_ops_test.py b/tensorflow/python/kernel_tests/list_ops_test.py index fc0e270667..ec6906f20c 100644 --- a/tensorflow/python/kernel_tests/list_ops_test.py +++ b/tensorflow/python/kernel_tests/list_ops_test.py @@ -189,6 +189,47 @@ class ListOpsTest(test_util.TensorFlowTestCase, parameterized.TestCase): t = list_ops.tensor_list_stack(l, element_dtype=dtypes.float32) self.evaluate(t) + def _testStackWithUninitializedTensors(self): + l = list_ops.tensor_list_reserve( + element_dtype=dtypes.float32, element_shape=[], num_elements=3) + t = list_ops.tensor_list_stack(l, element_dtype=dtypes.float32) + self.assertAllEqual(self.evaluate(t), [0., 0., 0.]) + + def testStackWithUninitializedTensors(self): + self._testStackWithUninitializedTensors() + + def testStackWithUninitializedTensorsGpu(self): + if not context.num_gpus(): + return + with context.device("gpu:0"): + self._testStackWithUninitializedTensors() + + def _testStackWithUninitializedTensorsInferShape(self): + l = list_ops.tensor_list_reserve( + element_dtype=dtypes.float32, element_shape=None, num_elements=3) + l = list_ops.tensor_list_set_item(l, 1, [1., 2.]) + t = list_ops.tensor_list_stack(l, element_dtype=dtypes.float32) + self.assertAllEqual(self.evaluate(t), [[0., 0.], [1., 2.], [0., 0.]]) + + def testStackWithUninitializedTensorsInferShape(self): + self._testStackWithUninitializedTensorsInferShape() + + def testStackWithUninitializedTensorsInferShapeGpu(self): + if not context.num_gpus(): + return + with context.device("gpu:0"): + self._testStackWithUninitializedTensorsInferShape() + + def testStackReservedListWithNoElementsAndPartialElementShapeFails(self): + l = list_ops.tensor_list_reserve( + element_dtype=dtypes.float32, element_shape=None, num_elements=3) + with self.assertRaisesRegexp(errors.InvalidArgumentError, + "Tried to stack list which only contains " + "uninitialized tensors and has a " + "non-fully-defined element_shape: "): + t = list_ops.tensor_list_stack(l, element_dtype=dtypes.float32) + self.evaluate(t) + @parameterized.named_parameters(("NoMaxNumElements", None), ("WithMaxNumElements", 2)) def testGatherGrad(self, max_num_elements): diff --git a/tensorflow/python/kernel_tests/tensor_array_ops_test.py b/tensorflow/python/kernel_tests/tensor_array_ops_test.py index 3f1bd60d1a..e8af998a70 100644 --- a/tensorflow/python/kernel_tests/tensor_array_ops_test.py +++ b/tensorflow/python/kernel_tests/tensor_array_ops_test.py @@ -185,8 +185,8 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual([[0.0, 0.0], [4.0, 5.0], [0.0, 0.0]], self.evaluate(ta.write(1, [[4.0, 5.0]]).concat())) - @test_util.disable_control_flow_v2("b/118890905") - @test_util.run_v1_only("b/118890905") + @test_util.disable_control_flow_v2("b/122324791") + @test_util.run_v1_only("b/122324791") def testTensorArrayReadOrPackNotAllValuesAvailableFillsZeros(self): self._testTensorArrayReadOrPackNotAllValuesAvailableFillsZeros() @@ -202,8 +202,8 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual([[0.0, 0.0], [4.0, 5.0], [0.0, 0.0]], self.evaluate(ta.write(1, [[4.0, 5.0]]).concat())) - @test_util.disable_control_flow_v2("b/118890905") - @test_util.run_v1_only("b/118890905") + @test_util.disable_control_flow_v2("b/122324791") + @test_util.run_v1_only("b/122324791") def testTensorArrayReadOrPackNotAllValuesAvailableInferShapeFillsZeros(self): self._testTensorArrayReadOrPackNotAllValuesAvailableInferShapeFillsZeros() @@ -1321,8 +1321,8 @@ class TensorArrayTest(test.TestCase): with self.cached_session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, size=0, dynamic_size=False, infer_shape=False) - v2_msg = ("Tried to stack elements of a empty list with " - "non-fully-defined shape") + v2_msg = ("Tried to stack elements of a empty list with non-fully-defined" + " element_shape") v1_msg = ( "TensorArray has size zero, but element shape is not " "fully defined. Currently only static shapes are supported when " @@ -1385,7 +1385,6 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual([10.0, -10.0], read_vals[1]) self.assertAllEqual([[2.0, 3.0], [4.0, 5.0]], grad_vals[0]) - @test_util.disable_control_flow_v2("b/118890905") @test_util.run_v1_only("b/118890905") def testTensorArrayWriteGatherAndGradients(self): with self.session(use_gpu=True) as session: -- GitLab From 9c091ca7134fc51f49ea930aac7a48bbdf84e7dc Mon Sep 17 00:00:00 2001 From: Yunxing Dai Date: Tue, 8 Jan 2019 11:15:25 -0800 Subject: [PATCH 0354/2345] Automated rollback of commit acfc65f0110e272abc8bd1abce867f2f534ba263 PiperOrigin-RevId: 228364189 --- tensorflow/compiler/tf2xla/kernels/shape_op.cc | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/shape_op.cc b/tensorflow/compiler/tf2xla/kernels/shape_op.cc index 85b0367f73..12830816ec 100644 --- a/tensorflow/compiler/tf2xla/kernels/shape_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/shape_op.cc @@ -20,7 +20,6 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/xla_helpers.h" #include "tensorflow/compiler/tf2xla/xla_op_kernel.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" -#include "tensorflow/compiler/xla/client/lib/constants.h" #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/core/framework/kernel_def_builder.h" #include "tensorflow/core/framework/tensor_shape.h" @@ -92,20 +91,14 @@ class SizeOp : public XlaOpKernel { void Compile(XlaOpKernelContext* ctx) override { const TensorShape input_shape = ctx->InputShape(0); - OP_REQUIRES(ctx, - FastBoundsCheck(input_shape.num_elements(), - std::numeric_limits::max()), + const int64 size = input_shape.num_elements(); + OP_REQUIRES(ctx, FastBoundsCheck(size, std::numeric_limits::max()), errors::InvalidArgument("Size does not work for tensors > " "int32 max.")); Tensor size_constant(DT_INT32, TensorShape({})); - const int rank = input_shape.dims(); - xla::XlaBuilder* builder = ctx->builder(); - auto size = xla::One(builder, xla::U32); - for (int64 i = 0; i < rank; ++i) { - size = xla::Mul(size, xla::GetDimensionSize(ctx->Input(0), i)); - } - size = xla::ConvertElementType(size, xla::S32); - ctx->SetOutput(0, size); + size_constant.scalar()() = static_cast(size); + + ctx->SetConstantOutput(0, size_constant); } }; -- GitLab From bdeda4c087a3515578e6287c647bc00fe5f35491 Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Tue, 8 Jan 2019 11:27:47 -0800 Subject: [PATCH 0355/2345] Internal Cleanup. PiperOrigin-RevId: 228366686 --- tensorflow/python/keras/BUILD | 2 +- tensorflow/python/keras/optimizers_test.py | 157 +++++++++++---------- 2 files changed, 81 insertions(+), 78 deletions(-) diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 3ffff61c2b..9f8da126bf 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -377,7 +377,7 @@ tf_py_test( "//tensorflow/python:client_testlib", "//tensorflow/python:training", ], - shard_count = 2, + shard_count = 4, tags = ["notsan"], ) diff --git a/tensorflow/python/keras/optimizers_test.py b/tensorflow/python/keras/optimizers_test.py index 18a20567ce..de24bf0536 100644 --- a/tensorflow/python/keras/optimizers_test.py +++ b/tensorflow/python/keras/optimizers_test.py @@ -44,116 +44,119 @@ def _get_model(input_dim, num_hidden, output_dim): return model -def _test_optimizer(optimizer, target=0.75): - np.random.seed(1337) - (x_train, y_train), _ = testing_utils.get_test_data(train_samples=1000, - test_samples=200, - input_shape=(10,), - num_classes=2) - y_train = keras.utils.to_categorical(y_train) - model = _get_model(x_train.shape[1], 20, y_train.shape[1]) - model.compile(loss='categorical_crossentropy', - optimizer=optimizer, - metrics=['accuracy']) - np.testing.assert_equal(keras.backend.get_value(model.optimizer.iterations), - 0) - history = model.fit(x_train, y_train, epochs=2, batch_size=16, verbose=0) - np.testing.assert_equal(keras.backend.get_value(model.optimizer.iterations), - 126) # 63 steps per epoch - assert history.history['acc'][-1] >= target - config = keras.optimizers.serialize(optimizer) - optim = keras.optimizers.deserialize(config) - new_config = keras.optimizers.serialize(optim) - new_config['class_name'] = new_config['class_name'].lower() - new_config['config'].pop('name', None) - if 'amsgrad' not in config['config']: - new_config['config'].pop('amsgrad', None) - if 'decay' in new_config['config'] and 'schedule_decay' in config['config']: - new_config['config']['schedule_decay'] = new_config['config'].pop('decay') - if 'momentum' not in config['config']: - new_config['config'].pop('momentum', None) - if 'centered' not in config['config']: - new_config['config'].pop('centered', None) - assert config == new_config - - # Test constraints. - model = keras.models.Sequential() - dense = keras.layers.Dense(10, - input_shape=(x_train.shape[1],), - kernel_constraint=lambda x: 0. * x + 1., - bias_constraint=lambda x: 0. * x + 2., - activation='relu') - model.add(dense) - model.add(keras.layers.Dense(y_train.shape[1], activation='softmax')) - model.compile(loss='categorical_crossentropy', - optimizer=optimizer, - metrics=['accuracy']) - np.testing.assert_equal(keras.backend.get_value(model.optimizer.iterations), - 126) # Using same optimizer from before - model.train_on_batch(x_train[:10], y_train[:10]) - np.testing.assert_equal(keras.backend.get_value(model.optimizer.iterations), - 127) - kernel, bias = dense.get_weights() - np.testing.assert_allclose(kernel, 1., atol=1e-3) - np.testing.assert_allclose(bias, 2., atol=1e-3) - - class KerasOptimizersTest(test.TestCase): + def _test_optimizer(self, optimizer, target=0.75): + np.random.seed(1337) + (x_train, y_train), _ = testing_utils.get_test_data( + train_samples=1000, test_samples=200, input_shape=(10,), num_classes=2) + y_train = keras.utils.to_categorical(y_train) + model = _get_model(x_train.shape[1], 20, y_train.shape[1]) + model.compile( + loss='categorical_crossentropy', + optimizer=optimizer, + metrics=['accuracy']) + np.testing.assert_equal( + keras.backend.get_value(model.optimizer.iterations), 0) + history = model.fit(x_train, y_train, epochs=2, batch_size=16, verbose=0) + np.testing.assert_equal( + keras.backend.get_value(model.optimizer.iterations), + 126) # 63 steps per epoch + assert history.history['acc'][-1] >= target + config = keras.optimizers.serialize(optimizer) + optim = keras.optimizers.deserialize(config) + new_config = keras.optimizers.serialize(optim) + new_config['class_name'] = new_config['class_name'].lower() + new_config['config'].pop('name', None) + if 'amsgrad' not in config['config']: + new_config['config'].pop('amsgrad', None) + if 'decay' in new_config['config'] and 'schedule_decay' in config['config']: + new_config['config']['schedule_decay'] = new_config['config'].pop('decay') + if 'momentum' not in config['config']: + new_config['config'].pop('momentum', None) + if 'centered' not in config['config']: + new_config['config'].pop('centered', None) + self.assertDictEqual(config, new_config) + + # Test constraints. + model = keras.models.Sequential() + dense = keras.layers.Dense( + 10, + input_shape=(x_train.shape[1],), + kernel_constraint=lambda x: 0. * x + 1., + bias_constraint=lambda x: 0. * x + 2., + activation='relu') + model.add(dense) + model.add(keras.layers.Dense(y_train.shape[1], activation='softmax')) + model.compile( + loss='categorical_crossentropy', + optimizer=optimizer, + metrics=['accuracy']) + np.testing.assert_equal( + keras.backend.get_value(model.optimizer.iterations), + 126) # Using same optimizer from before + model.train_on_batch(x_train[:10], y_train[:10]) + np.testing.assert_equal( + keras.backend.get_value(model.optimizer.iterations), 127) + kernel, bias = dense.get_weights() + np.testing.assert_allclose(kernel, 1., atol=1e-3) + np.testing.assert_allclose(bias, 2., atol=1e-3) + def test_sgd(self): with self.cached_session(): - _test_optimizer(keras.optimizers.SGD(lr=0.01, - momentum=0.9, - nesterov=True)) + self._test_optimizer(keras.optimizers.SGD()) + + def test_momentum(self): + with self.cached_session(): + self._test_optimizer( + keras.optimizers.SGD(lr=0.01, momentum=0.9, nesterov=True)) def test_rmsprop(self): with self.cached_session(): - _test_optimizer(keras.optimizers.RMSprop()) - _test_optimizer(keras.optimizers.RMSprop(decay=1e-3)) + self._test_optimizer(keras.optimizers.RMSprop()) + self._test_optimizer(keras.optimizers.RMSprop(decay=1e-3)) def test_adagrad(self): with self.cached_session(): - _test_optimizer(keras.optimizers.Adagrad()) - _test_optimizer(keras.optimizers.Adagrad(decay=1e-3)) + self._test_optimizer(keras.optimizers.Adagrad()) + self._test_optimizer(keras.optimizers.Adagrad(decay=1e-3)) def test_adadelta(self): with self.cached_session(): - _test_optimizer(keras.optimizers.Adadelta(), target=0.6) + self._test_optimizer(keras.optimizers.Adadelta(), target=0.6) # Accuracy seems dependent on the initialization. Even adding tf.Print # nodes in the graph seemed to affect the initialization seed, and hence # the accuracy. - _test_optimizer(keras.optimizers.Adadelta(decay=1e-3), target=0.4) + self._test_optimizer(keras.optimizers.Adadelta(decay=1e-3), target=0.4) def test_adam(self): with self.cached_session(): - _test_optimizer(keras.optimizers.Adam()) + self._test_optimizer(keras.optimizers.Adam()) # Accuracy seems dependent on the seed initialization. # TODO(b/121051441): fix test flakiness. - _test_optimizer(keras.optimizers.Adam(decay=1e-3), target=0.73) - _test_optimizer(keras.optimizers.Adam(amsgrad=True)) + self._test_optimizer(keras.optimizers.Adam(decay=1e-3), target=0.73) + self._test_optimizer(keras.optimizers.Adam(amsgrad=True)) def test_adamax(self): with self.cached_session(): - _test_optimizer(keras.optimizers.Adamax()) - _test_optimizer(keras.optimizers.Adamax(decay=1e-3)) + self._test_optimizer(keras.optimizers.Adamax()) + self._test_optimizer(keras.optimizers.Adamax(decay=1e-3)) def test_nadam(self): with self.cached_session(): - _test_optimizer(keras.optimizers.Nadam()) + self._test_optimizer(keras.optimizers.Nadam()) def test_clipnorm(self): with self.cached_session(): - _test_optimizer(keras.optimizers.SGD(lr=0.01, - momentum=0.9, - clipnorm=0.5)) + self._test_optimizer( + keras.optimizers.SGD(lr=0.01, momentum=0.9, clipnorm=0.5)) def test_clipvalue(self): with self.cached_session(): - _test_optimizer(keras.optimizers.SGD(lr=0.01, - momentum=0.9, - clipvalue=0.5)) + self._test_optimizer( + keras.optimizers.SGD(lr=0.01, momentum=0.9, clipvalue=0.5)) - def test_tfoptimizer(self): + def test_tf_optimizer(self): optimizer = keras.optimizers.TFOptimizer(AdamOptimizer(0.01)) model = keras.models.Sequential() model.add(keras.layers.Dense( @@ -188,7 +191,7 @@ class KerasOptimizersTest(test.TestCase): self.assertIs(optimizer_weak(), None) @test_util.run_in_graph_and_eager_modes - def test_tfoptimizer_iterations(self): + def test_tf_optimizer_iterations(self): with self.cached_session(): optimizer = keras.optimizers.TFOptimizer(AdamOptimizer(0.01)) model = keras.models.Sequential() -- GitLab From 35279e5c61d3ff5882c31d3d291256321ba27267 Mon Sep 17 00:00:00 2001 From: Doe Hyun Yoon Date: Tue, 8 Jan 2019 11:37:17 -0800 Subject: [PATCH 0356/2345] Replace the references to PredictCosts() with PredictCostsAndReturnRunMetadata(). PiperOrigin-RevId: 228368607 --- .../core/grappler/costs/analytical_cost_estimator_test.cc | 5 +++-- tensorflow/python/grappler/cost_analyzer.cc | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/grappler/costs/analytical_cost_estimator_test.cc b/tensorflow/core/grappler/costs/analytical_cost_estimator_test.cc index a9a1abfa98..8a56344378 100644 --- a/tensorflow/core/grappler/costs/analytical_cost_estimator_test.cc +++ b/tensorflow/core/grappler/costs/analytical_cost_estimator_test.cc @@ -98,9 +98,10 @@ TEST_F(AnalyticalCostEstimatorTest, SimpleTest) { AnalyticalCostEstimator estimator(cluster_.get(), true); TF_ASSERT_OK(estimator.Initialize(item)); - CostGraphDef cost_graph; + RunMetadata run_metadata; Costs summary; - TF_ASSERT_OK(estimator.PredictCosts(item.graph, &cost_graph, &summary)); + TF_ASSERT_OK(estimator.PredictCostsAndReturnRunMetadata( + item.graph, &run_metadata, &summary)); EXPECT_EQ(Costs::NanoSeconds(9151), summary.execution_time); // Note there are totally 17 nodes (RandomUniform creates 2 nodes), but diff --git a/tensorflow/python/grappler/cost_analyzer.cc b/tensorflow/python/grappler/cost_analyzer.cc index b474e19894..bb8c6d5b85 100644 --- a/tensorflow/python/grappler/cost_analyzer.cc +++ b/tensorflow/python/grappler/cost_analyzer.cc @@ -42,9 +42,13 @@ Status CostAnalyzer::GenerateReport(std::ostream& os, bool per_node_report, void CostAnalyzer::PredictCosts(CostEstimator* cost_estimator, CostGraphDef* cost_graph, int64* total_time) { TF_CHECK_OK(cost_estimator->Initialize(*item_)); + RunMetadata run_metadata; Costs costs; - const Status status = - cost_estimator->PredictCosts(item_->graph, cost_graph, &costs); + const Status status = cost_estimator->PredictCostsAndReturnRunMetadata( + item_->graph, &run_metadata, &costs); + if (cost_graph) { + cost_graph->Swap(run_metadata.mutable_cost_graph()); + } *total_time = costs.execution_time.count(); if (!status.ok()) { LOG(ERROR) << "Could not estimate the cost for item " << item_->id << ": " -- GitLab From a849894f02e41fdeb08f86aafad51a2f02b454b1 Mon Sep 17 00:00:00 2001 From: Karim Nosir Date: Tue, 8 Jan 2019 11:59:53 -0800 Subject: [PATCH 0357/2345] Reduce the number of tests inside unit-test and shard it to speed it up. PiperOrigin-RevId: 228373086 --- tensorflow/lite/kernels/internal/BUILD | 2 ++ .../lite/kernels/internal/logsoftmax_quantized_test.cc | 6 +++--- tensorflow/lite/kernels/internal/softmax_quantized_test.cc | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tensorflow/lite/kernels/internal/BUILD b/tensorflow/lite/kernels/internal/BUILD index 885f650dd5..74a11a72ef 100644 --- a/tensorflow/lite/kernels/internal/BUILD +++ b/tensorflow/lite/kernels/internal/BUILD @@ -641,6 +641,7 @@ cc_test( srcs = [ "softmax_quantized_test.cc", ], + shard_count = 3, deps = [ ":optimized_base", ":quantization_util", @@ -657,6 +658,7 @@ cc_test( srcs = [ "logsoftmax_quantized_test.cc", ], + shard_count = 3, tags = [ # TODO(b/122242739): Reenable after fixing the flakiness? "nomac", diff --git a/tensorflow/lite/kernels/internal/logsoftmax_quantized_test.cc b/tensorflow/lite/kernels/internal/logsoftmax_quantized_test.cc index 889a726f3a..945300dad1 100644 --- a/tensorflow/lite/kernels/internal/logsoftmax_quantized_test.cc +++ b/tensorflow/lite/kernels/internal/logsoftmax_quantized_test.cc @@ -225,7 +225,7 @@ bool TryOneSkyscraperLogSoftmax(bool small_depth) { } TEST(TestQuantizedLogSoftmax, UniformLogSoftmaxTests) { - const int kTestsToRun = 1000; + const int kTestsToRun = 100; for (int i = 0; i < kTestsToRun; i++) { while (!TryOneUniformLogSoftmax()) { } @@ -233,7 +233,7 @@ TEST(TestQuantizedLogSoftmax, UniformLogSoftmaxTests) { } TEST(TestQuantizedLogSoftmax, SkyscraperLogSoftmaxTests) { - const int kTestsToRun = 1000; + const int kTestsToRun = 100; for (int i = 0; i < kTestsToRun; i++) { while (!TryOneSkyscraperLogSoftmax(false)) { } @@ -241,7 +241,7 @@ TEST(TestQuantizedLogSoftmax, SkyscraperLogSoftmaxTests) { } TEST(TestQuantizedLogSoftmax, SmallSkyscraperLogSoftmaxTests) { - const int kTestsToRun = 1000; + const int kTestsToRun = 100; for (int i = 0; i < kTestsToRun; i++) { while (!TryOneSkyscraperLogSoftmax(true)) { } diff --git a/tensorflow/lite/kernels/internal/softmax_quantized_test.cc b/tensorflow/lite/kernels/internal/softmax_quantized_test.cc index 743ce0355c..8ac62d9af7 100644 --- a/tensorflow/lite/kernels/internal/softmax_quantized_test.cc +++ b/tensorflow/lite/kernels/internal/softmax_quantized_test.cc @@ -210,7 +210,7 @@ bool TryOneSkyscraperSoftmax(bool small_depth) { } TEST(TestQuantizedSoftmax, UniformSoftmaxTests) { - const int kTestsToRun = 1000; + const int kTestsToRun = 100; for (int i = 0; i < kTestsToRun; i++) { while (!TryOneUniformSoftmax()) { } @@ -218,7 +218,7 @@ TEST(TestQuantizedSoftmax, UniformSoftmaxTests) { } TEST(TestQuantizedSoftmax, SkyscraperSoftmaxTests) { - const int kTestsToRun = 1000; + const int kTestsToRun = 100; for (int i = 0; i < kTestsToRun; i++) { while (!TryOneSkyscraperSoftmax(false)) { } @@ -226,7 +226,7 @@ TEST(TestQuantizedSoftmax, SkyscraperSoftmaxTests) { } TEST(TestQuantizedSoftmax, SmallSkyscraperSoftmaxTests) { - const int kTestsToRun = 1000; + const int kTestsToRun = 100; for (int i = 0; i < kTestsToRun; i++) { while (!TryOneSkyscraperSoftmax(true)) { } -- GitLab From 68727289bca144c9d1a8f618d81474e4a7322256 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Tue, 8 Jan 2019 12:01:56 -0800 Subject: [PATCH 0358/2345] [XLA] Move ArgMin/ArgMax out of TF/XLA and into XLA client library. NFC intended. PiperOrigin-RevId: 228373518 --- .../compiler/tf2xla/kernels/categorical_op.cc | 4 +- .../compiler/tf2xla/kernels/index_ops.cc | 5 +- .../compiler/tf2xla/kernels/index_ops_cpu.cc | 4 +- tensorflow/compiler/tf2xla/xla_helpers.cc | 67 ------------------- tensorflow/compiler/tf2xla/xla_helpers.h | 10 --- tensorflow/compiler/xla/client/lib/BUILD | 15 +++++ .../compiler/xla/client/lib/arithmetic.cc | 60 +++++++++++++++++ .../compiler/xla/client/lib/arithmetic.h | 8 +++ .../xla/client/lib/arithmetic_test.cc | 67 +++++++++++++++++++ 9 files changed, 156 insertions(+), 84 deletions(-) create mode 100644 tensorflow/compiler/xla/client/lib/arithmetic_test.cc diff --git a/tensorflow/compiler/tf2xla/kernels/categorical_op.cc b/tensorflow/compiler/tf2xla/kernels/categorical_op.cc index 7199b9b6fe..c2b4c28d15 100644 --- a/tensorflow/compiler/tf2xla/kernels/categorical_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/categorical_op.cc @@ -99,8 +99,8 @@ class CategoricalOp : public XlaOpKernel { xla::PrimitiveType xla_output_type; OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(output_type(0), &xla_output_type)); - xla::XlaOp argmax = XlaHelpers::ArgMax(softmax_entries, xla_output_type, - /*axis=*/class_dimension); + xla::XlaOp argmax = xla::ArgMax(softmax_entries, xla_output_type, + /*axis=*/class_dimension); if (num_samples == 1) { argmax = xla::Reshape(argmax, {batch_size, 1}); } diff --git a/tensorflow/compiler/tf2xla/kernels/index_ops.cc b/tensorflow/compiler/tf2xla/kernels/index_ops.cc index 843b6bb4e6..978e9480ea 100644 --- a/tensorflow/compiler/tf2xla/kernels/index_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/index_ops.cc @@ -18,7 +18,6 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/kernels/index_ops.h" #include "tensorflow/compiler/tf2xla/type_util.h" -#include "tensorflow/compiler/tf2xla/xla_helpers.h" #include "tensorflow/compiler/tf2xla/xla_op_kernel.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" #include "tensorflow/compiler/xla/client/lib/arithmetic.h" @@ -66,9 +65,9 @@ void XlaArgMinMaxOp::Compile(XlaOpKernelContext* ctx) { xla::XlaOp input = ctx->Input(0); xla::XlaOp output; if (is_min_) { - output = XlaHelpers::ArgMin(input, index_xla_type, axis); + output = xla::ArgMin(input, index_xla_type, axis); } else { - output = XlaHelpers::ArgMax(input, index_xla_type, axis); + output = xla::ArgMax(input, index_xla_type, axis); } ctx->SetOutput(0, output); diff --git a/tensorflow/compiler/tf2xla/kernels/index_ops_cpu.cc b/tensorflow/compiler/tf2xla/kernels/index_ops_cpu.cc index 3e7e8eae6e..30b993045c 100644 --- a/tensorflow/compiler/tf2xla/kernels/index_ops_cpu.cc +++ b/tensorflow/compiler/tf2xla/kernels/index_ops_cpu.cc @@ -16,9 +16,9 @@ limitations under the License. // Native XLA implementations of indexing ops. #include "tensorflow/compiler/tf2xla/type_util.h" -#include "tensorflow/compiler/tf2xla/xla_helpers.h" #include "tensorflow/compiler/tf2xla/xla_op_kernel.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" +#include "tensorflow/compiler/xla/client/lib/arithmetic.h" #include "tensorflow/compiler/xla/literal_util.h" #include "tensorflow/core/framework/kernel_def_builder.h" #include "tensorflow/core/framework/op_kernel.h" @@ -74,7 +74,7 @@ class ArgMaxCustomCallOp : public XlaOpKernel { // shape isn't supported. if (!ctx->compiler()->options().allow_cpu_custom_calls || (input_dims != 1 && input_dims != 2)) { - xla::XlaOp output = XlaHelpers::ArgMax(ctx->Input(0), output_type, axis); + xla::XlaOp output = xla::ArgMax(ctx->Input(0), output_type, axis); ctx->SetOutput(0, output); return; } diff --git a/tensorflow/compiler/tf2xla/xla_helpers.cc b/tensorflow/compiler/tf2xla/xla_helpers.cc index 00035d24b7..04a5d93406 100644 --- a/tensorflow/compiler/tf2xla/xla_helpers.cc +++ b/tensorflow/compiler/tf2xla/xla_helpers.cc @@ -34,63 +34,6 @@ limitations under the License. namespace tensorflow { -namespace { - -xla::XlaOp ArgMinMax(xla::XlaOp input, xla::PrimitiveType output_type, int axis, - bool is_min) { - xla::XlaBuilder* builder = input.builder(); - return builder->ReportErrorOrReturn([&]() -> xla::StatusOr { - TF_ASSIGN_OR_RETURN(xla::Shape input_shape, builder->GetShape(input)); - xla::XlaOp init_value; - xla::XlaComputation reducer; - if (is_min) { - init_value = xla::MaxValue(builder, input_shape.element_type()); - reducer = - xla::CreateScalarMinComputation(input_shape.element_type(), builder); - } else { - init_value = xla::MinValue(builder, input_shape.element_type()); - reducer = - xla::CreateScalarMaxComputation(input_shape.element_type(), builder); - } - - xla::XlaOp input_max = xla::Reduce(input, init_value, reducer, - /*dimensions_to_reduce=*/{axis}); - std::vector broadcast_dims(input_shape.rank() - 1); - std::iota(broadcast_dims.begin(), broadcast_dims.begin() + axis, 0); - std::iota(broadcast_dims.begin() + axis, broadcast_dims.end(), axis + 1); - // Compute a mask that has 1s for elements equal to the maximum. - xla::XlaOp partial_mask = xla::ConvertElementType( - xla::Eq(input, input_max, broadcast_dims), output_type); - - // In order to make identity elements for a bitwise And, we: - // Left shift the 1 to the leftmost bit, yielding 0x10...0 - // Arithmetic right shift the 1 back to the rightmost bit, yielding - // 0xFF...F - int32 bits_in_type = - xla::ShapeUtil::ByteSizeOfPrimitiveType(output_type) * 8 - 1; - xla::XlaOp shift_amount = - xla::ConstantR0WithType(builder, output_type, bits_in_type); - xla::XlaOp full_mask = xla::ShiftRightArithmetic( - xla::ShiftLeft(partial_mask, shift_amount), shift_amount); - - // And with the vector [0, 1, 2, ...] to convert each 0xFF...F into its - // index. - - const int64 axis_size = xla::ShapeUtil::GetDimension(input_shape, axis); - xla::XlaOp iota = xla::Iota(builder, output_type, axis_size); - xla::XlaOp product = - xla::And(full_mask, iota, /*broadcast_dimensions=*/{axis}); - - // If there are multiple maximum elements, choose the one with the highest - // index. - return xla::Reduce(product, xla::MinValue(builder, output_type), - xla::CreateScalarMaxComputation(output_type, builder), - /*dimensions_to_reduce=*/{axis}); - }); -} - -} // namespace - xla::XlaOp XlaHelpers::Zero(xla::XlaBuilder* b, DataType data_type) { xla::PrimitiveType type; TF_CHECK_OK(DataTypeToPrimitiveType(data_type, &type)); @@ -148,16 +91,6 @@ static Tensor MakeLinspaceTensor(const TensorShape& shape, int64 depth) { return linspace; } -xla::XlaOp XlaHelpers::ArgMax(xla::XlaOp input, xla::PrimitiveType output_type, - int axis) { - return ArgMinMax(input, output_type, axis, /*is_min=*/false); -} - -xla::XlaOp XlaHelpers::ArgMin(xla::XlaOp input, xla::PrimitiveType output_type, - int axis) { - return ArgMinMax(input, output_type, axis, /*is_min=*/true); -} - Status XlaHelpers::OneHot(xla::XlaBuilder* builder, int64 depth, int axis, DataType index_type, const TensorShape& indices_shape, const xla::XlaOp& indices, const xla::XlaOp& on_value, diff --git a/tensorflow/compiler/tf2xla/xla_helpers.h b/tensorflow/compiler/tf2xla/xla_helpers.h index 4858dfee55..490923526b 100644 --- a/tensorflow/compiler/tf2xla/xla_helpers.h +++ b/tensorflow/compiler/tf2xla/xla_helpers.h @@ -53,16 +53,6 @@ class XlaHelpers { absl::Span shape, xla::Literal* output); - // Returns the argmax of `input` along `axis`. `output_type` is the type to - // use for the output. - static xla::XlaOp ArgMax(xla::XlaOp input, xla::PrimitiveType output_type, - int axis); - - // Returns the argmin of `input` along `axis`. `output_type` is the type to - // use for the output. - static xla::XlaOp ArgMin(xla::XlaOp input, xla::PrimitiveType output_type, - int axis); - // Converts `indices` into a one-hot representation. `depth` is the size // of the new axis to add. `axis` is the position at which to add the new // axis. `indices_shape` is the shape of `indices`. `on_value` and diff --git a/tensorflow/compiler/xla/client/lib/BUILD b/tensorflow/compiler/xla/client/lib/BUILD index d863ebfea1..df1ee330f1 100644 --- a/tensorflow/compiler/xla/client/lib/BUILD +++ b/tensorflow/compiler/xla/client/lib/BUILD @@ -34,6 +34,21 @@ cc_library( ], ) +xla_test( + name = "arithmetic_test", + srcs = ["arithmetic_test.cc"], + deps = [ + ":arithmetic", + "//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 = "cholesky", srcs = ["cholesky.cc"], diff --git a/tensorflow/compiler/xla/client/lib/arithmetic.cc b/tensorflow/compiler/xla/client/lib/arithmetic.cc index 33ff3971d7..3b875135af 100644 --- a/tensorflow/compiler/xla/client/lib/arithmetic.cc +++ b/tensorflow/compiler/xla/client/lib/arithmetic.cc @@ -123,4 +123,64 @@ XlaOp Any(XlaOp predicates) { }); } +namespace { + +XlaOp ArgMinMax(XlaOp input, PrimitiveType output_type, int axis, bool is_min) { + XlaBuilder* builder = input.builder(); + return builder->ReportErrorOrReturn([&]() -> StatusOr { + TF_ASSIGN_OR_RETURN(Shape input_shape, builder->GetShape(input)); + XlaOp init_value; + XlaComputation reducer; + if (is_min) { + init_value = MaxValue(builder, input_shape.element_type()); + reducer = CreateScalarMinComputation(input_shape.element_type(), builder); + } else { + init_value = MinValue(builder, input_shape.element_type()); + reducer = CreateScalarMaxComputation(input_shape.element_type(), builder); + } + + XlaOp input_max = Reduce(input, init_value, reducer, + /*dimensions_to_reduce=*/{axis}); + std::vector broadcast_dims(input_shape.rank() - 1); + std::iota(broadcast_dims.begin(), broadcast_dims.begin() + axis, 0); + std::iota(broadcast_dims.begin() + axis, broadcast_dims.end(), axis + 1); + // Compute a mask that has 1s for elements equal to the maximum. + XlaOp partial_mask = + ConvertElementType(Eq(input, input_max, broadcast_dims), output_type); + + // In order to make identity elements for a bitwise And, we: + // Left shift the 1 to the leftmost bit, yielding 0x10...0 + // Arithmetic right shift the 1 back to the rightmost bit, yielding + // 0xFF...F + int32 bits_in_type = + ShapeUtil::ByteSizeOfPrimitiveType(output_type) * 8 - 1; + XlaOp shift_amount = ConstantR0WithType(builder, output_type, bits_in_type); + XlaOp full_mask = ShiftRightArithmetic( + ShiftLeft(partial_mask, shift_amount), shift_amount); + + // And with the vector [0, 1, 2, ...] to convert each 0xFF...F into its + // index. + + const int64 axis_size = ShapeUtil::GetDimension(input_shape, axis); + XlaOp iota = Iota(builder, output_type, axis_size); + XlaOp product = And(full_mask, iota, /*broadcast_dimensions=*/{axis}); + + // If there are multiple maximum elements, choose the one with the highest + // index. + return Reduce(product, MinValue(builder, output_type), + CreateScalarMaxComputation(output_type, builder), + /*dimensions_to_reduce=*/{axis}); + }); +} + +} // namespace + +XlaOp ArgMax(XlaOp input, PrimitiveType output_type, int axis) { + return ArgMinMax(input, output_type, axis, /*is_min=*/false); +} + +XlaOp ArgMin(XlaOp input, PrimitiveType output_type, int axis) { + return ArgMinMax(input, output_type, axis, /*is_min=*/true); +} + } // namespace xla diff --git a/tensorflow/compiler/xla/client/lib/arithmetic.h b/tensorflow/compiler/xla/client/lib/arithmetic.h index 632e8cc8bc..d4a7812c44 100644 --- a/tensorflow/compiler/xla/client/lib/arithmetic.h +++ b/tensorflow/compiler/xla/client/lib/arithmetic.h @@ -57,6 +57,14 @@ XlaComputation CreateScalarOrComputation(PrimitiveType type, // Note: if predicates is zero-sized, Any() vacuously returns false. XlaOp Any(XlaOp predicates); +// Returns the argmax of `input` along `axis`. `output_type` is the type to +// use for the output. +XlaOp ArgMax(XlaOp input, PrimitiveType output_type, int axis); + +// Returns the argmin of `input` along `axis`. `output_type` is the type to +// use for the output. +XlaOp ArgMin(XlaOp input, PrimitiveType output_type, int axis); + } // namespace xla #endif // TENSORFLOW_COMPILER_XLA_CLIENT_LIB_ARITHMETIC_H_ diff --git a/tensorflow/compiler/xla/client/lib/arithmetic_test.cc b/tensorflow/compiler/xla/client/lib/arithmetic_test.cc new file mode 100644 index 0000000000..a13839f9db --- /dev/null +++ b/tensorflow/compiler/xla/client/lib/arithmetic_test.cc @@ -0,0 +1,67 @@ +/* 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/arithmetic.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" +#include "tensorflow/compiler/xla/xla_data.pb.h" + +namespace xla { +namespace { + +using ArithmeticTest = ClientLibraryTestBase; + +XLA_TEST_F(ArithmeticTest, ArgMinR2Axis0) { + XlaBuilder builder(TestName()); + auto x = ConstantR2(&builder, {{1, 7, 4}, {6, 3, 5}, {8, 3, 3}}); + ArgMin(x, S32, /*axis=*/0); + + std::vector expected = {0, 2, 2}; + ComputeAndCompareR1(&builder, expected, {}); +} + +XLA_TEST_F(ArithmeticTest, ArgMinR2Axis1) { + XlaBuilder builder(TestName()); + auto x = ConstantR2(&builder, {{1, 7, 4}, {6, 3, 5}, {8, 3, 3}}); + ArgMin(x, S32, /*axis=*/1); + + std::vector expected = {0, 1, 2}; + ComputeAndCompareR1(&builder, expected, {}); +} + +XLA_TEST_F(ArithmeticTest, ArgMaxR2Axis0) { + XlaBuilder builder(TestName()); + auto x = ConstantR2(&builder, {{1, 7, 4}, {6, 3, 5}, {8, 3, 3}}); + ArgMax(x, S32, /*axis=*/0); + + std::vector expected = {2, 0, 1}; + ComputeAndCompareR1(&builder, expected, {}); +} + +XLA_TEST_F(ArithmeticTest, ArgMaxR2Axis1) { + XlaBuilder builder(TestName()); + auto x = ConstantR2(&builder, {{1, 7, 4}, {6, 3, 5}, {8, 3, 3}}); + ArgMax(x, S32, /*axis=*/1); + + std::vector expected = {1, 0, 0}; + ComputeAndCompareR1(&builder, expected, {}); +} + +} // namespace +} // namespace xla -- GitLab From 989c8b40a9b3b7bd45aedef43315675b0d4baf8d Mon Sep 17 00:00:00 2001 From: Adam Roberts Date: Tue, 8 Jan 2019 12:03:30 -0800 Subject: [PATCH 0359/2345] Disable writing examples/sec and global_step/sec summaries on TPU if save_summary_steps is Falsey. PiperOrigin-RevId: 228373932 --- tensorflow/contrib/tpu/python/tpu/tpu_estimator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py index 98babbb543..2317300041 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py @@ -2654,7 +2654,7 @@ class TPUEstimator(estimator_lib.Estimator): if self._log_every_n_steps is not None: examples_hook = ExamplesPerSecondHook( ctx.global_batch_size, - output_dir=self.model_dir, + output_dir=self.model_dir if config.save_summary_steps else None, every_n_steps=self._log_every_n_steps) if ctx.is_running_on_cpu(is_export_mode=is_export_mode): -- GitLab From 67d8a0330b5bacc0bb45675b1e3f211bd930ec7f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 12:18:59 -0800 Subject: [PATCH 0360/2345] Fixing flaky test in quantile regression PiperOrigin-RevId: 228376536 --- .../estimator_batch/estimator_test.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/estimator_test.py b/tensorflow/contrib/boosted_trees/estimator_batch/estimator_test.py index ee052ac603..47d910d42a 100644 --- a/tensorflow/contrib/boosted_trees/estimator_batch/estimator_test.py +++ b/tensorflow/contrib/boosted_trees/estimator_batch/estimator_test.py @@ -487,8 +487,8 @@ class BoostedTreeEstimatorTest(test_util.TensorFlowTestCase): self.assertTrue(frac_below_upper_0 <= 0.98) self.assertTrue(frac_below_upper_1 >= 0.92) self.assertTrue(frac_below_upper_1 <= 0.98) - self.assertTrue(frac_both_below_upper >= 0.92) - self.assertTrue(frac_both_below_upper <= 0.98) + self.assertTrue(frac_both_below_upper >= 0.91) + self.assertTrue(frac_both_below_upper <= 0.99) train_input_fn, test_input_fn, _ = _quantile_regression_input_fns( two_dimension=True) @@ -516,8 +516,8 @@ class BoostedTreeEstimatorTest(test_util.TensorFlowTestCase): self.assertTrue(frac_above_lower_0 <= 0.98) self.assertTrue(frac_above_lower_1 >= 0.92) self.assertTrue(frac_above_lower_1 <= 0.98) - self.assertTrue(frac_both_above_lower >= 0.92) - self.assertTrue(frac_both_above_lower <= 0.98) + self.assertTrue(frac_both_above_lower >= 0.91) + self.assertTrue(frac_both_above_lower <= 0.99) class CoreGradientBoostedDecisionTreeEstimators(test_util.TensorFlowTestCase): @@ -806,8 +806,8 @@ class CoreGradientBoostedDecisionTreeEstimators(test_util.TensorFlowTestCase): self.assertTrue(frac_below_upper_0 <= 0.98) self.assertTrue(frac_below_upper_1 >= 0.92) self.assertTrue(frac_below_upper_1 <= 0.98) - self.assertTrue(frac_both_below_upper >= 0.92) - self.assertTrue(frac_both_below_upper <= 0.98) + self.assertTrue(frac_both_below_upper >= 0.91) + self.assertTrue(frac_both_below_upper <= 0.99) train_input_fn, test_input_fn, _ = _quantile_regression_input_fns( two_dimension=True) @@ -835,8 +835,8 @@ class CoreGradientBoostedDecisionTreeEstimators(test_util.TensorFlowTestCase): self.assertTrue(frac_above_lower_0 <= 0.98) self.assertTrue(frac_above_lower_1 >= 0.92) self.assertTrue(frac_above_lower_1 <= 0.98) - self.assertTrue(frac_both_above_lower >= 0.92) - self.assertTrue(frac_both_above_lower <= 0.98) + self.assertTrue(frac_both_above_lower >= 0.91) + self.assertTrue(frac_both_above_lower <= 0.99) if __name__ == "__main__": -- GitLab From a3d639a5a46df8f3523bd514cade1faaf602b66d Mon Sep 17 00:00:00 2001 From: Blake Hechtman Date: Tue, 8 Jan 2019 12:22:54 -0800 Subject: [PATCH 0361/2345] [XLA] Canonicalize dot dimension numbers on CPU and GPU backends cases that previously failed shape inference. PiperOrigin-RevId: 228377214 --- tensorflow/compiler/xla/service/BUILD | 3 +- .../compiler/xla/service/dot_decomposer.cc | 174 +++++++++++++++++- tensorflow/compiler/xla/service/gpu/BUILD | 1 + .../xla/service/gpu/nvptx_compiler.cc | 2 + tensorflow/compiler/xla/tests/BUILD | 2 + .../compiler/xla/tests/dot_operation_test.cc | 34 ++++ 6 files changed, 208 insertions(+), 8 deletions(-) diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index ec63ae1ac6..728adb67a0 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -1873,8 +1873,9 @@ cc_library( "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:status_macros", "//tensorflow/compiler/xla:types", - "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/core:lib", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/strings", ], ) diff --git a/tensorflow/compiler/xla/service/dot_decomposer.cc b/tensorflow/compiler/xla/service/dot_decomposer.cc index b2ba261790..855424067d 100644 --- a/tensorflow/compiler/xla/service/dot_decomposer.cc +++ b/tensorflow/compiler/xla/service/dot_decomposer.cc @@ -15,6 +15,8 @@ limitations under the License. #include "tensorflow/compiler/xla/service/dot_decomposer.h" +#include "absl/algorithm/container.h" +#include "absl/strings/str_join.h" #include "tensorflow/compiler/xla/service/hlo_computation.h" #include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/service/hlo_opcode.h" @@ -156,29 +158,187 @@ Status DecomposeBatchDot(HloInstruction* dot) { return computation->ReplaceInstruction(dot, new_dot); } +// Convert a dot into a canonical form where non-contracting and contracting +// dimensions are reshaped together and batch dimensions are the most major +// dimensions. The requires transposing and reshapes the lhs and rhs and +// reshaping the output batch to the original shape. +Status CanonicalizeDot(HloInstruction* original_dot) { + auto computation = original_dot->parent(); + const auto& original_dnums = original_dot->dot_dimension_numbers(); + const int64 num_batch_dims = original_dnums.lhs_batch_dimensions_size(); + const int64 num_contracting_dims = + original_dnums.lhs_contracting_dimensions_size(); + + const auto& lhs_shape = original_dot->operand(0)->shape(); + const int64 lhs_rank = lhs_shape.rank(); + const int64 num_lhs_non_contracting_dims = + lhs_rank - num_batch_dims - num_contracting_dims; + + std::vector lhs_non_contracting_dims; + lhs_non_contracting_dims.reserve(num_lhs_non_contracting_dims); + int64 lhs_contracting_size = 1; + int64 lhs_non_contracting_size = 1; + std::vector batch_dim_sizes; + batch_dim_sizes.reserve(num_batch_dims); + for (int64 i = 0; i < lhs_rank; ++i) { + if (absl::c_linear_search(original_dnums.lhs_contracting_dimensions(), i)) { + lhs_contracting_size *= lhs_shape.dimensions(i); + } else if (absl::c_linear_search(original_dnums.lhs_batch_dimensions(), + i)) { + batch_dim_sizes.push_back(lhs_shape.dimensions(i)); + } else { + lhs_non_contracting_dims.push_back(i); + lhs_non_contracting_size *= lhs_shape.dimensions(i); + } + } + // The canonical form of the lhs is + // [BatchDims, NonContractingDims, ContractingsDims] + std::vector lhs_transpose; + lhs_transpose.reserve(lhs_rank); + lhs_transpose.insert(lhs_transpose.end(), + original_dnums.lhs_batch_dimensions().begin(), + original_dnums.lhs_batch_dimensions().end()); + lhs_transpose.insert(lhs_transpose.end(), lhs_non_contracting_dims.begin(), + lhs_non_contracting_dims.end()); + lhs_transpose.insert(lhs_transpose.end(), + original_dnums.lhs_contracting_dimensions().begin(), + original_dnums.lhs_contracting_dimensions().end()); + HloInstruction* transposed_lhs = + computation->AddInstruction(HloInstruction::CreateTranspose( + ShapeUtil::PermuteDimensions(InversePermutation(lhs_transpose), + lhs_shape), + original_dot->mutable_operand(0), lhs_transpose)); + std::vector lhs_reshape_dims = batch_dim_sizes; + lhs_reshape_dims.push_back(lhs_non_contracting_size); + lhs_reshape_dims.push_back(lhs_contracting_size); + // Reshape the contracting and non-contracting dimensions together. + HloInstruction* reshaped_lhs = + computation->AddInstruction(HloInstruction::CreateReshape( + ShapeUtil::MakeShape(lhs_shape.element_type(), lhs_reshape_dims), + transposed_lhs)); + + const auto& rhs_shape = original_dot->operand(1)->shape(); + const int64 rhs_rank = rhs_shape.rank(); + const int64 num_rhs_non_contracting_dims = + rhs_rank - num_batch_dims - num_contracting_dims; + std::vector rhs_non_contracting_dims; + rhs_non_contracting_dims.reserve(num_rhs_non_contracting_dims); + int64 rhs_non_contracting_size = 1; + int64 rhs_contracting_size = 1; + for (int64 i = 0; i < rhs_rank; ++i) { + if (absl::c_linear_search(original_dnums.rhs_contracting_dimensions(), i)) { + rhs_contracting_size *= rhs_shape.dimensions(i); + } else if (!absl::c_linear_search(original_dnums.rhs_batch_dimensions(), + i)) { + rhs_non_contracting_dims.push_back(i); + rhs_non_contracting_size *= rhs_shape.dimensions(i); + } + } + + // The canonical form of the rhs is + // [BatchDims, ContractingsDims, NonContractingDims] + std::vector rhs_transpose; + rhs_transpose.reserve(rhs_rank); + rhs_transpose.insert(rhs_transpose.end(), + original_dnums.rhs_batch_dimensions().begin(), + original_dnums.rhs_batch_dimensions().end()); + rhs_transpose.insert(rhs_transpose.end(), + original_dnums.rhs_contracting_dimensions().begin(), + original_dnums.rhs_contracting_dimensions().end()); + rhs_transpose.insert(rhs_transpose.end(), rhs_non_contracting_dims.begin(), + rhs_non_contracting_dims.end()); + HloInstruction* transposed_rhs = + computation->AddInstruction(HloInstruction::CreateTranspose( + ShapeUtil::PermuteDimensions(InversePermutation(rhs_transpose), + rhs_shape), + original_dot->mutable_operand(1), rhs_transpose)); + + std::vector rhs_reshape_dims = batch_dim_sizes; + rhs_reshape_dims.push_back(rhs_contracting_size); + rhs_reshape_dims.push_back(rhs_non_contracting_size); + // Reshape the contracting and non-contracting dimensions together. + HloInstruction* reshaped_rhs = + computation->AddInstruction(HloInstruction::CreateReshape( + ShapeUtil::MakeShape(rhs_shape.element_type(), rhs_reshape_dims), + transposed_rhs)); + + std::vector dot_dims = batch_dim_sizes; + dot_dims.push_back(lhs_non_contracting_size); + dot_dims.push_back(rhs_non_contracting_size); + + DotDimensionNumbers dot_dnums; + for (int64 i = 0; i < num_batch_dims; ++i) { + dot_dnums.add_lhs_batch_dimensions(i); + dot_dnums.add_rhs_batch_dimensions(i); + } + dot_dnums.add_lhs_contracting_dimensions(num_batch_dims + 1); + dot_dnums.add_rhs_contracting_dimensions(num_batch_dims); + + HloInstruction* dot = computation->AddInstruction(HloInstruction::CreateDot( + ShapeUtil::MakeShape(original_dot->shape().element_type(), dot_dims), + reshaped_lhs, reshaped_rhs, dot_dnums, original_dot->precision_config())); + + return computation->ReplaceInstruction( + original_dot, computation->AddInstruction(HloInstruction::CreateReshape( + original_dot->shape(), dot))); +} + } // namespace StatusOr DotDecomposer::Run(HloModule* module) { XLA_VLOG_LINES(2, "DotDecomposer ENTRY\n" + module->ToString()); - // Gather all batch Dot operations. - std::vector batch_dots; + // Gather all Non-canonical Dot operations. + std::vector non_canonical_dots; for (auto* computation : module->MakeNonfusionComputations()) { for (auto* instruction : computation->instructions()) { if (instruction->opcode() != HloOpcode::kDot) { continue; } const DotDimensionNumbers& dnums = instruction->dot_dimension_numbers(); - if (dnums.lhs_batch_dimensions_size() > 0 && decompose_batch_dot_) { - batch_dots.push_back(instruction); + // A dot it not canonical if there are more than one contracting + // dimension. + if (dnums.lhs_contracting_dimensions_size() > 1) { + non_canonical_dots.push_back(instruction); + continue; + } + if (dnums.lhs_batch_dimensions().empty()) { + continue; + } + std::vector canonical_batch_dims( + dnums.lhs_batch_dimensions_size()); + absl::c_iota(canonical_batch_dims, 0); + if (!absl::c_equal(dnums.lhs_batch_dimensions(), canonical_batch_dims) || + !absl::c_equal(dnums.rhs_batch_dimensions(), canonical_batch_dims)) { + non_canonical_dots.push_back(instruction); } } } - // Decompose each batch Dot in 'batch_dots'. bool changed = false; - for (auto* dot : batch_dots) { - TF_RETURN_IF_ERROR(DecomposeBatchDot(dot)); + for (auto* dot : non_canonical_dots) { + TF_RETURN_IF_ERROR(CanonicalizeDot(dot)); changed = true; } + + if (decompose_batch_dot_) { + std::vector batch_dots; + for (auto* computation : module->MakeNonfusionComputations()) { + for (auto* instruction : computation->instructions()) { + if (instruction->opcode() != HloOpcode::kDot) { + continue; + } + const DotDimensionNumbers& dnums = instruction->dot_dimension_numbers(); + if (!dnums.lhs_batch_dimensions().empty()) { + batch_dots.push_back(instruction); + } + } + } + // Decompose each batch Dot in 'batch_dots'. + + for (auto* dot : batch_dots) { + TF_RETURN_IF_ERROR(DecomposeBatchDot(dot)); + changed = true; + } + } XLA_VLOG_LINES(2, "DotDecompose EXIT\n" + module->ToString()); return changed; } diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index 5e81281bae..30a8bda606 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -699,6 +699,7 @@ cc_library( "//tensorflow/compiler/xla/service:call_inliner", "//tensorflow/compiler/xla/service:conditional_simplifier", "//tensorflow/compiler/xla/service:convolution_group_converter", + "//tensorflow/compiler/xla/service:dot_decomposer", "//tensorflow/compiler/xla/service:dynamic_index_splitter", "//tensorflow/compiler/xla/service:executable", "//tensorflow/compiler/xla/service:flatten_call_graph", diff --git a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc index 9be7609508..d1522280ba 100644 --- a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc +++ b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc @@ -37,6 +37,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/call_inliner.h" #include "tensorflow/compiler/xla/service/conditional_simplifier.h" #include "tensorflow/compiler/xla/service/convolution_group_converter.h" +#include "tensorflow/compiler/xla/service/dot_decomposer.h" #include "tensorflow/compiler/xla/service/dynamic_index_splitter.h" #include "tensorflow/compiler/xla/service/flatten_call_graph.h" #include "tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.h" @@ -165,6 +166,7 @@ Status OptimizeHloModule(HloModule* hlo_module, se::StreamExecutor* stream_exec, // We need a cost model for GPUs. Currently, do nothing. return false; }; + pipeline.AddPass(false); pipeline.AddPass( cost_model, /*convert_batch_groups_only=*/true); diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 106b3fd6c9..0fd0fc108a 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -711,6 +711,7 @@ xla_test( "//tensorflow/compiler/xla/client:local_client", "//tensorflow/compiler/xla/client:xla_builder", "//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", @@ -768,6 +769,7 @@ xla_test( "//tensorflow/compiler/xla/client:local_client", "//tensorflow/compiler/xla/client:xla_builder", "//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", diff --git a/tensorflow/compiler/xla/tests/dot_operation_test.cc b/tensorflow/compiler/xla/tests/dot_operation_test.cc index c5d8b663f4..2e02968ac5 100644 --- a/tensorflow/compiler/xla/tests/dot_operation_test.cc +++ b/tensorflow/compiler/xla/tests/dot_operation_test.cc @@ -25,6 +25,7 @@ limitations under the License. #include "tensorflow/compiler/xla/reference_util.h" #include "tensorflow/compiler/xla/shape_util.h" #include "tensorflow/compiler/xla/tests/client_library_test_base.h" +#include "tensorflow/compiler/xla/tests/hlo_test_base.h" #include "tensorflow/compiler/xla/tests/literal_test_util.h" #include "tensorflow/compiler/xla/tests/test_macros.h" #include "tensorflow/compiler/xla/tests/test_utils.h" @@ -1147,5 +1148,38 @@ XLA_TEST_F(DotOperationTest, DotRank2AndRank2NonDefaultContractionDims) { ComputeAndCompareR2(&builder, expected, {}, error_spec_); } + +class DotOperationTextTest : public HloTestBase {}; + +XLA_TEST_F(DotOperationTextTest, DotReorderedDotDims) { + absl::string_view hlo_string = + R"( +HloModule ComplexDotMultipleNonContracting + +ENTRY %test { + %lhs = f32[7,17,10,13]{3,2,1,0} parameter(0) + %rhs = f32[7,9,10,13,6]{4,3,2,1,0} parameter(1) + ROOT %dot = f32[10,7,17,9,6]{4,3,2,1,0} dot(%lhs, %rhs), lhs_batch_dims={2,0}, rhs_batch_dims={2,0}, lhs_contracting_dims={3}, rhs_contracting_dims={3} +} +)"; + + EXPECT_TRUE(RunAndCompare(hlo_string, ErrorSpec{1e-3, 1e-3})); +} + +XLA_TEST_F(DotOperationTextTest, DotReorderedDotDimsAndMultipleContracting) { + absl::string_view hlo_string = + R"( +HloModule ComplexDotMultipleNonContracting + +ENTRY %test { + %lhs = f32[7,5,17,10,13]{4,3,2,1,0} parameter(0) + %rhs = f32[7,9,10,13,6,5]{5,4,3,2,1,0} parameter(1) + ROOT %dot = f32[10,7,17,9,6]{4,3,2,1,0} dot(%lhs, %rhs), lhs_batch_dims={3,0}, rhs_batch_dims={2,0}, lhs_contracting_dims={1,4}, rhs_contracting_dims={5,3} +} +)"; + + EXPECT_TRUE(RunAndCompare(hlo_string, ErrorSpec{1e-3, 1e-3})); +} + } // namespace } // namespace xla -- GitLab From 495b3eeef0386b1b89b7aa9df42f2cf438de6ebc Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 12:55:56 -0800 Subject: [PATCH 0362/2345] Enable fast GPU code path for solving many small linear systems in matrix_solve. PiperOrigin-RevId: 228382682 --- tensorflow/core/kernels/cuda_solvers.cc | 12 +++++----- tensorflow/core/kernels/cuda_solvers.h | 5 ++-- tensorflow/core/kernels/matrix_solve_op.cc | 28 +++++++++++++--------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/tensorflow/core/kernels/cuda_solvers.cc b/tensorflow/core/kernels/cuda_solvers.cc index a59baaa96f..39d0a998fd 100644 --- a/tensorflow/core/kernels/cuda_solvers.cc +++ b/tensorflow/core/kernels/cuda_solvers.cc @@ -692,8 +692,8 @@ static inline Status GetrsBatchedImpl( SolverFnT solver, CudaSolver* cuda_solver, OpKernelContext* context, cublasHandle_t cublas_handle, cublasOperation_t trans, int n, int nrhs, const Scalar* const host_a_dev_ptrs[], int lda, const int* dev_pivots, - const Scalar* const host_b_dev_ptrs[], int ldb, - DeviceLapackInfo* dev_lapack_info, int batch_size) { + const Scalar* const host_b_dev_ptrs[], int ldb, int* host_lapack_info, + int batch_size) { mutex_lock lock(handle_map_mutex); using CudaScalar = typename CUDAComplexT::type; ScratchSpace dev_a_dev_ptrs = @@ -714,7 +714,7 @@ static inline Status GetrsBatchedImpl( cublas_handle, trans, n, nrhs, reinterpret_cast(dev_a_dev_ptrs.data()), lda, dev_pivots, reinterpret_cast(dev_b_dev_ptrs.mutable_data()), - ldb, dev_lapack_info->mutable_data(), batch_size)); + ldb, host_lapack_info, batch_size)); return Status::OK(); } @@ -723,13 +723,13 @@ static inline Status GetrsBatchedImpl( Status CudaSolver::GetrsBatched( \ cublasOperation_t trans, int n, int nrhs, \ const Scalar* const host_a_dev_ptrs[], int lda, const int* dev_pivots, \ - const Scalar* const host_b_dev_ptrs[], int ldb, \ - DeviceLapackInfo* dev_lapack_info, int batch_size) { \ + const Scalar* const host_b_dev_ptrs[], int ldb, int* host_lapack_info, \ + int batch_size) { \ return GetrsBatchedImpl(reinterpret_cast( \ BLAS_SOLVER_FN(getrsBatched, type_prefix)), \ this, context_, cublas_handle_, trans, n, nrhs, \ host_a_dev_ptrs, lda, dev_pivots, host_b_dev_ptrs, \ - ldb, dev_lapack_info, batch_size); \ + ldb, host_lapack_info, batch_size); \ } TF_CALL_LAPACK_TYPES(GETRS_BATCHED_INSTANCE); diff --git a/tensorflow/core/kernels/cuda_solvers.h b/tensorflow/core/kernels/cuda_solvers.h index 2c30d036df..1fc344731c 100644 --- a/tensorflow/core/kernels/cuda_solvers.h +++ b/tensorflow/core/kernels/cuda_solvers.h @@ -235,13 +235,14 @@ class CudaSolver { int batch_size) TF_MUST_USE_RESULT; // Batched linear solver using LU factorization from getrfBatched. - // See: + // Notice that lapack_info is returned on the host, as opposed to + // most of the other functions that return it on the device. See: // http://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-getrsbatched template Status GetrsBatched(cublasOperation_t trans, int n, int nrhs, const Scalar* const dev_Aarray[], int lda, const int* devIpiv, const Scalar* const dev_Barray[], - int ldb, DeviceLapackInfo* dev_lapack_info, + int ldb, int* host_lapack_info, int batch_size) TF_MUST_USE_RESULT; // Computes matrix inverses for a batch of small matrices. Uses the outputs diff --git a/tensorflow/core/kernels/matrix_solve_op.cc b/tensorflow/core/kernels/matrix_solve_op.cc index 169f3dae76..f3919a16aa 100644 --- a/tensorflow/core/kernels/matrix_solve_op.cc +++ b/tensorflow/core/kernels/matrix_solve_op.cc @@ -214,9 +214,12 @@ class MatrixSolveOpGpu : public AsyncOpKernel { auto input_copy_ptrs = solver->GetScratchSpace( sizeof(Scalar*) * batch_size, "input_copt_ptrs", /* on_host */ true); - if (n / batch_size <= 128) { - // For small matrices or large batch sizes, we use the batched - // interface from cuBlas. + const int kMaxMatrixSizeToBatchSizeRatio = 128; + const bool use_batched_solver = + n <= kMaxMatrixSizeToBatchSizeRatio * batch_size; + if (use_batched_solver) { + // For small matrices or large batch sizes, we use the batched interface + // from cuBlas. const Scalar** input_copy_ptrs_base = reinterpret_cast(input_copy_ptrs.mutable_data()); for (int batch = 0; batch < batch_size; ++batch) { @@ -230,8 +233,8 @@ class MatrixSolveOpGpu : public AsyncOpKernel { &dev_info.back(), batch_size), done); } else { - // For small batch sizes we use the non-batched interface from cuSolver, - // which is much faster for large matrices. + // For small batch sizes or large matrices, we use the non-batched + // interface from cuSolver, which is much faster for large matrices. dev_info.push_back(solver->GetDeviceLapackInfo(batch_size, "getrf")); for (int batch = 0; batch < batch_size; ++batch) { OP_REQUIRES_OK_ASYNC( @@ -279,11 +282,7 @@ class MatrixSolveOpGpu : public AsyncOpKernel { /* on_host */ true); auto transposed_rhs_reshaped = transposed_rhs.template flat_inner_dims(); - // TODO(rmlarsen): Enable the following branch when I figure - // out why it causes a segfault. - if (false && n / batch_size <= 128) { - dev_info.push_back( - solver->GetDeviceLapackInfo(batch_size, "GetrsBatched")); + if (use_batched_solver) { const Scalar** input_copy_ptrs_base = reinterpret_cast(input_copy_ptr_array.mutable_data()); const Scalar** transposed_rhs_ptrs_base = @@ -293,13 +292,20 @@ class MatrixSolveOpGpu : public AsyncOpKernel { input_copy_ptrs_base[batch] = &input_copy_reshaped(batch, 0, 0); transposed_rhs_ptrs_base[batch] = &transposed_rhs_reshaped(batch, 0, 0); } + int host_info = 0; OP_REQUIRES_OK_ASYNC( context, solver->GetrsBatched(adjoint_ ? CUBLAS_OP_C : CUBLAS_OP_T, n, nrhs, input_copy_ptrs_base, n, pivots_mat.data(), - transposed_rhs_ptrs_base, n, &dev_info.back(), + transposed_rhs_ptrs_base, n, &host_info, batch_size), done); + OP_REQUIRES_ASYNC( + context, host_info == 0, + errors::InvalidArgument("The ", -host_info, + "'th argument to cublas*getrsBatched had " + "an illegal value."), + done); } else { dev_info.push_back(solver->GetDeviceLapackInfo(batch_size, "getrs")); for (int batch = 0; batch < batch_size; ++batch) { -- GitLab From de86e2fff13c5b2c41297ef923c2d60e5a7297e8 Mon Sep 17 00:00:00 2001 From: Thomas Deegan Date: Tue, 8 Jan 2019 13:29:33 -0800 Subject: [PATCH 0363/2345] Fix broken image The image tag in this markdown file seems to be broken when viewed on github. --- tensorflow/core/profiler/g3doc/profile_memory.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tensorflow/core/profiler/g3doc/profile_memory.md b/tensorflow/core/profiler/g3doc/profile_memory.md index 6eda5abdd9..a2c7cc6289 100644 --- a/tensorflow/core/profiler/g3doc/profile_memory.md +++ b/tensorflow/core/profiler/g3doc/profile_memory.md @@ -14,9 +14,7 @@ Open a Chrome browser, enter URL chrome://tracing and load the timeline file. ****************************************************** ``` - ![Timeline](graph_timeline.png) - ```python @@ -77,4 +75,4 @@ _TFProfRoot (--/74148.60MB) seq2seq_attention_model.py:320:_add_train_op:tf.summary.scalar... (0B/64B) seq2seq_attention_model.py:360:build_graph:self._add_seq2seq() (0B/25216.74MB) seq2seq_attention_model.py:192:_add_seq2seq:sequence_length=a... (0B/21542.55MB) -``` \ No newline at end of file +``` -- GitLab From 01dd371b8441aa96536732b804f1c5853fb8af6e Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Tue, 8 Jan 2019 13:29:19 -0800 Subject: [PATCH 0364/2345] remove JackOptions block. PiperOrigin-RevId: 228388690 --- tensorflow/lite/g3doc/demo_ios.md | 45 ++++++++++-------- .../g3doc/images/ios/bundle_identifier.png | Bin 0 -> 64902 bytes 2 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 tensorflow/lite/g3doc/images/ios/bundle_identifier.png diff --git a/tensorflow/lite/g3doc/demo_ios.md b/tensorflow/lite/g3doc/demo_ios.md index fbf1dd6392..f4b481dc61 100644 --- a/tensorflow/lite/g3doc/demo_ios.md +++ b/tensorflow/lite/g3doc/demo_ios.md @@ -7,22 +7,23 @@ instructions walk you through building and running the demo on an iOS device. ## Prerequisites -* You must have [Xcode](https://developer.apple.com/xcode/) installed and have a - valid Apple Developer ID, and have an iOS device set up and linked to your - developer account with all of the appropriate certificates. For these - instructions, we assume that you have already been able to build and deploy an - app to an iOS device with your current developer environment. +* You must have [Xcode](https://developer.apple.com/xcode/) installed and have + a valid Apple Developer ID, and have an iOS device set up and linked to your + developer account with all of the appropriate certificates. For these + instructions, we assume that you have already been able to build and deploy + an app to an iOS device with your current developer environment. -* The demo app requires a camera and must be executed on a real iOS device. You - can build it and run with the iPhone Simulator but it won't have any camera - information to classify. +* The demo app requires a camera and must be executed on a real iOS device. + You can build it and run with the iPhone Simulator but it won't have any + camera information to classify. -* You don't need to build the entire TensorFlow library to run the demo, but you - will need to clone the TensorFlow repository if you haven't already: +* You don't need to build the entire TensorFlow library to run the demo, but + you will need to clone the TensorFlow repository if you haven't already: git clone https://github.com/tensorflow/tensorflow + cd tensorflow -* You'll also need the Xcode command-line tools: +* You'll also need the Xcode command-line tools: xcode-select --install @@ -31,35 +32,41 @@ instructions walk you through building and running the demo on an iOS device. ## Building the iOS Demo App -1. Install CocoaPods if you don't have it: +1. Install CocoaPods if you don't have it: sudo gem install cocoapods -2. Download the model files used by the demo app (this is done from inside the - cloned directory): +2. Download the model files used by the demo app (this is done from inside the + cloned directory): sh tensorflow/lite/examples/ios/download_models.sh -3. Install the pod to generate the workspace file: +3. Install the pod to generate the workspace file: cd tensorflow/lite/examples/ios/camera pod install If you have installed this pod before and that command doesn't work, try - pod update + pod repo update - At the end of this step you should have a file called + At the end of this step you should have a file called `tflite_camera_example.xcworkspace`. -4. Open the project in Xcode by typing this on the command line: +4. Open the project in Xcode by typing this on the command line: open tflite_camera_example.xcworkspace This launches Xcode if it isn't open already and opens the `tflite_camera_example` project. -5. Build and run the app in Xcode. +5. Under `Project navigator -> 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/ios/bundle_identifier.png) + +6. Build and run the app in Xcode. 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 on a diff --git a/tensorflow/lite/g3doc/images/ios/bundle_identifier.png b/tensorflow/lite/g3doc/images/ios/bundle_identifier.png new file mode 100644 index 0000000000000000000000000000000000000000..398763916b353e61f236392e2b8898aad2aafe8e GIT binary patch literal 64902 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 Ae*gdg literal 0 HcmV?d00001 -- GitLab From 9a9f67ec10c4ad5e695df0f7d1c63a69f763fd56 Mon Sep 17 00:00:00 2001 From: Grzegorz Pawelczak Date: Tue, 8 Jan 2019 21:37:08 +0000 Subject: [PATCH 0365/2345] Add SortSimplifier pass to compiler pipelines --- tensorflow/compiler/xla/service/cpu/BUILD | 1 + tensorflow/compiler/xla/service/cpu/cpu_compiler.cc | 2 ++ tensorflow/compiler/xla/service/gpu/BUILD | 1 + tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc | 2 ++ tensorflow/compiler/xla/service/sort_simplifier.cc | 7 ++++--- 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index 1eb42cf5ad..e325b75260 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -133,6 +133,7 @@ cc_library( "//tensorflow/compiler/xla/service:llvm_compiler", "//tensorflow/compiler/xla/service:reduce_precision_insertion", "//tensorflow/compiler/xla/service:reshape_mover", + "//tensorflow/compiler/xla/service:sort_simplifier", "//tensorflow/compiler/xla/service:transpose_folding", "//tensorflow/compiler/xla/service:tuple_simplifier", "//tensorflow/compiler/xla/service:while_loop_constant_sinking", diff --git a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc index a6d92ce10e..60e39506bf 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc @@ -93,6 +93,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/reduce_precision_insertion.h" #include "tensorflow/compiler/xla/service/reshape_mover.h" #include "tensorflow/compiler/xla/service/scatter_expander.h" +#include "tensorflow/compiler/xla/service/sort_simplifier.h" #include "tensorflow/compiler/xla/service/transpose_folding.h" #include "tensorflow/compiler/xla/service/tuple_simplifier.h" #include "tensorflow/compiler/xla/service/while_loop_constant_sinking.h" @@ -285,6 +286,7 @@ Status CpuCompiler::RunHloPassesThroughLayoutAssn( [](const Shape&, const Shape&) { return false; }); options.set_enable_dot_strength_reduction(false); pass.AddPass(options); + pass.AddPass(); pass.AddPass(); // BatchNormExpander can create zero-sized ops, so zero-sized HLO diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index 5e81281bae..175f2aafc8 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -717,6 +717,7 @@ cc_library( "//tensorflow/compiler/xla/service:llvm_compiler", "//tensorflow/compiler/xla/service:reduce_precision_insertion", "//tensorflow/compiler/xla/service:reshape_mover", + "//tensorflow/compiler/xla/service:sort_simplifier", "//tensorflow/compiler/xla/service:transpose_folding", "//tensorflow/compiler/xla/service:tuple_simplifier", "//tensorflow/compiler/xla/service:while_loop_constant_sinking", diff --git a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc index 9be7609508..e1bf0d0547 100644 --- a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc +++ b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc @@ -79,6 +79,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/llvm_ir/llvm_util.h" #include "tensorflow/compiler/xla/service/reduce_precision_insertion.h" #include "tensorflow/compiler/xla/service/reshape_mover.h" +#include "tensorflow/compiler/xla/service/sort_simplifier.h" #include "tensorflow/compiler/xla/service/transpose_folding.h" #include "tensorflow/compiler/xla/service/tuple_simplifier.h" #include "tensorflow/compiler/xla/service/while_loop_constant_sinking.h" @@ -200,6 +201,7 @@ Status OptimizeHloModule(HloModule* hlo_module, se::StreamExecutor* stream_exec, [](const Shape&, const Shape&) { return false; }); options.set_enable_permutation_sort_replacement(true); pass.AddPass(options); + pass.AddPass(); pass.AddPass(); pass.AddPass(); pass.AddPass(); diff --git a/tensorflow/compiler/xla/service/sort_simplifier.cc b/tensorflow/compiler/xla/service/sort_simplifier.cc index 3280672574..ee91762253 100644 --- a/tensorflow/compiler/xla/service/sort_simplifier.cc +++ b/tensorflow/compiler/xla/service/sort_simplifier.cc @@ -26,13 +26,15 @@ namespace { // If the sort instruction has a tuple shape then looks for unused output // values and removes them from the sort instruction. Returns true if the -// graph have been modified. +// graph has been modified. StatusOr RemoveUnusedOperandFromSort(HloInstruction* sort) { if (!sort->shape().IsTuple()) { return false; } - if (sort->parent()->root_instruction() == sort) { + HloComputation* computation = sort->parent(); + + if (computation->root_instruction() == sort) { // Can't analyse users of the root instruction. return false; } @@ -60,7 +62,6 @@ StatusOr RemoveUnusedOperandFromSort(HloInstruction* sort) { new_shapes.push_back(sort->operand(i)->shape()); } } - HloComputation* computation = sort->parent(); Shape new_sort_shape = new_shapes.size() == 1 ? new_shapes[0] -- GitLab From 530d1b267f124fdb7dd6ce65b94423b8801152cd Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Tue, 8 Jan 2019 13:29:30 -0800 Subject: [PATCH 0366/2345] Add fine-grained BUILD files for stream executor. PiperOrigin-RevId: 228388744 --- tensorflow/compiler/xla/BUILD | 2 +- tensorflow/compiler/xla/service/cpu/BUILD | 1 + tensorflow/compiler/xla/service/gpu/BUILD | 1 + .../compiler/xla/service/interpreter/BUILD | 2 + tensorflow/compiler/xrt/kernels/BUILD | 2 +- tensorflow/core/kernels/BUILD | 3 + .../core/platform/default/build_config/BUILD | 39 +- tensorflow/opensource_only.files | 2 +- tensorflow/stream_executor/BUILD | 703 +++++++++++++++--- tensorflow/stream_executor/build_defs.bzl | 11 + tensorflow/stream_executor/cuda/BUILD | 365 +++++++++ tensorflow/stream_executor/host/BUILD | 108 +++ tensorflow/stream_executor/lib/BUILD | 62 ++ tensorflow/stream_executor/platform/BUILD | 47 ++ .../stream_executor/platform/default/BUILD | 25 + 15 files changed, 1277 insertions(+), 96 deletions(-) create mode 100644 tensorflow/stream_executor/build_defs.bzl create mode 100644 tensorflow/stream_executor/cuda/BUILD create mode 100644 tensorflow/stream_executor/host/BUILD create mode 100644 tensorflow/stream_executor/lib/BUILD create mode 100644 tensorflow/stream_executor/platform/BUILD create mode 100644 tensorflow/stream_executor/platform/default/BUILD diff --git a/tensorflow/compiler/xla/BUILD b/tensorflow/compiler/xla/BUILD index 45623c2718..aa43fc8dab 100644 --- a/tensorflow/compiler/xla/BUILD +++ b/tensorflow/compiler/xla/BUILD @@ -152,7 +152,7 @@ cc_library( ":status", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/stream_executor", + "//tensorflow/stream_executor/lib", ], ) diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index 1eb42cf5ad..de62aa60aa 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -241,6 +241,7 @@ cc_library( "//tensorflow/compiler/xla/service:tuple_points_to_analysis", "//tensorflow/core:lib", "//tensorflow/core:stream_executor_no_cuda", + "//tensorflow/stream_executor/host:host_stream", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index 30a8bda606..2be5d918ee 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -731,6 +731,7 @@ cc_library( "//tensorflow/core:lib_internal", "//tensorflow/core:regexp_internal", "//tensorflow/core:stream_executor_no_cuda", + "//tensorflow/stream_executor/cuda:cuda_diagnostics", "@com_google_absl//absl/container:node_hash_map", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", diff --git a/tensorflow/compiler/xla/service/interpreter/BUILD b/tensorflow/compiler/xla/service/interpreter/BUILD index 2e1d2cfbda..9b4cafa4b4 100644 --- a/tensorflow/compiler/xla/service/interpreter/BUILD +++ b/tensorflow/compiler/xla/service/interpreter/BUILD @@ -119,6 +119,8 @@ cc_library( "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/core:lib", "//tensorflow/core:stream_executor_headers_lib", + "//tensorflow/stream_executor/host:host_stream", + "//tensorflow/stream_executor/host:host_timer", "@com_google_absl//absl/types:span", ], ) diff --git a/tensorflow/compiler/xrt/kernels/BUILD b/tensorflow/compiler/xrt/kernels/BUILD index 67f475846e..78d093ec4a 100644 --- a/tensorflow/compiler/xrt/kernels/BUILD +++ b/tensorflow/compiler/xrt/kernels/BUILD @@ -62,7 +62,7 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", - "//tensorflow/stream_executor:stream_executor_headers_lib", + "//tensorflow/stream_executor:stream_executor_headers", "@com_google_absl//absl/strings", ], alwayslink = 1, diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index aaea09bdc4..5794ba520e 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -2313,6 +2313,7 @@ tf_kernel_library( "//tensorflow/core/grappler/clusters:virtual_cluster", "//tensorflow/core/grappler/optimizers:meta_optimizer", "//tensorflow/core/grappler/utils:functions", + "//tensorflow/stream_executor:stream", ], ) @@ -3819,6 +3820,8 @@ tf_kernel_library( deps = NN_DEPS + if_cuda([ ":reduction_ops", "@cub_archive//:cub", + "//tensorflow/core:stream_executor", + "//tensorflow/stream_executor/cuda:cuda_stream", ]), ) diff --git a/tensorflow/core/platform/default/build_config/BUILD b/tensorflow/core/platform/default/build_config/BUILD index da1f66dc67..f5a194428c 100644 --- a/tensorflow/core/platform/default/build_config/BUILD +++ b/tensorflow/core/platform/default/build_config/BUILD @@ -32,15 +32,25 @@ cc_library( tf_cuda_library( name = "stream_executor", - deps = [ - "//tensorflow/stream_executor", + cuda_deps = [ + "//tensorflow/stream_executor/cuda:cuda_activation", ] + select({ - "//tensorflow:using_cuda_clang": ["//tensorflow/stream_executor:cuda_platform"], - "//tensorflow:using_cuda_nvcc": ["//tensorflow/stream_executor:cuda_platform"], + "//tensorflow:using_cuda_clang": ["//tensorflow/stream_executor/cuda:all_runtime"], + "//tensorflow:using_cuda_nvcc": ["//tensorflow/stream_executor/cuda:all_runtime"], "//tensorflow:using_cuda_clang_with_dynamic_build": [], "//tensorflow:using_cuda_nvcc_with_dynamic_build": [], "//conditions:default": [], - }) + select({ + }), + deps = [ + "//tensorflow/stream_executor", + "//tensorflow/stream_executor:dnn", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:multi_platform_manager", + "//tensorflow/stream_executor:scratch_allocator", + "//tensorflow/stream_executor/cuda:cuda_platform_id", + "//tensorflow/stream_executor/host:host_platform_id", + "//tensorflow/stream_executor/platform:dso_loader", + ] + select({ "@local_config_cuda//cuda:darwin": ["IOKit"], "//conditions:default": [], }), @@ -49,9 +59,10 @@ tf_cuda_library( cc_library( name = "stream_executor_cuda", deps = [ - "//tensorflow/stream_executor", + ":stream_executor_no_cuda", + ":cuda", ] + if_static( - ["//tensorflow/stream_executor:cuda_platform"], + ["//tensorflow/stream_executor/cuda:all_runtime"], ) + select({ "@local_config_cuda//cuda:darwin": ["IOKit"], "//conditions:default": [], @@ -62,23 +73,31 @@ cc_library( name = "stream_executor_no_cuda", deps = [ "//tensorflow/stream_executor", + "//tensorflow/stream_executor:dnn", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:multi_platform_manager", + "//tensorflow/stream_executor:scratch_allocator", + "//tensorflow/stream_executor/cuda:cuda_platform_id", + "//tensorflow/stream_executor/host:host_platform", + "//tensorflow/stream_executor/host:host_platform_id", + "//tensorflow/stream_executor/platform:dso_loader", ], ) # Dummy stream executor cuda plugins. cc_library( name = "cublas_plugin", - srcs = [], + deps = if_static(["//tensorflow/stream_executor/cuda:cublas_plugin"]), ) cc_library( name = "cufft_plugin", - srcs = [], + deps = if_static(["//tensorflow/stream_executor/cuda:cufft_plugin"]), ) cc_library( name = "cudnn_plugin", - srcs = [], + deps = if_static(["//tensorflow/stream_executor/cuda:cudnn_plugin"]), ) # OSX framework for device driver access diff --git a/tensorflow/opensource_only.files b/tensorflow/opensource_only.files index e00d063cfe..bade64dcf8 100644 --- a/tensorflow/opensource_only.files +++ b/tensorflow/opensource_only.files @@ -241,7 +241,7 @@ tensorflow/third_party/tflite_ovic_testdata.BUILD tensorflow/third_party/libxsmm.BUILD tensorflow/third_party/zlib.BUILD tensorflow/third_party/eigen.BUILD -tensorflow/stream_executor/BUILD +tensorflow/stream_executor/build_defs.bzl tensorflow/api_template_v1.__init__.py tensorflow/compat_template_v1.__init__.py tensorflow/api_template.__init__.py diff --git a/tensorflow/stream_executor/BUILD b/tensorflow/stream_executor/BUILD index 3c45bf314a..7cf7e75a55 100644 --- a/tensorflow/stream_executor/BUILD +++ b/tensorflow/stream_executor/BUILD @@ -1,126 +1,663 @@ -licenses(["restricted"]) +# GPU executor library for data-parallel kernel launches and cross-platform +# HPC-library APIs. +# +# Throughout this file, all targets are built with the standard crosstool and +# do not link against restricted binary blobs. -load("@local_config_cuda//cuda:build_defs.bzl", "if_cuda_is_configured") +licenses(["notice"]) # Apache 2.0 + +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_root.bzl", "if_static") -load("//tensorflow:tensorflow.bzl", "cc_header_only_library") +load("//tensorflow/stream_executor:build_defs.bzl", "stream_executor_friends") + +package_group( + name = "friends", + packages = stream_executor_friends(), +) + +package( + default_visibility = [":friends"], +) + +# Filegroup used to collect source files for the dependency check. +filegroup( + name = "c_srcs", + data = glob([ + "**/*.cc", + "**/*.h", + ]), +) + +cc_library( + name = "launch_dim", + hdrs = [ + "gpu_launch_dim.h", + "launch_dim.h", + ], + deps = [ + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "device_description", + srcs = ["device_description.cc"], + hdrs = ["device_description.h"], + deps = [ + ":launch_dim", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "event", + srcs = [ + "blas.h", + "device_description.h", + "device_options.h", + "dnn.h", + "event.cc", + "fft.h", + "kernel_cache_config.h", + "launch_dim.h", + "plugin.h", + "plugin_registry.h", + "rng.h", + "shared_memory_config.h", + "stream_executor_pimpl.h", + "temporary_device_memory.h", + "temporary_memory_manager.h", + "trace_listener.h", + ], + hdrs = [ + "device_memory.h", + "event.h", + "kernel.h", + "kernel_spec.h", + "platform.h", + "stream.h", + "stream_executor_internal.h", + ], + deps = [ + ":dnn_proto_cc", + ":host_or_device_scalar", + ":stream_executor_headers", + "//tensorflow/core:lib", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "kernel", + srcs = [ + "dnn.h", + "fft.h", + "kernel.cc", + "plugin.h", + "rng.h", + "stream.h", + "stream_executor_pimpl.h", + "temporary_device_memory.h", + "temporary_memory_manager.h", + ], + hdrs = [ + "blas.h", + "device_description.h", + "device_options.h", + "event.h", + "kernel.h", + "kernel_spec.h", + "launch_dim.h", + "multi_platform_manager.h", + "platform.h", + "plugin_registry.h", + "shared_memory_config.h", + "stream_executor.h", + "stream_executor_internal.h", + "timer.h", + "trace_listener.h", + ], + deps = [ + ":device_memory", + ":dnn_proto_cc", + ":host_or_device_scalar", + ":kernel_cache_config", + ":stream_executor_headers", + "//tensorflow/core:lib", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + ], +) -STREAM_EXECUTOR_HEADERS = glob([ - "*.h", - "cuda/*.h", - "host/*.h", - "lib/*.h", - "lib/gtl/*.h", - "platform/**/*.h", -]) +cc_library( + name = "kernel_spec", + srcs = ["kernel_spec.cc"], + hdrs = ["kernel_spec.h"], + deps = [ + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "kernel_cache_config", + hdrs = ["kernel_cache_config.h"], +) + +cc_library( + name = "module_spec", + hdrs = ["module_spec.h"], + deps = [ + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + ], +) + +cc_library( + name = "shared_memory_config", + hdrs = ["shared_memory_config.h"], +) + +cc_library( + name = "stream_header", + hdrs = [ + "blas.h", + "device_memory.h", + "dnn.h", + "event.h", + "fft.h", + "gpu_launch_dim.h", + "kernel.h", + "kernel_cache_config.h", + "launch_dim.h", + "stream.h", + "temporary_device_memory.h", + "temporary_memory_manager.h", + ], + visibility = ["//visibility:public"], + deps = [ + ":dnn_proto_cc", + ":host_or_device_scalar", + ":stream_executor_headers", + "//tensorflow/core:lib", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + ], +) + +# It implements :stream_header +cc_library( + name = "stream", + srcs = [ + "host_buffer.h", + "stream.cc", + ], + hdrs = ["stream.h"], + deps = [ + ":blas", + ":device_memory", + ":dnn", + ":event", + ":fft", + ":host_or_device_scalar", + ":kernel", + ":launch_dim", + ":platform", + ":rng", + ":stream_executor_headers", + ":stream_executor_internal", + ":stream_executor_pimpl", + ":temporary_memory_manager", + "//tensorflow/core:lib", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//third_party/eigen3", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "timer", + srcs = [ + "device_description.h", + "kernel_cache_config.h", + "timer.cc", + ], + hdrs = [ + "blas.h", + "kernel.h", + "stream.h", + "stream_executor.h", + "timer.h", + ], + deps = [ + ":host_or_device_scalar", + ":platform", + ":stream_executor_headers", + ":stream_executor_internal", + ":stream_executor_pimpl_header", + "//tensorflow/core:lib", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "platform", + srcs = ["platform.cc"], + hdrs = ["platform.h"], + deps = [ + ":plugin", + ":stream_executor_headers", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "rng", + srcs = ["rng.cc"], + hdrs = ["rng.h"], + deps = ["//tensorflow/stream_executor/platform"], +) + +cc_library( + name = "temporary_device_memory", + srcs = [ + "event.h", + "temporary_device_memory.cc", + "temporary_memory_manager.h", + ], + hdrs = ["temporary_device_memory.h"], + deps = [ + ":device_memory", + ":stream_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + ], +) + +cc_library( + name = "temporary_memory_manager", + srcs = ["temporary_memory_manager.cc"], + hdrs = ["temporary_memory_manager.h"], + deps = [ + ":device_memory", + ":stream_executor_pimpl_header", + ":stream_header", + ":temporary_device_memory", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "fft", + hdrs = ["fft.h"], + deps = [ + "//tensorflow/stream_executor/platform", + ], +) + +cc_library( + name = "blas", + srcs = ["blas.cc"], + hdrs = ["blas.h"], + deps = [ + ":host_or_device_scalar", + ":stream_executor_headers", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "device_memory", + hdrs = ["device_memory.h"], + deps = ["//tensorflow/stream_executor/platform"], +) + +cc_library( + name = "host_or_device_scalar", + hdrs = ["host_or_device_scalar.h"], + deps = [ + ":device_memory", + "//tensorflow/stream_executor/platform", + ], +) + +cc_library( + name = "device_options", + hdrs = ["device_options.h"], + deps = [ + "//tensorflow/stream_executor/platform", + ], +) + +cc_library( + name = "executor_cache", + srcs = [ + "device_description.h", + "device_memory.h", + "device_options.h", + "event.h", + "executor_cache.cc", + "launch_dim.h", + "plugin.h", + "plugin_registry.h", + "rng.h", + "stream_executor_pimpl.h", + "temporary_device_memory.h", + "temporary_memory_manager.h", + ], + hdrs = [ + "blas.h", + "dnn.h", + "executor_cache.h", + "fft.h", + "kernel.h", + "kernel_cache_config.h", + "kernel_spec.h", + "platform.h", + "shared_memory_config.h", + "stream.h", + "stream_executor_internal.h", + "trace_listener.h", + ], + deps = [ + ":dnn_proto_cc", + ":host_or_device_scalar", + ":stream_executor_headers", + "//tensorflow/core:lib", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "multi_platform_manager", + srcs = ["multi_platform_manager.cc"], + hdrs = ["multi_platform_manager.h"], + deps = [ + ":platform", + ":stream_executor_headers", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + ], +) + +cc_library( + name = "plugin", + srcs = ["plugin.cc"], + hdrs = ["plugin.h"], +) + +cc_library( + name = "plugin_registry", + srcs = ["plugin_registry.cc"], + hdrs = ["plugin_registry.h"], + deps = [ + ":blas", + ":dnn", + ":fft", + ":multi_platform_manager", + ":platform", + ":plugin", + ":stream_executor_headers", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", + ], +) + +cc_library( + name = "scratch_allocator", + srcs = ["scratch_allocator.cc"], + hdrs = ["scratch_allocator.h"], + deps = [ + ":device_memory", + ":stream_header", + ":temporary_device_memory", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + ], +) + +cc_library( + name = "host_buffer", + hdrs = ["host_buffer.h"], + deps = [":dnn"], +) tf_proto_library( name = "dnn_proto", srcs = ["dnn.proto"], cc_api_version = 2, default_header = True, + provide_cc_alias = True, ) tf_proto_library( name = "logging_proto", srcs = ["logging.proto"], cc_api_version = 2, - default_header = True, protodeps = [":dnn_proto"], + provide_cc_alias = True, + visibility = [":friends"], ) cc_library( - name = "stream_executor_impl", - srcs = glob( - [ - "*.cc", - "host/*.cc", - "cuda/cuda_platform_id.cc", - "lib/*.cc", - "platform/default/*.cc", - ], - exclude = [ - "**/*_test.cc", - ], - ), - hdrs = STREAM_EXECUTOR_HEADERS, - linkopts = select({ - "//tensorflow:freebsd": [], - "//tensorflow:windows": [], - "//conditions:default": ["-ldl"], - }), - visibility = ["//visibility:public"], + name = "dnn", + srcs = ["dnn.cc"], + hdrs = ["dnn.h"], deps = [ - ":dnn_proto_cc_impl", - ":logging_proto_cc_impl", + ":device_memory", + ":dnn_proto_cc", + ":stream_executor_headers", "//tensorflow/core:lib", - "//tensorflow/core:logger", - "//tensorflow/core:ptr_util", - "@com_google_absl//absl/container:flat_hash_map", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/synchronization", - "@local_config_cuda//cuda:cuda_headers", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", ], - alwayslink = 1, ) cc_library( - name = "stream_executor", - hdrs = STREAM_EXECUTOR_HEADERS, + name = "stream_executor_internal", + srcs = [ + "dnn.h", + "stream_executor_internal.cc", + ], + hdrs = [ + "shared_memory_config.h", + "stream_executor_internal.h", + ], + deps = [ + ":device_description", + ":device_memory", + ":device_options", + ":dnn_proto_cc", + ":kernel", + ":kernel_cache_config", + ":kernel_spec", + ":launch_dim", + ":plugin_registry", + ":stream_executor_headers", + "//tensorflow/core:lib", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "stream_executor_pimpl_header", + hdrs = [ + "device_description.h", + "dnn.h", + "kernel.h", + "kernel_cache_config.h", + "shared_memory_config.h", + "stream_executor_pimpl.h", + ], visibility = ["//visibility:public"], deps = [ ":dnn_proto_cc", - ":logging_proto_cc", + ":platform", + ":stream_executor_headers", + ":stream_executor_internal", "//tensorflow/core:lib", - "//tensorflow/core:ptr_util", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/strings", - "@local_config_cuda//cuda:cuda_headers", - ] + if_static([":stream_executor_impl"]), + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + ], ) -cc_header_only_library( - name = "stream_executor_headers_lib", - visibility = ["//visibility:public"], +# It implements :stream_executor_pimpl_header +cc_library( + name = "stream_executor_pimpl", + srcs = ["stream_executor_pimpl.cc"], + hdrs = ["stream_executor_pimpl.h"], deps = [ - ":stream_executor", + ":blas", + ":executor_cache", + ":fft", + ":kernel", + ":platform", + ":rng", + ":stream_executor_headers", + ":stream_header", + ":timer", + "//tensorflow/core:lib_internal", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", ], ) +# The stream_executor_headers target does not prescribe an implementation. +# +# TODO(b/25131218) this is OBSOLETE/DEPRECATED -- get rid of this target altogether cc_library( - name = "cuda_platform", - srcs = if_cuda_is_configured( - glob( - [ - "cuda/*.cc", - ], - exclude = [ - "cuda/*_test.cc", - "cuda/cuda_platform_id.cc", - ], - ), - ), - copts = select({ - "//tensorflow:windows": ["/DNOGDI"], - "//conditions:default": [], - }), - data = [ - "@local_config_cuda//cuda:cudnn", - ], - linkopts = select({ - "//tensorflow:freebsd": [], - "//tensorflow:windows": [], - "//conditions:default": ["-ldl"], - }), + name = "stream_executor_headers", + hdrs = [ + "blas.h", + "device_description.h", + "device_memory.h", + "device_options.h", + "dnn.h", + "event.h", + "executor_cache.h", + "fft.h", + "gpu_launch_dim.h", + "kernel.h", + "kernel_cache_config.h", + "kernel_spec.h", + "launch_dim.h", + "module_spec.h", + "multi_platform_manager.h", + "platform.h", + "plugin.h", + "plugin_registry.h", + "rng.h", + "shared_memory_config.h", + "stream.h", + "stream_executor.h", + "stream_executor_internal.h", + "stream_executor_pimpl.h", + "temporary_device_memory.h", + "temporary_memory_manager.h", + "timer.h", + "trace_listener.h", + ], visibility = ["//visibility:public"], deps = [ - ":stream_executor", + ":dnn_proto_cc", + ":host_or_device_scalar", "//tensorflow/core:lib", - "//tensorflow/core/kernels:ops_util", - "@local_config_cuda//cuda:cuda_headers", - ] + if_cuda_is_configured([ - "//tensorflow/core:cuda", - "@local_config_cuda//cuda:cuda_driver", - ]), - alwayslink = 1, + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "stream_executor", + hdrs = ["stream_executor.h"], + deps = [":stream_executor_headers"] + if_static([":stream_executor_impl"]), +) + +cc_library( + name = "stream_executor_impl", + deps = [ + ":device_description", + ":device_memory", + ":dnn_proto_cc", + ":dnn_proto_cc_impl", + ":event", + ":kernel", + ":launch_dim", + ":multi_platform_manager", + ":platform", + ":stream", + ":stream_executor_headers", + ":stream_executor_pimpl", + ":timer", + ], +) + +tf_cc_test( + name = "stream_test", + size = "small", + srcs = ["stream_test.cc"], + deps = [ + ":stream_executor", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/stream_executor/host:host_platform", + ], +) + +alias( + name = "cuda_platform", + actual = "//tensorflow/stream_executor/cuda:all_runtime", ) diff --git a/tensorflow/stream_executor/build_defs.bzl b/tensorflow/stream_executor/build_defs.bzl new file mode 100644 index 0000000000..a7ddf5a0d7 --- /dev/null +++ b/tensorflow/stream_executor/build_defs.bzl @@ -0,0 +1,11 @@ +def stream_executor_friends(): + return ["//tensorflow/..."] + +def tf_additional_cuda_platform_deps(): + return [] + +def tf_additional_cuda_driver_deps(): + return ["@local_config_cuda//cuda:cuda_driver"] + +def tf_additional_cudnn_plugin_deps(): + return [] diff --git a/tensorflow/stream_executor/cuda/BUILD b/tensorflow/stream_executor/cuda/BUILD new file mode 100644 index 0000000000..58c9480884 --- /dev/null +++ b/tensorflow/stream_executor/cuda/BUILD @@ -0,0 +1,365 @@ +# Description: +# CUDA-platform specific StreamExecutor support code. + +licenses(["notice"]) # Apache 2.0 + +load("//tensorflow:tensorflow.bzl", "tf_cc_test") +load( + "//tensorflow/stream_executor:build_defs.bzl", + "stream_executor_friends", + "tf_additional_cuda_driver_deps", + "tf_additional_cuda_platform_deps", + "tf_additional_cudnn_plugin_deps", +) +load("//tensorflow:tensorflow.bzl", "tf_copts") +load("@local_config_cuda//cuda:build_defs.bzl", "if_cuda_is_configured") +load("//tensorflow/core:platform/default/build_config_root.bzl", "if_static") + +package_group( + name = "friends", + packages = stream_executor_friends(), +) + +package( + default_visibility = [":friends"], +) + +# Filegroup used to collect source files for the dependency check. +filegroup( + name = "c_srcs", + data = glob([ + "**/*.cc", + "**/*.h", + ]), +) + +cc_library( + name = "cuda_platform_id", + srcs = ["cuda_platform_id.cc"], + hdrs = ["cuda_platform_id.h"], + deps = ["//tensorflow/stream_executor:platform"], +) + +cc_library( + name = "cuda_platform", + srcs = ["cuda_platform.cc"], + hdrs = ["cuda_platform.h"], + visibility = ["//visibility:public"], + deps = [ + ":cuda_driver", + ":cuda_gpu_executor", + ":cuda_platform_id", + "//tensorflow/stream_executor", # buildcleaner: keep + "//tensorflow/stream_executor:executor_cache", + "//tensorflow/stream_executor:multi_platform_manager", + "//tensorflow/stream_executor:stream_executor_pimpl_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + ] + tf_additional_cuda_platform_deps(), + alwayslink = True, # Registers itself with the MultiPlatformManager. +) + +cc_library( + name = "cuda_diagnostics", + srcs = ["cuda_diagnostics.cc"], + hdrs = ["cuda_diagnostics.h"], + deps = [ + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "cuda_driver", + srcs = ["cuda_driver.cc"], + hdrs = ["cuda_driver.h"], + deps = [ + ":cuda_diagnostics", + "@com_google_absl//absl/base", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/strings", + "@local_config_cuda//cuda:cuda_headers", + "//tensorflow/stream_executor:device_options", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + ] + tf_additional_cuda_driver_deps(), +) + +# The activation library is tightly coupled to the executor library. +# TODO(leary) split up cuda_gpu_executor.cc so that this can stand alone. +cc_library( + name = "cuda_activation_header", + hdrs = ["cuda_activation.h"], + visibility = ["//visibility:public"], + deps = ["//tensorflow/stream_executor/platform"], +) + +cc_library( + name = "cuda_activation", + srcs = ["cuda_activation.cc"], + hdrs = ["cuda_activation.h"], + deps = [ + ":cuda_driver", + "//tensorflow/stream_executor", + "//tensorflow/stream_executor:stream_executor_internal", + "//tensorflow/stream_executor/platform", + "@local_config_cuda//cuda:cuda_headers", + ], +) + +cc_library( + name = "cuda_gpu_executor_header", + textual_hdrs = ["cuda_gpu_executor.h"], + visibility = ["//visibility:public"], + deps = [ + ":cuda_kernel", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + ], +) + +cc_library( + name = "cublas_plugin", + srcs = ["cuda_blas.cc"], + hdrs = ["cuda_blas.h"], + visibility = ["//visibility:public"], + deps = [ + ":cuda_activation", + ":cuda_gpu_executor", + ":cuda_helpers", + ":cuda_platform_id", + ":cuda_stream", + ":cuda_timer", + "@com_google_absl//absl/strings", + "//third_party/eigen3", + "@local_config_cuda//cuda:cuda_headers", + "//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/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + ] + if_static( + [ + "@local_config_cuda//cuda:cublas", + "@local_config_cuda//cuda:cudart_static", + ], + ["@local_config_cuda//cuda:cudart"], + ), + alwayslink = True, +) + +cc_library( + name = "cufft_plugin", + srcs = ["cuda_fft.cc"], + hdrs = ["cuda_fft.h"], + visibility = ["//visibility:public"], + deps = [ + ":cuda_activation_header", + ":cuda_gpu_executor_header", + ":cuda_helpers", + ":cuda_platform_id", + ":cuda_stream", + "@local_config_cuda//cuda:cuda_headers", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:fft", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:scratch_allocator", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + ] + if_static(["@local_config_cuda//cuda:cufft"]), + alwayslink = True, +) + +cc_library( + name = "cudnn_plugin", + srcs = ["cuda_dnn.cc"], + hdrs = ["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 = [ + ":cuda_activation", + ":cuda_diagnostics", + ":cuda_driver", + ":cuda_gpu_executor", + ":cuda_platform_id", + ":cuda_stream", + ":cuda_timer", + ":cudnn_version", + "@com_google_absl//absl/strings", + "//third_party/eigen3", + "@local_config_cuda//cuda:cuda_headers", + "//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", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + ] + tf_additional_cudnn_plugin_deps() + if_static( + [ + "@local_config_cuda//cuda:cudnn", + "@local_config_cuda//cuda:cudart_static", + ], + ["@local_config_cuda//cuda:cudart"], + ), + alwayslink = True, +) + +cc_library( + name = "curand_plugin", + srcs = ["cuda_rng.cc"], + hdrs = ["cuda_rng.h"], + deps = [ + ":cuda_activation", + ":cuda_gpu_executor", + ":cuda_helpers", + ":cuda_platform_id", + ":cuda_stream", + "@local_config_cuda//cuda:cuda_headers", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:rng", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + ] + if_static(["@local_config_cuda//cuda:curand"]), + alwayslink = True, +) + +cc_library( + name = "cuda_kernel", + hdrs = ["cuda_kernel.h"], + deps = [ + ":cuda_driver", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:stream_executor_pimpl_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@local_config_cuda//cuda:cuda_headers", + ], +) + +# TODO(leary) we likely need to canonicalize/eliminate this. +cc_library( + name = "cuda_helpers", + textual_hdrs = ["cuda_helpers.h"], +) + +cc_library( + name = "cuda_event", + srcs = ["cuda_event.cc"], + hdrs = ["cuda_event.h"], + deps = [ + ":cuda_driver", + ":cuda_gpu_executor_header", + ":cuda_stream", + "//tensorflow/stream_executor:stream_executor_headers", + "//tensorflow/stream_executor/lib", + ], +) + +cc_library( + name = "cuda_stream", + srcs = ["cuda_stream.cc"], + hdrs = ["cuda_stream.h"], + deps = [ + ":cuda_driver", + ":cuda_gpu_executor_header", + "//tensorflow/stream_executor:stream_executor_headers", + "//tensorflow/stream_executor:stream_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + ], +) + +cc_library( + name = "cuda_timer", + srcs = ["cuda_timer.cc"], + hdrs = ["cuda_timer.h"], + deps = [ + ":cuda_driver", + ":cuda_gpu_executor_header", + ":cuda_stream", + "//tensorflow/stream_executor:stream_executor_headers", + "//tensorflow/stream_executor/lib", + ], +) + +# It implements :cuda_gpu_executor_header +cc_library( + name = "cuda_gpu_executor", + srcs = ["cuda_gpu_executor.cc"], + hdrs = ["cuda_gpu_executor.h"], + deps = [ + ":cuda_activation", + ":cuda_diagnostics", + ":cuda_driver", + ":cuda_event", + ":cuda_kernel", + ":cuda_platform_id", + ":cuda_stream", + ":cuda_timer", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:stream_executor_internal", + "//tensorflow/stream_executor:stream_executor_pimpl_header", + "//tensorflow/stream_executor:timer", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/strings", + ], + alwayslink = True, +) + +cc_library( + name = "cudnn_version", + srcs = ["cudnn_version.cc"], + hdrs = ["cudnn_version.h"], + deps = [ + "//tensorflow/core:lib", + ], +) + +tf_cc_test( + name = "cudnn_version_test", + srcs = ["cudnn_version_test.cc"], + deps = [ + ":cudnn_version", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + ], +) + +cc_library( + name = "all_runtime", + copts = tf_copts(), + visibility = ["//visibility:public"], + deps = if_cuda_is_configured([ + ":cudnn_plugin", + ":cufft_plugin", + ":cublas_plugin", + ":curand_plugin", + ":cuda_driver", + ":cuda_platform", + ]), + alwayslink = 1, +) diff --git a/tensorflow/stream_executor/host/BUILD b/tensorflow/stream_executor/host/BUILD new file mode 100644 index 0000000000..59472b14c1 --- /dev/null +++ b/tensorflow/stream_executor/host/BUILD @@ -0,0 +1,108 @@ +# Description: +# Host-platform specific StreamExecutor support code. + +licenses(["notice"]) # Apache 2.0 + +load("//tensorflow/stream_executor:build_defs.bzl", "stream_executor_friends") + +package_group( + name = "friends", + packages = stream_executor_friends(), +) + +package(default_visibility = [":friends"]) + +# Filegroup used to collect source files for the dependency check. +filegroup( + name = "c_srcs", + data = glob([ + "**/*.cc", + "**/*.h", + ]), +) + +cc_library( + name = "host_platform_id", + srcs = [ + "host_platform_id.cc", + ], + hdrs = [ + "host_platform_id.h", + ], + deps = [ + "//tensorflow/stream_executor:platform", + ], +) + +cc_library( + name = "host_platform", + srcs = [ + "host_platform.cc", + ], + hdrs = [ + "host_platform.h", + ], + visibility = ["//visibility:public"], + deps = [ + ":host_gpu_executor", + ":host_platform_id", + "//tensorflow/stream_executor:executor_cache", + "//tensorflow/stream_executor:multi_platform_manager", + "//tensorflow/stream_executor:stream_executor_headers", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + ], + alwayslink = True, # Registers itself with the MultiPlatformManager. +) + +cc_library( + name = "host_stream", + srcs = [ + "host_stream.cc", + ], + hdrs = [ + "host_stream.h", + ], + deps = [ + "//tensorflow/stream_executor:kernel", + "//tensorflow/stream_executor/lib", + ], +) + +cc_library( + name = "host_timer", + srcs = [ + "host_timer.cc", + ], + hdrs = [ + "host_timer.h", + ], + deps = [ + "//tensorflow/stream_executor:stream_executor_internal", + "//tensorflow/stream_executor:timer", + "//tensorflow/stream_executor/platform", + ], +) + +# TODO(22689637): Rename this target. +cc_library( + name = "host_gpu_executor", + srcs = [ + "host_gpu_executor.cc", + ], + hdrs = [ + "host_gpu_executor.h", + ], + deps = [ + ":host_platform_id", + ":host_stream", + ":host_timer", + "//tensorflow/core:lib", + "//tensorflow/stream_executor:kernel", + "//tensorflow/stream_executor:rng", + "//tensorflow/stream_executor:stream_executor_internal", + "//tensorflow/stream_executor:timer", + "//tensorflow/stream_executor/lib", + ], + alwayslink = True, +) diff --git a/tensorflow/stream_executor/lib/BUILD b/tensorflow/stream_executor/lib/BUILD new file mode 100644 index 0000000000..133ff2b161 --- /dev/null +++ b/tensorflow/stream_executor/lib/BUILD @@ -0,0 +1,62 @@ +licenses(["notice"]) # Apache 2.0 + +load("//tensorflow:tensorflow.bzl", "tf_cc_test") +load("//tensorflow/stream_executor:build_defs.bzl", "stream_executor_friends") + +package_group( + name = "friends", + packages = stream_executor_friends(), +) + +package(default_visibility = [":friends"]) + +filegroup( + name = "c_srcs", + data = glob([ + "**/*.cc", + "**/*.h", + ]), +) + +cc_library( + name = "lib", + srcs = glob( + [ + "**/*.cc", + ], + exclude = [ + "**/*test*", + ], + ), + hdrs = glob(["**/*.h"]), + deps = [ + "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:ptr_util", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) + +tf_cc_test( + name = "statusor_test", + size = "small", + srcs = ["statusor_test.cc"], + deps = [ + ":lib", + "//tensorflow/core:lib", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + ], +) + +cc_library( + name = "utility_headers", + hdrs = [ + "ptr_util.h", + ], + deps = [ + "//tensorflow/core:ptr_util", + ], +) diff --git a/tensorflow/stream_executor/platform/BUILD b/tensorflow/stream_executor/platform/BUILD new file mode 100644 index 0000000000..702b2cdfe0 --- /dev/null +++ b/tensorflow/stream_executor/platform/BUILD @@ -0,0 +1,47 @@ +licenses(["notice"]) # Apache 2.0 + +load("//tensorflow/stream_executor:build_defs.bzl", "stream_executor_friends") +load("//tensorflow/core:platform/default/build_config.bzl", "tf_platform_hdrs") + +package_group( + name = "friends", + packages = stream_executor_friends(), +) + +package( + default_visibility = [":friends"], +) + +cc_library( + name = "platform", + textual_hdrs = [ + "logging.h", + "mutex.h", + "platform.h", + "port.h", + "thread_annotations.h", + "initialize.h", + ], + deps = [ + "//tensorflow/core:lib", + "//tensorflow/stream_executor/platform/default:platform", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "dso_loader", + hdrs = ["dso_loader.h"], + deps = [ + ":platform", + "//tensorflow/stream_executor/platform/default:dso_loader", + ], +) + +filegroup( + name = "c_srcs", + data = glob([ + "**/*.cc", + "**/*.h", + ]), +) diff --git a/tensorflow/stream_executor/platform/default/BUILD b/tensorflow/stream_executor/platform/default/BUILD new file mode 100644 index 0000000000..f1ae7d86ff --- /dev/null +++ b/tensorflow/stream_executor/platform/default/BUILD @@ -0,0 +1,25 @@ +licenses(["notice"]) # Apache 2.0 + +package(default_visibility = ["//tensorflow/stream_executor:__subpackages__"]) + +cc_library( + name = "platform", + textual_hdrs = [ + "initialize.h", + "mutex.h", + ], + deps = ["//tensorflow/core:lib"], +) + +cc_library( + name = "dso_loader", + srcs = ["dso_loader.cc"], + hdrs = ["dso_loader.h"], + deps = [ + "//tensorflow/stream_executor:platform", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/strings", + "@local_config_cuda//cuda:cuda_headers", + ], +) -- GitLab From 60bcd1c1fd2cf412b217d24e7672bc3699458a43 Mon Sep 17 00:00:00 2001 From: Duncan Riach Date: Tue, 11 Dec 2018 16:56:09 -0800 Subject: [PATCH 0367/2345] Add cuDNN deterministic env variable (only for convolution) --- tensorflow/stream_executor/cuda/cuda_dnn.cc | 40 +++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index a34aa9354d..e91caf3bdb 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -794,6 +794,19 @@ bool BatchnormSpatialPersistentEnabled() { return is_enabled; } +// A helper function to decide whether to enable deterministic functionality. +// TODO(pr/24355): Support all cuDNN functionality (currently only convolution). +bool Deterministic() { + static bool is_enabled = [] { + bool is_enabled = false; + TF_CHECK_OK( + tensorflow::ReadBoolFromEnvVar("TF_CUDNN_DETERMINISTIC", + /*default_val=*/false, &is_enabled)); + return is_enabled; + }(); + return is_enabled; +} + // Turns a ConvolutionDescriptor structure into a cudnn convolution handle // within a scope. class CudnnConvolutionDescriptor { @@ -3056,10 +3069,19 @@ bool CudnnSupport::GetConvolveAlgorithms( algo_types.push_back(CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD_NONFUSED); } + if (Deterministic()) { + algo_types.clear(); + algo_types.push_back(CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM); + } + out_algorithms->clear(); for (auto i : algo_types) { out_algorithms->push_back({i, /*use_tensor_ops=*/false}); if (cc_major >= 7 && CUDNN_VERSION >= 7000 && TensorOpMathEnabled()) { + if (Deterministic()) { + // For determinism: always use tensor op math, if it's available + out_algorithms->pop_back(); + } out_algorithms->push_back({i, /*use_tensor_ops=*/true}); } } @@ -3104,10 +3126,19 @@ bool CudnnSupport::GetConvolveBackwardDataAlgorithms( algo_types.push_back(CUDNN_CONVOLUTION_BWD_DATA_ALGO_WINOGRAD_NONFUSED); } + if (Deterministic()) { + algo_types.clear(); + algo_types.push_back(CUDNN_CONVOLUTION_BWD_DATA_ALGO_1); + } + out_algorithms->clear(); for (auto i : algo_types) { out_algorithms->push_back({i, /*use_tensor_ops=*/false}); if (cc_major >= 7 && CUDNN_VERSION >= 7000 && TensorOpMathEnabled()) { + if (Deterministic()) { + // For determinism: always use tensor op math, if it's available + out_algorithms->pop_back(); + } out_algorithms->push_back({i, /*use_tensor_ops=*/true}); } } @@ -3135,10 +3166,19 @@ bool CudnnSupport::GetConvolveBackwardFilterAlgorithms( algo_types.push_back(CUDNN_CONVOLUTION_BWD_FILTER_ALGO_WINOGRAD_NONFUSED); } + if (Deterministic()) { + algo_types.clear(); + algo_types.push_back(CUDNN_CONVOLUTION_BWD_FILTER_ALGO_1); + } + out_algorithms->clear(); for (auto i : algo_types) { out_algorithms->push_back({i, /*use_tensor_ops=*/false}); if (cc_major >= 7 && CUDNN_VERSION >= 7000 && TensorOpMathEnabled()) { + if (Deterministic()) { + // For determinism: always use tensor op math, if it's available + out_algorithms->pop_back(); + } out_algorithms->push_back({i, /*use_tensor_ops=*/true}); } } -- GitLab From df17a7db51000ccf7c6b062e37b5c3acb2cd286b Mon Sep 17 00:00:00 2001 From: Pete Warden Date: Tue, 8 Jan 2019 13:42:38 -0800 Subject: [PATCH 0368/2345] MacOS support for simple speech example PiperOrigin-RevId: 228391393 --- .../micro/examples/micro_speech/BUILD | 24 --- .../micro/examples/micro_speech/Makefile.inc | 27 +--- .../examples/micro_speech/audio_provider.cc | 6 + .../examples/micro_speech/audio_provider.h | 10 ++ .../micro_speech/audio_provider_test.cc | 26 ++++ .../micro/examples/micro_speech/main.cc | 4 +- .../examples/micro_speech/model_settings.h | 1 + .../examples/micro_speech/osx/Makefile.inc | 6 + .../micro_speech/osx/audio_provider.cc | 139 ++++++++++++++++++ .../micro/examples/micro_speech/timer.cc | 22 --- .../micro/examples/micro_speech/timer.h | 31 ---- .../micro/examples/micro_speech/timer_test.cc | 49 ------ .../experimental/micro/tools/make/Makefile | 35 ++++- .../micro/tools/make/targets/osx_makefile.inc | 10 ++ 14 files changed, 241 insertions(+), 149 deletions(-) create mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc create mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc delete mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/timer.cc delete mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/timer.h delete mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/timer_test.cc create mode 100644 tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/BUILD b/tensorflow/lite/experimental/micro/examples/micro_speech/BUILD index 70eeac1458..702b893e6f 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/BUILD +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/BUILD @@ -196,29 +196,6 @@ tflite_micro_cc_test( ], ) -cc_library( - name = "timer", - srcs = [ - "timer.cc", - ], - hdrs = [ - "timer.h", - ], -) - -tflite_micro_cc_test( - name = "timer_test", - srcs = [ - "timer_test.cc", - ], - deps = [ - ":timer", - "//tensorflow/lite/c:c_api_internal", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", - ], -) - cc_library( name = "recognize_commands", srcs = [ @@ -259,7 +236,6 @@ cc_binary( ":model_settings", ":preprocessor_reference", ":recognize_commands", - ":timer", ":tiny_conv_model_data", "//tensorflow/lite:schema_fbs_version", "//tensorflow/lite/experimental/micro:micro_framework", diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc b/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc index 5623eabdde..6f7563c02f 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc @@ -5,6 +5,7 @@ tensorflow/lite/experimental/micro/examples/micro_speech/micro_speech_test.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/tiny_conv_model_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/no_features_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/yes_features_data.cc +MICRO_SPEECH_TEST_SRCS := $(call specialize,$(MICRO_SPEECH_TEST_SRCS)) ALL_SRCS += $(MICRO_SPEECH_TEST_SRCS) MICRO_SPEECH_TEST_OBJS := $(addprefix $(OBJDIR), \ $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(MICRO_SPEECH_TEST_SRCS)))) @@ -32,6 +33,7 @@ tensorflow/lite/experimental/micro/examples/micro_speech/yes_power_spectrum_data PREPROCESSOR_REFERENCE_TEST_SRCS = \ $(PREPROCESSOR_TEST_SHARED_SRCS) \ tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc +PREPROCESSOR_REFERENCE_TEST_SRCS := $(call specialize,$(PREPROCESSOR_REFERENCE_TEST_SRCS)) ALL_SRCS += $(PREPROCESSOR_REFERENCE_TEST_SRCS) PREPROCESSOR_REFERENCE_TEST_OBJS := $(addprefix $(OBJDIR), \ $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(PREPROCESSOR_REFERENCE_TEST_SRCS)))) @@ -51,6 +53,7 @@ test_preprocessor_reference: $(PREPROCESSOR_REFERENCE_TEST_BINARY) PREPROCESSOR_FIXED_TEST_SRCS = \ $(PREPROCESSOR_TEST_SHARED_SRCS) \ tensorflow/lite/experimental/micro/examples/micro_speech/fixed_point/preprocessor.cc +PREPROCESSOR_FIXED_TEST_SRCS := $(call specialize,$(PREPROCESSOR_FIXED_TEST_SRCS)) ALL_SRCS += $(PREPROCESSOR_FIXED_TEST_SRCS) PREPROCESSOR_FIXED_TEST_OBJS := $(addprefix $(OBJDIR), \ $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(PREPROCESSOR_FIXED_TEST_SRCS)))) @@ -71,6 +74,7 @@ AUDIO_PROVIDER_TEST_SRCS := \ tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc +AUDIO_PROVIDER_TEST_SRCS := $(call specialize,$(AUDIO_PROVIDER_TEST_SRCS)) ALL_SRCS += $(AUDIO_PROVIDER_TEST_SRCS) AUDIO_PROVIDER_TEST_OBJS := $(addprefix $(OBJDIR), \ $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(AUDIO_PROVIDER_TEST_SRCS)))) @@ -93,6 +97,7 @@ tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc +FEATURE_PROVIDER_TEST_SRCS := $(call specialize,$(FEATURE_PROVIDER_TEST_SRCS)) ALL_SRCS += $(FEATURE_PROVIDER_TEST_SRCS) FEATURE_PROVIDER_TEST_OBJS := $(addprefix $(OBJDIR), \ $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(FEATURE_PROVIDER_TEST_SRCS)))) @@ -108,30 +113,12 @@ feature_provider_test_bin: $(FEATURE_PROVIDER_TEST_BINARY).bin test_feature_provider: $(FEATURE_PROVIDER_TEST_BINARY) $(TEST_SCRIPT) $(FEATURE_PROVIDER_TEST_BINARY) '~~~ALL TESTS PASSED~~~' -# Tests the timer module. -TIMER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/timer_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/timer.cc -ALL_SRCS += $(TIMER_TEST_SRCS) -TIMER_TEST_OBJS := $(addprefix $(OBJDIR), \ -$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(TIMER_TEST_SRCS)))) -TIMER_TEST_BINARY := $(BINDIR)timer_test -ALL_BINARIES += $(TIMER_TEST_BINARY) -$(TIMER_TEST_BINARY): $(TIMER_TEST_OBJS) $(MICROLITE_LIB_PATH) - @mkdir -p $(dir $@) - $(CXX) $(CXXFLAGS) $(INCLUDES) \ - -o $(TIMER_TEST_BINARY) $(TIMER_TEST_OBJS) \ - $(LIBFLAGS) $(MICROLITE_LIB_PATH) $(LDFLAGS) $(MICROLITE_LIBS) -timer_test: $(TIMER_TEST_BINARY) -timer_test_bin: $(TIMER_TEST_BINARY).bin -test_timer: $(TIMER_TEST_BINARY) - $(TEST_SCRIPT) $(TIMER_TEST_BINARY) '~~~ALL TESTS PASSED~~~' - # Tests the feature provider module. RECOGNIZE_COMMANDS_TEST_SRCS := \ tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands_test.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc +RECOGNIZE_COMMANDS_TEST_SRCS := $(call specialize,$(RECOGNIZE_COMMANDS_TEST_SRCS)) ALL_SRCS += $(RECOGNIZE_COMMANDS_TEST_SRCS) RECOGNIZE_COMMANDS_TEST_OBJS := $(addprefix $(OBJDIR), \ $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(RECOGNIZE_COMMANDS_TEST_SRCS)))) @@ -153,12 +140,12 @@ tensorflow/lite/experimental/micro/examples/micro_speech/main.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/timer.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/no_features_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/yes_features_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/tiny_conv_model_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc +MICRO_SPEECH_SRCS := $(call specialize,$(MICRO_SPEECH_SRCS)) ALL_SRCS += $(MICRO_SPEECH_SRCS) MICRO_SPEECH_OBJS := $(addprefix $(OBJDIR), \ $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(MICRO_SPEECH_SRCS)))) diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc index c0365d5690..52db18e686 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc @@ -19,6 +19,7 @@ limitations under the License. namespace { int16_t g_dummy_audio_data[kMaxAudioSampleSize]; +int32_t g_latest_audio_timestamp = 0; } // namespace TfLiteStatus GetAudioSamples(tflite::ErrorReporter* error_reporter, @@ -31,3 +32,8 @@ TfLiteStatus GetAudioSamples(tflite::ErrorReporter* error_reporter, *audio_samples = g_dummy_audio_data; return kTfLiteOk; } + +int32_t LatestAudioTimestamp() { + g_latest_audio_timestamp += 100; + return g_latest_audio_timestamp; +} diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h b/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h index 7e2442a5e8..b690673641 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h @@ -33,4 +33,14 @@ TfLiteStatus GetAudioSamples(tflite::ErrorReporter* error_reporter, int start_ms, int duration_ms, int* audio_samples_size, int16_t** audio_samples); +// Returns the time that audio data was last captured in milliseconds. There's +// no contract about what time zero represents, the accuracy, or the granularity +// of the result. Subsequent calls will generally not return a lower value, but +// even that's not guaranteed if there's an overflow wraparound. +// The reference implementation of this function just returns a constantly +// incrementing value for each call, since it would need a non-portable platform +// call to access time information. For real applications, you'll need to write +// your own platform-specific implementation. +int32_t LatestAudioTimestamp(); + #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_AUDIO_PROVIDER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc index 5f7c7605f0..85fbbb80a6 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc @@ -14,6 +14,9 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" + +#include + #include "tensorflow/lite/c/c_api_internal.h" #include "tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h" #include "tensorflow/lite/experimental/micro/micro_error_reporter.h" @@ -41,4 +44,27 @@ TF_LITE_MICRO_TEST(TestAudioProvider) { } } +TF_LITE_MICRO_TEST(TestTimer) { + // Make sure that the technically-undefined overflow behavior we rely on below + // works on this platform. It's still not guaranteed, but at least this is a + // sanity check. Turn off when running with ASan, as it will complain about + // the following undefined behavior. +#ifndef ADDRESS_SANITIZER + int32_t overflow_value = std::numeric_limits::max(); + overflow_value += 1; + TF_LITE_MICRO_EXPECT_EQ(std::numeric_limits::min(), overflow_value); +#endif + + const int32_t first_time = LatestAudioTimestamp(); + const int32_t second_time = LatestAudioTimestamp(); + + // It's possible that the timer may have wrapped around from +BIG_NUM to + // -BIG_NUM between the first and second calls, since we're storing + // milliseconds in a 32-bit integer. It's not reasonable that the call itself + // would have taken more than 2^31 milliseconds though, so look at the + // difference and rely on integer overflow to ensure it's accurate. + const int32_t time_delta = (second_time - first_time); + TF_LITE_MICRO_EXPECT_LE(0, time_delta); +} + TF_LITE_MICRO_TESTS_END diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/main.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/main.cc index 515f82fcbc..3a9a5a4df1 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/main.cc +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/main.cc @@ -13,10 +13,10 @@ 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 "tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h" #include "tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h" #include "tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/timer.h" #include "tensorflow/lite/experimental/micro/examples/micro_speech/tiny_conv_model_data.h" #include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" #include "tensorflow/lite/experimental/micro/micro_error_reporter.h" @@ -76,7 +76,7 @@ int main(int argc, char* argv[]) { // Keep reading and analysing audio data in an infinite loop. while (true) { // Fetch the spectrogram for the current time. - const int32_t current_time = TimeInMilliseconds(); + const int32_t current_time = LatestAudioTimestamp(); int how_many_new_slices = 0; TfLiteStatus feature_status = feature_provider.PopulateFeatureData( error_reporter, previous_time, current_time, &how_many_new_slices); diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h b/tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h index 1d8f3123a5..f48252d14d 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h @@ -23,6 +23,7 @@ limitations under the License. // frequency information. This has to be a power of two, and since we're dealing // with 30ms of 16KHz inputs, which means 480 samples, this is the next value. constexpr int kMaxAudioSampleSize = 512; +constexpr int kAudioSampleFrequency = 16000; // All of these values are derived from the values used during model training, // if you change your model you'll need to update these constants. diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc b/tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc new file mode 100644 index 0000000000..89d107cfe0 --- /dev/null +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc @@ -0,0 +1,6 @@ +#Settings for Mac OS platforms. +ifeq ($(TARGET), osx) + MICROLITE_LIBS += \ + -framework Foundation \ + -framework AudioToolbox +endif diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc new file mode 100644 index 0000000000..892757e799 --- /dev/null +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc @@ -0,0 +1,139 @@ +/* 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/experimental/micro/examples/micro_speech/audio_provider.h" + +#include + +#include "tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h" + +namespace { + +constexpr int kNumberRecordBuffers = 3; +bool g_is_audio_initialized = false; +constexpr int kAudioCaptureBufferSize = kAudioSampleFrequency * 0.5; +int16_t g_audio_capture_buffer[kAudioCaptureBufferSize]; +int16_t g_audio_output_buffer[kMaxAudioSampleSize]; +int32_t g_latest_audio_timestamp = 0; + +// Checks for MacOS errors, prints information and returns a TF Lite version. +#define RETURN_IF_OS_ERROR(error, error_reporter) \ + do { \ + if (error != noErr) { \ + error_reporter->Report("Error: %s:%d (%d)\n", __FILE__, __LINE__, \ + error); \ + return kTfLiteError; \ + } \ + } while (0); + +// Called when an audio input buffer has been filled. +void OnAudioBufferFilledCallback( + void* user_data, AudioQueueRef queue, AudioQueueBufferRef buffer, + const AudioTimeStamp* start_time, UInt32 num_packets, + const AudioStreamPacketDescription* packet_description) { + const int sample_size = buffer->mAudioDataByteSize / sizeof(float); + const int64_t sample_offset = start_time->mSampleTime; + const int32_t time_in_ms = + (sample_offset + sample_size) / (kAudioSampleFrequency / 1000); + const float* float_samples = static_cast(buffer->mAudioData); + for (int i = 0; i < sample_size; ++i) { + const int capture_index = (sample_offset + i) % kAudioCaptureBufferSize; + g_audio_capture_buffer[capture_index] = float_samples[i] * ((1 << 15) - 1); + } + // This is how we let the outside world know that new audio data has arrived. + g_latest_audio_timestamp = time_in_ms; + AudioQueueEnqueueBuffer(queue, buffer, 0, nullptr); +} + +// Set up everything we need to capture audio samples from the default recording +// device on MacOS. +TfLiteStatus InitAudioRecording(tflite::ErrorReporter* error_reporter) { + // Set up the format of the audio - single channel, 32-bit float at 16KHz. + AudioStreamBasicDescription recordFormat = {0}; + recordFormat.mSampleRate = kAudioSampleFrequency; + recordFormat.mFormatID = kAudioFormatLinearPCM; + recordFormat.mFormatFlags = + kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; + recordFormat.mBitsPerChannel = 8 * sizeof(float); + recordFormat.mChannelsPerFrame = 1; + recordFormat.mBytesPerFrame = sizeof(float) * recordFormat.mChannelsPerFrame; + recordFormat.mFramesPerPacket = 1; + recordFormat.mBytesPerPacket = + recordFormat.mBytesPerFrame * recordFormat.mFramesPerPacket; + recordFormat.mReserved = 0; + + UInt32 propSize = sizeof(recordFormat); + RETURN_IF_OS_ERROR(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, + NULL, &propSize, &recordFormat), + error_reporter); + + // Create a recording queue. + AudioQueueRef queue; + RETURN_IF_OS_ERROR( + AudioQueueNewInput(&recordFormat, OnAudioBufferFilledCallback, + error_reporter, nullptr, nullptr, 0, &queue), + error_reporter); + + // Set up the buffers we'll need. + int buffer_bytes = 512 * sizeof(float); + for (int i = 0; i < kNumberRecordBuffers; ++i) { + AudioQueueBufferRef buffer; + RETURN_IF_OS_ERROR(AudioQueueAllocateBuffer(queue, buffer_bytes, &buffer), + error_reporter); + RETURN_IF_OS_ERROR(AudioQueueEnqueueBuffer(queue, buffer, 0, nullptr), + error_reporter); + } + + // Start capturing audio. + RETURN_IF_OS_ERROR(AudioQueueStart(queue, nullptr), error_reporter); + + return kTfLiteOk; +} + +} // namespace + +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; + } + for (int i = 0; i < kMaxAudioSampleSize; ++i) { + g_audio_output_buffer[i] = 0; + } + g_is_audio_initialized = true; + } + // 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; + g_audio_output_buffer[i] = g_audio_capture_buffer[capture_index]; + } + + *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/examples/micro_speech/timer.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/timer.cc deleted file mode 100644 index 6c96a61ab5..0000000000 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/timer.cc +++ /dev/null @@ -1,22 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ - -#include "tensorflow/lite/experimental/micro/examples/micro_speech/timer.h" - -int32_t TimeInMilliseconds() { - static int current_time = 0; - current_time += 100; - return current_time; -} diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/timer.h b/tensorflow/lite/experimental/micro/examples/micro_speech/timer.h deleted file mode 100644 index 162952844a..0000000000 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/timer.h +++ /dev/null @@ -1,31 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ - -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_TIMER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_TIMER_H_ - -#include - -// Returns the time in milliseconds. There's no contract about what time zero -// represents, the accuracy, or the granularity of the result. Subsequent calls -// will generally not return a lower value, but even that's not guaranteed if -// there's an overflow wraparound. -// The reference implementation of this function just returns a constantly -// incrementing value for each call, since it would need a non-portable platform -// call to access time information. For real applications, you'll need to write -// your own platform-specific implementation. -int32_t TimeInMilliseconds(); - -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_TIMER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/timer_test.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/timer_test.cc deleted file mode 100644 index 0487a12b25..0000000000 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/timer_test.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ - -#include "tensorflow/lite/experimental/micro/examples/micro_speech/timer.h" - -#include - -#include "tensorflow/lite/c/c_api_internal.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" - -TF_LITE_MICRO_TESTS_BEGIN - -TF_LITE_MICRO_TEST(TestTimer) { - // Make sure that the technically-undefined overflow behavior we rely on below - // works on this platform. It's still not guaranteed, but at least this is a - // sanity check. Turn off when running with ASan, as it will complain about - // the following undefined behavior. -#ifndef ADDRESS_SANITIZER - int32_t overflow_value = std::numeric_limits::max(); - overflow_value += 1; - TF_LITE_MICRO_EXPECT_EQ(std::numeric_limits::min(), overflow_value); -#endif - - const int32_t first_time = TimeInMilliseconds(); - const int32_t second_time = TimeInMilliseconds(); - - // It's possible that the timer may have wrapped around from +BIG_NUM to - // -BIG_NUM between the first and second calls, since we're storing - // milliseconds in a 32-bit integer. It's not reasonable that the call itself - // would have taken more than 2^31 milliseconds though, so look at the - // difference and rely on integer overflow to ensure it's accurate. - const int32_t time_delta = (second_time - first_time); - TF_LITE_MICRO_EXPECT_LE(0, time_delta); -} - -TF_LITE_MICRO_TESTS_END diff --git a/tensorflow/lite/experimental/micro/tools/make/Makefile b/tensorflow/lite/experimental/micro/tools/make/Makefile index 6a7c875888..f9a2dffae2 100644 --- a/tensorflow/lite/experimental/micro/tools/make/Makefile +++ b/tensorflow/lite/experimental/micro/tools/make/Makefile @@ -21,6 +21,38 @@ HOST_ARCH := $(shell if [[ $(shell uname -m) =~ i[345678]86 ]]; then echo x86_32 TARGET := $(HOST_OS) TARGET_ARCH := $(HOST_ARCH) +# Look for platform or target-specific implementation files to replace reference +# implementations with, given a tag. These are expected to occur in subfolders +# of a directory where a reference implementation exists, and have the same +# interface and header file. For example, +# tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc +# defines a module for supplying audio data, but since no platform or OS can be +# presumed, it just always returns zeroes for its samples. The MacOS-specific +# tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc +# has an implementation that relies on CoreAudio, and there are equivalent +# versions for other operating systems. +# All lists of source files are put through this substitution process with the +# tags of their target OS and architecture, so that implementations can be added +# by simply placing them in the file tree, with no changes to the build files +# needed. +# One confusing thing about this implementation is that we're using wildcard to +# act as a 'does file exist?' function, rather than expanding an expression. +# Wildcard will return an empty string if given a plain file path with no actual +# wildcards, if the file doesn't exist, so taking the first word of the list +# between that and the reference path will pick the specialized one if it's +# available. +substitute_specialized_implementation = \ + $(firstword $(wildcard $(dir $(1))$(2)/$(notdir $(1))) $(wildcard $(1))) +substitute_specialized_implementations = \ + $(foreach source,$(1),$(call substitute_specialized_implementation,$(source),$(2))) +# Here we're first looking for specialized implementations in ref_dir/$(TARGET) +# and then ref_dir/$(TARGET_ARCH), before falling back to ref_dir's +# implementation. +# The argument to this function should be a list of space-separated file paths, +# with any wildcards already expanded. +specialize = \ + $(call substitute_specialized_implementations,$(call substitute_specialized_implementations,$(1),$(TARGET)),$(TARGET_ARCH)) + INCLUDES := \ -I. \ -I$(MAKEFILE_DIR)/../../../../../ \ @@ -67,6 +99,7 @@ tensorflow/lite/core/api/op_resolver.cc \ tensorflow/lite/kernels/kernel_util.cc \ tensorflow/lite/kernels/internal/quantization_util.cc MICROLITE_CC_SRCS := $(filter-out $(MICROLITE_TEST_SRCS), $(MICROLITE_CC_BASE_SRCS)) +MICROLITE_CC_SRCS := $(call specialize,$(MICROLITE_CC_SRCS)) # These target-specific makefiles should modify or replace options like # CXXFLAGS or LIBS to work for a specific targetted architecture. All logic @@ -115,7 +148,7 @@ $(OBJDIR)%.o: %.S $(CC) $(CCFLAGS) $(INCLUDES) -c $< -o $@ # The target that's compiled if there's no command-line arguments. -all: $(MICROLITE_LIB_PATH) $(MICRO_SPEECH_TEST_BINARY) $(PREPROCESSOR_TEST_BINARY) +all: $(MICROLITE_LIB_PATH) microlite: $(MICROLITE_LIB_PATH) diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc b/tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc new file mode 100644 index 0000000000..0e8aad1993 --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc @@ -0,0 +1,10 @@ +#Settings for Mac OS platforms. +ifeq ($(TARGET), osx) + + PLATFORM_FLAGS = \ + -DTF_LITE_DISABLE_X86_NEON + + CXXFLAGS += $(PLATFORM_FLAGS) + CCFLAGS += $(PLATFORM_FLAGS) + +endif \ No newline at end of file -- GitLab From f9b6cccd90939bf9516d0ae0e3bbdc4a7c4f4a54 Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Tue, 8 Jan 2019 13:50:00 -0800 Subject: [PATCH 0369/2345] [tf.data] Update `map` documentation -- calling out the fact that setting `num_parallel_calls` results in asynchronous computation. PiperOrigin-RevId: 228392788 --- 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 61210f3a7f..45e732be0d 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -970,8 +970,8 @@ class DatasetV2(object): shapes and types defined by `self.output_shapes` and `self.output_types`) to another nested structure of tensors. num_parallel_calls: (Optional.) A `tf.int32` scalar `tf.Tensor`, - representing the number elements to process in parallel. If not - specified, elements will be processed sequentially. If the value + representing the number elements to process asynchronously in parallel. + If not specified, elements will be processed sequentially. If the value `tf.data.experimental.AUTOTUNE` is used, then the number of parallel calls is set dynamically based on available CPU. -- GitLab From 4aaaee6112c40791e5dbc9730c2a06274ff35b3f Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Tue, 8 Jan 2019 13:50:17 -0800 Subject: [PATCH 0370/2345] [TF:XLA] Bump open source llvm revision to r350570 PiperOrigin-RevId: 228392842 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 834ec28447..ae26ba6e68 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -500,11 +500,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 = "20c0ebd54f433d68652a54e328d70b0ec7cf37ce734645a0259cf904d14c2c53", - strip_prefix = "llvm-0e2aef924e02d00a4d3454d63c1921a243d316c6", + sha256 = "83a4f199742f3d6892994dd6dc46d6a53019aedaa28590b460c120f3dfc7bc47", + strip_prefix = "llvm-671f057a2cf137914be6c786daf8000469adebab", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/0e2aef924e02d00a4d3454d63c1921a243d316c6.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/0e2aef924e02d00a4d3454d63c1921a243d316c6.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/671f057a2cf137914be6c786daf8000469adebab.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/671f057a2cf137914be6c786daf8000469adebab.tar.gz", ], ) -- GitLab From 731ae198b0d16e2b51299c6fd5050a428bc18152 Mon Sep 17 00:00:00 2001 From: Duncan Riach Date: Tue, 8 Jan 2019 14:13:09 -0800 Subject: [PATCH 0371/2345] Renaming and factoring --- tensorflow/stream_executor/cuda/cuda_dnn.cc | 78 +++++++++++---------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index e91caf3bdb..16d644629e 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -796,12 +796,12 @@ bool BatchnormSpatialPersistentEnabled() { // A helper function to decide whether to enable deterministic functionality. // TODO(pr/24355): Support all cuDNN functionality (currently only convolution). -bool Deterministic() { +bool RequireDeterminism() { static bool is_enabled = [] { bool is_enabled = false; - TF_CHECK_OK( - tensorflow::ReadBoolFromEnvVar("TF_CUDNN_DETERMINISTIC", - /*default_val=*/false, &is_enabled)); + TF_CHECK_OK(tensorflow::ReadBoolFromEnvVar("TF_CUDNN_DETERMINISTIC", + /*default_val=*/false, + &is_enabled)); return is_enabled; }(); return is_enabled; @@ -3049,9 +3049,22 @@ port::Status CudnnSupport::DoFusedConvolveImpl( return port::Status::OK(); } +inline bool TensorOpMathAvailable(int cc_major) { + return cc_major >= 7 && CUDNN_VERSION >= 7000 && TensorOpMathEnabled(); +} + bool CudnnSupport::GetConvolveAlgorithms( bool with_winograd_nonfused, int cc_major, int cc_minor, std::vector* out_algorithms) { + bool tensor_op_math_available = TensorOpMathAvailable(cc_major); + out_algorithms->clear(); + + if (RequireDeterminism()) { + out_algorithms->push_back({CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM, + tensor_op_math_available}); + return true; + } + std::vector algo_types = { // clang-format off CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM, @@ -3069,22 +3082,13 @@ bool CudnnSupport::GetConvolveAlgorithms( algo_types.push_back(CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD_NONFUSED); } - if (Deterministic()) { - algo_types.clear(); - algo_types.push_back(CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM); - } - - out_algorithms->clear(); for (auto i : algo_types) { out_algorithms->push_back({i, /*use_tensor_ops=*/false}); - if (cc_major >= 7 && CUDNN_VERSION >= 7000 && TensorOpMathEnabled()) { - if (Deterministic()) { - // For determinism: always use tensor op math, if it's available - out_algorithms->pop_back(); - } + if (tensor_op_math_available) { out_algorithms->push_back({i, /*use_tensor_ops=*/true}); } } + return true; } @@ -3113,6 +3117,15 @@ bool CudnnSupport::GetRnnAlgorithms( bool CudnnSupport::GetConvolveBackwardDataAlgorithms( bool with_winograd_nonfused, int cc_major, int cc_minor, std::vector* out_algorithms) { + bool tensor_op_math_available = TensorOpMathAvailable(cc_major); + out_algorithms->clear(); + + if (RequireDeterminism()) { + out_algorithms->push_back( + {CUDNN_CONVOLUTION_BWD_DATA_ALGO_1, tensor_op_math_available}); + return true; + } + std::vector algo_types = { // clang-format off CUDNN_CONVOLUTION_BWD_DATA_ALGO_0, @@ -3126,28 +3139,28 @@ bool CudnnSupport::GetConvolveBackwardDataAlgorithms( algo_types.push_back(CUDNN_CONVOLUTION_BWD_DATA_ALGO_WINOGRAD_NONFUSED); } - if (Deterministic()) { - algo_types.clear(); - algo_types.push_back(CUDNN_CONVOLUTION_BWD_DATA_ALGO_1); - } - - out_algorithms->clear(); for (auto i : algo_types) { out_algorithms->push_back({i, /*use_tensor_ops=*/false}); - if (cc_major >= 7 && CUDNN_VERSION >= 7000 && TensorOpMathEnabled()) { - if (Deterministic()) { - // For determinism: always use tensor op math, if it's available - out_algorithms->pop_back(); - } + if (tensor_op_math_available) { out_algorithms->push_back({i, /*use_tensor_ops=*/true}); } } + return true; } bool CudnnSupport::GetConvolveBackwardFilterAlgorithms( bool with_winograd_nonfused, int cc_major, int cc_minor, std::vector* out_algorithms) { + bool tensor_op_math_available = TensorOpMathAvailable(cc_major); + out_algorithms->clear(); + + if (RequireDeterminism()) { + out_algorithms->push_back( + {CUDNN_CONVOLUTION_BWD_FILTER_ALGO_1, tensor_op_math_available}); + return true; + } + std::vector algo_types = { // clang-format off CUDNN_CONVOLUTION_BWD_FILTER_ALGO_0, @@ -3166,22 +3179,13 @@ bool CudnnSupport::GetConvolveBackwardFilterAlgorithms( algo_types.push_back(CUDNN_CONVOLUTION_BWD_FILTER_ALGO_WINOGRAD_NONFUSED); } - if (Deterministic()) { - algo_types.clear(); - algo_types.push_back(CUDNN_CONVOLUTION_BWD_FILTER_ALGO_1); - } - - out_algorithms->clear(); for (auto i : algo_types) { out_algorithms->push_back({i, /*use_tensor_ops=*/false}); - if (cc_major >= 7 && CUDNN_VERSION >= 7000 && TensorOpMathEnabled()) { - if (Deterministic()) { - // For determinism: always use tensor op math, if it's available - out_algorithms->pop_back(); - } + if (tensor_op_math_available) { out_algorithms->push_back({i, /*use_tensor_ops=*/true}); } } + return true; } -- GitLab From c52d92f10021f8caa00ea271755d47a9c86259bd Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Tue, 8 Jan 2019 14:06:17 -0800 Subject: [PATCH 0372/2345] Internal change PiperOrigin-RevId: 228396077 --- tensorflow/lite/experimental/swift/BUILD | 101 ----- tensorflow/lite/experimental/swift/LICENSE | 202 ---------- tensorflow/lite/experimental/swift/README.md | 54 --- .../swift/Sources/Interpreter.swift | 265 -------------- .../swift/Sources/InterpreterError.swift | 99 ----- .../swift/Sources/InterpreterOptions.swift | 29 -- .../experimental/swift/Sources/Model.swift | 40 -- .../Sources/QuantizationParameters.swift | 38 -- .../experimental/swift/Sources/Tensor.swift | 138 ------- .../Configs/TensorFlowLite.tulsigen | 57 --- .../project.tulsiconf | 14 - .../project.pbxproj | 345 ------------------ .../TensorFlowLiteApp/AppDelegate.swift | 24 -- .../Array+TensorFlowLite.swift | 22 -- .../AppIcon.appiconset/Contents.json | 98 ----- .../Assets.xcassets/Contents.json | 6 - .../Base.lproj/LaunchScreen.storyboard | 44 --- .../Base.lproj/Main.storyboard | 95 ----- .../Data+TensorFlowLite.swift | 13 - .../TensorFlowLiteApp/Info.plist | 46 --- .../TensorFlowLiteApp/ViewController.swift | 299 --------------- .../swift/Tests/InterpreterOptionsTests.swift | 54 --- .../swift/Tests/InterpreterTests.swift | 315 ---------------- .../experimental/swift/Tests/ModelTests.swift | 59 --- .../Tests/QuantizationParametersTests.swift | 43 --- .../swift/Tests/TensorTests.swift | 83 ----- 26 files changed, 2583 deletions(-) delete mode 100644 tensorflow/lite/experimental/swift/BUILD delete mode 100644 tensorflow/lite/experimental/swift/LICENSE delete mode 100644 tensorflow/lite/experimental/swift/README.md delete mode 100644 tensorflow/lite/experimental/swift/Sources/Interpreter.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/InterpreterError.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/Model.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift delete mode 100644 tensorflow/lite/experimental/swift/Sources/Tensor.swift delete mode 100644 tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen delete mode 100644 tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist delete mode 100644 tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/ModelTests.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift delete mode 100644 tensorflow/lite/experimental/swift/Tests/TensorTests.swift diff --git a/tensorflow/lite/experimental/swift/BUILD b/tensorflow/lite/experimental/swift/BUILD deleted file mode 100644 index 53bcb0ecbd..0000000000 --- a/tensorflow/lite/experimental/swift/BUILD +++ /dev/null @@ -1,101 +0,0 @@ -# TensorFlow Lite for Swift. - -package(default_visibility = ["//visibility:private"]) - -licenses(["notice"]) # Apache 2.0 - -exports_files(["LICENSE"]) - -load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_unit_test") -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -MINIMUM_OS_VERSION = "9.0" - -SWIFT_COPTS = [ - "-wmo", -] - -swift_library( - name = "TensorFlowLite", - srcs = glob(["Sources/*.swift"]), - copts = SWIFT_COPTS, - module_name = "TensorFlowLite", - tags = ["manual"], - deps = [ - "//tensorflow/lite/experimental/c:c_api", - ], -) - -ios_unit_test( - name = "TensorFlowLiteTests", - size = "small", - minimum_os_version = MINIMUM_OS_VERSION, - tags = [ - "manual", - # DISABLED: Following sanitizer tests are not supported by iOS test targets. - "noasan", - "nomsan", - "notsan", - ], - deps = [":TensorFlowLiteTestsLib"], -) - -swift_library( - name = "TensorFlowLiteTestsLib", - testonly = 1, - srcs = glob(["Tests/*.swift"]), - copts = SWIFT_COPTS, - tags = ["manual"], - deps = [ - ":TensorFlowLite", - ":TestResources", - ], -) - -objc_library( - name = "TestResources", - resources = [ - "//tensorflow/lite:testdata/add.bin", - "//tensorflow/lite:testdata/add_quantized.bin", - "//tensorflow/lite:testdata/multi_add.bin", - ], - tags = ["manual"], -) - -ios_application( - name = "TensorFlowLiteApp", - app_icons = glob(["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/**"]), - bundle_id = "com.tensorflow.lite.swift.TensorFlowLite", - families = [ - "ipad", - "iphone", - ], - infoplists = ["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist"], - launch_storyboard = "TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard", - minimum_os_version = MINIMUM_OS_VERSION, - sdk_frameworks = [ - "CoreGraphics", - ], - tags = ["manual"], - deps = [":TensorFlowLiteAppLib"], -) - -swift_library( - name = "TensorFlowLiteAppLib", - srcs = glob(["TestApps/TensorFlowLiteApp/TensorFlowLiteApp/*.swift"]), - module_name = "TensorFlowLiteAppLib", - tags = ["manual"], - deps = [ - ":TensorFlowLite", - ":TensorFlowLiteAppResources", - ], -) - -objc_library( - name = "TensorFlowLiteAppResources", - storyboards = glob([ - "TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/*.storyboard", - ]), - tags = ["manual"], - deps = [":TestResources"], -) diff --git a/tensorflow/lite/experimental/swift/LICENSE b/tensorflow/lite/experimental/swift/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/tensorflow/lite/experimental/swift/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/tensorflow/lite/experimental/swift/README.md b/tensorflow/lite/experimental/swift/README.md deleted file mode 100644 index deb16c1e91..0000000000 --- a/tensorflow/lite/experimental/swift/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# TensorFlow Lite for Swift - -[TensorFlow Lite](https://www.tensorflow.org/lite/) is TensorFlow's lightweight -solution for Swift developers. It enables low-latency inference of on-device -machine learning models with a small binary size and fast performance supporting -hardware acceleration. - -## Getting Started - -### Bazel - -In your `BUILD` file, add the `TensorFlowLite` dependency: - -```python -swift_library( - # ... - deps = [ - "//tensorflow/lite/swift:TensorFlowLite", - ], -) -``` - -In your Swift files, import the module: - -```swift -import TensorFlowLite -``` - -### Tulsi - -Open the `TensorFlowLite.tulsiproj` using the [TulsiApp](https://github.com/bazelbuild/tulsi) or by -running the [`generate_xcodeproj.sh`](https://github.com/bazelbuild/tulsi/blob/master/src/tools/generate_xcodeproj.sh) -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 -``` diff --git a/tensorflow/lite/experimental/swift/Sources/Interpreter.swift b/tensorflow/lite/experimental/swift/Sources/Interpreter.swift deleted file mode 100644 index a14b5966b1..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/Interpreter.swift +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation -import TensorFlowLiteCAPI - -/// A TensorFlow Lite interpreter that performs inference from a given model. -public final class Interpreter { - - /// The `TFL_Interpreter` C pointer type represented as an `UnsafePointer`. - private typealias CInterpreter = OpaquePointer - - /// Total number of input tensors associated with the model. - public var inputTensorCount: Int { - return Int(TFL_InterpreterGetInputTensorCount(cInterpreter)) - } - - /// Total number of output tensors associated with the model. - public var outputTensorCount: Int { - return Int(TFL_InterpreterGetOutputTensorCount(cInterpreter)) - } - - /// The underlying `TFL_Interpreter` C pointer. - private var cInterpreter: CInterpreter? - - /// Creates a new model interpreter instance. - /// - /// - Parameters: - /// - modelPath: Local file path to a TensorFlow Lite model. - /// - options: Custom configurations for the interpreter. The default is `nil` indicating that - /// interpreter will determine the configuration options. - /// - Throws: An error if the model could not be loaded or the interpreter could not be created. - public init(modelPath: String, options: InterpreterOptions? = nil) throws { - guard let model = Model(filePath: modelPath) else { throw InterpreterError.failedToLoadModel } - - let cInterpreterOptions: OpaquePointer? = try options.map { options in - guard let cOptions = TFL_NewInterpreterOptions() else { - throw InterpreterError.failedToCreateInterpreter - } - if let threadCount = options.threadCount, threadCount > 0 { - TFL_InterpreterOptionsSetNumThreads(cOptions, Int32(threadCount)) - } - if options.isErrorLoggingEnabled { - TFL_InterpreterOptionsSetErrorReporter( - cOptions, - { (_, format, arguments) in - guard let cFormat = format, - let message = String(cFormat: cFormat, arguments: arguments) - else { - return - } - print(String(describing: InterpreterError.tensorFlowLiteError(message))) - }, - nil - ) - } - return cOptions - } - defer { TFL_DeleteInterpreterOptions(cInterpreterOptions) } - - guard let cInterpreter = TFL_NewInterpreter(model.cModel, cInterpreterOptions) else { - throw InterpreterError.failedToCreateInterpreter - } - self.cInterpreter = cInterpreter - } - - deinit { - TFL_DeleteInterpreter(cInterpreter) - } - - /// Invokes the interpreter to perform inference from the loaded graph. - /// - /// - Throws: An error if the model was not ready because tensors were not allocated. - public func invoke() throws { - guard TFL_InterpreterInvoke(cInterpreter) == kTfLiteOk else { - // TODO(b/117510052): Determine which error to throw. - throw InterpreterError.allocateTensorsRequired - } - } - - /// Returns the input tensor at the given index. - /// - /// - Parameters: - /// - index: The index for the input tensor. - /// - Throws: An error if the index is invalid or the tensors have not been allocated. - /// - Returns: The input tensor at the given index. - public func input(at index: Int) throws -> Tensor { - let maxIndex = inputTensorCount - 1 - guard case 0...maxIndex = index else { - throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) - } - guard let cTensor = TFL_InterpreterGetInputTensor(cInterpreter, Int32(index)), - let bytes = TFL_TensorData(cTensor), - let nameCString = TFL_TensorName(cTensor) - else { - throw InterpreterError.allocateTensorsRequired - } - guard let dataType = TensorDataType(type: TFL_TensorType(cTensor)) else { - throw InterpreterError.invalidTensorDataType - } - - let name = String(cString: nameCString) - let rank = TFL_TensorNumDims(cTensor) - let dimensions = (0.. Tensor { - let maxIndex = outputTensorCount - 1 - guard case 0...maxIndex = index else { - throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) - } - guard let cTensor = TFL_InterpreterGetOutputTensor(cInterpreter, Int32(index)), - let bytes = TFL_TensorData(cTensor), - let nameCString = TFL_TensorName(cTensor) - else { - // TODO(b/117510052): Determine which error to throw. - throw InterpreterError.invokeInterpreterRequired - } - guard let dataType = TensorDataType(type: TFL_TensorType(cTensor)) else { - throw InterpreterError.invalidTensorDataType - } - - let name = String(cString: nameCString) - let rank = TFL_TensorNumDims(cTensor) - let dimensions = (0.. Tensor { - let maxIndex = inputTensorCount - 1 - guard case 0...maxIndex = index else { - throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex) - } - guard let cTensor = TFL_InterpreterGetInputTensor(cInterpreter, Int32(index)) else { - throw InterpreterError.allocateTensorsRequired - } - - let byteCount = TFL_TensorByteSize(cTensor) - guard data.count == byteCount else { - throw InterpreterError.invalidTensorDataCount(provided: data.count, required: byteCount) - } - - let status = data.withUnsafeBytes { TFL_TensorCopyFromBuffer(cTensor, $0, data.count) } - guard status == kTfLiteOk else { throw InterpreterError.failedToCopyDataToInputTensor } - return try input(at: index) - } - - /// Allocates memory for all input tensors based on their `TensorShape`s. - /// - /// - Note: This is a relatively expensive operation and should only be called after creating the - /// interpreter and/or resizing any input tensors. - /// - Throws: An error if memory could not be allocated for the input tensors. - public func allocateTensors() throws { - guard TFL_InterpreterAllocateTensors(cInterpreter) == kTfLiteOk else { - throw InterpreterError.failedToAllocateTensors - } - } -} - -// MARK: - Extensions - -extension String { - /// Returns a new `String` initialized by using the given format C array as a template into which - /// the remaining argument values are substituted according to the user’s default locale. - /// - /// - Note: Returns `nil` if a new `String` could not be constructed from the given values. - /// - Parameters: - /// - cFormat: The format C array as a template for substituting values. - /// - arguments: A C pointer to a `va_list` of arguments to substitute into `cFormat`. - init?(cFormat: UnsafePointer, arguments: CVaListPointer) { - var buffer: UnsafeMutablePointer? - guard vasprintf(&buffer, cFormat, arguments) != 0, let cString = buffer else { return nil } - self.init(validatingUTF8: cString) - } -} diff --git a/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift b/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift deleted file mode 100644 index 5de58b997a..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/InterpreterError.swift +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation - -/// TensorFlow Lite interpreter errors. -public enum InterpreterError: Error { - case invalidTensorIndex(index: Int, maxIndex: Int) - case invalidTensorDataCount(provided: Int, required: Int) - case invalidTensorDataType - case failedToLoadModel - case failedToCreateInterpreter - case failedToResizeInputTensor(index: Int) - case failedToCopyDataToInputTensor - case failedToAllocateTensors - case allocateTensorsRequired - case invokeInterpreterRequired - case tensorFlowLiteError(String) -} - -// MARK: - Extensions - -extension InterpreterError: LocalizedError { - /// Localized description of the interpreter error. - public var errorDescription: String? { - switch self { - case .invalidTensorIndex(let index, let maxIndex): - return "Invalid tensor index \(index), max index is \(maxIndex)." - case .invalidTensorDataCount(let providedCount, let requiredCount): - return "Provided data count \(providedCount) must match the required count \(requiredCount)." - case .invalidTensorDataType: - return "Tensor data type is unsupported or could not be determined because of a model error." - case .failedToLoadModel: - return "Failed to load the given model." - case .failedToCreateInterpreter: - return "Failed to create the interpreter." - case .failedToResizeInputTensor(let index): - return "Failed to resize input tesnor at index \(index)." - case .failedToCopyDataToInputTensor: - return "Failed to copy data to input tensor." - case .failedToAllocateTensors: - return "Failed to allocate memory for input tensors." - case .allocateTensorsRequired: - return "Must call allocateTensors()." - case .invokeInterpreterRequired: - return "Must call invoke()." - case .tensorFlowLiteError(let message): - return "TensorFlow Lite Error: \(message)" - } - } -} - -extension InterpreterError: CustomStringConvertible { - /// Textual representation of the TensorFlow Lite interpreter error. - public var description: String { - return errorDescription ?? "Unknown error." - } -} - -#if swift(>=4.2) -extension InterpreterError: Equatable {} -#else -extension InterpreterError: Equatable { - public static func == (lhs: InterpreterError, rhs: InterpreterError) -> Bool { - switch (lhs, rhs) { - case (.invalidTensorDataType, .invalidTensorDataType), - (.failedToLoadModel, .failedToLoadModel), - (.failedToCreateInterpreter, .failedToCreateInterpreter), - (.failedToAllocateTensors, .failedToAllocateTensors), - (.allocateTensorsRequired, .allocateTensorsRequired), - (.invokeInterpreterRequired, .invokeInterpreterRequired): - return true - case (.invalidTensorIndex(let lhsIndex, let lhsMaxIndex), - .invalidTensorIndex(let rhsIndex, let rhsMaxIndex)): - return lhsIndex == rhsIndex && lhsMaxIndex == rhsMaxIndex - case (.invalidTensorDataCount(let lhsProvidedCount, let lhsRequiredCount), - .invalidTensorDataCount(let rhsProvidedCount, let rhsRequiredCount)): - return lhsProvidedCount == rhsProvidedCount && lhsRequiredCount == rhsRequiredCount - case (.failedToResizeInputTensor(let lhsIndex), .failedToResizeInputTensor(let rhsIndex)): - return lhsIndex == rhsIndex - case (.tensorFlowLiteError(let lhsMessage), .tensorFlowLiteError(let rhsMessage)): - return lhsMessage == rhsMessage - default: - return false - } - } -} -#endif // swift(>=4.2) diff --git a/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift b/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift deleted file mode 100644 index 2365fd7ade..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/InterpreterOptions.swift +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation - -/// Custom configuration options for a TensorFlow Lite interpreter. -public struct InterpreterOptions: Equatable { - - /// Maximum number of CPU threads that the interpreter should run on. Default is `nil` which - /// indicates that the `Interpreter` will decide the number of threads to use. - public var threadCount: Int? = nil - - /// Whether error logging to the console is enabled. The default is `false`. - public var isErrorLoggingEnabled = false - - /// Creates a new instance of interpreter options. - public init() {} -} diff --git a/tensorflow/lite/experimental/swift/Sources/Model.swift b/tensorflow/lite/experimental/swift/Sources/Model.swift deleted file mode 100644 index e8c49ff1ae..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/Model.swift +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation -import TensorFlowLiteCAPI - -/// A TensorFlow Lite model used by the 'Interpreter` to perform inference. -final class Model { - - /// The `TFL_Model` C pointer type represented as an `UnsafePointer`. - typealias CModel = OpaquePointer - - /// The underlying `TFL_Model` C pointer. - let cModel: CModel? - - /// Creates a new model instance. - /// - /// - Precondition: Initialization can fail if the given `filePath` is invalid. - /// - Parameters: - /// - filePath: Local file path to a TensorFlow Lite model. - init?(filePath: String) { - guard !filePath.isEmpty, let cModel = TFL_NewModelFromFile(filePath) else { return nil } - self.cModel = cModel - } - - deinit { - TFL_DeleteModel(cModel) - } -} diff --git a/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift b/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift deleted file mode 100644 index f367875644..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/QuantizationParameters.swift +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation - -/// Parameters that determine the mapping of quantized values to real values. Quantized values can -/// be mapped to float values using the following conversion: -/// `realValue = scale * (quantizedValue - zeroPoint)`. -public struct QuantizationParameters { - - /// Difference between real values corresponding to consecutive quantized values differing by 1. - /// For example, the range of quantized values for `UInt8` data type is [0, 255]. - public let scale: Float - - /// Quantized value that corresponds to the real 0 value. - public let zeroPoint: Int - - /// Creates a new quantization parameters instance. - /// - /// - Parameters: - /// - scale: Scale value for asymmetric quantization. - /// - zeroPoint: Zero point for asymmetric quantization. - init(scale: Float, zeroPoint: Int) { - self.scale = scale - self.zeroPoint = zeroPoint - } -} diff --git a/tensorflow/lite/experimental/swift/Sources/Tensor.swift b/tensorflow/lite/experimental/swift/Sources/Tensor.swift deleted file mode 100644 index b738d87549..0000000000 --- a/tensorflow/lite/experimental/swift/Sources/Tensor.swift +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2018 Google Inc. 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 Foundation -import TensorFlowLiteCAPI - -/// An input or output tensor in a TensorFlow Lite graph. -public struct Tensor { - - /// Name of the tensor. - public let name: String - - /// Data type of the tensor. - public let dataType: TensorDataType - - /// Shape of the tensor. - public let shape: TensorShape - - /// Data in the input or output tensor. - public let data: Data - - /// Quantization parameters for the tensor if using a quantized model. - public let quantizationParameters: QuantizationParameters? - - /// Creates a new input or output tensor instance. - /// - /// - Parameters: - /// - name: Name of the tensor. - /// - dataType: Data type of the tensor. - /// - data: Data in the input tensor. - /// - quantizationParameters Quantization parameters for the tensor if using a quantized model. - /// The default is `nil`. - init( - name: String, - dataType: TensorDataType, - shape: TensorShape, - data: Data, - quantizationParameters: QuantizationParameters? = nil - ) { - self.name = name - self.dataType = dataType - self.shape = shape - self.data = data - self.quantizationParameters = quantizationParameters - } -} - -/// Supported TensorFlow Lite tensor data types. -public enum TensorDataType: Equatable { - /// 32-bit single precision floating point tensor data type. - case float32 - /// 8-bit unsigned integer tensor data type. - case uInt8 - /// 16-bit signed integer tensor data type. - case int16 - /// 32-bit signed integer tensor data type. - case int32 - /// 64-bit signed integer tensor data type. - case int64 - /// Boolean tensor data type. - case bool - - /// Creates a new tensor data type from the given `TFL_Type` or `nil` if the data type is - /// unsupported or could not be determined because there was an error. - /// - /// - Parameter type: A data type supported by a tensor. - init?(type: TFL_Type) { - switch type { - case kTfLiteFloat32: - self = .float32 - case kTfLiteUInt8: - self = .uInt8 - case kTfLiteInt16: - self = .int16 - case kTfLiteInt32: - self = .int32 - case kTfLiteInt64: - self = .int64 - case kTfLiteBool: - self = .bool - case kTfLiteNoType: - fallthrough - default: - return nil - } - } -} - -/// The shape of a TensorFlow Lite tensor. -public struct TensorShape { - - /// The number of dimensions of the tensor. - public let rank: Int - - /// Array of dimensions for the tensor. - public let dimensions: [Int] - - /// Array of `Int32` dimensions for the tensor. - var int32Dimensions: [Int32] { return dimensions.map(Int32.init) } - - /// Creates a new tensor shape instance with the given array of dimensions. - /// - /// - Parameters: - /// - dimensions: Dimensions for the tensor. - public init(_ dimensions: [Int]) { - self.rank = dimensions.count - self.dimensions = dimensions - } - - /// Creates a new tensor shape instance with the given elements representing the dimensions. - /// - /// - Parameters: - /// - elements: Dimensions for the tensor. - public init(_ elements: Int...) { - self.init(elements) - } -} - -extension TensorShape: ExpressibleByArrayLiteral { - /// Creates a new tensor shape instance with the given array literal representing the dimensions. - /// - /// - Parameters: - /// - arrayLiteral: Dimensions for the tensor. - public init(arrayLiteral: Int...) { - self.init(arrayLiteral) - } -} diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen deleted file mode 100644 index 16bc6cbfe8..0000000000 --- a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/Configs/TensorFlowLite.tulsigen +++ /dev/null @@ -1,57 +0,0 @@ -{ - "sourceFilters" : [ - "tensorflow/lite/experimental/c", - "tensorflow/lite/experimental/swift", - "tensorflow/lite/experimental/swift/Sources", - "tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp", - "tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj", - "tensorflow/lite/experimental/swift/Tests", - ], - "buildTargets" : [ - "//tensorflow/lite/experimental/swift:TensorFlowLite", - "//tensorflow/lite/experimental/swift:TensorFlowLiteApp", - "//tensorflow/lite/experimental/swift:TensorFlowLiteTests", - ], - "projectName" : "TensorFlowLite", - "optionSet" : { - "LaunchActionPreActionScript" : { - "p" : "$(inherited)" - }, - "BazelBuildStartupOptionsRelease" : { - "p" : "$(inherited)" - }, - "BazelBuildOptionsRelease" : { - "p" : "$(inherited)" - }, - "BazelBuildOptionsDebug" : { - "p" : "$(inherited)" - }, - "EnvironmentVariables" : { - "p" : "$(inherited)" - }, - "BuildActionPreActionScript" : { - "p" : "$(inherited)" - }, - "CommandlineArguments" : { - "p" : "$(inherited)" - }, - "TestActionPreActionScript" : { - "p" : "$(inherited)" - }, - "BazelBuildStartupOptionsDebug" : { - "p" : "$(inherited)" - }, - "BuildActionPostActionScript" : { - "p" : "$(inherited)" - }, - "TestActionPostActionScript" : { - "p" : "$(inherited)" - }, - "LaunchActionPostActionScript" : { - "p" : "$(inherited)" - } - }, - "additionalFilePaths" : [ - "tensorflow/lite/experimental/swift/BUILD" - ] -} diff --git a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf b/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf deleted file mode 100644 index 82ac8aa381..0000000000 --- a/tensorflow/lite/experimental/swift/TensorFlowLite.tulsiproj/project.tulsiconf +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configDefaults" : { - "optionSet" : { - "ProjectPrioritizesSwift" : { - "p" : "YES" - } - } - }, - "projectName" : "TensorFlowLite", - "packages" : [ - "tensorflow/lite/experimental/swift" - ], - "workspaceRoot" : "../../../../.." -} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj deleted file mode 100644 index fbbf9a1de2..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp.xcodeproj/project.pbxproj +++ /dev/null @@ -1,345 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - -/* Begin PBXBuildFile section */ - 4A7304B421500B8400C90B21 /* Data+TensorFlowLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */; }; - 4AA72B732146ED64006C3AEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA72B722146ED64006C3AEF /* AppDelegate.swift */; }; - 4AA72B752146ED64006C3AEF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA72B742146ED64006C3AEF /* ViewController.swift */; }; - 4AA72B782146ED64006C3AEF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B762146ED64006C3AEF /* Main.storyboard */; }; - 4AA72B7A2146ED66006C3AEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B792146ED66006C3AEF /* Assets.xcassets */; }; - 4AA72B7D2146ED66006C3AEF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */; }; - 4ADDE0CE2176600E00FF07A2 /* Array+TensorFlowLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+TensorFlowLite.swift"; sourceTree = ""; }; - 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TensorFlowLiteApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 4AA72B722146ED64006C3AEF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 4AA72B742146ED64006C3AEF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - 4AA72B772146ED64006C3AEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 4AA72B792146ED66006C3AEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 4AA72B7C2146ED66006C3AEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 4AA72B7E2146ED66006C3AEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+TensorFlowLite.swift"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 4AA72B6C2146ED64006C3AEF /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 4AA72B662146ED64006C3AEF = { - isa = PBXGroup; - children = ( - 4AA72B712146ED64006C3AEF /* TensorFlowLiteApp */, - 4AA72B702146ED64006C3AEF /* Products */, - ); - sourceTree = ""; - }; - 4AA72B702146ED64006C3AEF /* Products */ = { - isa = PBXGroup; - children = ( - 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */, - ); - name = Products; - sourceTree = ""; - }; - 4AA72B712146ED64006C3AEF /* TensorFlowLiteApp */ = { - isa = PBXGroup; - children = ( - 4AA72B722146ED64006C3AEF /* AppDelegate.swift */, - 4ADDE0CD2176600900FF07A2 /* Array+TensorFlowLite.swift */, - 4A7304B321500B8300C90B21 /* Data+TensorFlowLite.swift */, - 4AA72B742146ED64006C3AEF /* ViewController.swift */, - 4AA72B762146ED64006C3AEF /* Main.storyboard */, - 4AA72B792146ED66006C3AEF /* Assets.xcassets */, - 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */, - 4AA72B7E2146ED66006C3AEF /* Info.plist */, - ); - path = TensorFlowLiteApp; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 4AA72B6E2146ED64006C3AEF /* TensorFlowLiteApp */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4AA72B812146ED66006C3AEF /* Build configuration list for PBXNativeTarget "TensorFlowLiteApp" */; - buildPhases = ( - 4AA72B6B2146ED64006C3AEF /* Sources */, - 4AA72B6C2146ED64006C3AEF /* Frameworks */, - 4AA72B6D2146ED64006C3AEF /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = TensorFlowLiteApp; - productName = TensorFlowLiteApp; - productReference = 4AA72B6F2146ED64006C3AEF /* TensorFlowLiteApp.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 4AA72B672146ED64006C3AEF /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0940; - LastUpgradeCheck = 0940; - ORGANIZATIONNAME = Google; - TargetAttributes = { - 4AA72B6E2146ED64006C3AEF = { - CreatedOnToolsVersion = 9.4.1; - }; - }; - }; - buildConfigurationList = 4AA72B6A2146ED64006C3AEF /* Build configuration list for PBXProject "TensorFlowLiteApp" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 4AA72B662146ED64006C3AEF; - productRefGroup = 4AA72B702146ED64006C3AEF /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 4AA72B6E2146ED64006C3AEF /* TensorFlowLiteApp */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 4AA72B6D2146ED64006C3AEF /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4AA72B7D2146ED66006C3AEF /* LaunchScreen.storyboard in Resources */, - 4AA72B7A2146ED66006C3AEF /* Assets.xcassets in Resources */, - 4AA72B782146ED64006C3AEF /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 4AA72B6B2146ED64006C3AEF /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4AA72B732146ED64006C3AEF /* AppDelegate.swift in Sources */, - 4ADDE0CE2176600E00FF07A2 /* Array+TensorFlowLite.swift in Sources */, - 4A7304B421500B8400C90B21 /* Data+TensorFlowLite.swift in Sources */, - 4AA72B752146ED64006C3AEF /* ViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 4AA72B762146ED64006C3AEF /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 4AA72B772146ED64006C3AEF /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 4AA72B7B2146ED66006C3AEF /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 4AA72B7C2146ED66006C3AEF /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 4AA72B7F2146ED66006C3AEF /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 4AA72B802146ED66006C3AEF /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 4AA72B822146ED66006C3AEF /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = TensorFlowLiteApp/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.tensorflow.lite.swift.TensorFlowLite; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 4AA72B832146ED66006C3AEF /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = TensorFlowLiteApp/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.tensorflow.lite.swift.TensorFlowLite; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 4AA72B6A2146ED64006C3AEF /* Build configuration list for PBXProject "TensorFlowLiteApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4AA72B7F2146ED66006C3AEF /* Debug */, - 4AA72B802146ED66006C3AEF /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4AA72B812146ED66006C3AEF /* Build configuration list for PBXNativeTarget "TensorFlowLiteApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4AA72B822146ED66006C3AEF /* Debug */, - 4AA72B832146ED66006C3AEF /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 4AA72B672146ED64006C3AEF /* Project object */; -} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift deleted file mode 100644 index ffa90a06ad..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/AppDelegate.swift +++ /dev/null @@ -1,24 +0,0 @@ -import UIKit - -@UIApplicationMain - -final class AppDelegate: UIResponder, UIApplicationDelegate { - - /// The main window of the app. - var window: UIWindow? - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil - ) -> Bool { - return true - } -} - -// MARK: - Extensions - -#if !swift(>=4.2) -extension UIApplication { - typealias LaunchOptionsKey = UIApplicationLaunchOptionsKey -} -#endif // !swift(>=4.2) diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift deleted file mode 100644 index 56df1ce659..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Array+TensorFlowLite.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -extension Array { - /// Creates a new array from the bytes of the given unsafe data. - /// - /// - Warning: The array's `Element` type must be trivial in that it can be copied bit for bit - /// with no indirection or reference-counting operations; otherwise, copying the raw bytes in - /// the `unsafeData`'s buffer to a new array returns an unsafe copy. - /// - Note: Returns `nil` if `unsafeData.count` is not a multiple of - /// `MemoryLayout.stride`. - /// - Parameter unsafeData: The data containing the bytes to turn into an array. - init?(unsafeData: Data) { - guard unsafeData.count % MemoryLayout.stride == 0 else { return nil } - let elements = unsafeData.withUnsafeBytes { - UnsafeBufferPointer( - start: $0, - count: unsafeData.count / MemoryLayout.stride - ) - } - self.init(elements) - } -} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d8db8d65fd..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" - }, - { - "idiom" : "ios-marketing", - "size" : "1024x1024", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json deleted file mode 100644 index da4a164c91..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index a07a1321be..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard deleted file mode 100644 index 10cae6e855..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/Main.storyboard +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift deleted file mode 100644 index bc8a70c848..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Data+TensorFlowLite.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension Data { - /// Creates a new buffer by copying the buffer pointer of the given array. - /// - /// - Warning: The given array's element type `T` must be trivial in that it can be copied bit - /// for bit with no indirection or reference-counting operations; otherwise, reinterpreting - /// data from the resulting buffer has undefined behavior. - /// - Parameter array: An array with elements of type `T`. - init(copyingBufferOf array: [T]) { - self = array.withUnsafeBufferPointer(Data.init) - } -} diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist deleted file mode 100644 index 3ca3875f04..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Info.plist +++ /dev/null @@ -1,46 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 0.0.1 - LSRequiresIPhoneOS - - NSCameraUsageDescription - NSCameraUsageDescription - NSPhotoLibraryUsageDescription - Select a photo to detect objects in. - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - - diff --git a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift b/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift deleted file mode 100644 index 73c74fd19c..0000000000 --- a/tensorflow/lite/experimental/swift/TestApps/TensorFlowLiteApp/TensorFlowLiteApp/ViewController.swift +++ /dev/null @@ -1,299 +0,0 @@ -import TensorFlowLite -import UIKit - -class ViewController: UIViewController { - - // MARK: - Properties - - /// TensorFlowLite interpreter object for performing inference from a given model. - private var interpreter: Interpreter? - - /// Serial dispatch queue for managing `Interpreter` calls. - private let interpreterQueue = DispatchQueue( - label: Constant.dispatchQueueLabel, - qos: .userInitiated - ) - - /// The currently selected model. - private var currentModel: Model { - guard let currentModel = Model(rawValue: modelControl.selectedSegmentIndex) else { - preconditionFailure("Invalid model for selected segment index.") - } - return currentModel - } - - /// A description of the current model. - private var modelDescription: String { - guard let interpreter = interpreter else { return "" } - let inputCount = interpreter.inputTensorCount - let outputCount = interpreter.outputTensorCount - let inputTensors = (0.. String = { - guard let results = [Float32](unsafeData: outputTensor.data) else { return "No results." } - return resultsText + results.description - } - self.updateResultsText(results()) - } catch let error { - self.updateResultsText( - "Failed to invoke the interpreter with error: \(error.localizedDescription)" - ) - return - } - } - } - - private func invokeAddQuantized() { - interpreterQueue.async { - guard let interpreter = self.interpreter else { - self.updateResultsText(Constant.nilInterpreterErrorMessage) - return - } - do { - try interpreter.resizeInput(at: 0, to: [2]) - try interpreter.allocateTensors() - let input: [UInt8] = [1, 3] - let resultsText = self.modelDescription + "\n\n" + - "Performing 2 add operations on quantized input \(input.description) equals: " - self.updateResultsText(resultsText) - let data = Data(input) - try interpreter.copy(data, toInputAt: 0) - try interpreter.invoke() - let outputTensor = try interpreter.output(at: 0) - let results: () -> String = { - guard let quantizationParameters = outputTensor.quantizationParameters else { - return "No results." - } - let quantizedResults = [UInt8](outputTensor.data) - let dequantizedResults = quantizedResults.map { - quantizationParameters.scale * Float(Int($0) - quantizationParameters.zeroPoint) - } - return resultsText + quantizedResults.description + - ", dequantized results: " + dequantizedResults.description - } - self.updateResultsText(results()) - } catch let error { - self.updateResultsText( - "Failed to invoke the interpreter with error: \(error.localizedDescription)" - ) - return - } - } - } - - private func invokeMultiAdd() { - interpreterQueue.async { - guard let interpreter = self.interpreter else { - self.updateResultsText(Constant.nilInterpreterErrorMessage) - return - } - do { - let shape = TensorShape(2) - try (0.. [Float32] in - let input = [Float32(index + 1), Float32(index + 2)] - let data = Data(copyingBufferOf: input) - try interpreter.copy(data, toInputAt: index) - return input - } - let resultsText = self.modelDescription + "\n\n" + - "Performing 3 add operations on inputs \(inputs.description) equals: " - self.updateResultsText(resultsText) - try interpreter.invoke() - let results = try (0.. [Float32] in - let tensor = try interpreter.output(at: index) - return [Float32](unsafeData: tensor.data) ?? [] - } - self.updateResultsText(resultsText + results.description) - } catch let error { - self.updateResultsText( - "Failed to invoke the interpreter with error: \(error.localizedDescription)" - ) - return - } - } - } - - private func updateResultsText(_ text: String? = nil) { - safeDispatchOnMain { self.resultsTextView.text = text } - } -} - -// MARK: - Constants - -private enum Constant { - static let dispatchQueueLabel = "TensorFlowLiteInterpreterQueue" - static let nilInterpreterErrorMessage = - "Failed to invoke the interpreter because the interpreter was nil." -} - -/// Models that can be loaded by the TensorFlow Lite `Interpreter`. -private enum Model: Int, CustomStringConvertible { - /// A float model that performs two add operations on one input tensor and returns the result in - /// one output tensor. - case add = 0 - /// A quantized model that performs two add operations on one input tensor and returns the result - /// in one output tensor. - case addQuantized = 1 - /// A float model that performs three add operations on four input tensors and returns the results - /// in 2 output tensors. - case multiAdd = 2 - - var fileInfo: (name: String, extension: String) { - switch self { - case .add: - return Add.fileInfo - case .addQuantized: - return AddQuantized.fileInfo - case .multiAdd: - return MultiAdd.fileInfo - } - } - - // MARK: - CustomStringConvertible - - var description: String { - switch self { - case .add: - return Add.name - case .addQuantized: - return AddQuantized.name - case .multiAdd: - return MultiAdd.name - } - } -} - -/// Values for the `Add` model. -private enum Add { - static let name = "Add" - static let fileInfo = (name: "add", extension: "bin") -} - -/// Values for the `AddQuantized` model. -private enum AddQuantized { - static let name = "AddQuantized" - static let fileInfo = (name: "add_quantized", extension: "bin") -} - -/// Values for the `MultiAdd` model. -private enum MultiAdd { - static let name = "MultiAdd" - static let fileInfo = (name: "multi_add", extension: "bin") -} - -// MARK: - Fileprivate - -/// Safely dispatches the given block on the main queue. If the current thread is `main`, the block -/// is executed synchronously; otherwise, the block is executed asynchronously on the main thread. -fileprivate func safeDispatchOnMain(_ block: @escaping () -> Void) { - if Thread.isMainThread { block(); return } - DispatchQueue.main.async { block() } -} diff --git a/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift b/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift deleted file mode 100644 index 54b4f59b28..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/InterpreterOptionsTests.swift +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class InterpreterOptionsTests: XCTestCase { - - func testInterpreterOptions_InitWithDefaultValues() { - let options = InterpreterOptions() - XCTAssertNil(options.threadCount) - XCTAssertFalse(options.isErrorLoggingEnabled) - } - - func testInterpreterOptions_InitWithCustomValues() { - var options = InterpreterOptions() - options.threadCount = 2 - XCTAssertEqual(options.threadCount, 2) - options.isErrorLoggingEnabled = true - XCTAssertTrue(options.isErrorLoggingEnabled) - } - - func testInterpreterOptions_Equatable() { - var options1 = InterpreterOptions() - var options2 = InterpreterOptions() - XCTAssertEqual(options1, options2) - - options1.threadCount = 2 - options2.threadCount = 2 - XCTAssertEqual(options1, options2) - - options2.threadCount = 3 - XCTAssertNotEqual(options1, options2) - options2.threadCount = 2 - - options1.isErrorLoggingEnabled = true - options2.isErrorLoggingEnabled = true - XCTAssertEqual(options1, options2) - - options2.isErrorLoggingEnabled = false - XCTAssertNotEqual(options1, options2) - } -} diff --git a/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift b/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift deleted file mode 100644 index e98da5f951..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/InterpreterTests.swift +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class InterpreterTests: XCTestCase { - - var interpreter: Interpreter! - - override func setUp() { - super.setUp() - - interpreter = try! Interpreter(modelPath: AddModel.path) - } - - override func tearDown() { - interpreter = nil - - super.tearDown() - } - - func testInterpreter_InitWithModelPath() { - XCTAssertNoThrow(try Interpreter(modelPath: AddModel.path)) - } - - func testInterpreter_Init_ThrowsFailedToLoadModel() { - XCTAssertThrowsError(try Interpreter(modelPath: "/invalid/path")) { error in - self.assertEqualErrors(actual: error, expected: .failedToLoadModel) - } - } - - func testInterpreter_InitWithModelPathAndOptions() { - var options = InterpreterOptions() - options.threadCount = 2 - XCTAssertNoThrow(try Interpreter(modelPath: AddModel.path, options: options)) - } - - func testInterpreter_InputTensorCount() { - XCTAssertEqual(interpreter.inputTensorCount, AddModel.inputTensorCount) - } - - func testInterpreter_OutputTensorCount() { - XCTAssertEqual(interpreter.outputTensorCount, AddModel.outputTensorCount) - } - - func testInterpreter_Invoke() throws { - try interpreter.allocateTensors() - XCTAssertNoThrow(try interpreter.invoke()) - } - - func testInterpreter_Invoke_ThrowsAllocateTensorsRequired_ModelNotReady() { - XCTAssertThrowsError(try interpreter.invoke()) { error in - self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired) - } - } - - func testInterpreter_InputTensorAtIndex() throws { - try setUpAddModelInputTensor() - let inputTensor = try interpreter.input(at: AddModel.validIndex) - XCTAssertEqual(inputTensor, AddModel.inputTensor) - } - - func testInterpreter_InputTensorAtIndex_QuantizedModel() throws { - interpreter = try Interpreter(modelPath: AddQuantizedModel.path) - try setUpAddQuantizedModelInputTensor() - let inputTensor = try interpreter.input(at: AddQuantizedModel.inputOutputIndex) - XCTAssertEqual(inputTensor, AddQuantizedModel.inputTensor) - } - - func testInterpreter_InputTensorAtIndex_ThrowsInvalidIndex() throws { - try interpreter.allocateTensors() - XCTAssertThrowsError(try interpreter.input(at: AddModel.invalidIndex)) { error in - let maxIndex = AddModel.inputTensorCount - 1 - self.assertEqualErrors( - actual: error, - expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) - ) - } - } - - func testInterpreter_InputTensorAtIndex_ThrowsAllocateTensorsRequired() { - XCTAssertThrowsError(try interpreter.input(at: AddModel.validIndex)) { error in - self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired) - } - } - - func testInterpreter_OutputTensorAtIndex() throws { - try setUpAddModelInputTensor() - try interpreter.invoke() - let outputTensor = try interpreter.output(at: AddModel.validIndex) - XCTAssertEqual(outputTensor, AddModel.outputTensor) - let expectedResults = [Float32](unsafeData: outputTensor.data) - XCTAssertEqual(expectedResults, AddModel.results) - } - - func testInterpreter_OutputTensorAtIndex_QuantizedModel() throws { - interpreter = try Interpreter(modelPath: AddQuantizedModel.path) - try setUpAddQuantizedModelInputTensor() - try interpreter.invoke() - let outputTensor = try interpreter.output(at: AddQuantizedModel.inputOutputIndex) - XCTAssertEqual(outputTensor, AddQuantizedModel.outputTensor) - let expectedResults = [UInt8](outputTensor.data) - XCTAssertEqual(expectedResults, AddQuantizedModel.results) - } - - func testInterpreter_OutputTensorAtIndex_ThrowsInvalidIndex() throws { - try interpreter.allocateTensors() - try interpreter.invoke() - XCTAssertThrowsError(try interpreter.output(at: AddModel.invalidIndex)) { error in - let maxIndex = AddModel.outputTensorCount - 1 - self.assertEqualErrors( - actual: error, - expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) - ) - } - } - - func testInterpreter_OutputTensorAtIndex_ThrowsInvokeInterpreterRequired() { - XCTAssertThrowsError(try interpreter.output(at: AddModel.validIndex)) { error in - self.assertEqualErrors(actual: error, expected: .invokeInterpreterRequired) - } - } - - func testInterpreter_ResizeInputTensorAtIndexToShape() { - XCTAssertNoThrow(try interpreter.resizeInput(at: AddModel.validIndex, to: [2, 2, 3])) - XCTAssertNoThrow(try interpreter.allocateTensors()) - } - - func testInterpreter_ResizeInputTensorAtIndexToShape_ThrowsInvalidIndex() { - XCTAssertThrowsError(try interpreter.resizeInput( - at: AddModel.invalidIndex, - to: [2, 2, 3] - )) { error in - let maxIndex = AddModel.inputTensorCount - 1 - self.assertEqualErrors( - actual: error, - expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) - ) - } - } - - func testInterpreter_CopyDataToInputTensorAtIndex() throws { - try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) - try interpreter.allocateTensors() - let inputTensor = try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex) - XCTAssertEqual(inputTensor.data, AddModel.inputData) - } - - func testInterpreter_CopyDataToInputTensorAtIndex_ThrowsInvalidIndex() { - XCTAssertThrowsError(try interpreter.copy( - AddModel.inputData, - toInputAt: AddModel.invalidIndex - )) { error in - let maxIndex = AddModel.inputTensorCount - 1 - self.assertEqualErrors( - actual: error, - expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex) - ) - } - } - - func testInterpreter_CopyDataToInputTensorAtIndex_ThrowsInvalidDataCount() throws { - try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) - try interpreter.allocateTensors() - let invalidData = Data(count: AddModel.dataCount - 1) - XCTAssertThrowsError(try interpreter.copy( - invalidData, - toInputAt: AddModel.validIndex - )) { error in - self.assertEqualErrors( - actual: error, - expected: .invalidTensorDataCount(provided: invalidData.count, required: AddModel.dataCount) - ) - } - } - - func testInterpreter_AllocateTensors() { - XCTAssertNoThrow(try interpreter.allocateTensors()) - } - - // MARK: - Private - - private func setUpAddModelInputTensor() throws { - precondition(interpreter != nil) - try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape) - try interpreter.allocateTensors() - try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex) - } - - private func setUpAddQuantizedModelInputTensor() throws { - precondition(interpreter != nil) - try interpreter.resizeInput(at: AddQuantizedModel.inputOutputIndex, to: AddQuantizedModel.shape) - try interpreter.allocateTensors() - try interpreter.copy(AddQuantizedModel.inputData, toInputAt: AddQuantizedModel.inputOutputIndex) - } - - private func assertEqualErrors(actual: Error, expected: InterpreterError) { - guard let actual = actual as? InterpreterError else { - XCTFail("Actual error should be of type InterpreterError.") - return - } - XCTAssertEqual(actual, expected) - } -} - -// MARK: - Constants - -/// Values for the `add.bin` model. -private enum AddModel { - static let info = (name: "add", extension: "bin") - static let inputTensorCount = 1 - static let outputTensorCount = 1 - static let invalidIndex = 1 - static let validIndex = 0 - static let shape: TensorShape = [2] - static let dataCount = inputData.count - static let inputData = Data(copyingBufferOf: [Float32(1.0), Float32(3.0)]) - static let outputData = Data(copyingBufferOf: [Float32(3.0), Float32(9.0)]) - static let results = [Float32(3.0), Float32(9.0)] - - static let inputTensor = Tensor( - name: "input", - dataType: .float32, - shape: shape, - data: inputData - ) - static let outputTensor = Tensor( - name: "output", - dataType: .float32, - shape: shape, - data: outputData - ) - - static var path: String = { - let bundle = Bundle(for: InterpreterTests.self) - guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" } - return path - }() -} - -/// Values for the `add_quantized.bin` model. -private enum AddQuantizedModel { - static let info = (name: "add_quantized", extension: "bin") - static let inputOutputIndex = 0 - static let shape: TensorShape = [2] - static let inputData = Data([1, 3]) - static let outputData = Data([3, 9]) - static let quantizationParameters = QuantizationParameters(scale: 0.003922, zeroPoint: 0) - static let results: [UInt8] = [3, 9] - - static let inputTensor = Tensor( - name: "input", - dataType: .uInt8, - shape: shape, - data: inputData, - quantizationParameters: quantizationParameters - ) - static let outputTensor = Tensor( - name: "output", - dataType: .uInt8, - shape: shape, - data: outputData, - quantizationParameters: quantizationParameters - ) - - static var path: String = { - let bundle = Bundle(for: InterpreterTests.self) - guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" } - return path - }() -} - -// MARK: - Extensions - -extension Array { - /// Creates a new array from the bytes of the given unsafe data. - /// - /// - Note: Returns `nil` if `unsafeData.count` is not a multiple of - /// `MemoryLayout.stride`. - /// - Parameter unsafeData: The data containing the bytes to turn into an array. - init?(unsafeData: Data) { - guard unsafeData.count % MemoryLayout.stride == 0 else { return nil } - let elements = unsafeData.withUnsafeBytes { - UnsafeBufferPointer( - start: $0, - count: unsafeData.count / MemoryLayout.stride - ) - } - self.init(elements) - } -} - -extension Data { - /// Creates a new buffer by copying the buffer pointer of the given array. - /// - /// - Warning: The given array's element type `T` must be trivial in that it can be copied bit - /// for bit with no indirection or reference-counting operations; otherwise, reinterpreting - /// data from the resulting buffer has undefined behavior. - /// - Parameter array: An array with elements of type `T`. - init(copyingBufferOf array: [T]) { - self = array.withUnsafeBufferPointer(Data.init) - } -} diff --git a/tensorflow/lite/experimental/swift/Tests/ModelTests.swift b/tensorflow/lite/experimental/swift/Tests/ModelTests.swift deleted file mode 100644 index 025db18906..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/ModelTests.swift +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class ModelTests: XCTestCase { - - var modelPath: String! - - override func setUp() { - super.setUp() - - let bundle = Bundle(for: type(of: self)) - guard let modelPath = bundle.path( - forResource: Constant.modelInfo.name, - ofType: Constant.modelInfo.extension) - else { - XCTFail("Failed to get the model file path.") - return - } - self.modelPath = modelPath - } - - override func tearDown() { - modelPath = nil - - super.tearDown() - } - - func testModel_InitWithFilePath() { - XCTAssertNotNil(Model(filePath: modelPath)) - } - - func testModel_InitWithEmptyFilePath_FailsInitialization() { - XCTAssertNil(Model(filePath: "")) - } - - func testModel_InitWithInvalidFilePath_FailsInitialization() { - XCTAssertNil(Model(filePath: "invalid/path")) - } -} - -// MARK: - Constants - -private enum Constant { - static let modelInfo = (name: "add", extension: "bin") -} diff --git a/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift b/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift deleted file mode 100644 index 65648c2698..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/QuantizationParametersTests.swift +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class QuantizationParametersTests: XCTestCase { - - func testQuantizationParameters_InitWithCustomValues() { - let parameters = QuantizationParameters(scale: 0.5, zeroPoint: 1) - XCTAssertEqual(parameters.scale, 0.5) - XCTAssertEqual(parameters.zeroPoint, 1) - } - - func testQuantizationParameters_Equatable() { - let parameters1 = QuantizationParameters(scale: 0.5, zeroPoint: 1) - let parameters2 = QuantizationParameters(scale: 0.5, zeroPoint: 1) - XCTAssertEqual(parameters1, parameters2) - - let parameters3 = QuantizationParameters(scale: 0.4, zeroPoint: 1) - XCTAssertNotEqual(parameters1, parameters3) - XCTAssertNotEqual(parameters2, parameters3) - } -} - -// MARK: - Extensions - -extension QuantizationParameters: Equatable { - public static func == (lhs: QuantizationParameters, rhs: QuantizationParameters) -> Bool { - return lhs.scale == rhs.scale && lhs.zeroPoint == rhs.zeroPoint - } -} diff --git a/tensorflow/lite/experimental/swift/Tests/TensorTests.swift b/tensorflow/lite/experimental/swift/Tests/TensorTests.swift deleted file mode 100644 index 4540043a16..0000000000 --- a/tensorflow/lite/experimental/swift/Tests/TensorTests.swift +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2018 Google Inc. 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. - -@testable import TensorFlowLite -import XCTest - -class TensorTests: XCTestCase { - - // MARK: - Tensor - - func testTensor_Init() { - let name = "InputTensor" - let dataType: TensorDataType = .uInt8 - let shape = TensorShape(Constant.dimensions) - guard let data = name.data(using: .utf8) else { XCTFail("Data should not be nil."); return } - let quantizationParameters = QuantizationParameters(scale: 0.5, zeroPoint: 1) - let inputTensor = Tensor( - name: name, - dataType: dataType, - shape: shape, - data: data, - quantizationParameters: quantizationParameters - ) - XCTAssertEqual(inputTensor.name, name) - XCTAssertEqual(inputTensor.dataType, dataType) - XCTAssertEqual(inputTensor.shape, shape) - XCTAssertEqual(inputTensor.data, data) - XCTAssertEqual(inputTensor.quantizationParameters, quantizationParameters) - } - - // MARK: - TensorShape - - func testTensorShape_InitWithArray() { - let shape = TensorShape(Constant.dimensions) - XCTAssertEqual(shape.rank, Constant.dimensions.count) - XCTAssertEqual(shape.dimensions, Constant.dimensions) - } - - func testTensorShape_InitWithElements() { - let shape = TensorShape(2, 2, 3) - XCTAssertEqual(shape.rank, Constant.dimensions.count) - XCTAssertEqual(shape.dimensions, Constant.dimensions) - } - - func testTensorShape_InitWithArrayLiteral() { - let shape: TensorShape = [2, 2, 3] - XCTAssertEqual(shape.rank, Constant.dimensions.count) - XCTAssertEqual(shape.dimensions, Constant.dimensions) - } -} - -// MARK: - Constants - -private enum Constant { - /// Array of 2 arrays of 2 arrays of 3 numbers: [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]]. - static let dimensions = [2, 2, 3] -} - -// MARK: - Extensions - -extension TensorShape: Equatable { - public static func == (lhs: TensorShape, rhs: TensorShape) -> Bool { - return lhs.rank == rhs.rank && lhs.dimensions == rhs.dimensions - } -} - -extension Tensor: Equatable { - public static func == (lhs: Tensor, rhs: Tensor) -> Bool { - return lhs.name == rhs.name && lhs.dataType == rhs.dataType && lhs.shape == rhs.shape && - lhs.data == rhs.data && lhs.quantizationParameters == rhs.quantizationParameters - } -} -- GitLab From fd54d400ba60e57daf431afe209133226e03acfc Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Tue, 8 Jan 2019 14:10:55 -0800 Subject: [PATCH 0373/2345] Unify RMSProp w/o momentum. PiperOrigin-RevId: 228396933 --- tensorflow/python/keras/optimizer_v2/BUILD | 1 + .../keras/optimizer_v2/optimizer_v2_test.py | 2 +- .../python/keras/optimizer_v2/rmsprop.py | 155 ++++++++++++------ .../python/keras/optimizer_v2/rmsprop_test.py | 106 ++++++++---- 4 files changed, 181 insertions(+), 83 deletions(-) diff --git a/tensorflow/python/keras/optimizer_v2/BUILD b/tensorflow/python/keras/optimizer_v2/BUILD index 964cf5fcba..2b990c9334 100644 --- a/tensorflow/python/keras/optimizer_v2/BUILD +++ b/tensorflow/python/keras/optimizer_v2/BUILD @@ -217,4 +217,5 @@ cuda_py_test( "//tensorflow/python:state_ops", "//tensorflow/python:variables", ], + shard_count = 2, ) diff --git a/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py b/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py index 58c8b1f343..b41e4aa064 100644 --- a/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py +++ b/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py @@ -507,7 +507,7 @@ class OptimizersCompatibilityTest(test.TestCase, parameterized.TestCase): ('adadelta', 'adadelta', True), ('adagrad', 'adagrad', True), ('adam', 'adam', True), ('adamax', 'adamax', True), ('nadam', 'nadam', True), ('momentum', 'momentum', True), - ('sgd', 'sgd', False)) + ('rmsprop', 'rmsprop', True), ('sgd', 'sgd', False)) def testOptimizersCompatibility(self, opt_str, test_weights): np.random.seed(1331) with self.cached_session(): diff --git a/tensorflow/python/keras/optimizer_v2/rmsprop.py b/tensorflow/python/keras/optimizer_v2/rmsprop.py index 3f153c4a41..83b81cbe5d 100644 --- a/tensorflow/python/keras/optimizer_v2/rmsprop.py +++ b/tensorflow/python/keras/optimizer_v2/rmsprop.py @@ -17,8 +17,14 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import numpy as np + from tensorflow.python.framework import ops from tensorflow.python.keras.optimizer_v2 import optimizer_v2 +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 state_ops from tensorflow.python.training import training_ops from tensorflow.python.util.tf_export import keras_export @@ -114,77 +120,122 @@ class RMSprop(optimizer_v2.OptimizerV2): def _create_slots(self, var_list): for var in var_list: self.add_slot(var, "rms") - self.add_slot(var, "momentum") - if self.centered: + if self._momentum: + for var in var_list: + self.add_slot(var, "momentum") + if self.centered: + for var in var_list: self.add_slot(var, "mg") def _resource_apply_dense(self, grad, var): var_dtype = var.dtype.base_dtype lr_t = self._decayed_lr(var_dtype) rms = self.get_slot(var, "rms") - mom = self.get_slot(var, "momentum") rho = self._get_hyper("rho", var_dtype) momentum = self._get_hyper("momentum", var_dtype) epsilon = self._get_hyper("epsilon", var_dtype) - if self.centered: - mg = self.get_slot(var, "mg") - return training_ops.resource_apply_centered_rms_prop( - var.handle, - mg.handle, - rms.handle, - mom.handle, - lr_t, - rho, - momentum, - epsilon, - grad, - use_locking=self._use_locking) + if self._momentum: + mom = self.get_slot(var, "momentum") + if self.centered: + mg = self.get_slot(var, "mg") + return training_ops.resource_apply_centered_rms_prop( + var.handle, + mg.handle, + rms.handle, + mom.handle, + lr_t, + rho, + momentum, + epsilon, + grad, + use_locking=self._use_locking) + else: + return training_ops.resource_apply_rms_prop( + var.handle, + rms.handle, + mom.handle, + lr_t, + rho, + momentum, + epsilon, + grad, + use_locking=self._use_locking) else: - return training_ops.resource_apply_rms_prop( - var.handle, - rms.handle, - mom.handle, - lr_t, - rho, - momentum, - epsilon, - grad, - use_locking=self._use_locking) + rms_t = rho * rms + (1. - rho) * math_ops.square(grad) + rms_t = state_ops.assign(rms, rms_t, use_locking=self._use_locking) + denom_t = rms_t + if self.centered: + mg = self.get_slot(var, "mg") + mg_t = rho * mg + (1. - rho) * grad + mg_t = state_ops.assign(mg, mg_t, use_locking=self._use_locking) + denom_t = rms_t - math_ops.square(mg_t) + var_t = var - lr_t * grad / (math_ops.sqrt(denom_t) + epsilon) + return state_ops.assign(var, var_t, use_locking=self._use_locking).op def _resource_apply_sparse(self, grad, var, indices): var_dtype = var.dtype.base_dtype lr_t = self._decayed_lr(var_dtype) rms = self.get_slot(var, "rms") - mom = self.get_slot(var, "momentum") rho = self._get_hyper("rho", var_dtype) momentum = self._get_hyper("momentum", var_dtype) epsilon = self._get_hyper("epsilon", var_dtype) - if self.centered: - mg = self.get_slot(var, "mg") - return training_ops.resource_sparse_apply_centered_rms_prop( - var.handle, - mg.handle, - rms.handle, - mom.handle, - lr_t, - rho, - momentum, - epsilon, - grad, - indices, - use_locking=self._use_locking) + if self._momentum: + mom = self.get_slot(var, "momentum") + if self.centered: + mg = self.get_slot(var, "mg") + return training_ops.resource_sparse_apply_centered_rms_prop( + var.handle, + mg.handle, + rms.handle, + mom.handle, + lr_t, + rho, + momentum, + epsilon, + grad, + indices, + use_locking=self._use_locking) + else: + return training_ops.resource_sparse_apply_rms_prop( + var.handle, + rms.handle, + mom.handle, + lr_t, + rho, + momentum, + epsilon, + grad, + indices, + use_locking=self._use_locking) else: - return training_ops.resource_sparse_apply_rms_prop( - var.handle, - rms.handle, - mom.handle, - lr_t, - rho, - momentum, - epsilon, - grad, - indices, - use_locking=self._use_locking) + rms_scaled_g_values = (grad * grad) * (1. - rho) + rms_t = state_ops.assign(rms, rms * rho, use_locking=self._use_locking) + with ops.control_dependencies([rms_t]): + rms_t = self._resource_scatter_add(rms, indices, rms_scaled_g_values) + rms_slice = array_ops.gather(rms_t, indices) + denom_slice = rms_slice + if self.centered: + mg = self.get_slot(var, "mg") + mg_scaled_g_values = grad * (1. - rho) + mg_t = state_ops.assign(mg, mg * rho, use_locking=self._use_locking) + with ops.control_dependencies([mg_t]): + mg_t = self._resource_scatter_add(mg, indices, mg_scaled_g_values) + mg_slice = array_ops.gather(mg_t, indices) + denom_slice = rms_slice - math_ops.square(mg_slice) + var_update = self._resource_scatter_add( + var, indices, -lr_t * grad / (math_ops.sqrt(denom_slice) + epsilon)) + if self.centered: + return control_flow_ops.group(*[var_update, rms_t, mg_t]) + return control_flow_ops.group(*[var_update, rms_t]) + + def set_weights(self, weights): + params = self.weights + # Override set_weights for backward compatibility of Keras V1 optimizer + # since it does not include iteration at head of the weight list. Set + # iteration to 0. + if len(params) == len(weights) + 1: + weights = [np.array(0)] + weights + super(RMSprop, self).set_weights(weights) def get_config(self): config = super(RMSprop, self).get_config() diff --git a/tensorflow/python/keras/optimizer_v2/rmsprop_test.py b/tensorflow/python/keras/optimizer_v2/rmsprop_test.py index 4d61cfbbc5..67662aae20 100644 --- a/tensorflow/python/keras/optimizer_v2/rmsprop_test.py +++ b/tensorflow/python/keras/optimizer_v2/rmsprop_test.py @@ -58,14 +58,18 @@ class RMSpropOptimizerTest(test.TestCase): def _rmsprop_update_numpy(self, var, g, mg, rms, mom, lr, rho, momentum, epsilon, centered): rms_t = rms * rho + (1 - rho) * g * g - denom_t = rms_t + epsilon if centered: mg_t = mg * rho + (1 - rho) * g - denom_t -= mg_t * mg_t + denom_t = rms_t - mg_t * mg_t else: mg_t = mg - mom_t = momentum * mom + lr * g / np.sqrt(denom_t, dtype=denom_t.dtype) - var_t = var - mom_t + denom_t = rms_t + if momentum > 0.: + mom_t = momentum * mom + lr * g / (np.sqrt(denom_t + epsilon)) + var_t = var - mom_t + else: + mom_t = mom + var_t = var - lr * g / (np.sqrt(denom_t) + epsilon) return var_t, mg_t, rms_t, mom_t def _sparse_rmsprop_update_numpy(self, var, gindexs, gvalues, mg, rms, mom, @@ -78,12 +82,18 @@ class RMSpropOptimizerTest(test.TestCase): gindex = gindexs[i] gvalue = gvalues[i] rms_t[gindex] = rms[gindex] * rho + (1 - rho) * gvalue * gvalue - denom_t = rms_t[gindex] + epsilon if centered: mg_t[gindex] = mg_t[gindex] * rho + (1 - rho) * gvalue - denom_t -= mg_t[gindex] * mg_t[gindex] - mom_t[gindex] = momentum * mom[gindex] + lr * gvalue / np.sqrt(denom_t) - var_t[gindex] = var[gindex] - mom_t[gindex] + denom_t = rms_t[gindex] - mg_t[gindex] * mg_t[gindex] + else: + denom_t = rms_t[gindex] + if momentum > 0.: + mom_t[gindex] = momentum * mom[gindex] + lr * gvalue / np.sqrt(denom_t + + epsilon) + var_t[gindex] = var[gindex] - mom_t[gindex] + else: + mom_t[gindex] = mom[gindex] + var_t[gindex] = var[gindex] - lr * gvalue / (np.sqrt(denom_t) + epsilon) return var_t, mg_t, rms_t, mom_t @test_util.run_deprecated_v1 @@ -117,14 +127,17 @@ class RMSpropOptimizerTest(test.TestCase): mg0 = None mg1 = None + if momentum > 0.: + mom0 = opt.get_slot(var0, "momentum") + mom1 = opt.get_slot(var1, "momentum") + else: + mom0 = None + mom1 = None + rms0 = opt.get_slot(var0, "rms") self.assertTrue(rms0 is not None) rms1 = opt.get_slot(var1, "rms") self.assertTrue(rms1 is not None) - mom0 = opt.get_slot(var0, "momentum") - self.assertTrue(mom0 is not None) - mom1 = opt.get_slot(var1, "momentum") - self.assertTrue(mom1 is not None) mg0_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) mg1_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) @@ -137,8 +150,8 @@ class RMSpropOptimizerTest(test.TestCase): self.assertAllClose([1.0, 2.0], self.evaluate(var0)) self.assertAllClose([3.0, 4.0], self.evaluate(var1)) - # Run 4 steps of RMSprop - for _ in range(1, 5): + # Run 3 steps of RMSprop + for _ in range(1, 4): self.evaluate(update) var0_np, mg0_np, rms0_np, mom0_np = self._rmsprop_update_numpy( @@ -152,10 +165,11 @@ class RMSpropOptimizerTest(test.TestCase): if centered: self.assertAllCloseAccordingToType(mg0_np, self.evaluate(mg0)) self.assertAllCloseAccordingToType(mg1_np, self.evaluate(mg1)) + if momentum > 0.: + self.assertAllCloseAccordingToType(mom0_np, self.evaluate(mom0)) + self.assertAllCloseAccordingToType(mom1_np, self.evaluate(mom1)) self.assertAllCloseAccordingToType(rms0_np, self.evaluate(rms0)) self.assertAllCloseAccordingToType(rms1_np, self.evaluate(rms1)) - self.assertAllCloseAccordingToType(mom0_np, self.evaluate(mom0)) - self.assertAllCloseAccordingToType(mom1_np, self.evaluate(mom1)) self.assertAllCloseAccordingToType(var0_np, self.evaluate(var0)) self.assertAllCloseAccordingToType(var1_np, self.evaluate(var1)) @@ -191,10 +205,12 @@ class RMSpropOptimizerTest(test.TestCase): self.assertTrue(rms0 is not None) rms1 = opt.get_slot(var1, "rms") self.assertTrue(rms1 is not None) - mom0 = opt.get_slot(var0, "momentum") - self.assertTrue(mom0 is not None) - mom1 = opt.get_slot(var1, "momentum") - self.assertTrue(mom1 is not None) + if momentum > 0.: + mom0 = opt.get_slot(var0, "momentum") + mom1 = opt.get_slot(var1, "momentum") + else: + mom0 = None + mom1 = None mg0_np = np.array([0.0, 0.0]) mg1_np = np.array([0.0, 0.0]) @@ -222,8 +238,9 @@ class RMSpropOptimizerTest(test.TestCase): # Validate updated params self.assertAllCloseAccordingToType(rms0_np, self.evaluate(rms0)) self.assertAllCloseAccordingToType(rms1_np, self.evaluate(rms1)) - self.assertAllCloseAccordingToType(mom0_np, self.evaluate(mom0)) - self.assertAllCloseAccordingToType(mom1_np, self.evaluate(mom1)) + if momentum > 0.: + self.assertAllCloseAccordingToType(mom0_np, self.evaluate(mom0)) + self.assertAllCloseAccordingToType(mom1_np, self.evaluate(mom1)) self.assertAllCloseAccordingToType(var0_np, self.evaluate(var0)) self.assertAllCloseAccordingToType(var1_np, self.evaluate(var1)) @@ -325,10 +342,12 @@ class RMSpropOptimizerTest(test.TestCase): self.assertTrue(rms0 is not None) rms1 = opt.get_slot(var1, "rms") self.assertTrue(rms1 is not None) - mom0 = opt.get_slot(var0, "momentum") - self.assertTrue(mom0 is not None) - mom1 = opt.get_slot(var1, "momentum") - self.assertTrue(mom1 is not None) + if momentum > 0.: + mom0 = opt.get_slot(var0, "momentum") + mom1 = opt.get_slot(var1, "momentum") + else: + mom0 = None + mom1 = None mg0_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) mg1_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) @@ -341,8 +360,8 @@ class RMSpropOptimizerTest(test.TestCase): self.assertAllClose([1.0, 2.0], self.evaluate(var0)) self.assertAllClose([3.0, 4.0], self.evaluate(var1)) - # Run 4 steps of RMSprop - for _ in range(1, 5): + # Run 3 steps of RMSprop + for _ in range(1, 4): self.evaluate(update) var0_np, mg0_np, rms0_np, mom0_np = self._sparse_rmsprop_update_numpy( @@ -358,8 +377,9 @@ class RMSpropOptimizerTest(test.TestCase): self.assertAllCloseAccordingToType(mg1_np, self.evaluate(mg1)) self.assertAllCloseAccordingToType(rms0_np, self.evaluate(rms0)) self.assertAllCloseAccordingToType(rms1_np, self.evaluate(rms1)) - self.assertAllCloseAccordingToType(mom0_np, self.evaluate(mom0)) - self.assertAllCloseAccordingToType(mom1_np, self.evaluate(mom1)) + if momentum > 0.: + self.assertAllCloseAccordingToType(mom0_np, self.evaluate(mom0)) + self.assertAllCloseAccordingToType(mom1_np, self.evaluate(mom1)) self.assertAllCloseAccordingToType(var0_np, self.evaluate(var0)) self.assertAllCloseAccordingToType(var1_np, self.evaluate(var1)) @@ -420,6 +440,32 @@ class RMSpropOptimizerTest(test.TestCase): opt_3 = rmsprop.RMSprop(learning_rate=0.1) self.assertEqual(opt_3.lr, 0.1) + def testSlotsUniqueEager(self): + with context.eager_mode(): + v1 = variables.Variable(1.) + v2 = variables.Variable(1.) + + opt = rmsprop.RMSprop(1., momentum=0., centered=False) + opt.minimize(lambda: v1 + v2, var_list=[v1, v2]) + # There should be iteration, and one unique slot variable for v1 and v2. + self.assertEqual(3, len(set(opt.variables()))) + self.assertEqual( + self.evaluate(opt.variables()[0]), self.evaluate(opt.iterations)) + + opt = rmsprop.RMSprop(learning_rate=1., momentum=0.2, centered=False) + opt.minimize(lambda: v1 + v2, var_list=[v1, v2]) + # There should be iteration, and two unique slot variables for v1 and v2. + self.assertEqual(5, len(set(opt.variables()))) + self.assertEqual( + self.evaluate(opt.variables()[0]), self.evaluate(opt.iterations)) + + opt = rmsprop.RMSprop(learning_rate=1., momentum=0.2, centered=True) + opt.minimize(lambda: v1 + v2, var_list=[v1, v2]) + # There should be iteration, and three unique slot variables for v1 and v2 + self.assertEqual(7, len(set(opt.variables()))) + self.assertEqual( + self.evaluate(opt.variables()[0]), self.evaluate(opt.iterations)) + if __name__ == "__main__": test.main() -- GitLab From 0ce305b6ce8560945be3a92eae1bcddd60af5e10 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Tue, 8 Jan 2019 14:53:40 -0800 Subject: [PATCH 0374/2345] [XLA:Python] Add Gather and Scatter to XLA Python bindings. Refactor handling of repeated fields in *DimensionNumbers SWIG rules. PiperOrigin-RevId: 228404715 --- .../xla/python/local_computation_builder.cc | 16 + .../xla/python/local_computation_builder.h | 9 + .../xla/python/local_computation_builder.i | 281 +++++++----------- tensorflow/compiler/xla/python/xla_client.py | 12 + .../compiler/xla/python/xla_client_test.py | 40 +++ 5 files changed, 183 insertions(+), 175 deletions(-) diff --git a/tensorflow/compiler/xla/python/local_computation_builder.cc b/tensorflow/compiler/xla/python/local_computation_builder.cc index b0f0e9e570..c153603105 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.cc +++ b/tensorflow/compiler/xla/python/local_computation_builder.cc @@ -927,6 +927,22 @@ LocalOp LocalComputationBuilder::TriangularSolve(const LocalOp& a, conjugate_a); } +LocalOp LocalComputationBuilder::Gather( + const LocalOp& input, const LocalOp& start_indices, + const GatherDimensionNumbers& dimension_numbers, + absl::Span slice_sizes) { + return xla::Gather(input.op(), start_indices.op(), dimension_numbers, + slice_sizes); +} + +LocalOp LocalComputationBuilder::Scatter( + const LocalOp& input, const LocalOp& scatter_indices, + const LocalOp& updates, const LocalComputation& update_computation, + const ScatterDimensionNumbers& dimension_numbers) { + return xla::Scatter(input.op(), scatter_indices.op(), updates.op(), + update_computation.computation(), dimension_numbers); +} + StatusOr LocalComputationBuilder::BuildConstantSubGraph( const LocalOp& operand) { TF_ASSIGN_OR_RETURN(XlaComputation computation, diff --git a/tensorflow/compiler/xla/python/local_computation_builder.h b/tensorflow/compiler/xla/python/local_computation_builder.h index 5e83415921..98759cf984 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.h +++ b/tensorflow/compiler/xla/python/local_computation_builder.h @@ -418,6 +418,15 @@ class LocalComputationBuilder { LocalOp TriangularSolve(const LocalOp& a, const LocalOp& b, bool left_side, bool lower, bool transpose_a, bool conjugate_a); + LocalOp Gather(const LocalOp& input, const LocalOp& start_indices, + const GatherDimensionNumbers& dimension_numbers, + absl::Span slice_sizes); + + LocalOp Scatter(const LocalOp& input, const LocalOp& scatter_indices, + const LocalOp& updates, + const LocalComputation& update_computation, + const ScatterDimensionNumbers& dimension_numbers); + StatusOr BuildConstantSubGraph(const LocalOp& operand); #define _FORWARD(method_name, return_sig, args_sig) \ diff --git a/tensorflow/compiler/xla/python/local_computation_builder.i b/tensorflow/compiler/xla/python/local_computation_builder.i index bf5d667c6a..cc96d84724 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.i +++ b/tensorflow/compiler/xla/python/local_computation_builder.i @@ -34,6 +34,8 @@ limitations under the License. // 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 // // Arrows indicate whether a conversion only ever occurs in one // direction, or whether it is maintained bidirectionally. @@ -167,8 +169,41 @@ bool HandleStringAttribute(PyObject* o, return true; // Handled string attribute, ok! } +bool HandleRepeatedInt64Attribute( + PyObject* o, const char* attr_name, + tensorflow::protobuf::RepeatedField* field) { + PyObject* seq = PyObject_GetAttrString(o, attr_name); + if (!seq) { + return false; + } + + int length = PySequence_Size(seq); + if (length == -1) { + Py_DECREF(seq); + return false; + } + + for (int i = 0; i < length; ++i) { + PyObject* item = PySequence_GetItem(seq, i); + if (!item) { + Py_DECREF(seq); + return false; + } + const int64 dimension = numpy::PyIntOrPyLongToLong(item); + if (dimension == -1 && PyErr_Occurred()) { + Py_DECREF(item); + Py_DECREF(seq); + return false; + } + *field->Add() = dimension; + Py_DECREF(item); + } + Py_DECREF(seq); + return true; } -} + +} // namespace swig +} // namespace xla %} // Required to use PyArray_* functions. @@ -657,128 +692,27 @@ tensorflow::ImportNumpy(); %typemap(in) const DotDimensionNumbers& (DotDimensionNumbers dimension_numbers) { - int length; - - /* lhs_contracting_dimensions */ - PyObject* lhs_contracting_dimensions = PyObject_GetAttrString( - $input, "lhs_contracting_dimensions"); - if (!lhs_contracting_dimensions) { - SWIG_fail; - } - - length = PySequence_Size(lhs_contracting_dimensions); - if (length == -1) { - Py_DECREF(lhs_contracting_dimensions); - SWIG_fail; - } - - for (int i = 0; i < length; ++i) { - PyObject* item = PySequence_GetItem(lhs_contracting_dimensions, i); - if (!item) { - Py_DECREF(lhs_contracting_dimensions); - SWIG_fail; - } - const int64 dimension = numpy::PyIntOrPyLongToLong(item); - if (dimension == -1 && PyErr_Occurred()) { - Py_DECREF(item); - Py_DECREF(lhs_contracting_dimensions); - SWIG_fail; - } - dimension_numbers.add_lhs_contracting_dimensions(dimension); - Py_DECREF(item); - } - Py_DECREF(lhs_contracting_dimensions); - - /* rhs_contracting_dimensions */ - PyObject* rhs_contracting_dimensions = PyObject_GetAttrString( - $input, "rhs_contracting_dimensions"); - if (!lhs_contracting_dimensions) { + if (!HandleRepeatedInt64Attribute( + $input, "lhs_contracting_dimensions", + dimension_numbers.mutable_lhs_contracting_dimensions())) { SWIG_fail; } - - length = PySequence_Size(rhs_contracting_dimensions); - if (length == -1) { - Py_DECREF(rhs_contracting_dimensions); + if (!HandleRepeatedInt64Attribute( + $input, "rhs_contracting_dimensions", + dimension_numbers.mutable_rhs_contracting_dimensions())) { SWIG_fail; } - - for (int i = 0; i < length; ++i) { - PyObject* item = PySequence_GetItem(rhs_contracting_dimensions, i); - if (!item) { - Py_DECREF(rhs_contracting_dimensions); - SWIG_fail; - } - const int64 dimension = numpy::PyIntOrPyLongToLong(item); - if (dimension == -1 && PyErr_Occurred()) { - Py_DECREF(item); - Py_DECREF(rhs_contracting_dimensions); - SWIG_fail; - } - dimension_numbers.add_rhs_contracting_dimensions(dimension); - Py_DECREF(item); - } - Py_DECREF(rhs_contracting_dimensions); - - /* lhs_batch_dimensions */ - PyObject* lhs_batch_dimensions = PyObject_GetAttrString( - $input, "lhs_batch_dimensions"); - if (!lhs_batch_dimensions) { + if (!HandleRepeatedInt64Attribute( + $input, "lhs_batch_dimensions", + dimension_numbers.mutable_lhs_batch_dimensions())) { SWIG_fail; } - - length = PySequence_Size(lhs_batch_dimensions); - if (length == -1) { - Py_DECREF(lhs_batch_dimensions); - SWIG_fail; - } - - for (int i = 0; i < length; ++i) { - PyObject* item = PySequence_GetItem(lhs_batch_dimensions, i); - if (!item) { - Py_DECREF(lhs_batch_dimensions); - SWIG_fail; - } - const int64 dimension = numpy::PyIntOrPyLongToLong(item); - if (dimension == -1 && PyErr_Occurred()) { - Py_DECREF(item); - Py_DECREF(lhs_batch_dimensions); - SWIG_fail; - } - dimension_numbers.add_lhs_batch_dimensions(dimension); - Py_DECREF(item); - } - Py_DECREF(lhs_batch_dimensions); - - /* rhs_batch_dimensions */ - PyObject* rhs_batch_dimensions = PyObject_GetAttrString( - $input, "rhs_batch_dimensions"); - if (!rhs_batch_dimensions) { - SWIG_fail; - } - - length = PySequence_Size(rhs_batch_dimensions); - if (length == -1) { - Py_DECREF(rhs_batch_dimensions); + if (!HandleRepeatedInt64Attribute( + $input, "rhs_batch_dimensions", + dimension_numbers.mutable_rhs_batch_dimensions())) { SWIG_fail; } - for (int i = 0; i < length; ++i) { - PyObject* item = PySequence_GetItem(rhs_batch_dimensions, i); - if (!item) { - Py_DECREF(rhs_batch_dimensions); - SWIG_fail; - } - const int64 dimension = numpy::PyIntOrPyLongToLong(item); - if (dimension == -1 && PyErr_Occurred()) { - Py_DECREF(item); - Py_DECREF(rhs_batch_dimensions); - SWIG_fail; - } - dimension_numbers.add_rhs_batch_dimensions(dimension); - Py_DECREF(item); - } - Py_DECREF(rhs_batch_dimensions); - $1 = &dimension_numbers; } @@ -861,85 +795,80 @@ tensorflow::ImportNumpy(); dimension_numbers.set_kernel_input_feature_dimension(value); PyObject* o; - int length; - o = PyObject_GetAttrString($input, "input_spatial_dimensions"); - if (!o) { + if (!HandleRepeatedInt64Attribute( + $input, "input_spatial_dimensions", + dimension_numbers.mutable_input_spatial_dimensions())) { SWIG_fail; } - length = PySequence_Size(o); - if (length == -1) { - Py_DECREF(o); + if (!HandleRepeatedInt64Attribute( + $input, "kernel_spatial_dimensions", + dimension_numbers.mutable_kernel_spatial_dimensions())) { SWIG_fail; } - for (int i = 0; i < length; ++i) { - PyObject* item = PySequence_GetItem(o, i); - if (!item) { - Py_DECREF(o); - SWIG_fail; - } - const int64 dimension = numpy::PyIntOrPyLongToLong(item); - if (dimension == -1 && PyErr_Occurred()) { - Py_DECREF(item); - Py_DECREF(o); - SWIG_fail; - } - dimension_numbers.add_input_spatial_dimensions(dimension); - Py_DECREF(item); + if (!HandleRepeatedInt64Attribute( + $input, "output_spatial_dimensions", + dimension_numbers.mutable_output_spatial_dimensions())) { + SWIG_fail; } - Py_DECREF(o); - o = PyObject_GetAttrString($input, "kernel_spatial_dimensions"); - if (!o) { + $1 = &dimension_numbers; +} + +// GatherDimensionNumbers + +%typemap(in) const GatherDimensionNumbers& + (GatherDimensionNumbers dimension_numbers) { + if (!HandleRepeatedInt64Attribute( + $input, "offset_dims", + dimension_numbers.mutable_offset_dims())) { SWIG_fail; } - length = PySequence_Size(o); - if (length == -1) { - Py_DECREF(o); + if (!HandleRepeatedInt64Attribute( + $input, "collapsed_slice_dims", + dimension_numbers.mutable_collapsed_slice_dims())) { SWIG_fail; } - for (int i = 0; i < length; ++i) { - PyObject* item = PySequence_GetItem(o, i); - if (!item) { - Py_DECREF(o); - SWIG_fail; - } - const int64 dimension = numpy::PyIntOrPyLongToLong(item); - if (dimension == -1 && PyErr_Occurred()) { - Py_DECREF(item); - Py_DECREF(o); - SWIG_fail; - } - dimension_numbers.add_kernel_spatial_dimensions(dimension); - Py_DECREF(item); + if (!HandleRepeatedInt64Attribute( + $input, "start_index_map", + dimension_numbers.mutable_start_index_map())) { + SWIG_fail; } - Py_DECREF(o); - o = PyObject_GetAttrString($input, "output_spatial_dimensions"); - if (!o) { + int64 value; + if (!GetIntAttr($input, "index_vector_dim", &value)) { SWIG_fail; } - length = PySequence_Size(o); - if (length == -1) { - Py_DECREF(o); + dimension_numbers.set_index_vector_dim(value); + + $1 = &dimension_numbers; +} + +// ScatterDimensionNumbers + +%typemap(in) const ScatterDimensionNumbers& + (ScatterDimensionNumbers dimension_numbers) { + if (!HandleRepeatedInt64Attribute( + $input, "update_window_dims", + dimension_numbers.mutable_update_window_dims())) { SWIG_fail; } - for (int i = 0; i < length; ++i) { - PyObject* item = PySequence_GetItem(o, i); - if (!item) { - Py_DECREF(o); - SWIG_fail; - } - const int64 dimension = numpy::PyIntOrPyLongToLong(item); - if (dimension == -1 && PyErr_Occurred()) { - Py_DECREF(item); - Py_DECREF(o); - SWIG_fail; - } - dimension_numbers.add_output_spatial_dimensions(dimension); - Py_DECREF(item); + if (!HandleRepeatedInt64Attribute( + $input, "inserted_window_dims", + dimension_numbers.mutable_inserted_window_dims())) { + SWIG_fail; + } + if (!HandleRepeatedInt64Attribute( + $input, "scatter_dims_to_operand_dims", + dimension_numbers.mutable_scatter_dims_to_operand_dims())) { + SWIG_fail; + } + + int64 value; + if (!GetIntAttr($input, "index_vector_dim", &value)) { + SWIG_fail; } - Py_DECREF(o); + dimension_numbers.set_index_vector_dim(value); $1 = &dimension_numbers; } @@ -1151,6 +1080,8 @@ tensorflow::ImportNumpy(); %unignore xla::swig::LocalComputationBuilder::QR; %unignore xla::swig::LocalComputationBuilder::TriangularSolve; %unignore xla::swig::LocalComputationBuilder::CustomCall; +%unignore xla::swig::LocalComputationBuilder::Gather; +%unignore xla::swig::LocalComputationBuilder::Scatter; %unignore xla::swig::DeleteLocalComputation; %unignore xla::swig::DestructureLocalShapedBufferTuple; %unignore xla::swig::DestructureXrtAllocationTuple; diff --git a/tensorflow/compiler/xla/python/xla_client.py b/tensorflow/compiler/xla/python/xla_client.py index 378bbdcb17..4e71121c09 100644 --- a/tensorflow/compiler/xla/python/xla_client.py +++ b/tensorflow/compiler/xla/python/xla_client.py @@ -1477,6 +1477,18 @@ class ComputationBuilder(object): return self._client.TriangularSolve( a, b, left_side, lower, transpose_a, conjugate_a) + 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) + + 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,) + def _forward_methods_to_local_builder(): """Forward remaining ComputationBuilder methods to the C API. diff --git a/tensorflow/compiler/xla/python/xla_client_test.py b/tensorflow/compiler/xla/python/xla_client_test.py index 002a20e60a..874e087eb6 100644 --- a/tensorflow/compiler/xla/python/xla_client_test.py +++ b/tensorflow/compiler/xla/python/xla_client_test.py @@ -1129,6 +1129,21 @@ class SingleOpTest(LocalComputationTest): self.assertFalse(c.IsConstant(non_const_expr)) # self.assertTrue(c.IsConstant(c.Sub(c.Add(x, a), x))) # TODO(b/77245564) + 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.offset_dims.append(1) + dnums.offset_dims.append(2) + dnums.start_index_map.append(0) + dnums.start_index_map.append(1) + dnums.index_vector_dim = 2 + c = self._NewComputation() + c.Gather(c.Constant(a), c.Constant(indices), dnums, slice_sizes=[1, 1]) + g = self._Execute(c, ()) + expected = np.array([[[[2, 7]]], [[[5, 6]]]], dtype=np.int32) + np.testing.assert_allclose(g, expected, rtol=1e-4) + class EmbeddedComputationsTest(LocalComputationTest): """Tests for XLA graphs with embedded computations (such as maps).""" @@ -1186,6 +1201,14 @@ class EmbeddedComputationsTest(LocalComputationTest): c.Mul(c.ParameterFromNumpy(NumpyArrayF64(0)), c.ConstantF64Scalar(2.0)) return c.Build() + def _CreateBinaryAddS32Computation(self): + """Computation (s32, s32) -> s32 that adds its two parameters.""" + c = self._NewComputation("add_param0_by_param1") + c.Add( + c.ParameterFromNumpy(NumpyArrayS32(0)), + c.ParameterFromNumpy(NumpyArrayS32(0))) + return c.Build() + def _CreateBinaryAddF32Computation(self): """Computation (f32, f32) -> f32 that adds its two parameters.""" c = self._NewComputation("add_param0_by_param1") @@ -1568,6 +1591,23 @@ class EmbeddedComputationsTest(LocalComputationTest): execution.join() self.assertEqual(want, got) + def testScatter(self): + a = np.arange(9).astype(np.int32).reshape((3, 3)) + 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.update_window_dims.append(1) + dnums.inserted_window_dims.append(0) + dnums.scatter_dims_to_operand_dims.append(0) + dnums.index_vector_dim = 1 + + c = self._NewComputation() + c.Scatter(c.Constant(a), c.Constant(scatter_indices), c.Constant(updates), + self._CreateBinaryAddS32Computation(), dnums) + expected = np.array([[10, 21, 32], [3, 4, 5], [76, 87, 98]], dtype=np.int32) + self._ExecuteAndCompareClose(c, expected=expected) + class ErrorTest(LocalComputationTest): -- GitLab From 1ad09edf03a1c4b96eff12a7d6c765a430603b7d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 14:55:09 -0800 Subject: [PATCH 0375/2345] Use nsync, version 1.20.2, which has a bazel BUILD bug fix for aarch64 systems. It requires bazel version 0.16.0 or higher, but TensorFlow already relies on higher versions than that. PiperOrigin-RevId: 228404987 --- tensorflow/contrib/cmake/external/nsync.cmake | 2 +- tensorflow/workspace.bzl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/cmake/external/nsync.cmake b/tensorflow/contrib/cmake/external/nsync.cmake index 479609458c..b15143bfc1 100644 --- a/tensorflow/contrib/cmake/external/nsync.cmake +++ b/tensorflow/contrib/cmake/external/nsync.cmake @@ -16,7 +16,7 @@ include (ExternalProject) set(nsync_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/external/nsync/public) set(nsync_URL https://github.com/google/nsync) -set(nsync_TAG 1.20.1) +set(nsync_TAG 1.20.2) set(nsync_BUILD ${CMAKE_CURRENT_BINARY_DIR}/nsync/src/nsync) set(nsync_INSTALL ${CMAKE_CURRENT_BINARY_DIR}/nsync/install) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index ae26ba6e68..5b0f6d4c84 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -395,12 +395,12 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "nsync", - sha256 = "692f9b30e219f71a6371b98edd39cef3cbda35ac3abc4cd99ce19db430a5591a", - strip_prefix = "nsync-1.20.1", + sha256 = "704be7f58afa47b99476bbac7aafd1a9db4357cef519db361716f13538547ffd", + strip_prefix = "nsync-1.20.2", system_build_file = clean_dep("//third_party/systemlibs:nsync.BUILD"), urls = [ - "https://mirror.bazel.build/github.com/google/nsync/archive/1.20.1.tar.gz", - "https://github.com/google/nsync/archive/1.20.1.tar.gz", + "https://mirror.bazel.build/github.com/google/nsync/archive/1.20.2.tar.gz", + "https://github.com/google/nsync/archive/1.20.2.tar.gz", ], ) -- GitLab From 9f0ab258f7d715171f878080e6ed5c880aea004c Mon Sep 17 00:00:00 2001 From: Mihai Maruseac Date: Tue, 8 Jan 2019 15:17:41 -0800 Subject: [PATCH 0376/2345] Prevent OOM in decoding GIF images. PiperOrigin-RevId: 228409275 --- tensorflow/core/lib/gif/gif_io.cc | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/lib/gif/gif_io.cc b/tensorflow/core/lib/gif/gif_io.cc index 9a5215320f..3fad8c8b14 100644 --- a/tensorflow/core/lib/gif/gif_io.cc +++ b/tensorflow/core/lib/gif/gif_io.cc @@ -82,9 +82,20 @@ uint8* Decode(const void* srcdata, int datasize, return nullptr; } + // Don't request more memory than needed for each frame, preventing OOM + int max_frame_width = 0; + int max_frame_height = 0; + for (int k = 0; k < gif_file->ImageCount; k++) { + SavedImage* si = &gif_file->SavedImages[k]; + if (max_frame_height < si->ImageDesc.Height) + max_frame_height = si->ImageDesc.Height; + if (max_frame_width < si->ImageDesc.Width) + max_frame_width = si->ImageDesc.Width; + } + const int num_frames = gif_file->ImageCount; - const int width = gif_file->SWidth; - const int height = gif_file->SHeight; + const int width = max_frame_width; // gif_file->SWidth; + const int height = max_frame_height; // gif_file->SHeight; const int channel = 3; uint8* const dstdata = allocate_output(num_frames, width, height, channel); -- GitLab From bef89aba9c73880ed0db8109e2d029b8e65cb367 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 15:17:50 -0800 Subject: [PATCH 0377/2345] Improves Flex performance. - Avoid recreating the EagerOperation every time - Avoid buffer_map churn by keeping the inputs and outputs along with the EagerOperation in OpNode. PiperOrigin-RevId: 228409298 --- tensorflow/lite/delegates/flex/kernel.cc | 179 +++++++++++++----- tensorflow/lite/delegates/flex/kernel_test.cc | 7 +- 2 files changed, 138 insertions(+), 48 deletions(-) diff --git a/tensorflow/lite/delegates/flex/kernel.cc b/tensorflow/lite/delegates/flex/kernel.cc index 84745ba9cb..d2853cf9f8 100644 --- a/tensorflow/lite/delegates/flex/kernel.cc +++ b/tensorflow/lite/delegates/flex/kernel.cc @@ -51,6 +51,15 @@ namespace tflite { namespace flex { namespace kernel { +struct OpNode; + +// Represents the origin of a given tensor as a reference to the output +// of an upstream node. +struct TensorSource { + OpNode* node; + int node_output_index; +}; + // A list of inputs of a given node of the TensorFlow/Eager graph. class OpInputs { public: @@ -65,8 +74,26 @@ class OpInputs { int TfLiteIndex(int i) const { return inputs_[i]; } + // Given a map relating tensors to the node that originates them, populate a + // list of sources for the tensors in this class. + void InitializeTensorSources( + const std::map& tflite_tensor_sources) { + sources_.clear(); + for (int i : inputs_) { + auto it = tflite_tensor_sources.find(i); + if (it == tflite_tensor_sources.end()) { + sources_.push_back({nullptr, 0}); + } else { + sources_.push_back(it->second); + } + } + } + + TensorSource GetTensorSource(int i) const { return sources_[i]; } + private: std::vector inputs_; + std::vector sources_; }; // A list of outputs of a given node of the TensorFlow/Eager graph, along with @@ -81,6 +108,33 @@ class OpOutputs { } ~OpOutputs() { ResetTensorHandles(); } + // Stores information about which of the tensors in this class are also + // outputs of the sugbraph. + void InitializeGraphOutputs(const std::set& subgraph_outputs) { + subgraph_outputs_.clear(); + for (int i : outputs_) { + subgraph_outputs_.push_back(subgraph_outputs.count(i) > 0); + } + } + + // Returns true if the tensor given by index 'i' is an output of the entire + // subgraph. + bool IsSubgraphOutput(int i) const { return subgraph_outputs_[i]; } + + // Returns a handle to a given tensor and, optionally, remove it from the + // internal vector. + tensorflow::TensorHandle* GetHandle(int i, bool remove) { + auto* handle = vector_[i]; + if (!remove) { + handle->Ref(); + } else { + // Don't increase the ref-count. Instead, simply take it out of the + // vector. + vector_[i] = nullptr; + } + return handle; + } + int Size() const { return outputs_.size(); } int TfLiteIndex(int i) const { return outputs_[i]; } @@ -102,6 +156,7 @@ class OpOutputs { private: std::vector outputs_; + std::vector subgraph_outputs_; tensorflow::gtl::InlinedVector vector_; }; @@ -111,7 +166,9 @@ class OpNode { public: OpNode(const TfLiteIntArray* inputs, const TfLiteIntArray* outputs) : inputs_(inputs), outputs_(outputs) {} - ~OpNode() {} + ~OpNode() { + if (op_) ClearEagerInputs(); + } const string& name() const { return name_; } void set_name(const string& name) { name_ = name; } @@ -130,6 +187,8 @@ class OpNode { int NumInputs() const { return inputs_.Size(); } int NumOutputs() const { return outputs_.Size(); } + tensorflow::EagerOperation* op() { return op_.get(); } + tensorflow::Status InitializeNodeDef(const void* custom_initial_data, int custom_initial_data_size) { if (!custom_initial_data) { @@ -162,10 +221,8 @@ class OpNode { // Build thew new EagerOperation. In case of error, the returned 'op' is // guaranteed to be 'nullptr'. - tensorflow::Status BuildEagerOp( - tensorflow::EagerContext* eager_context, - std::unique_ptr* op) { - op->reset(); + tensorflow::Status BuildEagerOp(tensorflow::EagerContext* eager_context) { + op_.reset(); const tensorflow::AttrTypeMap* attr_types; bool is_function = false; @@ -179,33 +236,47 @@ class OpNode { "')"); } - op->reset(new tensorflow::EagerOperation(eager_context, name_.c_str(), + op_.reset(new tensorflow::EagerOperation(eager_context, name_.c_str(), /*is_function=*/false, attr_types)); + + op_->MutableAttrs()->NumInputs(inputs_.Size()); for (const auto& attr : nodedef_.attr()) { - (*op)->MutableAttrs()->Set(attr.first, attr.second); + op_->MutableAttrs()->Set(attr.first, attr.second); } return tensorflow::Status::OK(); } - tensorflow::Status BuildEagerInputs(BufferMap* buffer_map, - tensorflow::EagerOperation* op) { + void ClearEagerInputs() { + for (tensorflow::TensorHandle* h : *op_->MutableInputs()) { + if (h) h->Unref(); + } + op_->MutableInputs()->clear(); + } + + tensorflow::Status BuildEagerInputs(const BufferMap* buffer_map) { for (int i = 0; i < inputs_.Size(); ++i) { int input_index = inputs_.TfLiteIndex(i); - if (!buffer_map->HasTensor(input_index)) { - return tensorflow::errors::Internal( - "Cannot read from invalid tensor index ", input_index); - } - auto* handle = new tensorflow::TensorHandle( - buffer_map->GetTensor(input_index), nullptr, nullptr, nullptr); - op->AddInput(handle); - handle->Unref(); - - if (buffer_map->IsForwardable(input_index)) { - // Take it out of the map, so Eager/TF can reuse the buffer for an - // output tensor of the op. - buffer_map->RemoveTensor(input_index); + TensorSource s = inputs_.GetTensorSource(i); + if (!s.node) { + // This input is not produced by this Eager subgraph (it could be a TF + // Lite native buffer, or could be produced by a separater subgraph). We + // need to fetch it from the delegate's buffer_map. + if (!buffer_map->HasTensor(input_index)) { + return tensorflow::errors::Internal( + "Cannot read from invalid tensor index ", input_index); + } + auto* handle = new tensorflow::TensorHandle( + buffer_map->GetTensor(input_index), nullptr, nullptr, nullptr); + op_->MutableInputs()->push_back(handle); + } else { + // If this is a forwardable tensor, we will remove it from the previous + // op's list, giving TF the opportunity to reuse its buffer. + bool unref_handle = buffer_map->IsForwardable(input_index); + auto* handle = + s.node->outputs_.GetHandle(s.node_output_index, unref_handle); + op_->MutableInputs()->push_back(handle); } } return tensorflow::Status::OK(); @@ -214,11 +285,12 @@ class OpNode { tensorflow::Status PersistEagerOutputs(BufferMap* buffer_map) { auto* handles = outputs_.GetTensorHandles(); for (int i = 0; i < outputs_.Size(); ++i) { - const tensorflow::Tensor* tensor = nullptr; - TF_RETURN_IF_ERROR(handles->at(i)->Tensor(&tensor)); - buffer_map->SetFromTensorFlow(outputs_.TfLiteIndex(i), *tensor); + if (outputs_.IsSubgraphOutput(i)) { + const tensorflow::Tensor* tensor = nullptr; + TF_RETURN_IF_ERROR(handles->at(i)->Tensor(&tensor)); + buffer_map->SetFromTensorFlow(outputs_.TfLiteIndex(i), *tensor); + } } - outputs_.ResetTensorHandles(); return tensorflow::Status::OK(); } @@ -236,19 +308,23 @@ class OpNode { OpInputs inputs_; // List of outputs, as TF Lite tensor indices. OpOutputs outputs_; + + std::unique_ptr op_; }; // Executes the TensorFlow op given by 'op_name', with the attributes specified // in 'nodedef'. Inputs and outputs are given as indices into the 'buffer_map'. -tensorflow::Status ExecuteFlexOp(tensorflow::EagerContext* eager_context, - BufferMap* buffer_map, OpNode* node_data) { - std::unique_ptr op; - TF_RETURN_IF_ERROR(node_data->BuildEagerOp(eager_context, &op)); - TF_RETURN_IF_ERROR(node_data->BuildEagerInputs(buffer_map, op.get())); +tensorflow::Status ExecuteFlexOp(TfLiteContext* context, BufferMap* buffer_map, + OpNode* node_data) { + TF_RETURN_WITH_CONTEXT_IF_ERROR(node_data->BuildEagerInputs(buffer_map), + " (while executing '", node_data->name(), + "' via Eager)"); + node_data->mutable_outputs()->ResetTensorHandles(); int num_retvals = node_data->NumOutputs(); TF_RETURN_WITH_CONTEXT_IF_ERROR( - EagerExecute(op.get(), node_data->mutable_outputs()->GetTensorHandles(), + EagerExecute(node_data->op(), + node_data->mutable_outputs()->GetTensorHandles(), &num_retvals), " (while executing '", node_data->name(), "' via Eager)"); @@ -259,6 +335,8 @@ tensorflow::Status ExecuteFlexOp(tensorflow::EagerContext* eager_context, TF_RETURN_IF_ERROR(node_data->PersistEagerOutputs(buffer_map)); + node_data->ClearEagerInputs(); + return tensorflow::Status::OK(); } @@ -286,8 +364,10 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { ->GetBufferMap(context); CHECK(params->output_tensors); + std::set output_set; for (auto tensor_index : TfLiteIntArrayView(params->output_tensors)) { op_data->subgraph_outputs.push_back(tensor_index); + output_set.insert(tensor_index); } CHECK(params->input_tensors); @@ -313,6 +393,8 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { status = node_data.InitializeNodeDef(node->custom_initial_data, node->custom_initial_data_size); if (!status.ok()) break; + status = node_data.BuildEagerOp(op_data->eager_context); + if (!status.ok()) break; } if (ConvertStatus(context, status) != kTfLiteOk) { @@ -322,6 +404,26 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { return op_data; } + // Given a TfLite tensor index, return the OpNode that produces it, + // along with it index into that OpNodes list of outputs. + std::map tflite_tensor_sources; + + // Find out how each tensor is produced. This does not account for + // tensors that are not produce by eager ops. + for (auto& node_data : op_data->nodes) { + node_data->mutable_outputs()->InitializeGraphOutputs(output_set); + for (int i = 0; i < node_data->outputs().Size(); ++i) { + int output_index = node_data->outputs().TfLiteIndex(i); + tflite_tensor_sources[output_index] = TensorSource{node_data.get(), i}; + } + } + + // For each node, resolve the inputs, so we can keep pointers to the nodes + // that produces them. + for (auto& node_data : op_data->nodes) { + node_data->mutable_inputs()->InitializeTensorSources(tflite_tensor_sources); + } + return op_data; } @@ -370,6 +472,7 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { node_data->name().c_str()); return kTfLiteError; } + TF_LITE_ENSURE(context, node_data->op()); for (int i = 0; i < node_data->inputs().Size(); ++i) { ++tensor_ref_count[node_data->inputs().TfLiteIndex(i)]; @@ -392,7 +495,6 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { auto* op_data = reinterpret_cast(node->user_data); BufferMap* buffer_map = op_data->buffer_map; - tensorflow::EagerContext* eager_context = op_data->eager_context; // Insert a tensor in the buffer map for all inputs that are not constant. // Constants were handled in Prepare() already. @@ -414,7 +516,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { reinterpret_cast(context->profiler), node_data->name().c_str(), node_data->index()); - auto status = ExecuteFlexOp(eager_context, buffer_map, node_data.get()); + auto status = ExecuteFlexOp(context, buffer_map, node_data.get()); TF_LITE_ENSURE_OK(context, ConvertStatus(context, status)); } @@ -433,15 +535,6 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { tensor->data_is_stale = true; } - // We don't need to keep track of internal TF tensors any longer, so take - // them out of the buffer_map, but make sure we keep all the ones we might - // need for other subgraphs, or as final output of inference. - const auto& outputs = op_data->subgraph_outputs; - std::set keep(outputs.begin(), outputs.end()); - const auto& inputs = op_data->subgraph_inputs; - keep.insert(inputs.begin(), inputs.end()); - buffer_map->RemoveTensorsNotInSet(keep); - return kTfLiteOk; } diff --git a/tensorflow/lite/delegates/flex/kernel_test.cc b/tensorflow/lite/delegates/flex/kernel_test.cc index cef8017e6e..5b3a6d1647 100644 --- a/tensorflow/lite/delegates/flex/kernel_test.cc +++ b/tensorflow/lite/delegates/flex/kernel_test.cc @@ -144,12 +144,9 @@ TEST_F(KernelTest, BadTensorFlowOp) { return GenericPrepare(context, delegate, {0}); }); - SetShape(0, {2, 2, 1}); - SetValues(0, {1.1f, 2.2f, 3.3f, 4.4f}); - - ASSERT_FALSE(Invoke()); + ASSERT_NE(interpreter_->AllocateTensors(), kTfLiteOk); ASSERT_THAT(error_reporter().error_messages(), - ContainsRegex("while processing attributes of 'NonExistentOp'")); + ContainsRegex("Op type not registered 'NonExistentOp'")); } TEST_F(KernelTest, BadNumberOfOutputs) { -- GitLab From 2c6316172f2c17929d34aa64df45f08210f08a83 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Tue, 8 Jan 2019 15:29:53 -0800 Subject: [PATCH 0378/2345] Add a new experimental initialize method only to TPU strategy PiperOrigin-RevId: 228411389 --- .../contrib/distribute/python/tpu_strategy.py | 8 ++++++++ tensorflow/python/training/session_manager.py | 15 +++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/tensorflow/contrib/distribute/python/tpu_strategy.py b/tensorflow/contrib/distribute/python/tpu_strategy.py index 9cb2e461ab..f498f58b25 100644 --- a/tensorflow/contrib/distribute/python/tpu_strategy.py +++ b/tensorflow/contrib/distribute/python/tpu_strategy.py @@ -455,6 +455,14 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended): 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 _initialize(self): if context.executing_eagerly(): # TODO(priyag): Add appopriate call here when eager is supported for TPUs. diff --git a/tensorflow/python/training/session_manager.py b/tensorflow/python/training/session_manager.py index 1ec9639177..cd22e7e7de 100644 --- a/tensorflow/python/training/session_manager.py +++ b/tensorflow/python/training/session_manager.py @@ -182,13 +182,16 @@ class SessionManager(object): set. """ self._target = master + + # This is required to so that we initialize the TPU device before + # restoring from checkpoint since we'll be placing variables on the device + # and TPUInitialize wipes out the memory of the device. + strategy = distribution_strategy_context.get_distribution_strategy() + if strategy and hasattr(strategy.extended, + "_experimental_initialize_system"): + strategy.extended._experimental_initialize_system() # pylint: disable=protected-access + sess = session.Session(self._target, graph=self._graph, config=config) - # TODO(jhseu): Delete once tpu.initialize_system() goes away. - initialize_ops = ( - distribution_strategy_context.get_distribution_strategy().initialize() - ) - if initialize_ops: - sess.run(initialize_ops) if checkpoint_dir and checkpoint_filename_with_path: raise ValueError("Can not provide both checkpoint_dir and " "checkpoint_filename_with_path.") -- GitLab From d4985a621d8e71b128d34fec2a0260109bb4fb7d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 15:32:32 -0800 Subject: [PATCH 0379/2345] This CL isolates collections between instances of wrap_function calls. PiperOrigin-RevId: 228411886 --- tensorflow/python/eager/wrap_function.py | 3 +- tensorflow/python/eager/wrap_function_test.py | 39 +++++++++++++++++++ tensorflow/python/framework/func_graph.py | 34 ++++++++++------ tensorflow/python/ops/cond_v2.py | 7 ++-- tensorflow/python/ops/while_v2.py | 12 +++--- 5 files changed, 74 insertions(+), 21 deletions(-) diff --git a/tensorflow/python/eager/wrap_function.py b/tensorflow/python/eager/wrap_function.py index 2c65e97871..6f978feb3c 100644 --- a/tensorflow/python/eager/wrap_function.py +++ b/tensorflow/python/eager/wrap_function.py @@ -197,6 +197,7 @@ def wrap_function(fn, signature, name=None): name, holder, args=None, kwargs=None, signature=signature, - add_control_dependencies=False), + add_control_dependencies=False, + collections={}), variable_holder=holder, signature=signature) diff --git a/tensorflow/python/eager/wrap_function_test.py b/tensorflow/python/eager/wrap_function_test.py index 17e204b4d8..a6e1931fcd 100644 --- a/tensorflow/python/eager/wrap_function_test.py +++ b/tensorflow/python/eager/wrap_function_test.py @@ -104,6 +104,45 @@ class WrapFunctionTest(test.TestCase): fetches=f_wrapped.graph.get_tensor_by_name('fetch:0')) self.assertAllEqual(6.0, pruned()) + def testCollectionsIsolation(self): + + v1 = variables.Variable(2.) + v2_holder = [] + def f(): + v2 = variables.Variable(3.) + v2_holder.append(v2) + ops.add_to_collection(ops.GraphKeys.LOSSES, v2 * constant_op.constant(3.)) + return array_ops.identity(v1 * v2 * constant_op.constant(1.), 'fetch') + + f_wrapped = wrap_function.wrap_function(f, []) + self.assertAllEqual(6.0, f_wrapped()) + self.assertEqual( + len(f_wrapped.graph.get_collection(ops.GraphKeys.LOSSES)), 1) + f_var_collection = f_wrapped.graph.get_collection( + ops.GraphKeys.TRAINABLE_VARIABLES) + self.assertEqual(len(f_var_collection), 1) + self.assertIs(f_var_collection[0], v2_holder[0]) + + v3_holder = [] + def g(): + v3 = variables.Variable(4.) + v3_holder.append(v3) + ops.add_to_collection(ops.GraphKeys.LOSSES, v3 * constant_op.constant(3.)) + return array_ops.identity(v1 * v3 * constant_op.constant(1.), 'fetch') + + g_wrapped = wrap_function.wrap_function(g, []) + self.assertAllEqual(8.0, g_wrapped()) + self.assertEqual( + len(g_wrapped.graph.get_collection(ops.GraphKeys.LOSSES)), 1) + g_var_collection = g_wrapped.graph.get_collection( + ops.GraphKeys.TRAINABLE_VARIABLES) + self.assertEqual(len(g_var_collection), 1) + self.assertIs(g_var_collection[0], v3_holder[0]) + + # Both have only one value, and their values aren't equal. So no sharing. + self.assertNotEqual(g_wrapped.graph.get_collection(ops.GraphKeys.LOSSES), + f_wrapped.graph.get_collection(ops.GraphKeys.LOSSES)) + def testGradientsOfPrune(self): v1 = variables.Variable(2.) diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index 9528a24b46..9603c1536d 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -18,7 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections +import collections as py_collections import weakref from tensorflow.core.framework import attr_value_pb2 @@ -80,7 +80,7 @@ class FuncGraph(ops.Graph): seed: The graph-level random seed. """ - def __init__(self, name, read_only_collections=True): + def __init__(self, name, collections=None): """Construct a new FuncGraph. The graph will inherit its graph key, collections, seed, and distribution @@ -88,8 +88,13 @@ class FuncGraph(ops.Graph): Args: name: the name of the function. - read_only_collections: whether to not write function graph collections - back to default graph. Defaults to True. + collections: a dictionary of collections this FuncGraph should start + with. If not specified (None), the FuncGraph will read (but not write + to) the outer graph's collections that are not whitelisted, and both + read and write to the outer graph's collections that are whitelisted. + The current whitelisted collections are the global variables, the + local variables, and the trainable variables. + Defaults to None. """ super(FuncGraph, self).__init__() @@ -97,10 +102,9 @@ class FuncGraph(ops.Graph): self.inputs = [] self.outputs = [] self.structured_outputs = None - self._read_only_collections = read_only_collections self._weak_variables = [] self.outer_graph = ops.get_default_graph() - self.captures = collections.OrderedDict() + self.captures = py_collections.OrderedDict() self._building_function = True # Map from resource tensor name to last op (in program order) which uses @@ -122,9 +126,7 @@ class FuncGraph(ops.Graph): # specialization (currently used in cond_v2), here and in the cache key. self._colocation_stack = graph._colocation_stack.copy() # pylint: disable=protected-access - if not self._read_only_collections: - self._collections = graph._collections # pylint: disable=protected-access - else: + if collections is None: for collection_name in graph.get_all_collection_keys(): if collection_name not in WHITELIST_COLLECTIONS: self._collections[collection_name] = graph.get_collection( @@ -132,6 +134,8 @@ class FuncGraph(ops.Graph): for collection_name in WHITELIST_COLLECTIONS: self._collections[collection_name] = graph.get_collection_ref( collection_name) + else: + self._collections = collections def as_default(self): outer_cm = super(FuncGraph, self).as_default() @@ -338,7 +342,8 @@ def func_graph_from_py_func(name, autograph=False, add_control_dependencies=True, arg_names=None, - op_return_value=None): + op_return_value=None, + collections=None): """Returns a `FuncGraph` generated from `python_func`. Args: @@ -365,6 +370,13 @@ def func_graph_from_py_func(name, op_return_value: Optional. A Tensor. If set and `python_func` returns Operations, those return values will be replaced with this value. If not set, returning an Operation triggers an error. + collections: a dictionary of collections this FuncGraph should start + with. If not specified (None), the FuncGraph will read (but not write to) + the outer graph's collections that are not whitelisted, and both + read and write to the outer graph's collections that are whitelisted. + The current whitelisted collections are the global variables, the + local variables, and the trainable variables. + Defaults to None. Returns: A FuncGraph. @@ -376,7 +388,7 @@ def func_graph_from_py_func(name, if op_return_value is not None: assert isinstance(op_return_value, ops.Tensor), op_return_value if func_graph is None: - func_graph = FuncGraph(name) + func_graph = FuncGraph(name, collections=collections) assert isinstance(func_graph, FuncGraph) if add_control_dependencies: control_manager = AutomaticControlDependencies diff --git a/tensorflow/python/ops/cond_v2.py b/tensorflow/python/ops/cond_v2.py index 7d09e32e24..5dca8e501b 100644 --- a/tensorflow/python/ops/cond_v2.py +++ b/tensorflow/python/ops/cond_v2.py @@ -68,14 +68,14 @@ def cond_v2(pred, true_fn, false_fn, name="cond"): true_name, true_fn, [], {}, func_graph=util.CondBranchFuncGraph( - true_name, read_only_collections=False), + true_name, collections=ops.get_default_graph()._collections), # pylint: disable=protected-access add_control_dependencies=add_control_dependencies, op_return_value=pred) false_graph = func_graph_module.func_graph_from_py_func( false_name, false_fn, [], {}, func_graph=util.CondBranchFuncGraph( - false_name, read_only_collections=False), + false_name, collections=ops.get_default_graph()._collections), # pylint: disable=protected-access add_control_dependencies=add_control_dependencies, op_return_value=pred) @@ -554,7 +554,8 @@ class _CondGradFuncGraph(util.CondBranchFuncGraph): """ def __init__(self, name, forward_graph): - super(_CondGradFuncGraph, self).__init__(name, read_only_collections=False) + super(_CondGradFuncGraph, self).__init__( + name, collections=ops.get_default_graph()._collections) # pylint: disable=protected-access self.if_op_needs_rewrite = False self._forward_graph = forward_graph # Maps from forward intermediate tensor -> the unwrapped captured diff --git a/tensorflow/python/ops/while_v2.py b/tensorflow/python/ops/while_v2.py index f9fea0e5bd..f5a51bb1bc 100644 --- a/tensorflow/python/ops/while_v2.py +++ b/tensorflow/python/ops/while_v2.py @@ -115,15 +115,15 @@ def while_loop(cond, loop_counter < maximum_iterations, cond(*_pack_sequence_as(orig_loop_vars, args))) - # NOTE(skyewm): we set read_only_collections=False for compatibility with - # TPUEstimator. + # NOTE(skyewm): we set collections to the outer graph's collections for + # compatibility with TPUEstimator. cond_graph = func_graph_module.func_graph_from_py_func( cond_name, wrapped_cond, loop_vars, {}, signature=_build_signature(loop_vars, shape_invariants), - func_graph=util.WhileCondFuncGraph(cond_name, - read_only_collections=False), + func_graph=util.WhileCondFuncGraph( + cond_name, collections=ops.get_default_graph()._collections), # pylint: disable=protected-access add_control_dependencies=add_control_dependencies) # Add external_captures of cond to the list of loop vars. @@ -174,8 +174,8 @@ def while_loop(cond, wrapped_body, loop_vars, {}, signature=_build_signature(loop_vars, shape_invariants), - func_graph=util.WhileBodyFuncGraph(body_name, - read_only_collections=False), + func_graph=util.WhileBodyFuncGraph( + body_name, collections=ops.get_default_graph()._collections), # pylint: disable=protected-access add_control_dependencies=add_control_dependencies) # Add external captures of body to the list of loop vars. # Note that external tensors will be treated as loop invariants, i.e., -- GitLab From f71e77471c140bac2f4dcb9a003139fb5c657302 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Tue, 8 Jan 2019 15:35:49 -0800 Subject: [PATCH 0380/2345] Don't check for alignment before calling into a tiled dot (Eigen or LLVM) The alignment restrictions on Eigen was lifted in cr/226091175. This CL lifts the alignment restriction on the tiled LLVM IR GEMM. PiperOrigin-RevId: 228412524 --- tensorflow/compiler/xla/service/cpu/BUILD | 2 - .../cpu/cpu_eigen_tensor_alignment_test.cc | 38 ------ .../xla/service/cpu/dot_op_emitter.cc | 117 ++++++++++++------ .../xla/service/cpu/dot_op_emitter_internal.h | 88 ------------- 4 files changed, 77 insertions(+), 168 deletions(-) delete mode 100644 tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index de62aa60aa..a197bdddc8 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -385,7 +385,6 @@ cc_library( srcs = ["dot_op_emitter.cc"], hdrs = [ "dot_op_emitter.h", - "dot_op_emitter_internal.h", ], deps = [ ":cpu_options", @@ -1029,7 +1028,6 @@ tf_cc_test( size = "small", srcs = ["cpu_eigen_tensor_alignment_test.cc"], deps = [ - ":dot_op_emitter", ":ir_emission_utils", ":target_machine_features_fake", "//tensorflow/compiler/xla:test", diff --git a/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc b/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc index 823bdf259c..485769a373 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc @@ -13,7 +13,6 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h" #include "tensorflow/compiler/xla/service/cpu/ir_emission_utils.h" #include "tensorflow/compiler/xla/service/cpu/target_machine_features_fake.h" #include "tensorflow/compiler/xla/service/hlo_parser.h" @@ -23,48 +22,11 @@ namespace xla { namespace cpu { namespace { -using internal::DotImplementationStrategy; -using internal::DotInfo; -using internal::GetDotImplementationStrategy; - // Test that we don't call into Eigen with tensors too small to be aligned // reliably. class CpuEigenTensorAlignmentTest : public ::testing::Test {}; -TEST_F(CpuEigenTensorAlignmentTest, EigenDotAlignment) { - string hlo_string = R"( -HloModule DotOperation - -ENTRY DotOperation { - arg0 = f32[5,256] parameter(0) - arg1 = f32[256,1024] parameter(1) - ROOT dot = f32[5,1024] dot(arg0, arg1), lhs_contracting_dims={1}, rhs_contracting_dims={0} -} -)"; - - TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr module, - ParseHloString(hlo_string)); - - HloInstruction* dot = module->entry_computation()->root_instruction(); - - TargetMachineFeaturesWithFakeAlignmentLogic target_machine_with_no_alignment( - [](int64 size) { return 1; }); - - EXPECT_EQ(GetDotImplementationStrategy(HloModuleConfig{}, DotInfo(*dot), - target_machine_with_no_alignment), - DotImplementationStrategy::kNaiveLlvmIr); - - TargetMachineFeaturesWithFakeAlignmentLogic - target_machine_with_full_alignment([](int64 size) { - return TargetMachineFeatures::kEigenExpectedTensorAlignment; - }); - - EXPECT_NE(GetDotImplementationStrategy(HloModuleConfig{}, DotInfo(*dot), - target_machine_with_full_alignment), - DotImplementationStrategy::kNaiveLlvmIr); -} - TEST_F(CpuEigenTensorAlignmentTest, EigenConvAlignment) { string hlo_string = R"( HloModule ConvOperation diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc index e8a84ebe6b..b018e0cd46 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc @@ -14,7 +14,6 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/compiler/xla/service/cpu/dot_op_emitter.h" -#include "tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h" #include #include @@ -43,17 +42,63 @@ namespace xla { using llvm_ir::SetToFirstInsertPoint; namespace cpu { - -using internal::DotImplementationStrategy; -using internal::DotInfo; -using internal::GetDotImplementationStrategy; - namespace { // Returns true if we should call into multi-threaded Eigen routines. bool ShouldUseMultiThreadedEigen(const HloModuleConfig& config) { return config.debug_options().xla_cpu_multi_thread_eigen(); } +// Represents a dot operation. We use this in lieu of an `HloInstruction` +// because we want to be able to create this for the "inner" dot operation in a +// batch dot, for which there is no separate HLO instruction. +struct DotInfo { + Shape lhs_shape; + Shape rhs_shape; + Shape result_shape; + DotDimensionNumbers dim_nums; + + explicit DotInfo(const HloInstruction& instr) { + CHECK_EQ(instr.opcode(), HloOpcode::kDot); + lhs_shape = instr.operand(0)->shape(); + rhs_shape = instr.operand(1)->shape(); + result_shape = instr.shape(); + dim_nums = instr.dot_dimension_numbers(); + } +}; + +// Dictates how a dot operation is implemented. +enum class DotImplementationStrategy { + // The dot operation is lowered into LLVM IR that implements a naive nested + // loop that computes the result one element at a time. This is our + // "fallback"; we don't really want this to kick in for any non-trival dot + // operation. + kNaiveLlvmIr, + + // The dot operation is lowered into LLVM IR that implements a tiled + // Matrix*Vector operation. This strategy also allows fusing in a bias add + // into the dot. The matrix can be row major or column major, both are + // supported. + kTiledLlvmIrGemv, + + // The dot operation is lowered into LLVM IR that implemetns a tiled + // Matrix*Matrix operation. No fusions are supported. The two inputs + // and the output have to be row major. + kTiledLlvmIrGemm, + + // The dot operation is lowered into a call into an Eigen routine. No fusions + // are supported today. The two inputs and the output have to be row major. + // However, we do allow transposing either the LHS or the RHS as part of the + // GEMM -- we expose this flexibility as flexibility in the contraction + // dimensions, but we can also see this as flexibility in the input layouts. + kEigen, +}; + +// Returns the implementation strategy for a dot with the configuration +// `dot_info`. +DotImplementationStrategy GetDotImplementationStrategy( + const HloModuleConfig& config, const DotInfo& dot_info, + const TargetMachineFeatures& target_machine_features); + // Helper class for emitting LLVM IR to perform the dot operation. class DotOpEmitter { public: @@ -190,9 +235,8 @@ void DotOpEmitter::EmitTiledLlvmIrGemm() { } int64 size_bytes = m * n * ShapeUtil::ByteSizeOfPrimitiveType(primitive_type); - b_->CreateMemSet( - target, b_->getInt8(0), size_bytes, - target_machine_features_.minimum_alignment_for_allocation(size_bytes)); + b_->CreateMemSet(target, b_->getInt8(0), /*Size=*/size_bytes, + /*Align=*/1); int64 max_target_vector_width = target_machine_features_.vector_register_num_elements( @@ -696,40 +740,34 @@ absl::optional ProfitableToMakeDotOperandColumnMajor( return {}; } -namespace internal { namespace { // Return whether the given shape is rank 2. bool IsRank2(const Shape& shape) { return shape.rank() == 2; } +bool IsSimpleLayout(const Layout& layout) { + return layout.tiles().empty() && layout.format() == DENSE; +} + // In a gemm operation where output = lhs * rhs, check whether the given shapes // are valid for the operation. -bool AreAlignedGemmShapes( - const Shape& lhs_shape, const Shape& rhs_shape, const Shape& output_shape, - const TargetMachineFeatures& target_machine_features) { - // The inputs and the output must - // 1) be matrices with no padding, and - // 2) have an allowed element type. - PrimitiveType output_primitive_type = output_shape.element_type(); - if (!(output_primitive_type == F64 || output_primitive_type == F32 || - output_primitive_type == F16)) { - return false; - } - - if (!(IsRank2(lhs_shape) && IsRank2(rhs_shape) && IsRank2(output_shape))) { - return false; - } - - auto is_aligned = [&](const Shape& shape) { - return GetMinimumAlignmentForArray(shape, target_machine_features) >= - TargetMachineFeatures::kEigenExpectedTensorAlignment; - }; - - if (!is_aligned(lhs_shape) || !is_aligned(rhs_shape) || - !is_aligned(output_shape)) { - return false; +bool AreGemmShapes(const Shape& lhs_shape, const Shape& rhs_shape, + const Shape& output_shape, + const TargetMachineFeatures& target_machine_features) { + CHECK(!lhs_shape.has_layout() || IsSimpleLayout(lhs_shape.layout())) + << lhs_shape.DebugString(); + CHECK(!rhs_shape.has_layout() || IsSimpleLayout(rhs_shape.layout())) + << rhs_shape.DebugString(); + CHECK(!output_shape.has_layout() || IsSimpleLayout(output_shape.layout())) + << output_shape.DebugString(); + + switch (output_shape.element_type()) { + case F64: + case F32: + case F16: + return IsRank2(lhs_shape) && IsRank2(rhs_shape) && IsRank2(output_shape); + default: + return false; } - - return true; } bool IsAlignedGemm(const DotInfo& dot_info, @@ -739,8 +777,8 @@ bool IsAlignedGemm(const DotInfo& dot_info, return false; } - return AreAlignedGemmShapes(dot_info.lhs_shape, dot_info.rhs_shape, - dot_info.result_shape, target_machine_features); + return AreGemmShapes(dot_info.lhs_shape, dot_info.rhs_shape, + dot_info.result_shape, target_machine_features); } bool CanEmitTiledLlvmIrGemm( @@ -781,7 +819,6 @@ bool CanEmitTiledLlvmIrGemm( return true; } -} // namespace DotImplementationStrategy GetDotImplementationStrategy( const HloModuleConfig& config, const DotInfo& dot_info, @@ -806,7 +843,7 @@ DotImplementationStrategy GetDotImplementationStrategy( return DotImplementationStrategy::kNaiveLlvmIr; } -} // namespace internal +} // namespace bool DotImplementationCanHandleTranspose( const HloInstruction& dot_instr, diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h b/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h deleted file mode 100644 index cc28918ed6..0000000000 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#ifndef TENSORFLOW_COMPILER_XLA_SERVICE_CPU_DOT_OP_EMITTER_INTERNAL_H_ -#define TENSORFLOW_COMPILER_XLA_SERVICE_CPU_DOT_OP_EMITTER_INTERNAL_H_ - -#include "tensorflow/compiler/xla/service/cpu/target_machine_features.h" -#include "tensorflow/compiler/xla/service/hlo_instruction.h" -#include "tensorflow/compiler/xla/service/hlo_module.h" - -// ----------------------------------------------------------------------------- -// INTERNAL HEADER. -// -// This file exposes internal implementation details from dot_op_emitter.cc for -// unit tests. Please do not depend on this! -// -// ----------------------------------------------------------------------------- - -namespace xla { -namespace cpu { -namespace internal { - -// Represents a dot operation. We use this in lieu of an `HloInstruction` -// because we want to be able to create this for the "inner" dot operation in a -// batch dot, for which there is no separate HLO instruction. -struct DotInfo { - Shape lhs_shape; - Shape rhs_shape; - Shape result_shape; - DotDimensionNumbers dim_nums; - - explicit DotInfo(const HloInstruction& instr) { - CHECK_EQ(instr.opcode(), HloOpcode::kDot); - lhs_shape = instr.operand(0)->shape(); - rhs_shape = instr.operand(1)->shape(); - result_shape = instr.shape(); - dim_nums = instr.dot_dimension_numbers(); - } -}; - -// Dictates how a dot operation is implemented. -enum class DotImplementationStrategy { - // The dot operation is lowered into LLVM IR that implements a naive nested - // loop that computes the result one element at a time. This is our - // "fallback"; we don't really want this to kick in for any non-trival dot - // operation. - kNaiveLlvmIr, - - // The dot operation is lowered into LLVM IR that implements a tiled - // Matrix*Vector operation. This strategy also allows fusing in a bias add - // into the dot. The matrix can be row major or column major, both are - // supported. - kTiledLlvmIrGemv, - - // The dot operation is lowered into LLVM IR that implemetns a tiled - // Matrix*Matrix operation. No fusions are supported. The two inputs - // and the output have to be row major. - kTiledLlvmIrGemm, - - // The dot operation is lowered into a call into an Eigen routine. No fusions - // are supported today. The two inputs and the output have to be row major. - // However, we do allow transposing either the LHS or the RHS as part of the - // GEMM -- we expose this flexibility as flexibility in the contraction - // dimensions, but we can also see this as flexibility in the input layouts. - kEigen, -}; - -// Returns the implementation strategy for a dot with the configuration -// `dot_info`. -DotImplementationStrategy GetDotImplementationStrategy( - const HloModuleConfig& config, const DotInfo& dot_info, - const TargetMachineFeatures& target_machine_features); -} // namespace internal -} // namespace cpu -} // namespace xla - -#endif // TENSORFLOW_COMPILER_XLA_SERVICE_CPU_DOT_OP_EMITTER_INTERNAL_H_ -- GitLab From cb0ecda8555f827c4fa4d9c160c68ea028bdc305 Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Tue, 8 Jan 2019 15:41:12 -0800 Subject: [PATCH 0381/2345] Unbreak windows builds. cuda_deps seems not to work well with select() on windows. PiperOrigin-RevId: 228413463 --- .../core/platform/default/build_config/BUILD | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tensorflow/core/platform/default/build_config/BUILD b/tensorflow/core/platform/default/build_config/BUILD index f5a194428c..c0c1022c16 100644 --- a/tensorflow/core/platform/default/build_config/BUILD +++ b/tensorflow/core/platform/default/build_config/BUILD @@ -32,15 +32,7 @@ cc_library( tf_cuda_library( name = "stream_executor", - cuda_deps = [ - "//tensorflow/stream_executor/cuda:cuda_activation", - ] + select({ - "//tensorflow:using_cuda_clang": ["//tensorflow/stream_executor/cuda:all_runtime"], - "//tensorflow:using_cuda_nvcc": ["//tensorflow/stream_executor/cuda:all_runtime"], - "//tensorflow:using_cuda_clang_with_dynamic_build": [], - "//tensorflow:using_cuda_nvcc_with_dynamic_build": [], - "//conditions:default": [], - }), + cuda_deps = ["//tensorflow/stream_executor/cuda:cuda_activation"], deps = [ "//tensorflow/stream_executor", "//tensorflow/stream_executor:dnn", @@ -53,6 +45,12 @@ tf_cuda_library( ] + select({ "@local_config_cuda//cuda:darwin": ["IOKit"], "//conditions:default": [], + }) + select({ + "//tensorflow:using_cuda_clang": ["//tensorflow/stream_executor/cuda:all_runtime"], + "//tensorflow:using_cuda_nvcc": ["//tensorflow/stream_executor/cuda:all_runtime"], + "//tensorflow:using_cuda_clang_with_dynamic_build": [], + "//tensorflow:using_cuda_nvcc_with_dynamic_build": [], + "//conditions:default": [], }), ) -- GitLab From 29579a2e548800994f1acdf37fc2f8bf42b043ee Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Tue, 8 Jan 2019 15:50:48 -0800 Subject: [PATCH 0382/2345] Fix tf_driver_test Use the proper path for the tested model. PiperOrigin-RevId: 228415121 --- tensorflow/lite/testing/tf_driver_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/testing/tf_driver_test.cc b/tensorflow/lite/testing/tf_driver_test.cc index 363d162d56..e79704d616 100644 --- a/tensorflow/lite/testing/tf_driver_test.cc +++ b/tensorflow/lite/testing/tf_driver_test.cc @@ -93,7 +93,7 @@ TEST(TfDriverTest, SimpleTest) { {"1,8,8,3", "1,8,8,3", "1,8,8,3", "1,8,8,3"}, {"x", "y"})); runner->LoadModel( - "third_party/tensorflow/lite/testdata/multi_add.pb"); + "tensorflow/lite/testdata/multi_add.pb"); EXPECT_TRUE(runner->IsValid()) << runner->GetErrorMessage(); ASSERT_THAT(runner->GetInputs(), ElementsAre(0, 1, 2, 3)); -- GitLab From 0b53b291f31d9ac9d1bb9cffbc037fe978f593fd Mon Sep 17 00:00:00 2001 From: Reed Wanderman-Milne Date: Tue, 8 Jan 2019 15:57:52 -0800 Subject: [PATCH 0383/2345] Automated rollback of changelist 205730535 PiperOrigin-RevId: 228416180 --- tensorflow/core/BUILD | 11 ++++ .../core/common_runtime/gpu/gpu_device.cc | 51 ++++++++++++++++++- .../core/common_runtime/gpu/gpu_device.h | 11 ++++ .../gpu/gpu_device_kernel_check.cu.cc | 37 ++++++++++++++ .../gpu/gpu_device_kernel_check.h | 32 ++++++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.cu.cc create mode 100644 tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index e6af9211b5..6a842a432f 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -87,6 +87,7 @@ load( "tf_gen_op_libs", "tf_generate_proto_text_sources", "tf_genrule_cmd_append_to_srcs", + "tf_gpu_kernel_library", "tf_opts_nortti_if_android", "transitive_hdrs", ) @@ -3162,6 +3163,15 @@ cc_library( ], ) +tf_gpu_kernel_library( + name = "gpu_device_kernel_check", + srcs = ["common_runtime/gpu/gpu_device_kernel_check.cu.cc"], + hdrs = ["common_runtime/gpu/gpu_device_kernel_check.h"], + deps = [ + "//tensorflow/core:stream_executor", + ], +) + GPU_RUNTIME_HEADERS = [ "common_runtime/gpu/cuda_host_allocator.h", "common_runtime/gpu/gpu_bfc_allocator.h", @@ -3200,6 +3210,7 @@ tf_cuda_library( ":core_cpu_lib", ":framework", ":framework_internal", + ":gpu_device_kernel_check", ":gpu_id_impl", ":gpu_init_impl", ":gpu_lib", diff --git a/tensorflow/core/common_runtime/gpu/gpu_device.cc b/tensorflow/core/common_runtime/gpu/gpu_device.cc index 010fdff4e9..891b6e0e2a 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_device.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_device.cc @@ -31,6 +31,7 @@ limitations under the License. #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h" #include "tensorflow/core/common_runtime/gpu/gpu_event_mgr.h" #include "tensorflow/core/common_runtime/gpu/gpu_id.h" #include "tensorflow/core/common_runtime/gpu/gpu_id_manager.h" @@ -388,7 +389,7 @@ Status BaseGPUDevice::Init(const SessionOptions& options) { } } - return Status::OK(); + return CheckGPU(); } bool BaseGPUDevice::RequiresRecordingAccessedTensors() const { @@ -907,6 +908,54 @@ Allocator* BaseGPUDevice::GetScopedAllocator(AllocatorAttributes attr, return gpu_allocator_; } +Status BaseGPUDevice::CheckGPU() { + se::Stream* stream = tensorflow_gpu_device_info()->stream; + TF_RETURN_IF_ERROR(stream->BlockHostUntilDone()); + Tensor device_tensor(gpu_allocator_, DT_FLOAT, {}); + if (!device_tensor.IsInitialized()) { + return errors::ResourceExhausted("Failed to allocate ", sizeof(float), + " bytes on the GPU for initialization " + "checks"); + } + float* val_dev = device_tensor.scalar().data(); + const cudaStream_t cu_stream = *reinterpret_cast( + stream->implementation()->GpuStreamMemberHack()); + { + se::cuda::ScopedActivateExecutorContext scoped_activation{stream->parent()}; + run_test_kernel(val_dev, cu_stream); + // We have to use the CUDA runtime function cudaPeekAtLastError here, + // because 'stream' does not provide a way to check if a kernel launch + // succeeds. Calling 'stream->BlockHostUntilDone()', which internally calls + // 'cuCtxSynchronize()', does not catch all kernel launch errors. + cudaError_t cuda_error = cudaPeekAtLastError(); + if (cuda_error == cudaSuccess) { + cuda_error = cudaDeviceSynchronize(); + } + TF_RETURN_IF_ERROR(CudaErrorToStatus(cuda_error, *stream)); + } + + float val_host = 0.; + stream->ThenMemcpy(&val_host, se::DeviceMemoryBase(val_dev, sizeof(float)), + sizeof(float)); + TF_RETURN_IF_ERROR(stream->BlockHostUntilDone()); + if (val_host != 12345.) { + return errors::Internal( + "GPU kernel for initialization returned wrong value: ", val_host); + } + return Status::OK(); +} + +Status BaseGPUDevice::CudaErrorToStatus(cudaError_t cuda_error, + const se::Stream& stream) { + if (cuda_error != cudaSuccess) { + return errors::Internal( + "Failed to run GPU kernel for the initialization check. Received " + "error ", + cudaGetErrorName(cuda_error), " after running GPU kernel."); + } + return Status::OK(); +} + const int BaseGPUDeviceFactory::InterconnectMap::kSameDeviceStrength = 1000; const int BaseGPUDeviceFactory::InterconnectMap::kStreamExecutorStrength = 1; diff --git a/tensorflow/core/common_runtime/gpu/gpu_device.h b/tensorflow/core/common_runtime/gpu/gpu_device.h index d002d02c51..86622dad49 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_device.h +++ b/tensorflow/core/common_runtime/gpu/gpu_device.h @@ -26,6 +26,7 @@ limitations under the License. #include #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" +#include "cuda/include/cuda_runtime_api.h" #include "tensorflow/core/common_runtime/device_factory.h" #include "tensorflow/core/common_runtime/gpu/gpu_event_mgr.h" #include "tensorflow/core/common_runtime/gpu/gpu_id.h" @@ -121,6 +122,12 @@ class BaseGPUDevice : public LocalDevice { se::StreamExecutor* executor_; // not owned std::unique_ptr scoped_allocator_mgr_; + // Returns a Status corresponding to a cudaError_t. The CUDA error must have + // been obtained from a CUDA kernel launch used to check if the GPU is + // initialized properly. + virtual Status CudaErrorToStatus(cudaError_t cuda_error, + const se::Stream& stream); + private: struct StreamGroup { se::Stream* compute = nullptr; @@ -161,6 +168,10 @@ class BaseGPUDevice : public LocalDevice { Status MaybeCopyTensorToGPU(const AllocatorAttributes& alloc_attrs, const Tensor& from, Tensor* to, StatusCallback done); + + // Checks that the GPU is capable of doing work, by running a test kernel on + // it. + Status CheckGPU(); }; class BaseGPUDeviceFactory : public DeviceFactory { diff --git a/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.cu.cc b/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.cu.cc new file mode 100644 index 0000000000..017565195b --- /dev/null +++ b/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.cu.cc @@ -0,0 +1,37 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA + +#include "tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h" +#include "tensorflow/stream_executor/cuda/cuda_activation.h" + +namespace { +__global__ void test_kernel(float* val) { + if (blockIdx.x == 0 && threadIdx.x == 0) { + (*val) = 12345.; + } +} +} // namespace + +namespace tensorflow { + +void run_test_kernel(float* val, cudaStream_t cu_stream) { + test_kernel<<<1, 1, 0, cu_stream>>>(val); +} + +} // namespace tensorflow + +#endif // GOOGLE_CUDA diff --git a/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h b/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h new file mode 100644 index 0000000000..064fb7a49f --- /dev/null +++ b/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h @@ -0,0 +1,32 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_GPU_GPU_DEVICE_KERNEL_CHECK_H_ +#define TENSORFLOW_CORE_COMMON_RUNTIME_GPU_GPU_DEVICE_KERNEL_CHECK_H_ + +#if GOOGLE_CUDA + +#include "tensorflow/core/platform/stream_executor.h" + +namespace tensorflow { + +// Runs a GPU kernel to test that it functions correctly. Sets 'val' to 12345. +void run_test_kernel(float* val, cudaStream_t cu_stream); + +} // namespace tensorflow + +#endif // GOOGLE_CUDA + +#endif // TENSORFLOW_CORE_COMMON_RUNTIME_GPU_GPU_DEVICE_KERNEL_CHECK_H_ -- GitLab From 03ec3f7b11572b70ef7f33f2a9af9bebbb6ce473 Mon Sep 17 00:00:00 2001 From: Toby Boyd Date: Tue, 8 Jan 2019 16:24:00 -0800 Subject: [PATCH 0384/2345] Installs TensorRT --- tensorflow/tools/ci_build/Dockerfile.gpu | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tensorflow/tools/ci_build/Dockerfile.gpu b/tensorflow/tools/ci_build/Dockerfile.gpu index ff1fefe892..f5a28ff163 100644 --- a/tensorflow/tools/ci_build/Dockerfile.gpu +++ b/tensorflow/tools/ci_build/Dockerfile.gpu @@ -7,6 +7,12 @@ LABEL maintainer="Jan Prach " RUN cp -P /usr/include/cudnn.h /usr/local/cuda/include RUN cp -P /usr/lib/x86_64-linux-gnu/libcudnn* /usr/local/cuda/lib64 +# Installs TensorRT, which is not included in NVIDIA Docker containers. +RUN apt-get update \ + && apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda10.0 \ + && apt-get update \ + && apt-get install -y --no-install-recommends libnvinfer-dev=5.0.2-1+cuda10.0 + # Copy and run the install scripts. COPY install/*.sh /install/ ARG DEBIAN_FRONTEND=noninteractive -- GitLab From f3b8bf9aca77b6264d791425593bd8cd02aa8843 Mon Sep 17 00:00:00 2001 From: Doe Hyun Yoon Date: Tue, 8 Jan 2019 16:23:50 -0800 Subject: [PATCH 0385/2345] Add aggressive_shape_inference option to VirtualScheduler, and enable it in analytical_cost_estimator, but not in VirtualCluster::Run(). PiperOrigin-RevId: 228420860 --- .../core/grappler/clusters/virtual_cluster.cc | 6 ++++- .../costs/analytical_cost_estimator.cc | 6 +++-- .../costs/analytical_cost_estimator.h | 3 ++- .../core/grappler/costs/virtual_scheduler.cc | 27 +++---------------- .../core/grappler/costs/virtual_scheduler.h | 12 +++------ .../grappler/costs/virtual_scheduler_test.cc | 10 ++++--- 6 files changed, 25 insertions(+), 39 deletions(-) diff --git a/tensorflow/core/grappler/clusters/virtual_cluster.cc b/tensorflow/core/grappler/clusters/virtual_cluster.cc index e80d6b54d9..118f74e8b0 100644 --- a/tensorflow/core/grappler/clusters/virtual_cluster.cc +++ b/tensorflow/core/grappler/clusters/virtual_cluster.cc @@ -73,7 +73,11 @@ Status VirtualCluster::Run(const GraphDef& graph, item.graph = graph; item.feed = feed; item.fetch = fetch; - VirtualScheduler scheduler(true, this, node_manager_.get()); + // Note that we do not use aggressive shape inference to preserve unknown + // shapes from the input graph. + VirtualScheduler scheduler(/*use_static_shapes=*/true, + /*use_aggressive_shape_inference=*/false, this, + node_manager_.get()); TF_RETURN_IF_ERROR(scheduler.Init(&item)); if (metadata) { diff --git a/tensorflow/core/grappler/costs/analytical_cost_estimator.cc b/tensorflow/core/grappler/costs/analytical_cost_estimator.cc index eac6a0de3f..09cddad8ba 100644 --- a/tensorflow/core/grappler/costs/analytical_cost_estimator.cc +++ b/tensorflow/core/grappler/costs/analytical_cost_estimator.cc @@ -113,8 +113,10 @@ AnalyticalCostEstimator::AnalyticalCostEstimator( node_estimator_(std::move(node_estimator)), node_manager_(std::move(node_manager)), use_static_shapes_(use_static_shapes) { - scheduler_ = absl::make_unique(use_static_shapes_, cluster_, - node_manager_.get()); + // Use aggressive static shape inference to minimize unknown shapes. + scheduler_ = absl::make_unique( + use_static_shapes_, + /*use_aggressive_shape_inference=*/true, cluster_, node_manager_.get()); } Status AnalyticalCostEstimator::Initialize(const GrapplerItem& item) { diff --git a/tensorflow/core/grappler/costs/analytical_cost_estimator.h b/tensorflow/core/grappler/costs/analytical_cost_estimator.h index ade61f4051..6275c62198 100644 --- a/tensorflow/core/grappler/costs/analytical_cost_estimator.h +++ b/tensorflow/core/grappler/costs/analytical_cost_estimator.h @@ -34,7 +34,8 @@ class Cluster; struct GrapplerItem; // Estimate the cost of running a Grappler item based on the theoretical -// performance of the hardware that will run the model. +// performance of the hardware that will run the model. Note that this +// internally uses aggressive shape inference with static shape inference. class AnalyticalCostEstimator : public CostEstimator { public: // Does not take ownership of cluster. diff --git a/tensorflow/core/grappler/costs/virtual_scheduler.cc b/tensorflow/core/grappler/costs/virtual_scheduler.cc index bb8425a5a1..0aac0348b5 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler.cc +++ b/tensorflow/core/grappler/costs/virtual_scheduler.cc @@ -310,29 +310,15 @@ ReadyNodeManager* VirtualScheduler::ReadyNodeManagerFactory( LOG(FATAL) << "Not a valid ready node manager: " << ready_node_manager; } -VirtualScheduler::VirtualScheduler(const GrapplerItem* grappler_item, - const bool use_static_shapes, - Cluster* cluster, - ReadyNodeManager* ready_nodes) - : ready_nodes_(ready_nodes), - graph_costs_(Costs::ZeroCosts()), - graph_properties_(new GraphProperties(*grappler_item)), - cluster_(cluster), - grappler_item_(grappler_item), - use_static_shapes_(use_static_shapes), - placer_(cluster) { - graph_costs_.num_ops_total = 0; - initialized_ = false; - track_mem_usage_snapshot_ = VLOG_IS_ON(1); -} - VirtualScheduler::VirtualScheduler(const bool use_static_shapes, + const bool use_aggressive_shape_inference, Cluster* cluster, ReadyNodeManager* ready_nodes) : ready_nodes_(ready_nodes), graph_costs_(Costs::ZeroCosts()), cluster_(cluster), use_static_shapes_(use_static_shapes), + use_aggressive_shape_inference_(use_aggressive_shape_inference), placer_(cluster) { graph_costs_.num_ops_total = 0; initialized_ = false; @@ -343,12 +329,6 @@ Status VirtualScheduler::Init(const GrapplerItem* item) { grappler_item_ = item; graph_properties_ = absl::make_unique(*item); - return Init(); -} - -// TODO(pcma): Merge with Init(const GrapplerItem* item) when this -// deprecated API is deleted -Status VirtualScheduler::Init() { initialized_ = false; // Clear all internal states so that the VirtualScheduler is reusable for @@ -372,7 +352,8 @@ Status VirtualScheduler::Init() { // Construct graph properties. if (use_static_shapes_) { - TF_RETURN_IF_ERROR(graph_properties_->InferStatically(true)); + TF_RETURN_IF_ERROR(graph_properties_->InferStatically( + true, use_aggressive_shape_inference_)); } else { TF_RETURN_IF_ERROR(graph_properties_->InferDynamically(cluster_)); } diff --git a/tensorflow/core/grappler/costs/virtual_scheduler.h b/tensorflow/core/grappler/costs/virtual_scheduler.h index ffc5e9187c..d96371bcab 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler.h +++ b/tensorflow/core/grappler/costs/virtual_scheduler.h @@ -260,16 +260,9 @@ std::unique_ptr ReadyNodeManagerFactory( // dependencies, device, etc. class VirtualScheduler { public: - // TODO(pcma): Modify power_analyzer.cc to use new API's. - // DEPRECATED - VirtualScheduler(const GrapplerItem* grappler_item, - const bool use_static_shapes, Cluster* cluster, - ReadyNodeManager* ready_nodes); - // DEPRECATED - Status Init(); - // Does not take ownership of cluster or ready_nodes. - VirtualScheduler(bool use_static_shapes, Cluster* cluster, + VirtualScheduler(const bool use_static_shapes, + const bool use_aggressive_shape_inference, Cluster* cluster, ReadyNodeManager* ready_nodes); // Initializes the scheduler for the specific grappler item. // Should be called immediately after the c'tor or when the scheduler will be @@ -367,6 +360,7 @@ class VirtualScheduler { bool use_static_shapes_; bool initialized_; bool track_mem_usage_snapshot_; + const bool use_aggressive_shape_inference_; // Whether the input graph includes Switch nodes annotated with output slots // information. diff --git a/tensorflow/core/grappler/costs/virtual_scheduler_test.cc b/tensorflow/core/grappler/costs/virtual_scheduler_test.cc index 5e044b2037..128cb986f1 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler_test.cc +++ b/tensorflow/core/grappler/costs/virtual_scheduler_test.cc @@ -30,8 +30,11 @@ namespace grappler { // Class for testing virtual scheduler. class TestVirtualScheduler : public VirtualScheduler { public: - TestVirtualScheduler(const bool use_static_shapes, Cluster* cluster) - : VirtualScheduler(use_static_shapes, cluster, &ready_node_manager_) { + TestVirtualScheduler(const bool use_static_shapes, + const bool use_aggressive_shape_inference, + Cluster* cluster) + : VirtualScheduler(use_static_shapes, use_aggressive_shape_inference, + cluster, &ready_node_manager_) { enable_mem_usage_tracking(); } @@ -68,7 +71,8 @@ class VirtualSchedulerTest : public ::testing::Test { devices[kCPU1] = cpu_device; cluster_ = absl::make_unique(devices); scheduler_ = absl::make_unique( - /* use_static_shapes = */ true, cluster_.get()); + /*use_static_shapes=*/true, + /*use_aggressive_shape_inference=*/true, cluster_.get()); } NodeDef node1_, node2_, node3_, node4_, node5_, node6_; -- GitLab From 46137ebafbb56857622403115ba99051d896351d Mon Sep 17 00:00:00 2001 From: Martin Wicke Date: Tue, 8 Jan 2019 16:25:48 -0800 Subject: [PATCH 0386/2345] Remove disabled test (should live with pasta). PiperOrigin-RevId: 228421179 --- .../tools/compatibility/ast_edits_test.py | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/tensorflow/tools/compatibility/ast_edits_test.py b/tensorflow/tools/compatibility/ast_edits_test.py index b2e9f67638..4accd8fe29 100644 --- a/tensorflow/tools/compatibility/ast_edits_test.py +++ b/tensorflow/tools/compatibility/ast_edits_test.py @@ -39,8 +39,6 @@ following new APIs: from __future__ import absolute_import from __future__ import division from __future__ import print_function -import ast -import pasta import six from tensorflow.python.framework import test_util from tensorflow.python.platform import test as test_lib @@ -418,26 +416,5 @@ class TestAstEdits(test_util.TensorFlowTestCase): self.assertNotIn("not good", report) -class ManualEditsTest(test_util.TensorFlowTestCase): - - def disabled_test_arg_order(self): - """Tests that generated arg order is sane.""" - text = "f(a)" - t = pasta.parse(text) - node = pasta.ast_utils.find_nodes_by_type(t, (ast.Call,))[0] - arg = ast.keyword(arg="b", value=ast.Num(n=0)) - node.keywords.append(arg) - - # This is only needed in Python3, and I think it's a bug (but maybe in ast). - arg.value.lineno = 0 - arg.value.col_offset = 0 - - # pasta.dump should never put kwargs before args, even if the col_offset is - # messed up. - # This fails if run with python3, but works find for python2. - # In python3, the dump yields "f(b=0, a)". - self.assertEqual(pasta.dump(t), "f(a, b=0)") - - if __name__ == "__main__": test_lib.main() -- GitLab From 5439aec55231818d345767b5038410bf647844e2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 16:36:37 -0800 Subject: [PATCH 0387/2345] Internal change PiperOrigin-RevId: 228422956 --- .../g3doc/images/convert/sample_after.png | Bin 185267 -> 166567 bytes .../g3doc/images/convert/sample_before.png | Bin 155610 -> 136956 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tensorflow/lite/g3doc/images/convert/sample_after.png b/tensorflow/lite/g3doc/images/convert/sample_after.png index 6c451f97903f7f70a9f28dee8abf6daeb7ec5693..db09d0a6ca70695205833acfd2bd8ac6682cb065 100644 GIT binary patch literal 166567 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%0D3xss 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 literal 185267 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 Y{-@dbB|mq!zXI_+UHx3vIVCg!0O{5<+5i9m diff --git a/tensorflow/lite/g3doc/images/convert/sample_before.png b/tensorflow/lite/g3doc/images/convert/sample_before.png index e5317ef295062e79c66430512ef1c45925858ce0..55440d324977f0ff5b795bc80898857918066e96 100644 GIT binary patch literal 136956 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

747KGxnT6?`G~Iy0y5x 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 Date: Tue, 8 Jan 2019 16:45:50 -0800 Subject: [PATCH 0388/2345] [XLA] Use Iota in Cholesky rather than a std::iota constant. NFC intended, just a cleanup noticed in passing. PiperOrigin-RevId: 228424323 --- .../compiler/xla/client/lib/cholesky.cc | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/tensorflow/compiler/xla/client/lib/cholesky.cc b/tensorflow/compiler/xla/client/lib/cholesky.cc index e100d47922..414bd1494c 100644 --- a/tensorflow/compiler/xla/client/lib/cholesky.cc +++ b/tensorflow/compiler/xla/client/lib/cholesky.cc @@ -68,29 +68,26 @@ XlaOp CholeskyUnblocked(XlaOp a, PrecisionConfig::Precision precision) { auto body_fn = [&](XlaOp i, absl::Span loop_vars, XlaBuilder* body_builder) -> StatusOr> { - Shape col_shape; - Shape row_shape; - for (int64 d : major_dims) { - row_shape.add_dimensions(d); - col_shape.add_dimensions(d); - } - row_shape.add_dimensions(1); - row_shape.add_dimensions(n); - row_shape.set_element_type(a_shape.element_type()); - auto mask_zeros_row = Zeros(body_builder, row_shape); - - col_shape.add_dimensions(n); - col_shape.add_dimensions(1); - col_shape.set_element_type(a_shape.element_type()); - auto mask_zeros_col = Zeros(body_builder, col_shape); - - std::vector mask_vector(n); - std::iota(mask_vector.begin(), mask_vector.end(), 0); - auto mask_range = ConstantR1(body_builder, mask_vector); + std::vector row_shape_dims(major_dims.begin(), major_dims.end()); + std::vector col_shape_dims(major_dims.begin(), major_dims.end()); + row_shape_dims.push_back(1); + row_shape_dims.push_back(n); + auto mask_zeros_row = + Zeros(body_builder, + ShapeUtil::MakeShape(a_shape.element_type(), row_shape_dims)); + + col_shape_dims.push_back(n); + col_shape_dims.push_back(1); + auto mask_zeros_col = + Zeros(body_builder, + ShapeUtil::MakeShape(a_shape.element_type(), col_shape_dims)); + auto mask_range_row = - Broadcast(Reshape(mask_range, {0}, {1, n}), major_dims); + Iota(body_builder, ShapeUtil::MakeShape(S32, row_shape_dims), + /*iota_dimension=*/n_dims - 1); auto mask_range_col = - Broadcast(Reshape(mask_range, {0}, {n, 1}), major_dims); + Iota(body_builder, ShapeUtil::MakeShape(S32, col_shape_dims), + /*iota_dimension=*/n_dims - 2); auto body_a = loop_vars[0]; auto body_l = loop_vars[1]; -- GitLab From 2c7444faf3d23ddb959f887b5589ec53ca431e9d Mon Sep 17 00:00:00 2001 From: Michael Kuperstein Date: Tue, 8 Jan 2019 16:53:15 -0800 Subject: [PATCH 0389/2345] [TF:XLA] Move some bridge implementations over to the new DS/DUS API Move some bridge and xla client library op implementations over to the new span-of-scalars API for DynamicSlice and DynamicUpdateSlice. PiperOrigin-RevId: 228425409 --- .../compiler/tf2xla/kernels/image_ops.cc | 17 ++++--- .../compiler/tf2xla/kernels/stack_ops.cc | 12 ++--- tensorflow/compiler/xla/client/lib/slicing.cc | 49 ++++++++++--------- .../xla/client/lib/triangular_solve.cc | 16 +++--- 4 files changed, 49 insertions(+), 45 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/image_ops.cc b/tensorflow/compiler/tf2xla/kernels/image_ops.cc index 96ddd42e2a..a31b5a2cfd 100644 --- a/tensorflow/compiler/tf2xla/kernels/image_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/image_ops.cc @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#include "absl/types/span.h" #include "tensorflow/compiler/tf2xla/kernels/gather_op_helpers.h" #include "tensorflow/compiler/tf2xla/lib/util.h" #include "tensorflow/compiler/tf2xla/xla_helpers.h" @@ -351,24 +352,26 @@ struct SuppressBodyFn { auto num_outputs_so_far = values[1]; auto iou_mask = values[2]; auto included_iou = values[3]; - auto zero_r1 = xla::ConstantR1(builder, {0}); + auto zero = xla::ConstantR0(builder, 0); // Determine if current elem is active using a slice. - auto row_idx_r1 = xla::Reshape(row_idx, {1}); - auto active_elem = xla::DynamicSlice(included_iou, row_idx_r1, {1}); + // TODO(b/118437727): The only reason we need an explicit vector is because + // some old GCCs can't deduce the right type for MakeConstSpan, and + // providing a single-value initializer list directly uses the wrong + // overload. Delete this once the deprecated overload is gone. + std::vector row_idx_vector = {row_idx}; + auto active_elem = xla::DynamicSlice(included_iou, row_idx_vector, {1}); active_elem = xla::Reshape(active_elem, {}); // Increment output count iff current elem is not suppressed. num_outputs_so_far = xla::Select( active_elem, num_outputs_so_far + xla::ConstantR0(builder, 1), num_outputs_so_far); // Slice out the row_idx. - auto starts = xla::ConcatInDim(builder, {row_idx_r1, zero_r1}, 0); - auto row_iou = xla::DynamicSlice(iou_mask, starts, {1, num_boxes}); + auto row_iou = xla::DynamicSlice(iou_mask, {row_idx, zero}, {1, num_boxes}); // Remove the diagonal from consideration. An elem cannot suppress // itself. - auto update_starts = xla::ConcatInDim(builder, {zero_r1, row_idx_r1}, 0); row_iou = xla::DynamicUpdateSlice( row_iou, xla::ConstantR2FromArray2D(builder, {{false}}), - update_starts); + {zero, row_idx}); // Create a suppression by inverting polarity. row_iou = xla::Reshape(row_iou, {num_boxes}); auto supp_mask = xla::Not(row_iou); diff --git a/tensorflow/compiler/tf2xla/kernels/stack_ops.cc b/tensorflow/compiler/tf2xla/kernels/stack_ops.cc index 02d71a3942..d0c5231e84 100644 --- a/tensorflow/compiler/tf2xla/kernels/stack_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/stack_ops.cc @@ -146,9 +146,9 @@ class StackPushOp : public XlaOpKernel { xla::XlaOp value = ctx->Input(1); // start_indices of the DynamicUpdateSlice are [index, 0, 0, ..., 0]. - auto start_indices = - xla::Pad(xla::Reshape(index, {1}), xla::ConstantR0(b, 0), - xla::MakeEdgePaddingConfig({{0, elem_shape.dims()}})); + std::vector start_indices(elem_shape.dims() + 1, + xla::ConstantR0(b, 0)); + start_indices[0] = index; TensorShape slice_shape = elem_shape; slice_shape.InsertDim(0, 1LL); @@ -202,9 +202,9 @@ class StackPopOp : public XlaOpKernel { OP_REQUIRES_OK(ctx, resource->SetValue(xla::Tuple(b, {ta, index}))); // start_indices of the DynamicSlice are [index, 0, 0, ..., 0]. - auto start_indices = - xla::Pad(xla::Reshape(index, {1}), xla::ConstantR0(b, 0), - xla::MakeEdgePaddingConfig({{0, stack_shape.dims() - 1}})); + std::vector start_indices(stack_shape.dims(), + xla::ConstantR0(b, 0)); + start_indices[0] = index; auto slice_shape = stack_shape.dim_sizes(); slice_shape[0] = 1LL; diff --git a/tensorflow/compiler/xla/client/lib/slicing.cc b/tensorflow/compiler/xla/client/lib/slicing.cc index 611fffba8d..77145ba7d4 100644 --- a/tensorflow/compiler/xla/client/lib/slicing.cc +++ b/tensorflow/compiler/xla/client/lib/slicing.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/compiler/xla/client/lib/slicing.h" +#include "tensorflow/compiler/xla/client/xla_builder.h" namespace xla { @@ -51,17 +52,17 @@ XlaOp SliceInMinorDims(XlaOp x, absl::Span start, XlaOp UpdateSlice(XlaOp x, XlaOp update, absl::Span start) { XlaBuilder* builder = x.builder(); return builder->ReportErrorOrReturn([&]() -> StatusOr { - // TODO(phawkins): make int64 work on all backends, remove the int32 cast. - std::vector start_as_int32(start.begin(), start.end()); - auto start_constant = ConstantR1(builder, start_as_int32); TF_ASSIGN_OR_RETURN(Shape shape, builder->GetShape(x)); const int64 n_dims = shape.rank(); - TF_ASSIGN_OR_RETURN(Shape start_constant_shape, - builder->GetShape(start_constant)); - const int64 start_length = - ShapeUtil::GetDimension(start_constant_shape, -1); - TF_RET_CHECK(start_length == n_dims); - return DynamicUpdateSlice(x, update, start_constant); + TF_RET_CHECK(start.size() == n_dims); + + // TODO(phawkins): make int64 work on all backends, remove the int32 cast. + std::vector start_as_int32(start.begin(), start.end()); + std::vector start_ops(start.size()); + for (int i = 0; i < start.size(); ++i) { + start_ops[i] = ConstantR0(builder, start_as_int32[i]); + } + return DynamicUpdateSlice(x, update, start_ops); }); } @@ -90,18 +91,17 @@ std::vector ConcatVectors(absl::Span xs, return output; } -XlaOp PrependZerosInMajorDims(XlaOp x, absl::Span starts) { +StatusOr> PrependZerosInMajorDims( + XlaOp x, absl::Span starts) { XlaBuilder* builder = x.builder(); - return builder->ReportErrorOrReturn([&]() -> StatusOr { - TF_ASSIGN_OR_RETURN(Shape shape, builder->GetShape(x)); - const int64 n_dims = shape.rank(); - auto zero = Reshape(ConstantR0(builder, 0), {1}); - std::vector padded_starts(n_dims, zero); - for (int i = 0; i < starts.size(); ++i) { - padded_starts[n_dims - starts.size() + i] = Reshape(starts[i], {1}); - } - return ConcatInDim(builder, padded_starts, 0); - }); + TF_ASSIGN_OR_RETURN(Shape shape, builder->GetShape(x)); + const int64 n_dims = shape.rank(); + auto zero = ConstantR0(builder, 0); + std::vector padded_starts(n_dims, zero); + for (int i = 0; i < starts.size(); ++i) { + padded_starts[n_dims - starts.size() + i] = starts[i]; + } + return padded_starts; } } // namespace @@ -119,7 +119,7 @@ XlaOp DynamicSliceInMinorDims(XlaOp x, absl::Span starts, .subspan( /*pos=*/0, /*len=*/n_dims - sizes.size()); - auto padded_starts = PrependZerosInMajorDims(x, starts); + TF_ASSIGN_OR_RETURN(auto padded_starts, PrependZerosInMajorDims(x, starts)); auto padded_sizes = ConcatVectors(major_dims, sizes); return DynamicSlice(x, padded_starts, padded_sizes); }); @@ -127,8 +127,11 @@ XlaOp DynamicSliceInMinorDims(XlaOp x, absl::Span starts, XlaOp DynamicUpdateSliceInMinorDims(XlaOp x, XlaOp update, absl::Span starts) { - auto padded_starts = PrependZerosInMajorDims(x, starts); - return DynamicUpdateSlice(x, update, padded_starts); + XlaBuilder* builder = x.builder(); + return builder->ReportErrorOrReturn([&]() -> StatusOr { + TF_ASSIGN_OR_RETURN(auto padded_starts, PrependZerosInMajorDims(x, starts)); + return DynamicUpdateSlice(x, update, padded_starts); + }); } } // namespace xla diff --git a/tensorflow/compiler/xla/client/lib/triangular_solve.cc b/tensorflow/compiler/xla/client/lib/triangular_solve.cc index 6061e64656..c2f31742e9 100644 --- a/tensorflow/compiler/xla/client/lib/triangular_solve.cc +++ b/tensorflow/compiler/xla/client/lib/triangular_solve.cc @@ -165,10 +165,10 @@ XlaOp InvertDiagonalBlocks(XlaOp diag_blocks, bool lower, bool transpose_a, // The first or last diagonal element should be set to 1 instead of -1 // though, since we never update it auto pos_one = Reshape(One(builder, shape.element_type()), {1, 1}); - auto start_index = (lower) ? 0 : block_size - 1; - auto output_block = DynamicUpdateSlice( - neg_identity, pos_one, - /*start_indices=*/ConstantR1(builder, 2, start_index)); + auto start_index = ConstantR0(builder, (lower) ? 0 : block_size - 1); + auto output_block = + DynamicUpdateSlice(neg_identity, pos_one, + /*start_indices=*/{start_index, start_index}); // Broadcast diag([1, -1, -1, ...]) to every block XlaOp output = Broadcast(output_block, @@ -211,12 +211,10 @@ XlaOp InvertDiagonalBlocks(XlaOp diag_blocks, bool lower, bool transpose_a, auto body_out = GetTupleElement(input_tuple, 1); auto body_input = GetTupleElement(input_tuple, 2); - auto zero = ConstantR1(bodyb.get(), 1, 0); + auto zero = ConstantR0(bodyb.get(), 0); auto j = (lower) ? i : ScalarLike(i, block_size - 1) - i; - auto start_indices = - ConcatInDim(bodyb.get(), {zero, Reshape(j, {1}), zero}, 0); auto input_row = - DynamicSlice(body_input, start_indices, + DynamicSlice(body_input, {zero, j, zero}, /*slice_sizes=*/{num_blocks, 1, block_size}); // We want -L21 L11^{-1} @@ -230,7 +228,7 @@ XlaOp InvertDiagonalBlocks(XlaOp diag_blocks, bool lower, bool transpose_a, precision_proto.add_operand_precision(precision); auto update = -DotGeneral(input_row, body_out, dnums, &precision_proto); - body_out = DynamicUpdateSlice(body_out, update, start_indices); + body_out = DynamicUpdateSlice(body_out, update, {zero, j, zero}); auto next_i = i + ScalarLike(i, 1); Tuple(bodyb.get(), {next_i, body_out, body_input}); -- GitLab From c842394896de32c4160fc0f5f94dabf06890476d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 16:57:19 -0800 Subject: [PATCH 0390/2345] Support not fully defined batch dimension in the inputs to group_norm. PiperOrigin-RevId: 228425992 --- .../layers/python/layers/normalization.py | 18 +++++++++++++----- .../layers/python/layers/normalization_test.py | 9 +++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/layers/python/layers/normalization.py b/tensorflow/contrib/layers/python/layers/normalization.py index 11033a2e9c..76b03ff514 100644 --- a/tensorflow/contrib/layers/python/layers/normalization.py +++ b/tensorflow/contrib/layers/python/layers/normalization.py @@ -186,7 +186,7 @@ def group_norm(inputs, Args: inputs: A Tensor with at least 2 dimensions one which is channels. All - shape dimensions must be fully defined. + shape dimensions except for batch must be fully defined. groups: Integer. Divide the channels into this number of groups over which normalization statistics are computed. This number must be commensurate with the number of channels in `inputs`. @@ -249,13 +249,21 @@ def group_norm(inputs, """ # TODO(shlens): Support partially defined shapes for the inputs. inputs = ops.convert_to_tensor(inputs) - original_shape = inputs.shape if inputs.shape.ndims is None: raise ValueError('Inputs %s has undefined rank.' % inputs.name) if channels_axis > (inputs.shape.ndims - 1): raise ValueError('Axis is out of bounds.') + # Use dynamic shape for not fully defined dimensions in the inputs. + dyanmic_shape = array_ops.shape(inputs) + input_shape_list = [] + for i, dim in enumerate(inputs.shape): + if dim.value is None: + input_shape_list.append(dyanmic_shape[i]) + else: + input_shape_list.append(dim) + # Standardize the channels_axis to be positive and identify # of channels. if channels_axis < 0: channels_axis = inputs.shape.ndims + channels_axis @@ -289,8 +297,8 @@ def group_norm(inputs, # Determine axes before channels. Some examples of common image formats: # 'NCHW': before = [N], after = [HW] # 'NHWC': before = [NHW], after = [] - axes_before_channels = inputs.shape.as_list()[:channels_axis] - axes_after_channels = inputs.shape.as_list()[channels_axis+1:] + axes_before_channels = input_shape_list[:channels_axis] + axes_after_channels = input_shape_list[channels_axis+1:] # Manually broadcast the parameters to conform to the number of groups. params_shape_broadcast = ([1] * len(axes_before_channels) + @@ -369,7 +377,7 @@ def group_norm(inputs, outputs = inputs * gain + offset # Collapse the groups into the channel dimension. - outputs = array_ops.reshape(outputs, original_shape) + outputs = array_ops.reshape(outputs, input_shape_list) if activation_fn is not None: outputs = activation_fn(outputs) diff --git a/tensorflow/contrib/layers/python/layers/normalization_test.py b/tensorflow/contrib/layers/python/layers/normalization_test.py index c8d3c91b10..9a85084b23 100644 --- a/tensorflow/contrib/layers/python/layers/normalization_test.py +++ b/tensorflow/contrib/layers/python/layers/normalization_test.py @@ -221,6 +221,15 @@ class GroupNormTest(test.TestCase): normalization.group_norm(inputs, channels_axis=-1, reduction_axes=[-3, -2]) + def testParamsShapeNotFullyDefinedBatchAxis(self): + height, width, groups = 3, 3, 4 + inputs = array_ops.placeholder(dtypes.float32, + shape=(None, height, width, 2*groups)) + output = normalization.group_norm(inputs, channels_axis=-1, + reduction_axes=[-3, -2], groups=groups) + self.assertListEqual([None, height, width, 2 * groups], + output.shape.as_list()) + def testCreateOp(self): height, width, groups = 3, 3, 4 images = random_ops.random_uniform((5, height, width, 2*groups), seed=1) -- GitLab From dde706e1d3561656f0a6d842a79c50cfbe1ad508 Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 9 Jan 2019 06:48:03 +0530 Subject: [PATCH 0391/2345] Changed result.error_message() to result --- tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc index 2879b00f55..f8c67b60c3 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc @@ -46,7 +46,7 @@ bool CheckMask(se::StreamExecutor* exec, void* ptr, int64* mask) { Status result = exec->SynchronousMemcpyD2H(gpu_ptr, MASK_BYTES, tmp); if (!result.ok()) { - LOG(FATAL) << "Could not copy debug mask, " << result.error_message(); + LOG(FATAL) << "Could not copy debug mask, " << result; } bool ok = true; @@ -66,7 +66,7 @@ void InitMask(se::StreamExecutor* exec, void* ptr, int64* mask) { se::DeviceMemory gpu_ptr{se::DeviceMemoryBase{ptr, MASK_BYTES}}; Status result = exec->SynchronousMemcpyH2D(mask, MASK_BYTES, &gpu_ptr); if (!result.ok()) { - LOG(FATAL) << "Could not copy debug mask, " << result.error_message(); + LOG(FATAL) << "Could not copy debug mask, " << result; } } @@ -176,7 +176,7 @@ void* GPUNanResetAllocator::AllocateRaw(size_t alignment, size_t num_bytes) { Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, &nan_ptr); if (!result.ok()) { - LOG(ERROR) << "Could not initialize to NaNs, " << result.error_message(); + LOG(ERROR) << "Could not initialize to NaNs, " << result; } return allocated_ptr; @@ -192,7 +192,7 @@ void GPUNanResetAllocator::DeallocateRaw(void* ptr) { Status result = stream_exec_->SynchronousMemcpyH2D(&nans[0], req_size, &nan_ptr); if (!result.ok()) { - LOG(ERROR) << "Could not initialize to NaNs, " << result.error_message(); + LOG(ERROR) << "Could not initialize to NaNs, " << result; } } -- GitLab From fa8ed584884fae406e2398718bbcd6e20498c7a2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 17:24:16 -0800 Subject: [PATCH 0392/2345] Mark certain tests in export_output_test as v1 only as export_output.as_signature_def does not support v2 due to its dependent method saved_model.build_tensor_info not working in eager. Also raise a runtime exception inside build_tensor_info when running in eager. PiperOrigin-RevId: 228429679 --- .../saved_model/model_utils/export_test.py | 255 +++++++++--------- tensorflow/python/saved_model/utils_impl.py | 6 + tensorflow/python/saved_model/utils_test.py | 7 + 3 files changed, 140 insertions(+), 128 deletions(-) diff --git a/tensorflow/python/saved_model/model_utils/export_test.py b/tensorflow/python/saved_model/model_utils/export_test.py index 776bfff886..ef512150a2 100644 --- a/tensorflow/python/saved_model/model_utils/export_test.py +++ b/tensorflow/python/saved_model/model_utils/export_test.py @@ -22,7 +22,6 @@ import os import tempfile import time -from tensorflow.python.eager import context from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops @@ -52,110 +51,110 @@ ops.register_tensor_conversion_function(LabeledTensorMock, class ExportTest(test_util.TensorFlowTestCase): + @test_util.deprecated_graph_mode_only def test_build_all_signature_defs_without_receiver_alternatives(self): - with context.graph_mode(): - receiver_tensor = array_ops.placeholder(dtypes.string) - output_1 = constant_op.constant([1.]) - output_2 = constant_op.constant(["2"]) - output_3 = constant_op.constant(["3"]) - export_outputs = { - signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: - export_output.RegressionOutput(value=output_1), - "head-2": export_output.ClassificationOutput(classes=output_2), - "head-3": export_output.PredictOutput(outputs={ - "some_output_3": output_3 - }), - } - - signature_defs = export_utils.build_all_signature_defs( - receiver_tensor, export_outputs) - - expected_signature_defs = { - "serving_default": - signature_def_utils.regression_signature_def(receiver_tensor, - output_1), - "head-2": - signature_def_utils.classification_signature_def(receiver_tensor, - output_2, None), - "head-3": - signature_def_utils.predict_signature_def({ - "input": receiver_tensor - }, {"some_output_3": output_3}) - } - - self.assertDictEqual(expected_signature_defs, signature_defs) + receiver_tensor = array_ops.placeholder(dtypes.string) + output_1 = constant_op.constant([1.]) + output_2 = constant_op.constant(["2"]) + output_3 = constant_op.constant(["3"]) + export_outputs = { + signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: + export_output.RegressionOutput(value=output_1), + "head-2": export_output.ClassificationOutput(classes=output_2), + "head-3": export_output.PredictOutput(outputs={ + "some_output_3": output_3 + }), + } + + signature_defs = export_utils.build_all_signature_defs( + receiver_tensor, export_outputs) + expected_signature_defs = { + "serving_default": + signature_def_utils.regression_signature_def(receiver_tensor, + output_1), + "head-2": + signature_def_utils.classification_signature_def(receiver_tensor, + output_2, None), + "head-3": + signature_def_utils.predict_signature_def({ + "input": receiver_tensor + }, {"some_output_3": output_3}) + } + + self.assertDictEqual(expected_signature_defs, signature_defs) + + @test_util.deprecated_graph_mode_only def test_build_all_signature_defs_with_dict_alternatives(self): - with context.graph_mode(): - receiver_tensor = array_ops.placeholder(dtypes.string) - receiver_tensors_alternative_1 = { - "foo": array_ops.placeholder(dtypes.int64), - "bar": array_ops.sparse_placeholder(dtypes.float32)} - receiver_tensors_alternatives = {"other": receiver_tensors_alternative_1} - output_1 = constant_op.constant([1.]) - output_2 = constant_op.constant(["2"]) - output_3 = constant_op.constant(["3"]) - export_outputs = { - signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: - export_output.RegressionOutput(value=output_1), - "head-2": export_output.ClassificationOutput(classes=output_2), - "head-3": export_output.PredictOutput(outputs={ - "some_output_3": output_3 - }), - } - - signature_defs = export_utils.build_all_signature_defs( - receiver_tensor, export_outputs, receiver_tensors_alternatives) - - expected_signature_defs = { - "serving_default": - signature_def_utils.regression_signature_def( - receiver_tensor, - output_1), - "head-2": - signature_def_utils.classification_signature_def( - receiver_tensor, - output_2, None), - "head-3": - signature_def_utils.predict_signature_def( - {"input": receiver_tensor}, - {"some_output_3": output_3}), - "other:head-3": - signature_def_utils.predict_signature_def( - receiver_tensors_alternative_1, - {"some_output_3": output_3}) - - # Note that the alternatives 'other:serving_default' and - # 'other:head-2' are invalid, because regession and classification - # signatures must take a single string input. Here we verify that - # these invalid signatures are not included in the export_utils. - } - - self.assertDictEqual(expected_signature_defs, signature_defs) + receiver_tensor = array_ops.placeholder(dtypes.string) + receiver_tensors_alternative_1 = { + "foo": array_ops.placeholder(dtypes.int64), + "bar": array_ops.sparse_placeholder(dtypes.float32)} + receiver_tensors_alternatives = {"other": receiver_tensors_alternative_1} + output_1 = constant_op.constant([1.]) + output_2 = constant_op.constant(["2"]) + output_3 = constant_op.constant(["3"]) + export_outputs = { + signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: + export_output.RegressionOutput(value=output_1), + "head-2": export_output.ClassificationOutput(classes=output_2), + "head-3": export_output.PredictOutput(outputs={ + "some_output_3": output_3 + }), + } + signature_defs = export_utils.build_all_signature_defs( + receiver_tensor, export_outputs, receiver_tensors_alternatives) + + expected_signature_defs = { + "serving_default": + signature_def_utils.regression_signature_def( + receiver_tensor, + output_1), + "head-2": + signature_def_utils.classification_signature_def( + receiver_tensor, + output_2, None), + "head-3": + signature_def_utils.predict_signature_def( + {"input": receiver_tensor}, + {"some_output_3": output_3}), + "other:head-3": + signature_def_utils.predict_signature_def( + receiver_tensors_alternative_1, + {"some_output_3": output_3}) + + # Note that the alternatives 'other:serving_default' and + # 'other:head-2' are invalid, because regession and classification + # signatures must take a single string input. Here we verify that + # these invalid signatures are not included in the export_utils. + } + + self.assertDictEqual(expected_signature_defs, signature_defs) + + @test_util.deprecated_graph_mode_only def test_build_all_signature_defs_with_single_alternatives(self): - with context.graph_mode(): - receiver_tensor = array_ops.placeholder(dtypes.string) - receiver_tensors_alternative_1 = array_ops.placeholder(dtypes.int64) - receiver_tensors_alternative_2 = array_ops.sparse_placeholder( - dtypes.float32) - # Note we are passing single Tensors as values of - # receiver_tensors_alternatives, where normally that is a dict. - # In this case a dict will be created using the default receiver tensor - # name "input". - receiver_tensors_alternatives = {"other1": receiver_tensors_alternative_1, - "other2": receiver_tensors_alternative_2} - output_1 = constant_op.constant([1.]) - output_2 = constant_op.constant(["2"]) - output_3 = constant_op.constant(["3"]) - export_outputs = { - signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: - export_output.RegressionOutput(value=output_1), - "head-2": export_output.ClassificationOutput(classes=output_2), - "head-3": export_output.PredictOutput(outputs={ - "some_output_3": output_3 - }), - } + receiver_tensor = array_ops.placeholder(dtypes.string) + receiver_tensors_alternative_1 = array_ops.placeholder(dtypes.int64) + receiver_tensors_alternative_2 = array_ops.sparse_placeholder( + dtypes.float32) + # Note we are passing single Tensors as values of + # receiver_tensors_alternatives, where normally that is a dict. + # In this case a dict will be created using the default receiver tensor + # name "input". + receiver_tensors_alternatives = {"other1": receiver_tensors_alternative_1, + "other2": receiver_tensors_alternative_2} + output_1 = constant_op.constant([1.]) + output_2 = constant_op.constant(["2"]) + output_3 = constant_op.constant(["3"]) + export_outputs = { + signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: + export_output.RegressionOutput(value=output_1), + "head-2": export_output.ClassificationOutput(classes=output_2), + "head-3": export_output.PredictOutput(outputs={ + "some_output_3": output_3 + }), + } signature_defs = export_utils.build_all_signature_defs( receiver_tensor, export_outputs, receiver_tensors_alternatives) @@ -222,35 +221,35 @@ class ExportTest(test_util.TensorFlowTestCase): self.assertTrue(int(time_1) < int(time_2)) self.assertTrue(int(time_2) < int(time_3)) + @test_util.deprecated_graph_mode_only def test_build_all_signature_defs_serving_only(self): - with context.graph_mode(): - receiver_tensor = {"input": array_ops.placeholder(dtypes.string)} - output_1 = constant_op.constant([1.]) - export_outputs = { - signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: - export_output.PredictOutput(outputs=output_1), - "train": export_output.TrainOutput(loss=output_1), - } - - signature_defs = export_utils.build_all_signature_defs( - receiver_tensor, export_outputs) - - expected_signature_defs = { - "serving_default": signature_def_utils.predict_signature_def( - receiver_tensor, {"output": output_1}) - } - - self.assertDictEqual(expected_signature_defs, signature_defs) - - signature_defs = export_utils.build_all_signature_defs( - receiver_tensor, export_outputs, serving_only=False) - - expected_signature_defs.update({ - "train": signature_def_utils.supervised_train_signature_def( - receiver_tensor, loss={"loss": output_1}) - }) - - self.assertDictEqual(expected_signature_defs, signature_defs) + receiver_tensor = {"input": array_ops.placeholder(dtypes.string)} + output_1 = constant_op.constant([1.]) + export_outputs = { + signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: + export_output.PredictOutput(outputs=output_1), + "train": export_output.TrainOutput(loss=output_1), + } + + signature_defs = export_utils.build_all_signature_defs( + receiver_tensor, export_outputs) + + expected_signature_defs = { + "serving_default": signature_def_utils.predict_signature_def( + receiver_tensor, {"output": output_1}) + } + + self.assertDictEqual(expected_signature_defs, signature_defs) + + signature_defs = export_utils.build_all_signature_defs( + receiver_tensor, export_outputs, serving_only=False) + + expected_signature_defs.update({ + "train": signature_def_utils.supervised_train_signature_def( + receiver_tensor, loss={"loss": output_1}) + }) + + self.assertDictEqual(expected_signature_defs, signature_defs) if __name__ == "__main__": diff --git a/tensorflow/python/saved_model/utils_impl.py b/tensorflow/python/saved_model/utils_impl.py index 5caabe59fe..d43cfdf2b3 100644 --- a/tensorflow/python/saved_model/utils_impl.py +++ b/tensorflow/python/saved_model/utils_impl.py @@ -22,6 +22,7 @@ import os from tensorflow.core.framework import types_pb2 from tensorflow.core.protobuf import meta_graph_pb2 +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 @@ -53,7 +54,12 @@ def build_tensor_info(tensor): Returns: A TensorInfo protocol buffer constructed based on the supplied argument. + + Raises: + RuntimeError: If eager execution is enabled. """ + if context.executing_eagerly(): + raise RuntimeError("build_tensor_info is not supported in Eager mode.") tensor_info = meta_graph_pb2.TensorInfo( dtype=dtypes.as_dtype(tensor.dtype).as_datatype_enum, tensor_shape=tensor.get_shape().as_proto()) diff --git a/tensorflow/python/saved_model/utils_test.py b/tensorflow/python/saved_model/utils_test.py index 2afe8abfd6..1e12de91b8 100644 --- a/tensorflow/python/saved_model/utils_test.py +++ b/tensorflow/python/saved_model/utils_test.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function from tensorflow.core.framework import types_pb2 +from tensorflow.python.eager import context from tensorflow.python.eager import function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes @@ -81,6 +82,12 @@ class UtilsTest(test.TestCase): self.assertEqual(42, x_tensor_info.tensor_shape.dim[0].size) self.assertEqual(69, x_tensor_info.tensor_shape.dim[1].size) + def testBuildTensorInfoEager(self): + x = constant_op.constant(1, name="x") + with context.eager_mode(), self.assertRaisesRegexp( + RuntimeError, "build_tensor_info is not supported in Eager mode"): + utils.build_tensor_info(x) + @test_util.run_v1_only("b/120545219") def testGetTensorFromInfoDense(self): expected = array_ops.placeholder(dtypes.float32, 1, name="x") -- GitLab From f9c3fea70f7304c1ee96e8d293dd57623c06fe19 Mon Sep 17 00:00:00 2001 From: Davide Libenzi Date: Tue, 8 Jan 2019 17:24:41 -0800 Subject: [PATCH 0393/2345] The HLO input/ouput alias config can be setup by the user, expecting a must alias semantics, and opportunistically by the compiler. Since the upper layer of the code will perform aliasing actions on their own, based on the HLO input/output aliasing configuration, we need a way for the upper layer to distringuish whether a given aliasing is coming from the user, or the compiler. This CL adds a single field to the alias data, to differentiate between user and compiler/system aliases. PiperOrigin-RevId: 228429740 --- tensorflow/compiler/xla/client/xla_builder.cc | 5 +- .../compiler/xla/service/buffer_assignment.cc | 9 +- .../compiler/xla/service/copy_insertion.cc | 18 +-- .../xla/service/copy_insertion_test.cc | 21 ++-- tensorflow/compiler/xla/service/hlo.proto | 14 +++ .../xla/service/hlo_alias_analysis.cc | 9 +- .../xla/service/hlo_alias_analysis_test.cc | 24 ++-- .../service/hlo_input_output_alias_config.cc | 119 ++++++++++-------- .../service/hlo_input_output_alias_config.h | 51 ++++++-- .../hlo_input_output_alias_config_test.cc | 60 +++++---- 10 files changed, 209 insertions(+), 121 deletions(-) diff --git a/tensorflow/compiler/xla/client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_builder.cc index 328abbc7b9..7a04c0ec69 100644 --- a/tensorflow/compiler/xla/client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_builder.cc @@ -350,8 +350,9 @@ StatusOr XlaBuilder::Build(int64 root_id) { alias.param_number, alias.param_index.ToString().c_str()); } - TF_RETURN_IF_ERROR(config.SetUpAlias(alias.output_index, alias.param_number, - alias.param_index)); + TF_RETURN_IF_ERROR(config.SetUpAlias( + alias.output_index, alias.param_number, alias.param_index, + HloInputOutputAliasConfig::AliasKind::kUserAlias)); } *module->mutable_input_output_alias() = config.ToProto(); return Status::OK(); diff --git a/tensorflow/compiler/xla/service/buffer_assignment.cc b/tensorflow/compiler/xla/service/buffer_assignment.cc index c3285f516b..d07615b828 100644 --- a/tensorflow/compiler/xla/service/buffer_assignment.cc +++ b/tensorflow/compiler/xla/service/buffer_assignment.cc @@ -1530,15 +1530,16 @@ void BufferAssigner::BuildColocatedBufferSets( VLOG(4) << "Input/Output Alias Config: "; VLOG(4) << module->input_output_alias_config(); module->input_output_alias_config().ForEachAlias( - [&](const ShapeIndex& output_index, int64 param_number, - const ShapeIndex& param_index) { + [&](const ShapeIndex& output_index, + const HloInputOutputAliasConfig::Alias& alias) { std::vector colocated_set; AddBufferToColocatedSet(module->entry_computation()->root_instruction(), output_index, points_to_analysis, &colocated_set); AddBufferToColocatedSet( - module->entry_computation()->parameter_instruction(param_number), - param_index, points_to_analysis, &colocated_set); + module->entry_computation()->parameter_instruction( + alias.parameter_number), + alias.parameter_index, points_to_analysis, &colocated_set); AddSetToColocatedBufferSets(colocated_set, colocated_buffer_sets); }); diff --git a/tensorflow/compiler/xla/service/copy_insertion.cc b/tensorflow/compiler/xla/service/copy_insertion.cc index 2b837901f0..5e26a63ceb 100644 --- a/tensorflow/compiler/xla/service/copy_insertion.cc +++ b/tensorflow/compiler/xla/service/copy_insertion.cc @@ -349,11 +349,12 @@ Status AddCopiesForAliasedInputOutputs(HloModule* module) { ShapeTree param_indices_to_copy(param->shape()); module->input_output_alias_config().ForEachAlias( - [&](const ShapeIndex& output_index, int64 param_number, - const ShapeIndex& param_index) { - if (param_number == param->parameter_number()) { + [&](const ShapeIndex& output_index, + const HloInputOutputAliasConfig::Alias& alias) { + if (alias.parameter_number == param->parameter_number()) { param_has_alias = true; - *(param_indices_to_copy.mutable_element(param_index)) = true; + *(param_indices_to_copy.mutable_element(alias.parameter_index)) = + true; *(output_indices_to_copy.mutable_element(output_index)) = true; } }); @@ -395,13 +396,14 @@ Status AddCopiesForAliasedInputOutputs(HloModule* module) { // Add control dependencies between the input/output copies. TF_RETURN_IF_ERROR(module->input_output_alias_config().ForEachAliasWithStatus( - [&](const ShapeIndex& output_index, int64 param_number, - const ShapeIndex& input_index) -> Status { - if (!copied_parameters[param_number]) { + [&](const ShapeIndex& output_index, + const HloInputOutputAliasConfig::Alias& alias) -> Status { + if (!copied_parameters[alias.parameter_number]) { return Status::OK(); } HloInstruction* from = - copied_parameters[param_number]->element(input_index); + copied_parameters[alias.parameter_number]->element( + alias.parameter_index); HloInstruction* to = output_copy_tree.element(output_index); TF_RET_CHECK(from != nullptr); diff --git a/tensorflow/compiler/xla/service/copy_insertion_test.cc b/tensorflow/compiler/xla/service/copy_insertion_test.cc index e4e9d7ba05..4d4074943e 100644 --- a/tensorflow/compiler/xla/service/copy_insertion_test.cc +++ b/tensorflow/compiler/xla/service/copy_insertion_test.cc @@ -1376,9 +1376,11 @@ TEST_F(CopyInsertionTest, CrossingParameters) { builder.AddInstruction(HloInstruction::CreateTuple({gte1, gte0})); module->AddEntryComputation(builder.Build()); ASSERT_IS_OK(module->input_output_alias_config().SetUpAlias( - /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0})); + /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); ASSERT_IS_OK(module->input_output_alias_config().SetUpAlias( - /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1})); + /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); InsertCopies(module.get()); EXPECT_EQ(CountCopies(*module), 4); @@ -1409,9 +1411,11 @@ TEST_F(CopyInsertionTest, ParametersAliasing) { builder.AddInstruction(HloInstruction::CreateTuple({gte0, gte1})); module->AddEntryComputation(builder.Build()); ASSERT_IS_OK(module->input_output_alias_config().SetUpAlias( - /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0})); + /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); ASSERT_IS_OK(module->input_output_alias_config().SetUpAlias( - /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1})); + /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); InsertCopies(module.get()); EXPECT_EQ(CountCopies(*module), 0); @@ -1475,7 +1479,8 @@ TEST_F(CopyInsertionTest, ParameterWithPartialAliasing) { builder.AddInstruction(HloInstruction::CreateTuple({gte0, gte1})); module->AddEntryComputation(builder.Build()); ASSERT_IS_OK(module->input_output_alias_config().SetUpAlias( - /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0})); + /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); InsertCopies(module.get()); EXPECT_THAT(module->entry_computation()->root_instruction(), @@ -1516,7 +1521,8 @@ TEST_F(CopyInsertionTest, ParameterAndParallelOpsWithPartialAliasing) { builder.AddInstruction(HloInstruction::CreateTuple({negate0, negate1})); module->AddEntryComputation(builder.Build()); ASSERT_IS_OK(module->input_output_alias_config().SetUpAlias( - /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0})); + /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); InsertCopies(module.get()); EXPECT_EQ(CountCopies(*module), 0); @@ -1557,7 +1563,8 @@ TEST_F(CopyInsertionTest, ParameterAndOpsWithPartialAliasing) { builder.AddInstruction(HloInstruction::CreateTuple({add, negate1})); module->AddEntryComputation(builder.Build()); ASSERT_IS_OK(module->input_output_alias_config().SetUpAlias( - /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0})); + /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); InsertCopies(module.get()); EXPECT_EQ(CountCopies(*module), 0); diff --git a/tensorflow/compiler/xla/service/hlo.proto b/tensorflow/compiler/xla/service/hlo.proto index 9b50f1ca5b..263b42a29d 100644 --- a/tensorflow/compiler/xla/service/hlo.proto +++ b/tensorflow/compiler/xla/service/hlo.proto @@ -229,6 +229,18 @@ message HloScheduleProto { } message HloInputOutputAliasProto { + enum Kind { + // Define a UNDEFINED_ALIAS equal to zero to get around the default-0 proto3 + // behavior and missing has_*() APIs. + UNDEFINED_ALIAS = 0; + // An alias setup by the user as must alias. A use setting USER_ALIAS is + // expecting the designed output to be dropped over the given input + // parameter number+index. + USER_ALIAS = 1; + // An alias setup by the compiler as part of its optimizations. + SYSTEM_ALIAS = 2; + } + // The following proto describes a pair of aliased an input // (described by parameter number and a ShapeIndex of the parameter) // and an output (described by a ShapeIndex of the root @@ -249,6 +261,8 @@ message HloInputOutputAliasProto { int64 parameter_number = 2; // ShapeIndex of the parameter instruction. repeated int64 parameter_shape_index = 3; + // The kind of alias to be setup. + Kind kind = 4; } repeated AliasEntryProto entries = 1; diff --git a/tensorflow/compiler/xla/service/hlo_alias_analysis.cc b/tensorflow/compiler/xla/service/hlo_alias_analysis.cc index 4c46ea595e..e511f1951c 100644 --- a/tensorflow/compiler/xla/service/hlo_alias_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_alias_analysis.cc @@ -176,13 +176,12 @@ class BufferValueMap { const HloValue& value, std::vector* aliased_buffers) { // Get parameter value from an aliased_input object. const auto get_parameter_value = - [this](const std::pair& aliased_input) + [this](const HloInputOutputAliasConfig::Alias& aliased_input) -> const HloValue& { - int64 param_number = aliased_input.first; - const ShapeIndex& param_index = aliased_input.second; return dataflow_.GetUniqueValueAt( - module_->entry_computation()->parameter_instruction(param_number), - param_index); + module_->entry_computation()->parameter_instruction( + aliased_input.parameter_number), + aliased_input.parameter_index); }; // If the value shows up in a root instruction, alias it with parameter diff --git a/tensorflow/compiler/xla/service/hlo_alias_analysis_test.cc b/tensorflow/compiler/xla/service/hlo_alias_analysis_test.cc index 7e6150e941..b6dbf07959 100644 --- a/tensorflow/compiler/xla/service/hlo_alias_analysis_test.cc +++ b/tensorflow/compiler/xla/service/hlo_alias_analysis_test.cc @@ -238,13 +238,16 @@ TEST_F(HloAliasAnalysisTest, ParametersWithAliasing) { builder.AddInstruction(HloInstruction::CreateTuple({negate0, negate1})); module_->AddEntryComputation(builder.Build()); TF_ASSERT_OK(module_->input_output_alias_config().SetUpAlias( - /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0})); + /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); TF_ASSERT_OK(module_->input_output_alias_config().SetUpAlias( - /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1})); + /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); // Cannot alias an output twice. ASSERT_IS_NOT_OK(module_->input_output_alias_config().SetUpAlias( - /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{0})); + /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); const HloAliasAnalysis& analysis = RunAnalysis(); @@ -279,13 +282,16 @@ TEST_F(HloAliasAnalysisTest, ParametersWithCrossAliasing) { builder.AddInstruction(HloInstruction::CreateTuple({gte0, gte1})); module_->AddEntryComputation(builder.Build()); TF_ASSERT_OK(module_->input_output_alias_config().SetUpAlias( - /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{1})); + /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{1}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); TF_ASSERT_OK(module_->input_output_alias_config().SetUpAlias( - /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{0})); + /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); // Cannot alias an output twice. ASSERT_IS_NOT_OK(module_->input_output_alias_config().SetUpAlias( - /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1})); + /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); const HloAliasAnalysis& analysis = RunAnalysis(); @@ -365,9 +371,11 @@ TEST_F(HloAliasAnalysisTest, InputOutputAliasingWithWhile) { builder.AddInstruction(HloInstruction::CreateTuple({negate_1, negate_2})); module_->AddEntryComputation(builder.Build()); TF_ASSERT_OK(module_->input_output_alias_config().SetUpAlias( - /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0})); + /*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); TF_ASSERT_OK(module_->input_output_alias_config().SetUpAlias( - /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1})); + /*output_index=*/{1}, /*param_number=*/0, /*param_index=*/{1}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); const HloAliasAnalysis& analysis = RunAnalysis(); diff --git a/tensorflow/compiler/xla/service/hlo_input_output_alias_config.cc b/tensorflow/compiler/xla/service/hlo_input_output_alias_config.cc index 70d4df5d1c..b01c00121b 100644 --- a/tensorflow/compiler/xla/service/hlo_input_output_alias_config.cc +++ b/tensorflow/compiler/xla/service/hlo_input_output_alias_config.cc @@ -20,28 +20,31 @@ namespace xla { bool HloInputOutputAliasConfig::OutputHasAlias( const ShapeIndex& output_index) const { - return aliased_output_indices_.count(output_index) > 0; + return alias_.element(output_index).has_value(); } Status HloInputOutputAliasConfig::SetUpAlias(const ShapeIndex& output_index, int64 param_number, - const ShapeIndex& param_index) { - TF_RET_CHECK(!OutputHasAlias(output_index)) - << "Output index " << output_index << " already has an alias setup"; + const ShapeIndex& param_index, + AliasKind kind) { + TF_RET_CHECK(kind == AliasKind::kUserAlias || kind == AliasKind::kSystemAlias) + << kind; TF_RET_CHECK(ShapeUtil::IndexIsValid(alias_.shape(), output_index)) << absl::StrCat("Tring to set up alias at ", output_index.ToString(), " which is an invalid index for shape ", ShapeUtil::HumanString(alias_.shape())); + TF_RET_CHECK(param_number >= 0) << param_number; + TF_RET_CHECK(!OutputHasAlias(output_index)) + << "Output index " << output_index << " already has an alias setup"; // Output can't be aliased with multiple parameters. TF_RET_CHECK(!alias_.element(output_index)) << absl::StrFormat( "Trying to set up output alias for param %lld at %s but failed: output " "index %s is already aliased with param %lld at %s", param_number, param_index.ToString(), output_index.ToString(), - alias_.element(output_index)->first, - alias_.element(output_index)->second.ToString()); + alias_.element(output_index)->parameter_number, + alias_.element(output_index)->parameter_index.ToString()); (*alias_.mutable_element(output_index)) = - std::make_pair(param_number, param_index); - aliased_output_indices_.insert(output_index); + Alias(kind, param_number, param_index); VLOG(4) << "Set up alias between output index " << output_index.ToString() << " and parameter " << param_index << " at index " << param_index.ToString(); @@ -51,15 +54,24 @@ Status HloInputOutputAliasConfig::SetUpAlias(const ShapeIndex& output_index, HloInputOutputAliasProto HloInputOutputAliasConfig::ToProto() const { HloInputOutputAliasProto result; alias_.ForEachElement( - [&](const ShapeIndex& index, - const absl::optional>& data) { + [&](const ShapeIndex& index, const absl::optional& data) { if (data) { HloInputOutputAliasProto::AliasEntryProto entry; + switch (data->kind) { + case AliasKind::kUserAlias: + entry.set_kind(HloInputOutputAliasProto::USER_ALIAS); + break; + case AliasKind::kSystemAlias: + entry.set_kind(HloInputOutputAliasProto::SYSTEM_ALIAS); + break; + default: + LOG(FATAL) << "Unknown alias kind " << data->kind; + } for (int64 i : index) { entry.add_output_shape_index(i); } - entry.set_parameter_number(data->first); - for (int64 i : data->second) { + entry.set_parameter_number(data->parameter_number); + for (int64 i : data->parameter_index) { entry.add_parameter_shape_index(i); } result.add_entries()->Swap(&entry); @@ -75,14 +87,18 @@ StatusOr HloInputOutputAliasConfig::CreateFromProto( proto.entries()) { ShapeIndex output_index(entry.output_shape_index().begin(), entry.output_shape_index().end()); - int64 param_number = entry.parameter_number(); ShapeIndex param_index(entry.parameter_shape_index().begin(), entry.parameter_shape_index().end()); + // Handle backward compatibility with existing protos, which only knew of + // system aliases. + AliasKind kind = AliasKind::kSystemAlias; + if (entry.kind() == HloInputOutputAliasProto::USER_ALIAS) { + kind = AliasKind::kUserAlias; + } TF_RETURN_IF_ERROR( - result.SetUpAlias(output_index, param_number, param_index)); + result.SetUpAlias(output_index, param_number, param_index, kind)); } - return result; } @@ -90,45 +106,44 @@ string HloInputOutputAliasConfig::ToString() const { std::vector pieces; pieces.push_back("HloInputOutputAliasConfig"); - ForEachAlias([&](const ShapeIndex& output_index, int64 param_number, - const ShapeIndex& param_index) { + ForEachAlias([&](const ShapeIndex& output_index, const Alias& alias) { + const char* kind = alias.kind == AliasKind::kUserAlias ? "USER" : "SYSTEM"; pieces.push_back(absl::StrFormat( - " OutputIndex %s is aliased with parameter %lld at %s:", - output_index.ToString(), param_number, param_index.ToString())); + " OutputIndex %s is aliased (kind=%s) with parameter %lld at %s:", + output_index.ToString(), kind, alias.parameter_number, + alias.parameter_index.ToString())); }); - return absl::StrJoin(pieces, "\n"); } -bool HloInputOutputAliasConfig::ParameterHasAlias( +HloInputOutputAliasConfig::AliasKind +HloInputOutputAliasConfig::ParameterAliasKind( int64 param_number, const ShapeIndex& param_index) const { - bool output = false; + AliasKind kind = AliasKind::kNoAlias; alias_.ForEachElement( - [&](const xla::ShapeIndex&, - absl::optional> alias) { - if (alias && alias->first == param_number && - alias->second == param_index) { - output = true; + [&](const xla::ShapeIndex&, absl::optional alias) { + if (alias && alias->parameter_number == param_number && + alias->parameter_index == param_index) { + kind = alias->kind; } }); - return output; + return kind; } absl::optional HloInputOutputAliasConfig::GetAliasedOutput( int64 param_number, const ShapeIndex& param_index) const { absl::optional output; alias_.ForEachElement( - [&](const xla::ShapeIndex& output_index, - absl::optional> alias) { - if (alias && alias->first == param_number && - alias->second == param_index) { + [&](const xla::ShapeIndex& output_index, absl::optional alias) { + if (alias && alias->parameter_number == param_number && + alias->parameter_index == param_index) { output = output_index; } }); return output; } -absl::optional> +absl::optional HloInputOutputAliasConfig::GetAliasedParameter( const ShapeIndex& output_index) const { CHECK(ShapeUtil::IndexIsValid(alias_.shape(), output_index)); @@ -137,10 +152,9 @@ HloInputOutputAliasConfig::GetAliasedParameter( void HloInputOutputAliasConfig::ForEachAlias(AliasFn fn) const { alias_.ForEachElement( - [&](const ShapeIndex& output_index, - absl::optional> aliased) { + [&](const ShapeIndex& output_index, absl::optional aliased) { if (aliased) { - fn(output_index, aliased->first, aliased->second); + fn(output_index, *aliased); } }); } @@ -148,10 +162,9 @@ void HloInputOutputAliasConfig::ForEachAlias(AliasFn fn) const { Status HloInputOutputAliasConfig::ForEachAliasWithStatus( AliasFnWithStatus fn) const { return alias_.ForEachElementWithStatus( - [&](const ShapeIndex& output_index, - absl::optional> aliased) { + [&](const ShapeIndex& output_index, absl::optional aliased) { if (aliased) { - TF_RETURN_IF_ERROR(fn(output_index, aliased->first, aliased->second)); + TF_RETURN_IF_ERROR(fn(output_index, *aliased)); } return Status::OK(); }); @@ -167,20 +180,19 @@ Status HloInputOutputAliasConfig::Verify( param_has_seen.emplace_back(param->shape()); } return ForEachAliasWithStatus([&](const ShapeIndex& output_index, - int64 param_number, - const ShapeIndex& param_index) -> Status { + const Alias& alias) -> Status { const HloInstruction* root = entry->root_instruction(); - TF_RET_CHECK(0 <= param_number); - TF_RET_CHECK(entry->num_parameters() > param_number); + TF_RET_CHECK(0 <= alias.parameter_number); + TF_RET_CHECK(entry->num_parameters() > alias.parameter_number); const Shape& param_shape = - entry->parameter_instruction(param_number)->shape(); + entry->parameter_instruction(alias.parameter_number)->shape(); const Shape& output_shape = root->shape(); - TF_RET_CHECK(ShapeUtil::IndexIsValid(param_shape, param_index)); + TF_RET_CHECK(ShapeUtil::IndexIsValid(param_shape, alias.parameter_index)); TF_RET_CHECK(ShapeUtil::IndexIsValid(output_shape, output_index)); const Shape& param_subshape = - ShapeUtil::GetSubshape(param_shape, param_index); + ShapeUtil::GetSubshape(param_shape, alias.parameter_index); const Shape& output_subshape = ShapeUtil::GetSubshape(output_shape, output_index); TF_RET_CHECK(LayoutUtil::IsDenseArray(param_subshape)); @@ -191,19 +203,20 @@ Status HloInputOutputAliasConfig::Verify( "Expected aliased input %lld at index %s and output at index %s to " "have the same size. Input sub-shape is %s with size %lld, output " "sub-shape is %s with size %lld", - param_number, param_index.ToString(), output_index.ToString(), + alias.parameter_number, alias.parameter_index.ToString(), + output_index.ToString(), ShapeUtil::HumanStringWithLayout(param_subshape), size_func(param_subshape), ShapeUtil::HumanStringWithLayout(output_subshape), size_func(output_subshape)); } - // Check each param_number and param_index pair only show up once. No - // input can be aliased with output buffers. - TF_RET_CHECK(param_has_seen[param_number].element(param_index) == false); - - *(param_has_seen[param_number].mutable_element(param_index)) = true; - + // Check each alias.parameter_number and alias.parameter_index pair only + // show up once. No input can be aliased with output buffers. + TF_RET_CHECK(param_has_seen[alias.parameter_number].element( + alias.parameter_index) == false); + *(param_has_seen[alias.parameter_number].mutable_element( + alias.parameter_index)) = true; return Status::OK(); }); } diff --git a/tensorflow/compiler/xla/service/hlo_input_output_alias_config.h b/tensorflow/compiler/xla/service/hlo_input_output_alias_config.h index 3967743d53..b0b71dece8 100644 --- a/tensorflow/compiler/xla/service/hlo_input_output_alias_config.h +++ b/tensorflow/compiler/xla/service/hlo_input_output_alias_config.h @@ -32,6 +32,28 @@ class HloModule; // parameter index in the entry computation. class HloInputOutputAliasConfig { public: + // The kind of aliases which can be set. A kUserAlias is one setup at + // compilation time by the user, and has to be respected. A kSystemAlias one + // might be setup by the compiler, if it decides it is convenient to do so. + enum AliasKind { + kNoAlias, + kUserAlias, + kSystemAlias, + }; + + // Defines the alias information for a given output buffer. A given output + // buffer shape index can refer only to one parameter+index. + struct Alias { + Alias(AliasKind kind, int64 parameter_number, ShapeIndex parameter_index) + : kind(kind), + parameter_number(parameter_number), + parameter_index(std::move(parameter_index)) {} + + AliasKind kind; + int64 parameter_number; + ShapeIndex parameter_index; + }; + HloInputOutputAliasConfig() = default; explicit HloInputOutputAliasConfig(Shape shape) : alias_(shape) {} @@ -41,12 +63,19 @@ class HloInputOutputAliasConfig { // Sets up alias config from `output_index` to `param_index` at // `param_number`. Status SetUpAlias(const ShapeIndex& output_index, int64 param_number, - const ShapeIndex& param_index); + const ShapeIndex& param_index, AliasKind kind); + + // Returns the kind of alias for the given parameter number and parameter + // index. If no alias exists, AliasKind::kNoAlias is returned. + AliasKind ParameterAliasKind(int64 param_number, + const ShapeIndex& param_index) const; // Returns true if the given parameter is aliased with one of the output // buffers. bool ParameterHasAlias(int64 param_number, - const ShapeIndex& param_index) const; + const ShapeIndex& param_index) const { + return ParameterAliasKind(param_number, param_index) != AliasKind::kNoAlias; + } // Checks whether the provided output index has already been aliased. bool OutputHasAlias(const ShapeIndex& output_index) const; @@ -67,19 +96,17 @@ class HloInputOutputAliasConfig { // Returns the number of parameter and index of the parameter buffer that the // given output buffer index is aliased with. A nullopt is returned if there // is no parameter is aliased with the specific output. - absl::optional> GetAliasedParameter( + absl::optional GetAliasedParameter( const ShapeIndex& output_index) const; using AliasFn = - std::function; + std::function; // Iterates through each aliased output and input. void ForEachAlias(AliasFn fn) const; using AliasFnWithStatus = - std::function; + std::function; // Verifies that the given config is valid for the given module. // Specifically, the config's input and output should be in-bound and size of @@ -94,12 +121,10 @@ class HloInputOutputAliasConfig { private: // A ShapeTree which indicates the list of buffers that's expected to be // aliased. The key on this shape tree represents the output index. The value - // is a pair of parameter number and index into the buffer. If the value is - // nullopt, it means there is no parameter aliasing for this output. - ShapeTree>> alias_; - - // The indices of the output which have been aliased. - absl::flat_hash_set aliased_output_indices_; + // is an Alias data structure which defines the input parameter coordinates. + // If the value is nullopt, it means there is no parameter aliasing for this + // output. + ShapeTree> alias_; }; std::ostream& operator<<(std::ostream& out, diff --git a/tensorflow/compiler/xla/service/hlo_input_output_alias_config_test.cc b/tensorflow/compiler/xla/service/hlo_input_output_alias_config_test.cc index aeb9b0fdc8..a46a107723 100644 --- a/tensorflow/compiler/xla/service/hlo_input_output_alias_config_test.cc +++ b/tensorflow/compiler/xla/service/hlo_input_output_alias_config_test.cc @@ -45,11 +45,12 @@ class HloInputOutputAliasConfigTest : public HloTestBase { EXPECT_TRUE(aliased_output); EXPECT_EQ(aliased_output.value(), output_index); - absl::optional> aliased_param = + absl::optional aliased_param = config.GetAliasedParameter(output_index); EXPECT_TRUE(aliased_param); - EXPECT_EQ(aliased_param.value(), std::make_pair(param_number, param_index)); + EXPECT_EQ(aliased_param->parameter_number, param_number); + EXPECT_EQ(aliased_param->parameter_index, param_index); } void expect_not_aliased(const ShapeIndex& output_index, int64 param_number, @@ -60,11 +61,12 @@ class HloInputOutputAliasConfigTest : public HloTestBase { EXPECT_FALSE(aliased_output && aliased_output == output_index); - absl::optional> aliased_param = + absl::optional aliased_param = config.GetAliasedParameter(output_index); - EXPECT_FALSE(aliased_param && aliased_param->first == param_number && - aliased_param->second == param_index); + EXPECT_FALSE(aliased_param && + aliased_param->parameter_number == param_number && + aliased_param->parameter_index == param_index); } }; @@ -84,8 +86,10 @@ ENTRY main { HloInputOutputAliasConfig config( module->entry_computation()->root_instruction()->shape()); - TF_ASSERT_OK(config.SetUpAlias(/*output_index=*/{0}, /*param_number=*/1, - /*param_index=*/{})); + TF_ASSERT_OK(config.SetUpAlias( + /*output_index=*/{0}, /*param_number=*/1, + /*param_index=*/{}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); expect_aliased(/*output_index=*/{0}, /*param_number=*/1, /*param_index=*/{}, config); @@ -114,11 +118,15 @@ ENTRY main { HloInputOutputAliasConfig config( module->entry_computation()->root_instruction()->shape()); - TF_ASSERT_OK(config.SetUpAlias(/*output_index=*/{0}, /*param_number=*/0, - /*param_index=*/{0})); + TF_ASSERT_OK(config.SetUpAlias( + /*output_index=*/{0}, /*param_number=*/0, + /*param_index=*/{0}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); - TF_ASSERT_OK(config.SetUpAlias(/*output_index=*/{1}, /*param_number=*/0, - /*param_index=*/{1})); + TF_ASSERT_OK(config.SetUpAlias( + /*output_index=*/{1}, /*param_number=*/0, + /*param_index=*/{1}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); expect_aliased(/*output_index=*/{0}, /*param_number=*/0, /*param_index=*/{0}, config); @@ -149,11 +157,15 @@ ENTRY main { HloInputOutputAliasConfig config( module->entry_computation()->root_instruction()->shape()); - TF_ASSERT_OK(config.SetUpAlias(/*output_index=*/{0}, /*param_number=*/0, - /*param_index=*/{})); + TF_ASSERT_OK(config.SetUpAlias( + /*output_index=*/{0}, /*param_number=*/0, + /*param_index=*/{}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); - TF_ASSERT_OK(config.SetUpAlias(/*output_index=*/{1}, /*param_number=*/0, - /*param_index=*/{})); + TF_ASSERT_OK(config.SetUpAlias( + /*output_index=*/{1}, /*param_number=*/0, + /*param_index=*/{}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); ASSERT_IS_NOT_OK(config.Verify(*module, [](const Shape& shape) { return ShapeUtil::ByteSizeOf(shape); @@ -176,8 +188,10 @@ ENTRY main { HloInputOutputAliasConfig config( module->entry_computation()->root_instruction()->shape()); - TF_ASSERT_OK(config.SetUpAlias(/*output_index=*/{1}, /*param_number=*/0, - /*param_index=*/{})); + TF_ASSERT_OK(config.SetUpAlias( + /*output_index=*/{1}, /*param_number=*/0, + /*param_index=*/{}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); ASSERT_IS_NOT_OK(config.Verify(*module, [](const Shape& shape) { return ShapeUtil::ByteSizeOf(shape); @@ -200,11 +214,15 @@ ENTRY main { HloInputOutputAliasConfig config( module->entry_computation()->root_instruction()->shape()); - TF_ASSERT_OK(config.SetUpAlias(/*output_index=*/{0}, /*param_number=*/0, - /*param_index=*/{})); + TF_ASSERT_OK(config.SetUpAlias( + /*output_index=*/{0}, /*param_number=*/0, + /*param_index=*/{}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); - ASSERT_IS_NOT_OK(config.SetUpAlias(/*output_index=*/{0}, /*param_number=*/1, - /*param_index=*/{})); + ASSERT_IS_NOT_OK(config.SetUpAlias( + /*output_index=*/{0}, /*param_number=*/1, + /*param_index=*/{}, + /*kind=*/HloInputOutputAliasConfig::AliasKind::kUserAlias)); } } // namespace } // namespace xla -- GitLab From fb8a14fde29f2dc19331d73795c2ead0e8ef0e8e Mon Sep 17 00:00:00 2001 From: Yifei Feng Date: Tue, 8 Jan 2019 17:33:23 -0800 Subject: [PATCH 0394/2345] Remove cuda runtime dependencies from stream executor. PiperOrigin-RevId: 228430943 --- tensorflow/stream_executor/cuda/BUILD | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tensorflow/stream_executor/cuda/BUILD b/tensorflow/stream_executor/cuda/BUILD index 58c9480884..87c8eae416 100644 --- a/tensorflow/stream_executor/cuda/BUILD +++ b/tensorflow/stream_executor/cuda/BUILD @@ -146,13 +146,7 @@ cc_library( "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", "//tensorflow/stream_executor/platform:dso_loader", - ] + if_static( - [ - "@local_config_cuda//cuda:cublas", - "@local_config_cuda//cuda:cudart_static", - ], - ["@local_config_cuda//cuda:cudart"], - ), + ] + if_static(["@local_config_cuda//cuda:cublas"]), alwayslink = True, ) @@ -214,13 +208,7 @@ cc_library( "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", "//tensorflow/stream_executor/platform:dso_loader", - ] + tf_additional_cudnn_plugin_deps() + if_static( - [ - "@local_config_cuda//cuda:cudnn", - "@local_config_cuda//cuda:cudart_static", - ], - ["@local_config_cuda//cuda:cudart"], - ), + ] + tf_additional_cudnn_plugin_deps() + if_static(["@local_config_cuda//cuda:cudnn"]), alwayslink = True, ) -- GitLab From 9e0e9e255fb9210ed2da50dffba156f1d5617b8b Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 9 Jan 2019 02:03:37 +0000 Subject: [PATCH 0395/2345] Update docstring for tf.train.init_from_checkpoint This fix updates docstring for tf.train.init_from_checkpoint, to match the actual implementation. (thanks sharvil) This fix fixes 24756. Signed-off-by: Yong Tang --- tensorflow/python/training/checkpoint_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/training/checkpoint_utils.py b/tensorflow/python/training/checkpoint_utils.py index 74b46179e7..5e18f4b722 100644 --- a/tensorflow/python/training/checkpoint_utils.py +++ b/tensorflow/python/training/checkpoint_utils.py @@ -180,8 +180,8 @@ def init_from_checkpoint(ckpt_dir_or_file, assignment_map): (in default graph). Raises: - tf.errors.OpError: If missing checkpoints or tensors in checkpoints. - ValueError: If missing variables in current graph. + ValueError: If missing variables in current graph, or if missing + checkpoints or tensors in checkpoints. """ if distribution_strategy_context.get_cross_replica_context(): _init_from_checkpoint(None, ckpt_dir_or_file, assignment_map) -- GitLab From c8191270e47ece2bba777c271428e205d34d0827 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 18:41:13 -0800 Subject: [PATCH 0396/2345] Remove Identity nodes where all inputs and outputs are on the same device, but the identity is on a different device. PiperOrigin-RevId: 228438774 --- .../optimizers/dependency_optimizer.cc | 17 ++++++------ .../optimizers/dependency_optimizer_test.cc | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc index 7fee3ae9d5..8b81cb2430 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc @@ -205,14 +205,6 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( num_cross_out += static_cast(output_node->device() != node_dev); } - if ((is_identity || is_multi_input_identity_n) && num_cross_in > 0 && - num_cross_out > 0) { - // This identity node follows a device crossing, so it might be - // following a _Recv node after partioning. Do not remove such nodes, - // unless they only have consumers on the same device as themselves. - return false; - } - // 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; @@ -225,6 +217,15 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( if (num_cross_after > num_cross_before) { return false; } + + if ((is_identity || is_multi_input_identity_n) && num_cross_in > 0 && + num_cross_out > 0 && num_cross_after > 0) { + // This identity node follows a device crossing, so it might be + // following a _Recv node after partioning. Do not remove such nodes, + // unless they only have consumers on the same device as themselves. + return false; + } + return true; } diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc b/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc index 8d70d9d5c7..5883fcb926 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc @@ -356,6 +356,32 @@ TEST_F(DependencyOptimizerTest, RemoveIdentityOps_DeviceBoundaries) { VerifyGraphsEqual(item.graph, output, __FUNCTION__); } +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); + auto id_a = ops::Identity(s.WithOpName("id_a").WithDevice("/CPU:1"), x); + Output id = + ops::Identity(s.WithControlDependencies(id_a).WithDevice("/CPU:0"), id_a); + + GrapplerItem item; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + item.fetch.push_back("Identity"); + + DependencyOptimizer optimizer; + GraphDef output; + Status status = optimizer.Optimize(nullptr, item, &output); + TF_EXPECT_OK(status); + + 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") { + EXPECT_EQ(node.input(0), "x"); + } + } +} + TEST_F(DependencyOptimizerTest, RemoveNoOps_SingleInputOrOutput) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x = ops::RandomUniform(s.WithOpName("x"), {1, 2}, DT_FLOAT); -- GitLab From ec81825aaf7e848d9f8ddffdf1e0d20aebe9172c Mon Sep 17 00:00:00 2001 From: Reed Wanderman-Milne Date: Tue, 8 Jan 2019 18:50:27 -0800 Subject: [PATCH 0397/2345] Add GPU explicit padding to tf.nn.conv2d. Benchmark results: All benchmark results were run on a Z840 with a Titan V, with internal TensorFlow. 1. Resnet50 Eager results The internal resnet50 Eager benchmarks were run, to ensure no regressions in Resnet50 in Eager mode that could have occurred due to the extra Python overhead this change adds. The benchmarks run are here: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/resnet50/resnet50_test.py. Each row was run 150 times and the average was taken. Note none of these benchmarks use explicit padding. Numbers represent time, so lower is better. Benchmark Name After Before % diff apply_async_gpu_batch_64_channels_first 0.0726 0.0726 -0.06% apply_gpu_batch_64_channels_first 0.0725 0.0725 -0.06% apply_with_defun_gpu_batch_64_channels_first 0.0755 0.0756 0.07% train_async_gpu_batch_16_channels_first 0.0776 0.0778 0.27% train_async_gpu_batch_32_channels_first 0.1268 0.1271 0.23% train_dataset_gpu_batch_16_channels_first 0.1085 0.1094 0.77% train_dataset_gpu_batch_32_channels_first 0.1473 0.1477 0.28% train_dataset_with_defun_gpu_batch_16_channels_first 0.0800 0.0803 0.37% train_dataset_with_defun_gpu_batch_32_channels_first 0.1325 0.1326 0.09% train_gpu_batch_16_channels_first 0.0812 0.0813 0.18% train_gpu_batch_32_channels_first 0.1329 0.1325 -0.32% train_with_defun_gpu_batch_16_channels_first 0.0789 0.0791 0.26% train_with_defun_gpu_batch_32_channels_first 0.1325 0.1325 -0.02% There is minimal impact to Eager performance. 2. tf_cnn_benchmarks tf_cnn_benchmarks was run internally with the following flags: --batch_size=128 --model=resnet50 It was run 60 times with and without this change. With this change, tf_cnn_benchmarks had all instances of a tf.pad followed by Conv2D replaced with an explicitly padded Conv2d. It got 330.96 images/sec with this change and 330.80 without, and the difference is likely noise. Therefore, this change does not improve tf_cnn_benchmarks performance. 3. Conv2D benchmarks The added benchmarks to conv_ops_test.py were run with this change, each 400 times and the average was taken. They were not run without this change. The table groups the 8 benchmarks into 4 pairs, with each pair running two similar benchmarks, one with explicit padding, and one without explicit padding. Benchmark name Explicit Non-explicit % diff explicit/manual pad forward 0.001815 0.002006 10.56% explicit/manual pad backward 0.006261 0.006937 10.79% eager explicit/same pad 0.039320 0.038403 -2.33% graph explicit/same pad 0.037039 0.037034 -0.11% The first two rows show there is theoretical performance gains to using explicit padding over a manual tf.pad followed by the convolution. On Resnet50, we were not able to achieve this performance gain in practice, as tf_cnn_benchmarks saw no improvement. On models that use larger paddings than in Resnet50, the performance gain will less negligible. The last two rows compare explicit padding padding to the equivalent same padding, to see if explicit padding adds any overhead. In Graph mode, there is no overhead, but Eager mode has some overhead with explicit padding over SAME padding. PiperOrigin-RevId: 228439591 --- .../tf2xla/kernels/conv_op_helpers.cc | 9 +- .../api_def/base_api/api_def_Conv2D.pbtxt | 9 + .../api_def_Conv2DBackpropFilter.pbtxt | 9 + .../api_def_Conv2DBackpropInput.pbtxt | 9 + tensorflow/core/framework/common_shape_fns.cc | 84 +- tensorflow/core/framework/common_shape_fns.h | 48 +- .../core/framework/common_shape_fns_test.cc | 82 +- tensorflow/core/framework/ops_util.cc | 3 + .../large_function_graph.pbtxt | 7 + .../grappler/optimizers/layout_optimizer.cc | 23 + .../optimizers/layout_optimizer_test.cc | 64 +- .../core/kernels/conv_grad_filter_ops.cc | 134 +- .../core/kernels/conv_grad_input_ops.cc | 146 +- tensorflow/core/kernels/conv_grad_ops.cc | 51 +- tensorflow/core/kernels/conv_grad_ops.h | 20 +- tensorflow/core/kernels/conv_grad_ops_3d.cc | 21 +- tensorflow/core/kernels/conv_ops.cc | 223 +-- tensorflow/core/kernels/conv_ops.h | 13 +- .../core/kernels/depthwise_conv_grad_op.cc | 6 +- tensorflow/core/kernels/depthwise_conv_op.cc | 3 +- tensorflow/core/ops/nn_ops.cc | 11 +- tensorflow/core/util/padding.cc | 43 + tensorflow/core/util/padding.h | 19 +- tensorflow/core/util/tensor_format.h | 27 +- .../python/keras/layers/convolutional.py | 4 +- tensorflow/python/keras/utils/conv_utils.py | 4 +- .../python/kernel_tests/conv_ops_test.py | 1232 +++++++++++++++-- tensorflow/python/ops/nn_grad.py | 15 +- tensorflow/python/ops/nn_ops.py | 316 ++++- 29 files changed, 2218 insertions(+), 417 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/conv_op_helpers.cc b/tensorflow/compiler/tf2xla/kernels/conv_op_helpers.cc index 6b049f653c..5b4f863f74 100644 --- a/tensorflow/compiler/tf2xla/kernels/conv_op_helpers.cc +++ b/tensorflow/compiler/tf2xla/kernels/conv_op_helpers.cc @@ -212,8 +212,8 @@ Status ConvBackpropComputeDimensionsV2XlaShapes( XLAShapeToTensorShape(out_backprop_shape, &out_backprop_tensor_shape)); return ConvBackpropComputeDimensionsV2( label, num_spatial_dims, input_tensor_shape, filter_tensor_shape, - out_backprop_tensor_shape, dilations, strides, padding, data_format, - dims); + out_backprop_tensor_shape, dilations, strides, padding, + /*explicit_paddings=*/{}, data_format, dims); } } // anonymous namespace @@ -227,6 +227,11 @@ xla::StatusOr ConvOpAttrs::Create(int num_spatial_dims, TF_RETURN_IF_ERROR(ctx->GetAttr("dilations", &attrs.dilations)); TF_RETURN_IF_ERROR(ctx->GetAttr("strides", &attrs.strides)); TF_RETURN_IF_ERROR(ctx->GetAttr("padding", &attrs.padding)); + // TODO(reedwm): Support explicit padding. + if (attrs.padding == EXPLICIT) { + return errors::Unimplemented( + "XLA does not yet support Conv2D with explicit padding."); + } string data_format; TF_RETURN_IF_ERROR(ctx->GetAttr("data_format", &data_format)); diff --git a/tensorflow/core/api_def/base_api/api_def_Conv2D.pbtxt b/tensorflow/core/api_def/base_api/api_def_Conv2D.pbtxt index 070d6adb97..d0794de4ba 100644 --- a/tensorflow/core/api_def/base_api/api_def_Conv2D.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_Conv2D.pbtxt @@ -33,6 +33,15 @@ END name: "padding" description: < 0, but got ", stride); } @@ -137,6 +152,11 @@ Status GetWindowedOutputSizeFromDimsV2( // See also the parallel implementation in GetWindowedOutputSizeVerbose. switch (padding_type) { case Padding::VALID: + padding_before = padding_after = 0; + TF_FALLTHROUGH_INTENDED; + case Padding::EXPLICIT: + TF_RETURN_IF_ERROR( + c->Add(input_size, padding_before + padding_after, &input_size)); if (dilation_rate > 1) { DimensionHandle window_size; TF_RETURN_IF_ERROR( @@ -166,9 +186,18 @@ Status GetWindowedOutputSizeFromDims( shape_inference::DimensionHandle input_size, shape_inference::DimensionOrConstant filter_size, int64 stride, Padding padding_type, shape_inference::DimensionHandle* output_size) { + if (padding_type == Padding::EXPLICIT) { + return errors::Internal( + "GetWindowedOutputSizeFromDims does not handle EXPLICIT padding; call " + "GetWindowedOutputSizeFromDimsV2 instead"); + } return GetWindowedOutputSizeFromDimsV2(c, input_size, filter_size, /*dilation_rate=*/1, stride, - padding_type, output_size); + padding_type, + // Give dummy values of -1 to + // padding_before and padding_after, + // since explicit padding is not used. + -1, -1, output_size); } Status UnchangedShape(shape_inference::InferenceContext* c) { @@ -371,7 +400,10 @@ Status ShapeFromDimensions(DimensionHandle batch_dim, return tensorflow::Status::OK(); } -Status Conv2DShape(shape_inference::InferenceContext* c) { +namespace { + +Status Conv2DShapeImpl(shape_inference::InferenceContext* c, + bool supports_explicit_padding) { string data_format_str, filter_format_str; if (!c->GetAttr("data_format", &data_format_str).ok()) { data_format_str = "NHWC"; @@ -464,13 +496,30 @@ Status Conv2DShape(shape_inference::InferenceContext* c) { Padding padding; TF_RETURN_IF_ERROR(c->GetAttr("padding", &padding)); + std::vector explicit_paddings; + if (supports_explicit_padding) { + TF_RETURN_IF_ERROR(c->GetAttr("explicit_paddings", &explicit_paddings)); + TF_RETURN_IF_ERROR(CheckValidPadding(padding, explicit_paddings, + /*num_dims=*/4, data_format)); + } else { + DCHECK(padding != Padding::EXPLICIT); + } + DimensionHandle output_rows, output_cols; + int64 pad_rows_before = -1, pad_rows_after = -1; + int64 pad_cols_before = -1, pad_cols_after = -1; + if (padding == Padding::EXPLICIT) { + GetExplicitPaddingForDim(explicit_paddings, data_format, 'H', + &pad_rows_before, &pad_rows_after); + GetExplicitPaddingForDim(explicit_paddings, data_format, 'W', + &pad_cols_before, &pad_cols_after); + } TF_RETURN_IF_ERROR(GetWindowedOutputSizeFromDimsV2( c, input_spatial_dims[0], filter_rows_dim, dilation_rows, stride_rows, - padding, &output_rows)); + padding, pad_rows_before, pad_rows_after, &output_rows)); TF_RETURN_IF_ERROR(GetWindowedOutputSizeFromDimsV2( c, input_spatial_dims[1], filter_cols_dim, dilation_cols, stride_cols, - padding, &output_cols)); + padding, pad_cols_before, pad_cols_after, &output_cols)); ShapeHandle output_shape; TF_RETURN_IF_ERROR( @@ -480,6 +529,19 @@ Status Conv2DShape(shape_inference::InferenceContext* c) { return Status::OK(); } +} // namespace + +// Shape function for Conv2D-like operations that support explicit padding. +Status Conv2DShapeWithExplicitPadding(shape_inference::InferenceContext* c) { + return Conv2DShapeImpl(c, true); +} + +// Shape function for Conv2D-like operations that do not support explicit +// padding. +Status Conv2DShape(shape_inference::InferenceContext* c) { + return Conv2DShapeImpl(c, false); +} + // TODO(mjanusz): Unify all conv/pooling shape functions. Status Conv3DShape(shape_inference::InferenceContext* c) { ShapeHandle input_shape; @@ -551,13 +613,13 @@ Status Conv3DShape(shape_inference::InferenceContext* c) { TF_RETURN_IF_ERROR(GetWindowedOutputSizeFromDimsV2( c, in_planes_dim, filter_planes_dim, dilation_planes, stride_planes, - padding, &output_planes)); + padding, -1, -1, &output_planes)); TF_RETURN_IF_ERROR(GetWindowedOutputSizeFromDimsV2( - c, in_rows_dim, filter_rows_dim, dilation_rows, stride_rows, padding, - &output_rows)); + c, in_rows_dim, filter_rows_dim, dilation_rows, stride_rows, padding, -1, + -1, &output_rows)); TF_RETURN_IF_ERROR(GetWindowedOutputSizeFromDimsV2( - c, in_cols_dim, filter_cols_dim, dilation_cols, stride_cols, padding, - &output_cols)); + c, in_cols_dim, filter_cols_dim, dilation_cols, stride_cols, padding, -1, + -1, &output_cols)); ShapeHandle output_shape; if (data_format == "NCDHW") { diff --git a/tensorflow/core/framework/common_shape_fns.h b/tensorflow/core/framework/common_shape_fns.h index 362899b947..14b9688bdc 100644 --- a/tensorflow/core/framework/common_shape_fns.h +++ b/tensorflow/core/framework/common_shape_fns.h @@ -38,11 +38,12 @@ namespace tensorflow { // // Padding (P): the padding we apply to the input tensor along each // dimension. This is usually used to make sure that the spatial dimensions -// do not shrink when we progress with convolutions. Two types of padding are -// often used: +// do not shrink when we progress with convolutions. This function supports two +// types of padding. // SAME: the pad value is computed so that the output will have size H/S. // VALID: no padding is carried out. -// The padded area is zero-filled. +// If you want to use EXPLICIT padding, GetWindowedOutputSizeVerbose must be +// called instead. Note the padded area is zero-filled. // // The output dimensions for convolution and many other operations, when given // all the parameters above, are as follows: @@ -95,6 +96,9 @@ Status GetWindowedOutputSize(int64 input_size, int64 filter_size, int64 stride, // When the stride is 1, the expression simplifies to // H' = H-K'+1. // +// If you want to use EXPLICIT padding, GetWindowedOutputSizeVerboseV2 must be +// called instead +// // TODO(b/67112639): Merge V2 versions and the original versions eventually. Status GetWindowedOutputSizeV2(int64 input_size, int64 filter_size, int64 dilation_rate, int64 stride, @@ -102,9 +106,12 @@ Status GetWindowedOutputSizeV2(int64 input_size, int64 filter_size, int64* padding_size); // Returns the same output dimensions as in GetWindowedOutputSize, but returns -// verbose padding dimensions (before/after). Any excess padding -// (caused by an odd padding size value) is added to the 'padding_after' -// dimension. +// verbose padding dimensions (before/after), and EXPLICIT padding is supported. +// When padding_type is EXPLICIT, *padding_before and *padding_after must +// already point to initialized integers with the padding amounts. Otherwise, +// *padding_before and *padding_after are set by this function, and any +// excess padding (caused by an odd padding size value) is added to the +// 'padding_after' dimension. Status GetWindowedOutputSizeVerbose(int64 input_size, int64 filter_size, int64 stride, Padding padding_type, int64* output_size, int64* padding_before, @@ -122,7 +129,8 @@ Status GetWindowedOutputSizeVerboseV2(int64 input_size, int64 filter_size, // of the output tensor and padding to be applied to the input tensor at the // lower end of every dimension. Use for 3D convolutions, where the input data // is padded with zeros, as well as for 3D avg/max pooling, where the input data -// is padded with invalid values that are not considered for pooling. +// is padded with invalid values that are not considered for pooling. EXPLICIT +// padding is not supported. Status Get3dOutputSize(const std::array& input, const std::array& window, const std::array& strides, @@ -140,21 +148,23 @@ Status Get3dOutputSizeV2(const std::array& input, namespace shape_inference { -// Like GetWindowedOutputSize, but deals with DimensionHandles. +// Like GetWindowedOutputSize, but deals with DimensionHandles. Does not support +// EXPLICIT padding. Status GetWindowedOutputSizeFromDims(InferenceContext* c, DimensionHandle input_size, DimensionOrConstant filter_size, int64 stride, Padding padding_type, DimensionHandle* output_size); -// The V2 version computes the same outputs with arbitrary dilation_rate. For -// detailed equations, refer to the comments for GetWindowedOutputSizeV2(). -Status GetWindowedOutputSizeFromDimsV2(InferenceContext* c, - DimensionHandle input_size, - DimensionOrConstant filter_size, - int64 dilation_rate, int64 stride, - Padding padding_type, - DimensionHandle* output_size); +// The V2 version computes the same outputs with arbitrary dilation_rate, and +// supports EXPLICIT padding. For detailed equations, refer to the comments +// for GetWindowedOutputSizeV2(). The 'padding_before' and 'padding_after' +// parameters are only used if padding_type == EXPLICIT. +Status GetWindowedOutputSizeFromDimsV2( + InferenceContext* c, DimensionHandle input_size, + DimensionOrConstant filter_size, int64 dilation_rate, int64 stride, + Padding padding_type, int64 padding_before, int64 padding_after, + DimensionHandle* output_size); // Transfers shape of input(0) to output(0). Status UnchangedShape(shape_inference::InferenceContext* c); @@ -222,7 +232,11 @@ Status BiasAddShape(shape_inference::InferenceContext* c); // Shape function for BiasAddGrad-like operations. Status BiasAddGradShape(shape_inference::InferenceContext* c); -// Shape function for Conv2D-like operations. +// Shape function for Conv2D-like operations that support explicit padding. +Status Conv2DShapeWithExplicitPadding(shape_inference::InferenceContext* c); + +// Shape function for Conv2D-like operations that do not support explicit +// padding. Status Conv2DShape(shape_inference::InferenceContext* c); // Shape function for Conv3D-like operations. diff --git a/tensorflow/core/framework/common_shape_fns_test.cc b/tensorflow/core/framework/common_shape_fns_test.cc index 7c395679d3..b94925c04e 100644 --- a/tensorflow/core/framework/common_shape_fns_test.cc +++ b/tensorflow/core/framework/common_shape_fns_test.cc @@ -408,12 +408,14 @@ TEST(CommonShapeFnsTest, BiasAddGradShapeTest) { TEST(CommonShapeFnsTest, Conv2DShapeTest) { ShapeInferenceTestOp op("Conv2D"); auto set_op = [&op](const std::vector& strides, const string& padding, - const string& data_format, const string& filter_format) { + const string& data_format, const string& filter_format, + const std::vector& explicit_paddings = {}) { TF_CHECK_OK(NodeDefBuilder("test", "Conv2D") .Input("input", 0, DT_FLOAT) .Input("filter", 0, DT_FLOAT) .Attr("strides", strides) .Attr("padding", padding) + .Attr("explicit_paddings", explicit_paddings) .Attr("data_format", data_format) .Attr("filter_format", filter_format) .Finalize(&op.node_def)); @@ -536,19 +538,73 @@ TEST(CommonShapeFnsTest, Conv2DShapeTest) { INFER_OK(op, "[1,?,4,1];[?,?,?,?]", "[d0_0,?,2,d1_3]"); INFER_OK(op, "[1,4,?,1];[?,?,?,?]", "[d0_0,2,?,d1_3]"); INFER_OK(op, "[1,4,4,?];[?,?,?,?]", "[d0_0,2,2,d1_3]"); + + // Some tests for "EXPLICIT" padding + + // 4x4 input, 1x1 filter, 1x1 stride, [0, 2, 1, 4] padding + set_op({{1, 1, 1, 1}}, "EXPLICIT", "NHWC", "HWIO", {0, 0, 0, 2, 1, 4, 0, 0}); + INFER_OK(op, "[1,4,4,1];[1,1,1,1]", "[d0_0,6,9,d1_3]"); + + // 3x3 input, 2x2 filter, 1x1 stride, [1, 0, 1, 2] padding + set_op({{1, 1, 1, 1}}, "EXPLICIT", "NHWC", "HWIO", {0, 0, 1, 0, 1, 2, 0, 0}); + INFER_OK(op, "[1,3,3,1];[2,2,1,1]", "[d0_0,3,5,d1_3]"); + + // 4x4 input, 2x2 filter, 2x2 stride, [3, 2, 1, 0] padding + set_op({{1, 2, 2, 1}}, "EXPLICIT", "NHWC", "HWIO", {0, 0, 3, 2, 1, 0, 0, 0}); + INFER_OK(op, "[1,4,4,2];[2,2,2,3]", "[d0_0,4,2,d1_3]"); + + // 2x2 input, 2x1 filter, 1x2 stride, [1, 1, 2, 2] padding + set_op({{1, 1, 2, 1}}, "EXPLICIT", "NHWC", "HWIO", {0, 0, 1, 1, 2, 2, 0, 0}); + INFER_OK(op, "[1,2,2,1];[2,1,1,1]", "[d0_0,3,3,d1_3]"); + + // Unknown dims in the critical fields lead to partial inference. + INFER_OK(op, "[1,4,4,1];[2,1,1,1]", "[d0_0,5,4,d1_3]"); + INFER_OK(op, "[1,?,4,1];[2,1,1,1]", "[d0_0,?,4,d1_3]"); + INFER_OK(op, "[1,4,?,1];[2,1,1,1]", "[d0_0,5,?,d1_3]"); + INFER_OK(op, "[1,4,4,?];[2,1,1,1]", "[d0_0,5,4,d1_3]"); + INFER_OK(op, "[1,4,4,1];[?,1,1,1]", "[d0_0,?,4,d1_3]"); + INFER_OK(op, "[1,4,4,1];[2,?,1,1]", "[d0_0,5,?,d1_3]"); + + // Explicit padding errors + // Negative padding + set_op({{1, 1, 1, 1}}, "EXPLICIT", "NHWC", "HWIO", {0, 0, 0, -1, 0, 0, 0, 0}); + INFER_ERROR("must be nonnegative", op, "[1,2,2,1];[1,1,1,1]"); + + // Too little padding (7 explicit paddings instead of 8) + set_op({{1, 1, 1, 1}}, "EXPLICIT", "NHWC", "HWIO", {0, 0, 0, 0, 0, 0, 0}); + INFER_ERROR("must contain 8 values", op, "[1,2,2,1];[1,1,1,1]"); + + // Too much padding (9 explicit paddings instead of 8) + set_op({{1, 1, 1, 1}}, "EXPLICIT", "NHWC", "HWIO", + {0, 0, 0, 0, 0, 0, 0, 0, 0}); + INFER_ERROR("must contain 8 values", op, "[1,2,2,1];[1,1,1,1]"); + + // Padding in batch dimension + set_op({{1, 1, 1, 1}}, "EXPLICIT", "NHWC", "HWIO", {1, 0, 0, 0, 0, 0, 0, 0}); + INFER_ERROR("batch or depth dimensions", op, "[1,2,2,1];[1,1,1,1]"); + + // Padding in depth dimension + set_op({{1, 1, 1, 1}}, "EXPLICIT", "NHWC", "HWIO", {0, 0, 0, 0, 0, 0, 1, 0}); + INFER_ERROR("batch or depth dimensions", op, "[1,2,2,1];[1,1,1,1]"); + + // Padding explicit_paddings when padding is not EXPLICIT + set_op({{1, 1, 1, 1}}, "VALID", "NHWC", "HWIO", {0, 0, 0, 0, 0, 0, 0, 0}); + INFER_ERROR("must be empty", op, "[1,2,2,1];[1,1,1,1]"); } TEST(CommonShapeFnsTest, Conv2DDilatedShapeTest) { ShapeInferenceTestOp op("Conv2D"); auto set_op = [&op](const std::vector& dilations, const std::vector& strides, const string& padding, - const string& data_format) { + const string& data_format, + const std::vector& explicit_paddings = {}) { TF_CHECK_OK(NodeDefBuilder("test", "Conv2D") .Input("input", 0, DT_FLOAT) .Input("filter", 0, DT_FLOAT) .Attr("dilations", dilations) .Attr("strides", strides) .Attr("padding", padding) + .Attr("explicit_paddings", explicit_paddings) .Attr("data_format", data_format) .Finalize(&op.node_def)); }; @@ -628,6 +684,28 @@ TEST(CommonShapeFnsTest, Conv2DDilatedShapeTest) { // 4x4 input, 2x2 filter, 2x2 dilations, 1x1 stride set_op({{1, 2, 2, 1}}, {{1, 1, 1, 1}}, "SAME", "NHWC"); INFER_OK(op, "[1,4,4,1];[2,2,1,1]", "[d0_0,d0_1,d0_2,d1_3]"); + + // Some tests for "EXPLICIT" padding + + // 4x4 input, 1x1 filter, 2x1 dilations, 1x1 stride, [0, 2, 1, 4] padding + set_op({{1, 2, 1, 1}}, {{1, 1, 1, 1}}, "EXPLICIT", "NHWC", + {0, 0, 0, 2, 1, 4, 0, 0}); + INFER_OK(op, "[1,4,4,1];[1,1,1,1]", "[d0_0,6,9,d1_3]"); + + // 3x3 input, 2x2 filter, 2x2 dilations, 1x1 stride, [1, 0, 1, 2] padding + set_op({{1, 2, 2, 1}}, {{1, 1, 1, 1}}, "EXPLICIT", "NHWC", + {0, 0, 1, 0, 1, 2, 0, 0}); + INFER_OK(op, "[1,3,3,1];[2,2,1,1]", "[d0_0,2,4,d1_3]"); + + // 4x4 input, 2x2 filter, 1x2 dilations, 2x2 stride, [3, 2, 1, 0] padding + set_op({{1, 1, 2, 1}}, {{1, 2, 2, 1}}, "EXPLICIT", "NHWC", + {0, 0, 3, 2, 1, 0, 0, 0}); + INFER_OK(op, "[1,4,4,1];[2,2,1,1]", "[d0_0,4,2,d1_3]"); + + // 4x4 input, 2x2 filter, 2x2 dilations, 1x1 stride, [1, 1, 2, 2] padding + set_op({{1, 2, 2, 1}}, {{1, 1, 1, 1}}, "EXPLICIT", "NHWC", + {0, 0, 1, 1, 2, 2, 0, 0}); + INFER_OK(op, "[1,4,4,1];[2,2,1,1]", "[d0_0,4,6,d1_3]"); } TEST(CommonShapeFnsTest, Conv3DShapeTest) { diff --git a/tensorflow/core/framework/ops_util.cc b/tensorflow/core/framework/ops_util.cc index e8cf014ca0..4e603b9598 100644 --- a/tensorflow/core/framework/ops_util.cc +++ b/tensorflow/core/framework/ops_util.cc @@ -30,6 +30,9 @@ Eigen::PaddingType BrainPadding2EigenPadding(Padding padding) { return Eigen::PADDING_VALID; case Padding::SAME: return Eigen::PADDING_SAME; + case Padding::EXPLICIT: + LOG(FATAL) << "Eigen does not have explicit padding enum " // Crash OK + "value"; } return Eigen::PADDING_SAME; // Prevent compiler warning about missing return } diff --git a/tensorflow/core/grappler/costs/graph_properties_testdata/large_function_graph.pbtxt b/tensorflow/core/grappler/costs/graph_properties_testdata/large_function_graph.pbtxt index 415c347a1d..d4e23e901a 100644 --- a/tensorflow/core/grappler/costs/graph_properties_testdata/large_function_graph.pbtxt +++ b/tensorflow/core/grappler/costs/graph_properties_testdata/large_function_graph.pbtxt @@ -511,6 +511,13 @@ library { s: "VALID" } } + attr { + key: "explicit_paddings" + value { + list { + } + } + } attr { key: "strides" value { diff --git a/tensorflow/core/grappler/optimizers/layout_optimizer.cc b/tensorflow/core/grappler/optimizers/layout_optimizer.cc index 8f25a1c8c1..e9b706a583 100644 --- a/tensorflow/core/grappler/optimizers/layout_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/layout_optimizer.cc @@ -503,6 +503,7 @@ class NodeProcessor : public GraphProcessor { UpdateAttrKSize(); UpdateAttrStrides(); UpdateAttrDilations(); + UpdateAttrExplicitPaddings(); UpdateAttrShape(); TF_RETURN_IF_ERROR(AddLayoutTransposeToInputs()); TF_RETURN_IF_ERROR(AddLayoutTransposeToOutputs()); @@ -753,6 +754,28 @@ class NodeProcessor : public GraphProcessor { } } + void UpdateAttrExplicitPaddings() { + if (node_->attr().find("explicit_paddings") != node_->attr().end()) { + auto list = node_->mutable_attr()->at("explicit_paddings").mutable_list(); + int size = list->i_size(); + if (size == 8) { + int64 height_before = list->i(2); + int64 height_after = list->i(3); + int64 width_before = list->i(4); + int64 width_after = list->i(5); + list->set_i(2, 0); + list->set_i(3, 0); + list->set_i(4, height_before); + list->set_i(5, height_after); + list->set_i(6, width_before); + list->set_i(7, width_after); + } else if (size != 0) { + LOG(ERROR) << "Cannot handle explicit_paddings attribute of size " + << size; + } + } + } + void UpdateAttrDataFormat() { if (node_->attr().find("data_format") != node_->attr().end()) { if (node_->attr().at("data_format").s().compare("NHWC") == 0) { diff --git a/tensorflow/core/grappler/optimizers/layout_optimizer_test.cc b/tensorflow/core/grappler/optimizers/layout_optimizer_test.cc index 20e47c1b26..eb2a8e87dd 100644 --- a/tensorflow/core/grappler/optimizers/layout_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/layout_optimizer_test.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/layout_optimizer.h" #include "tensorflow/cc/ops/standard_ops.h" +#include "tensorflow/core/framework/common_shape_fns.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/tensor_testutil.h" #include "tensorflow/core/grappler/clusters/single_machine.h" @@ -80,8 +81,13 @@ class LayoutOptimizerTest : public GrapplerTest { Output filter = ops::Const(s->WithOpName("Filter"), Input::Initializer(filter_data)); + ops::Conv2D::Attrs attrs; + if (padding == "EXPLICIT") { + attrs = attrs.ExplicitPaddings({0, 0, 1, 2, 3, 4, 0, 0}); + } + Output conv = ops::Conv2D(s->WithOpName("Conv2D").WithDevice(device), input, - filter, {1, stride, stride, 1}, padding); + filter, {1, stride, stride, 1}, padding, attrs); return conv; } @@ -100,6 +106,28 @@ class LayoutOptimizerTest : public GrapplerTest { int input_depth = 3; int filter_count = 2; int stride = 1; + int dilation = dilated ? 2 : 1; + int64 padding_top = 1; + int64 padding_bottom = 2; + int64 padding_left = 3; + int64 padding_right = 4; + int64 output_height; + int64 output_width; + Padding padding_enum; + if (padding == "SAME") { + padding_enum = SAME; + } else if (padding == "VALID") { + padding_enum = VALID; + } else { + CHECK_EQ(padding, "EXPLICIT"); + padding_enum = EXPLICIT; + } + TF_CHECK_OK(GetWindowedOutputSizeVerboseV2( + input_height, filter_size, dilation, stride, padding_enum, + &output_height, &padding_top, &padding_bottom)); + TF_CHECK_OK(GetWindowedOutputSizeVerboseV2( + input_width, filter_size, dilation, stride, padding_enum, &output_width, + &padding_left, &padding_right)); TensorShape input_sizes_shape({4}); Tensor input_data(DT_INT32, input_sizes_shape); test::FillValues(&input_data, @@ -112,8 +140,6 @@ class LayoutOptimizerTest : public GrapplerTest { Output filter = ops::Variable(s->WithOpName("Filter"), filter_shape, DT_FLOAT); - int output_height = input_height; - int output_width = input_width; TensorShape output_shape( {batch_size, output_height, output_width, filter_count}); Tensor output_data(DT_FLOAT, output_shape); @@ -124,10 +150,21 @@ class LayoutOptimizerTest : public GrapplerTest { Output conv_backprop_input; Output input_sizes_i = ops::Identity(s->WithOpName("InputSizesIdentity"), input_sizes); - ops::Conv2DBackpropInput::Attrs attrs; - if (dilated) { - attrs = attrs.Dilations({1, 2, 2, 1}); + std::vector dilations{1, dilation, dilation, 1}; + std::vector explicit_paddings; + if (padding == "EXPLICIT") { + explicit_paddings = {0, + 0, + static_cast(padding_top), + static_cast(padding_bottom), + static_cast(padding_left), + static_cast(padding_right), + 0, + 0}; } + auto attrs = + ops::Conv2DBackpropInput::Attrs().Dilations(dilations).ExplicitPaddings( + explicit_paddings); if (const_input_size) { conv_backprop_input = ops::Conv2DBackpropInput( s->WithOpName("Conv2DBackpropInput"), input_sizes, filter, output, @@ -186,7 +223,7 @@ class LayoutOptimizerTest : public GrapplerTest { TEST_F(LayoutOptimizerTest, Conv2DBackpropInput) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); - auto conv = SimpleConv2DBackpropInput(&s, 7, 2, "SAME"); + auto conv = SimpleConv2DBackpropInput(&s, 7, 2, "EXPLICIT"); Output fetch = ops::Identity(s.WithOpName("Fetch"), {conv}); GrapplerItem item; TF_CHECK_OK(s.ToGraphDef(&item.graph)); @@ -306,6 +343,19 @@ TEST_F(LayoutOptimizerTest, NotEqualSizeWithValidPadding) { EXPECT_TRUE(node_map.GetNode("Conv2D-0-TransposeNHWCToNCHW-LayoutOptimizer")); } +TEST_F(LayoutOptimizerTest, ExplicitPadding) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + auto conv = SimpleConv2D(&s, 4, 2, "EXPLICIT"); + Output fetch = ops::Identity(s.WithOpName("Fetch"), {conv}); + GrapplerItem item; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + LayoutOptimizer optimizer; + GraphDef output; + Status status = optimizer.Optimize(virtual_cluster_.get(), item, &output); + NodeMap node_map(&output); + EXPECT_TRUE(node_map.GetNode("Conv2D-0-TransposeNHWCToNCHW-LayoutOptimizer")); +} + TEST_F(LayoutOptimizerTest, Pad) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); auto conv = SimpleConv2D(&s, 4, 2, "VALID"); diff --git a/tensorflow/core/kernels/conv_grad_filter_ops.cc b/tensorflow/core/kernels/conv_grad_filter_ops.cc index 4e3de33e83..0df05ceb02 100644 --- a/tensorflow/core/kernels/conv_grad_filter_ops.cc +++ b/tensorflow/core/kernels/conv_grad_filter_ops.cc @@ -102,6 +102,7 @@ struct LaunchConv2DBackpropFilterOp { const Tensor& out_backprop, const Tensor& input, int row_dilation, int col_dilation, int row_stride, int col_stride, const Padding& padding, + const std::vector& explicit_paddings, Tensor* filter_backprop, TensorFormat data_format) { const CPUDevice& d = ctx->eigen_device(); functor::SpatialConvolutionBackwardFilter()( @@ -204,6 +205,15 @@ class Conv2DFastBackpropFilterOp : public OpKernel { errors::InvalidArgument( "Row and column strides should be larger than 0.")); OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_)); + OP_REQUIRES( + context, padding_ != Padding::EXPLICIT, + errors::Unimplemented("Current CPU implementation does not support " + "EXPLICIT padding yet.")); + std::vector explicit_paddings; + OP_REQUIRES_OK(context, + context->GetAttr("explicit_paddings", &explicit_paddings)); + OP_REQUIRES_OK(context, CheckValidPadding(padding_, explicit_paddings, + /*num_dims=*/4, data_format_)); OP_REQUIRES_OK(context, context->GetAttr("dilations", &dilations_)); OP_REQUIRES(context, dilations_.size() == 4, errors::InvalidArgument("Sliding window dilations field must " @@ -282,7 +292,8 @@ class Conv2DFastBackpropFilterOp : public OpKernel { LaunchConv2DBackpropFilterOp()( context, false, false, out_backprop, input, /*row_dilation=*/1, /*col_dilation=*/1, dims.spatial_dims[0].stride, - dims.spatial_dims[1].stride, padding_, filter_backprop, data_format_); + dims.spatial_dims[1].stride, padding_, /*explicit_paddings=*/{}, + filter_backprop, data_format_); } private: @@ -319,6 +330,15 @@ class Conv2DCustomBackpropFilterOp : public OpKernel { errors::InvalidArgument( "Row and column strides should be larger than 0.")); OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_)); + OP_REQUIRES( + context, padding_ != Padding::EXPLICIT, + errors::Unimplemented("Current CPU implementation does not support " + "EXPLICIT padding yet.")); + std::vector explicit_paddings; + OP_REQUIRES_OK(context, + context->GetAttr("explicit_paddings", &explicit_paddings)); + OP_REQUIRES_OK(context, CheckValidPadding(padding_, explicit_paddings, + /*num_dims=*/4, data_format_)); OP_REQUIRES_OK(context, context->GetAttr("dilations", &dilations_)); OP_REQUIRES(context, dilations_.size() == 4, errors::InvalidArgument("Sliding window dilations field must " @@ -587,6 +607,10 @@ class Conv2DSlowBackpropFilterOp : public OpKernel { use_cudnn_ &= CanUseCudnn(); cudnn_use_autotune_ = CudnnUseAutotune(); OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_)); + OP_REQUIRES_OK(context, + context->GetAttr("explicit_paddings", &explicit_paddings_)); + OP_REQUIRES_OK(context, CheckValidPadding(padding_, explicit_paddings_, + /*num_dims=*/4, data_format_)); } void Compute(OpKernelContext* context) override { @@ -626,13 +650,14 @@ class Conv2DSlowBackpropFilterOp : public OpKernel { launcher_(context, use_cudnn_, cudnn_use_autotune_, out_backprop, input, dilation_rows, dilation_cols, stride_rows, stride_cols, padding_, - filter_backprop, data_format_); + explicit_paddings_, filter_backprop, data_format_); } private: std::vector dilations_; std::vector strides_; Padding padding_; + std::vector explicit_paddings_; bool use_cudnn_; TensorFormat data_format_; LaunchConv2DBackpropFilterOp launcher_; @@ -646,7 +671,8 @@ void LaunchConv2DBackpropFilterOp::operator()( OpKernelContext* ctx, bool use_cudnn, bool cudnn_use_autotune, const Tensor& out_backprop, const Tensor& input, int row_dilation, int col_dilation, int row_stride, int col_stride, const Padding& padding, - Tensor* filter_backprop, TensorFormat data_format) { + const std::vector& explicit_paddings, Tensor* filter_backprop, + TensorFormat data_format) { using se::dnn::AlgorithmConfig; using se::dnn::AlgorithmDesc; using se::dnn::ProfileResult; @@ -661,35 +687,33 @@ void LaunchConv2DBackpropFilterOp::operator()( TensorShape filter_shape = filter_backprop->shape(); ConvBackpropDimensions dims; - OP_REQUIRES_OK(ctx, ConvBackpropComputeDimensionsV2( - "Conv2DSlowBackpropFilter", /*num_spatial_dims=*/2, - input.shape(), filter_shape, out_backprop.shape(), - dilations, strides, padding, data_format, &dims)); - - // TODO(yangzihao): The padding computations should be done in - // GetWindowedOutputSize() functions. - const int padding_rows = - (padding == VALID) - ? 0 - : std::max(0, (dims.spatial_dims[0].output_size - 1) * - dims.spatial_dims[0].stride + - (dims.spatial_dims[0].filter_size - 1) * - dims.spatial_dims[0].dilation + - 1 - dims.spatial_dims[0].input_size); - const int padding_cols = - (padding == VALID) - ? 0 - : std::max(0, (dims.spatial_dims[1].output_size - 1) * - dims.spatial_dims[1].stride + - (dims.spatial_dims[1].filter_size - 1) * - dims.spatial_dims[1].dilation + - 1 - dims.spatial_dims[1].input_size); - - // TODO(zhengxq): cuDNN only supports equal padding on both sides, so only - // calling it when that is true. Remove this check when (if?) cuDNN starts - // supporting different padding. - bool rows_odd = (padding_rows % 2 != 0); - bool cols_odd = (padding_cols % 2 != 0); + OP_REQUIRES_OK( + ctx, ConvBackpropComputeDimensionsV2( + "Conv2DSlowBackpropFilter", /*num_spatial_dims=*/2, + input.shape(), filter_shape, out_backprop.shape(), dilations, + strides, padding, explicit_paddings, data_format, &dims)); + + int64 padding_top = -1, padding_bottom = -1; + int64 padding_left = -1, padding_right = -1; + if (padding == EXPLICIT) { + GetExplicitPaddingForDim(explicit_paddings, data_format, 'H', &padding_top, + &padding_bottom); + GetExplicitPaddingForDim(explicit_paddings, data_format, 'W', &padding_left, + &padding_right); + } + int64 expected_out_rows, expected_out_cols; + // The function is guaranteed to succeed because we checked the output and + // padding was valid earlier. + TF_CHECK_OK(GetWindowedOutputSizeVerboseV2( + dims.spatial_dims[0].input_size, dims.spatial_dims[0].filter_size, + row_dilation, row_stride, padding, &expected_out_rows, &padding_top, + &padding_bottom)); + DCHECK_EQ(dims.spatial_dims[0].output_size, expected_out_rows); + TF_CHECK_OK(GetWindowedOutputSizeVerboseV2( + dims.spatial_dims[1].input_size, dims.spatial_dims[1].filter_size, + col_dilation, col_stride, padding, &expected_out_cols, &padding_left, + &padding_right)); + DCHECK_EQ(dims.spatial_dims[1].output_size, expected_out_cols); auto* stream = ctx->op_device_context()->stream(); OP_REQUIRES(ctx, stream, errors::Internal("No GPU stream available.")); @@ -711,7 +735,7 @@ void LaunchConv2DBackpropFilterOp::operator()( dims.spatial_dims[0].filter_size == 1 && dims.spatial_dims[1].filter_size == 1 && !is_grouped_convolution && dims.spatial_dims[0].stride == 1 && dims.spatial_dims[1].stride == 1 && - data_format == FORMAT_NHWC) { + data_format == FORMAT_NHWC && (padding == VALID || padding == SAME)) { const uint64 m = dims.in_depth; const uint64 k = dims.batch_size * dims.spatial_dims[0].input_size * dims.spatial_dims[1].input_size; @@ -779,31 +803,43 @@ void LaunchConv2DBackpropFilterOp::operator()( return; } + const int64 common_padding_rows = std::min(padding_top, padding_bottom); + const int64 common_padding_cols = std::min(padding_left, padding_right); Tensor compatible_input; - if (rows_odd || cols_odd) { - // If a padding dimension is odd, we have one more element on the right - // side or the bottom side. This is unsupported in cudnn. Therefore, - // we pad that extra element and make it compatible. + if (padding_top != padding_bottom || padding_left != padding_right) { + // Pad the input in the same way we did during the forward pass, so that + // cuDNN receives the same input during the backward pass function as it did + // during the forward pass function. + const int64 padding_rows_diff = std::abs(padding_bottom - padding_top); + const int64 padding_cols_diff = std::abs(padding_right - padding_left); + const int64 new_in_rows = + dims.spatial_dims[0].input_size + padding_rows_diff; + const int64 new_in_cols = + dims.spatial_dims[1].input_size + padding_cols_diff; + const int64 input_pad_top = padding_top - common_padding_rows; + const int64 input_pad_bottom = padding_bottom - common_padding_rows; + const int64 input_pad_left = padding_left - common_padding_cols; + const int64 input_pad_right = padding_right - common_padding_cols; OP_REQUIRES_OK( ctx, ctx->allocate_temp( DataTypeToEnum::value, - ShapeFromFormat(data_format, dims.batch_size, - dims.spatial_dims[0].input_size + rows_odd, - dims.spatial_dims[1].input_size + cols_odd, - dims.in_depth), + ShapeFromFormat(data_format, dims.batch_size, new_in_rows, + new_in_cols, dims.in_depth), &compatible_input)); functor::PadInput()( ctx->template eigen_device(), To32Bit(input.tensor()), - {{0, 0}}, {{rows_odd, cols_odd}}, + {{static_cast(input_pad_top), static_cast(input_pad_left)}}, + {{static_cast(input_pad_bottom), + static_cast(input_pad_right)}}, To32Bit(compatible_input.tensor()), data_format); } else { compatible_input = input; } - CHECK(padding_rows >= 0 && padding_cols >= 0) - << "Negative row or col paddings: (" << padding_rows << ", " - << padding_cols << ")"; + CHECK(common_padding_rows >= 0 && common_padding_cols >= 0) // Crash OK + << "Negative row or col paddings: (" << common_padding_rows << ", " + << common_padding_cols << ")"; se::dnn::BatchDescriptor input_desc; input_desc.set_count(dims.batch_size) .set_height(GetTensorDim(compatible_input, data_format, 'H')) @@ -826,8 +862,8 @@ void LaunchConv2DBackpropFilterOp::operator()( .set_horizontal_dilation_rate(dims.spatial_dims[1].dilation) .set_vertical_filter_stride(dims.spatial_dims[0].stride) .set_horizontal_filter_stride(dims.spatial_dims[1].stride) - .set_zero_padding_height(padding_rows / 2) - .set_zero_padding_width(padding_cols / 2) + .set_zero_padding_height(common_padding_rows) + .set_zero_padding_width(common_padding_cols) .set_group_count(dims.in_depth / filter_shape.dim_size(2)); // NOTE(zhengxq): @@ -922,8 +958,8 @@ void LaunchConv2DBackpropFilterOp::operator()( dims.spatial_dims[1].dilation}}, // dilation_cols {{dims.spatial_dims[0].stride, // stride_rows dims.spatial_dims[1].stride}}, // stride_cols - {{padding_rows, // padding_rows - padding_cols}}, // padding_cols + {{common_padding_rows, // padding_rows + common_padding_cols}}, // padding_cols dtype, // tensor datatype device_id, // device_id }; diff --git a/tensorflow/core/kernels/conv_grad_input_ops.cc b/tensorflow/core/kernels/conv_grad_input_ops.cc index 9f983ed816..74b97b9864 100644 --- a/tensorflow/core/kernels/conv_grad_input_ops.cc +++ b/tensorflow/core/kernels/conv_grad_input_ops.cc @@ -106,8 +106,9 @@ struct LaunchConv2DBackpropInputOp { void operator()(OpKernelContext* ctx, bool use_cudnn, bool cudnn_use_autotune, const Tensor& out_backprop, const Tensor& filter, int row_dilation, int col_dilation, int row_stride, - int col_stride, const Padding& padding, Tensor* in_backprop, - TensorFormat data_format) { + int col_stride, const Padding& padding, + const std::vector& explicit_paddings, + Tensor* in_backprop, TensorFormat data_format) { const CPUDevice& d = ctx->eigen_device(); functor::SpatialConvolutionBackwardInput()( d, in_backprop->tensor(), filter.tensor(), @@ -220,6 +221,15 @@ class Conv2DFastBackpropInputOp : public OpKernel { errors::InvalidArgument( "Current Eigen and libxsmm implementations do not " "yet support dilation rates larger than 1.")); + OP_REQUIRES( + context, padding_ != Padding::EXPLICIT, + errors::Unimplemented("Current CPU implementation does not support " + "EXPLICIT padding yet.")); + std::vector explicit_paddings; + OP_REQUIRES_OK(context, + context->GetAttr("explicit_paddings", &explicit_paddings)); + OP_REQUIRES_OK(context, CheckValidPadding(padding_, explicit_paddings, + /*num_dims=*/4, data_format_)); } void Compute(OpKernelContext* context) override { @@ -286,7 +296,8 @@ class Conv2DFastBackpropInputOp : public OpKernel { LaunchConv2DBackpropInputOp()( context, false, false, out_backprop, filter, /*row_dilation=*/1, /*col_dilation=*/1, dims.spatial_dims[0].stride, - dims.spatial_dims[1].stride, padding_, in_backprop, data_format_); + dims.spatial_dims[1].stride, padding_, /*explicit_paddings=*/{}, + in_backprop, data_format_); } private: @@ -336,6 +347,15 @@ class Conv2DCustomBackpropInputOp : public OpKernel { errors::InvalidArgument( "Current libxsmm and customized CPU implementations do " "not yet support dilation rates larger than 1.")); + OP_REQUIRES( + context, padding_ != Padding::EXPLICIT, + errors::Unimplemented("Current CPU implementation does not support " + "EXPLICIT padding yet.")); + std::vector explicit_paddings; + OP_REQUIRES_OK(context, + context->GetAttr("explicit_paddings", &explicit_paddings)); + OP_REQUIRES_OK(context, CheckValidPadding(padding_, explicit_paddings, + /*num_dims=*/4, data_format_)); } void Compute(OpKernelContext* context) override { @@ -661,6 +681,16 @@ class Conv2DSlowBackpropInputOp : public OpKernel { use_cudnn_ &= CanUseCudnn(); cudnn_use_autotune_ = CudnnUseAutotune(); OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_)); + if (!std::is_same::value) { + OP_REQUIRES( + context, padding_ != Padding::EXPLICIT, + errors::Unimplemented("Current CPU implementation does not support " + "EXPLICIT padding yet.")); + } + OP_REQUIRES_OK(context, + context->GetAttr("explicit_paddings", &explicit_paddings_)); + OP_REQUIRES_OK(context, CheckValidPadding(padding_, explicit_paddings_, + /*num_dims=*/4, data_format_)); } void Compute(OpKernelContext* context) override { @@ -694,13 +724,14 @@ class Conv2DSlowBackpropInputOp : public OpKernel { launcher_(context, use_cudnn_, cudnn_use_autotune_, out_backprop, filter, dilation_rows, dilation_cols, stride_rows, stride_cols, padding_, - in_backprop, data_format_); + explicit_paddings_, in_backprop, data_format_); } private: std::vector dilations_; std::vector strides_; Padding padding_; + std::vector explicit_paddings_; bool use_cudnn_; TensorFormat data_format_; LaunchConv2DBackpropInputOp launcher_; @@ -714,7 +745,8 @@ void LaunchConv2DBackpropInputOp::operator()( OpKernelContext* ctx, bool use_cudnn, bool cudnn_use_autotune, const Tensor& out_backprop, const Tensor& filter, int row_dilation, int col_dilation, int row_stride, int col_stride, const Padding& padding, - Tensor* in_backprop, TensorFormat data_format) { + const std::vector& explicit_paddings, Tensor* in_backprop, + TensorFormat data_format) { using se::dnn::AlgorithmConfig; using se::dnn::AlgorithmDesc; using se::dnn::ProfileResult; @@ -731,35 +763,33 @@ void LaunchConv2DBackpropInputOp::operator()( const TensorShape& filter_shape = filter.shape(); ConvBackpropDimensions dims; - OP_REQUIRES_OK(ctx, ConvBackpropComputeDimensionsV2( - "Conv2DSlowBackpropInput", /*num_spatial_dims=*/2, - input_shape, filter_shape, out_backprop.shape(), - dilations, strides, padding, data_format, &dims)); - - // TODO(yangzihao): The padding computations should be done in - // GetWindowedOutputSize() functions. - const int padding_rows = - (padding == VALID) - ? 0 - : std::max(0, (dims.spatial_dims[0].output_size - 1) * - dims.spatial_dims[0].stride + - (dims.spatial_dims[0].filter_size - 1) * - dims.spatial_dims[0].dilation + - 1 - dims.spatial_dims[0].input_size); - const int padding_cols = - (padding == VALID) - ? 0 - : std::max(0, (dims.spatial_dims[1].output_size - 1) * - dims.spatial_dims[1].stride + - (dims.spatial_dims[1].filter_size - 1) * - dims.spatial_dims[1].dilation + - 1 - dims.spatial_dims[1].input_size); - - // TODO(keveman): cuDNN only supports equal padding on both sides, so only - // calling it when that is true. Remove this check when (if?) cuDNN starts - // supporting different padding. - bool rows_odd = (padding_rows % 2 != 0); - bool cols_odd = (padding_cols % 2 != 0); + OP_REQUIRES_OK( + ctx, ConvBackpropComputeDimensionsV2( + "Conv2DSlowBackpropInput", /*num_spatial_dims=*/2, input_shape, + filter_shape, out_backprop.shape(), dilations, strides, padding, + explicit_paddings, data_format, &dims)); + + int64 padding_top = -1, padding_bottom = -1; + int64 padding_left = -1, padding_right = -1; + if (padding == EXPLICIT) { + GetExplicitPaddingForDim(explicit_paddings, data_format, 'H', &padding_top, + &padding_bottom); + GetExplicitPaddingForDim(explicit_paddings, data_format, 'W', &padding_left, + &padding_right); + } + int64 expected_out_rows, expected_out_cols; + // The function is guaranteed to succeed because we checked the output and + // padding was valid earlier. + TF_CHECK_OK(GetWindowedOutputSizeVerboseV2( + dims.spatial_dims[0].input_size, dims.spatial_dims[0].filter_size, + row_dilation, row_stride, padding, &expected_out_rows, &padding_top, + &padding_bottom)); + DCHECK_EQ(dims.spatial_dims[0].output_size, expected_out_rows); + TF_CHECK_OK(GetWindowedOutputSizeVerboseV2( + dims.spatial_dims[1].input_size, dims.spatial_dims[1].filter_size, + col_dilation, col_stride, padding, &expected_out_cols, &padding_left, + &padding_right)); + DCHECK_EQ(dims.spatial_dims[1].output_size, expected_out_cols); auto* stream = ctx->op_device_context()->stream(); OP_REQUIRES(ctx, stream, errors::Internal("No GPU stream available.")); @@ -779,7 +809,7 @@ void LaunchConv2DBackpropInputOp::operator()( if (dims.spatial_dims[0].filter_size == 1 && dims.spatial_dims[1].filter_size == 1 && !is_grouped_convolution && dims.spatial_dims[0].stride == 1 && dims.spatial_dims[1].stride == 1 && - data_format == FORMAT_NHWC) { + data_format == FORMAT_NHWC && (padding == VALID || padding == SAME)) { // 1x1 filter, so call cublas directly. const uint64 m = dims.batch_size * dims.spatial_dims[0].input_size * dims.spatial_dims[1].input_size; @@ -841,22 +871,28 @@ void LaunchConv2DBackpropInputOp::operator()( return; } + const int64 common_padding_rows = std::min(padding_top, padding_bottom); + const int64 common_padding_cols = std::min(padding_left, padding_right); TensorShape compatible_input_shape; - if (rows_odd || cols_odd) { - // If a padding dimension is odd, we have one more element on the right - // side or the bottom side. This is unsupported in cudnn. Therefore, - // we pad that extra element and make it compatible. + if (padding_top != padding_bottom || padding_left != padding_right) { + // Pad the input in the same way we did during the forward pass, so that + // cuDNN receives the same input during the backward pass function as it did + // during the forward pass function. + const int64 padding_rows_diff = std::abs(padding_bottom - padding_top); + const int64 padding_cols_diff = std::abs(padding_right - padding_left); + const int64 new_in_rows = + dims.spatial_dims[0].input_size + padding_rows_diff; + const int64 new_in_cols = + dims.spatial_dims[1].input_size + padding_cols_diff; compatible_input_shape = ShapeFromFormat( - data_format, dims.batch_size, - dims.spatial_dims[0].input_size + rows_odd, - dims.spatial_dims[1].input_size + cols_odd, dims.in_depth); + data_format, dims.batch_size, new_in_rows, new_in_cols, dims.in_depth); } else { compatible_input_shape = input_shape; } - CHECK(padding_rows >= 0 && padding_cols >= 0) - << "Negative row or col paddings: (" << padding_rows << ", " - << padding_cols << ")"; + CHECK(common_padding_rows >= 0 && common_padding_cols >= 0) // Crash OK + << "Negative row or col paddings: (" << common_padding_rows << ", " + << common_padding_cols << ")"; se::dnn::BatchDescriptor input_desc; input_desc.set_count(dims.batch_size) .set_height(GetTensorDim(compatible_input_shape, data_format, 'H')) @@ -879,8 +915,8 @@ void LaunchConv2DBackpropInputOp::operator()( .set_horizontal_dilation_rate(dims.spatial_dims[1].dilation) .set_vertical_filter_stride(dims.spatial_dims[0].stride) .set_horizontal_filter_stride(dims.spatial_dims[1].stride) - .set_zero_padding_height(padding_rows / 2) - .set_zero_padding_width(padding_cols / 2) + .set_zero_padding_height(common_padding_rows) + .set_zero_padding_width(common_padding_cols) .set_group_count(dims.in_depth / filter_shape.dim_size(2)); // NOTE(keveman): @@ -971,8 +1007,8 @@ void LaunchConv2DBackpropInputOp::operator()( dims.spatial_dims[1].dilation}}, // dilation_cols {{dims.spatial_dims[0].stride, // stride_rows dims.spatial_dims[1].stride}}, // stride_cols - {{padding_rows, // padding_rows - padding_cols}}, // padding_cols + {{common_padding_rows, // padding_rows + common_padding_cols}}, // padding_cols dtype, // tensor data type device_id, // device_id }; @@ -1041,7 +1077,7 @@ void LaunchConv2DBackpropInputOp::operator()( return; } - if (rows_odd || cols_odd) { + if (padding_top != padding_bottom || padding_left != padding_right) { Tensor in_backprop_remove_padding; OP_REQUIRES_OK( ctx, ctx->allocate_temp( @@ -1053,12 +1089,18 @@ void LaunchConv2DBackpropInputOp::operator()( GetTensorDim(input_shape, data_format, 'C')), &in_backprop_remove_padding)); - // Remove the padding for odd rows or cols. + // Remove the padding that was added to the input shape above. + const int64 input_pad_top = padding_top - common_padding_rows; + const int64 input_pad_bottom = padding_bottom - common_padding_rows; + const int64 input_pad_left = padding_left - common_padding_cols; + const int64 input_pad_right = padding_right - common_padding_cols; functor::PadInput()( ctx->template eigen_device(), To32Bit(const_cast(pre_transformed_in_backprop) .tensor()), - {{0, 0}}, {{-rows_odd, -cols_odd}}, + {{static_cast(-input_pad_top), static_cast(-input_pad_left)}}, + {{static_cast(-input_pad_bottom), + static_cast(-input_pad_right)}}, To32Bit(in_backprop_remove_padding.tensor()), FORMAT_NCHW); pre_transformed_in_backprop = in_backprop_remove_padding; diff --git a/tensorflow/core/kernels/conv_grad_ops.cc b/tensorflow/core/kernels/conv_grad_ops.cc index 507720c998..0fd7550830 100644 --- a/tensorflow/core/kernels/conv_grad_ops.cc +++ b/tensorflow/core/kernels/conv_grad_ops.cc @@ -52,24 +52,23 @@ int ConvBackpropDimensions::SpatialPadding(const Padding& padding, 1 - input_size(dim))); } -// The V2 version computes windowed output size with arbitrary dilation_rate, -// while the original version only handles the cases where dilation_rates equal -// to 1. -Status ConvBackpropExtractAndVerifyDimensionV2( +namespace { + +Status ConvBackpropExtractAndVerifyDimension( StringPiece label, const TensorShape& input_shape, const TensorShape& filter_shape, const TensorShape& output_shape, const gtl::ArraySlice& dilations, const std::vector& strides, - Padding padding, int spatial_dim, int filter_spatial_dim, - ConvBackpropSpatialDimension* dim) { + Padding padding, int64 padding_before, int64 padding_after, int spatial_dim, + int filter_spatial_dim, ConvBackpropSpatialDimension* dim) { dim->input_size = input_shape.dim_size(spatial_dim); dim->filter_size = filter_shape.dim_size(filter_spatial_dim); dim->output_size = output_shape.dim_size(spatial_dim); dim->stride = strides[spatial_dim]; dim->dilation = dilations[spatial_dim]; - int64 out_size = 0, pad_size = 0; - TF_RETURN_IF_ERROR(GetWindowedOutputSizeV2(dim->input_size, dim->filter_size, - dim->dilation, dim->stride, - padding, &out_size, &pad_size)); + int64 out_size = 0; + TF_RETURN_IF_ERROR(GetWindowedOutputSizeVerboseV2( + dim->input_size, dim->filter_size, dim->dilation, dim->stride, padding, + &out_size, &padding_before, &padding_after)); if (dim->output_size != out_size) { return errors::InvalidArgument( label, ": Size of out_backprop doesn't match computed: ", "actual = ", @@ -79,10 +78,13 @@ Status ConvBackpropExtractAndVerifyDimensionV2( " stride: ", dim->stride, " dilation: ", dim->dilation); } + // TODO(reedwm): Correctly handle explicit padding here. The rest of the + // fields set on 'dim' are only used in XLA. TensorFlow ops do not yet support + // explicit padding for XLA. int64 effective_filter_size = (dim->filter_size - 1) * dim->dilation + 1; dim->expanded_output_size = (dim->output_size - 1) * dim->stride + 1; const auto padded_out_size = dim->input_size + effective_filter_size - 1; - dim->pad_before = effective_filter_size - 1 - pad_size; + dim->pad_before = effective_filter_size - 1 - padding_before; dim->pad_after = padded_out_size - dim->expanded_output_size - dim->pad_before; VLOG(2) << label << ": expanded_out = " << dim->expanded_output_size @@ -94,22 +96,14 @@ Status ConvBackpropExtractAndVerifyDimensionV2( return Status::OK(); } -Status ConvBackpropExtractAndVerifyDimension( - StringPiece label, const TensorShape& input_shape, - const TensorShape& filter_shape, const TensorShape& output_shape, - const std::vector& strides, Padding padding, int spatial_dim, - int filter_spatial_dim, ConvBackpropSpatialDimension* dim) { - static constexpr std::array one_dilations = {{1, 1, 1, 1, 1}}; - return ConvBackpropExtractAndVerifyDimensionV2( - label, input_shape, filter_shape, output_shape, one_dilations, strides, - padding, spatial_dim, filter_spatial_dim, dim); -} +} // namespace Status ConvBackpropComputeDimensionsV2( StringPiece label, int num_spatial_dims, const TensorShape& input_shape, const TensorShape& filter_shape, const TensorShape& out_backprop_shape, const gtl::ArraySlice& dilations, const std::vector& strides, - Padding padding, TensorFormat data_format, ConvBackpropDimensions* dims) { + Padding padding, const std::vector& explicit_paddings, + TensorFormat data_format, ConvBackpropDimensions* dims) { // The + 2 in the following line is for the batch and feature dimensions. const int num_dims = num_spatial_dims + 2; if (input_shape.dims() != num_dims) { @@ -152,9 +146,15 @@ Status ConvBackpropComputeDimensionsV2( dims->spatial_dims.resize(num_spatial_dims); for (int i = 0; i < num_spatial_dims; ++i) { int image_dim = GetTensorSpatialDimIndex(num_dims, data_format, i); - TF_RETURN_IF_ERROR(ConvBackpropExtractAndVerifyDimensionV2( + int64 padding_before = -1, padding_after = -1; + if (padding == EXPLICIT) { + padding_before = explicit_paddings[2 * image_dim]; + padding_after = explicit_paddings[2 * image_dim + 1]; + } + TF_RETURN_IF_ERROR(ConvBackpropExtractAndVerifyDimension( label, input_shape, filter_shape, out_backprop_shape, dilations, - strides, padding, image_dim, i, &dims->spatial_dims[i])); + strides, padding, padding_before, padding_after, image_dim, i, + &dims->spatial_dims[i])); } return Status::OK(); } @@ -169,7 +169,8 @@ Status ConvBackpropComputeDimensions(StringPiece label, int num_spatial_dims, static constexpr std::array one_dilations = {{1, 1, 1, 1, 1}}; return ConvBackpropComputeDimensionsV2( label, num_spatial_dims, input_shape, filter_shape, out_backprop_shape, - one_dilations, strides, padding, data_format, dims); + one_dilations, strides, padding, /*explicit_paddings=*/{}, data_format, + dims); } } // namespace tensorflow diff --git a/tensorflow/core/kernels/conv_grad_ops.h b/tensorflow/core/kernels/conv_grad_ops.h index 9551959463..c8e8cf28c5 100644 --- a/tensorflow/core/kernels/conv_grad_ops.h +++ b/tensorflow/core/kernels/conv_grad_ops.h @@ -176,8 +176,9 @@ struct LaunchConv2DBackpropInputOp { void operator()(OpKernelContext* ctx, bool use_cudnn, bool cudnn_use_autotune, const Tensor& out_backprop, const Tensor& filter, int row_dilation, int col_dilation, int row_stride, - int col_stride, const Padding& padding, Tensor* in_backprop, - TensorFormat data_format); + int col_stride, const Padding& padding, + const std::vector& explicit_paddings, + Tensor* in_backprop, TensorFormat data_format); }; template @@ -186,6 +187,7 @@ struct LaunchConv2DBackpropFilterOp { const Tensor& out_backprop, const Tensor& input, int row_dilation, int col_dilation, int row_stride, int col_stride, const Padding& padding, + const std::vector& explicit_paddings, Tensor* filter_backprop, TensorFormat data_format); }; @@ -195,7 +197,8 @@ struct LaunchConv2DBackpropInputOp { void operator()(OpKernelContext* ctx, bool use_cudnn, bool cudnn_use_autotune, const Tensor& input, const Tensor& filter, int row_dilation, int col_dilation, int row_stride, int col_stride, - const Padding& padding, Tensor* output, + const Padding& padding, + const std::vector& explicit_paddings, Tensor* output, TensorFormat data_format); }; @@ -205,6 +208,7 @@ struct LaunchConv2DBackpropFilterOp { const Tensor& out_backprop, const Tensor& input, int row_dilation, int col_dilation, int row_stride, int col_stride, const Padding& padding, + const std::vector& explicit_paddings, Tensor* filter_backprop, TensorFormat data_format); }; #endif // GOOGLE_CUDA @@ -217,6 +221,8 @@ struct ConvBackpropSpatialDimension { int64 output_size; int64 stride; int64 dilation; + + // The following fields are valid only if the padding is not EXPLICIT. int64 expanded_output_size; // Number of padding elements to be added before/after this dimension of @@ -248,7 +254,7 @@ struct ConvBackpropDimensions { // Common code between implementations of Conv?DBackpropInput and // Conv?DBackpropFilter. Verifies that the dimensions all match, and computes -// sizes/padding for the spatial dimensions. +// sizes/padding for the spatial dimensions. Does not support explicit padding. Status ConvBackpropComputeDimensions(StringPiece label, int num_spatial_dims, const TensorShape& input_shape, const TensorShape& filter_shape, @@ -257,13 +263,15 @@ Status ConvBackpropComputeDimensions(StringPiece label, int num_spatial_dims, Padding padding, TensorFormat data_format, ConvBackpropDimensions* dims); -// The V2 version computes the same outputs with arbitrary dilation rate. +// The V2 version computes the same outputs with arbitrary dilation rate and +// supports explicit padding. // TODO(b/67112639): Merge V2 versions and the original versions eventually. Status ConvBackpropComputeDimensionsV2( StringPiece label, int num_spatial_dims, const TensorShape& input_shape, const TensorShape& filter_shape, const TensorShape& out_backprop_shape, const gtl::ArraySlice& dilations, const std::vector& strides, - Padding padding, TensorFormat data_format, ConvBackpropDimensions* dims); + Padding padding, const std::vector& explicit_paddings, + TensorFormat data_format, ConvBackpropDimensions* dims); } // namespace tensorflow #endif // TENSORFLOW_CORE_KERNELS_CONV_GRAD_OPS_H_ diff --git a/tensorflow/core/kernels/conv_grad_ops_3d.cc b/tensorflow/core/kernels/conv_grad_ops_3d.cc index 562a9c8aed..ca46da6ba3 100644 --- a/tensorflow/core/kernels/conv_grad_ops_3d.cc +++ b/tensorflow/core/kernels/conv_grad_ops_3d.cc @@ -1152,11 +1152,11 @@ class Conv3DBackpropInputOp : public OpKernel { } ConvBackpropDimensions dims; - OP_REQUIRES_OK(context, - ConvBackpropComputeDimensionsV2( - "Conv3DBackpropInputOp", /*num_spatial_dims=*/3, - input_shape, filter_shape, out_backprop_shape, dilation_, - stride_, padding_, data_format_, &dims)); + OP_REQUIRES_OK(context, ConvBackpropComputeDimensionsV2( + "Conv3DBackpropInputOp", /*num_spatial_dims=*/3, + input_shape, filter_shape, out_backprop_shape, + dilation_, stride_, padding_, + /*explicit_paddings=*/{}, data_format_, &dims)); Tensor* in_backprop; OP_REQUIRES_OK(context, @@ -1537,11 +1537,12 @@ class Conv3DBackpropFilterOp : public OpKernel { } ConvBackpropDimensions dims; - OP_REQUIRES_OK(context, - ConvBackpropComputeDimensionsV2( - "Conv3DBackpropFilterOp", /*num_spatial_dims=*/3, - input_shape, filter_shape, out_backprop_shape, dilation_, - stride_, padding_, data_format_, &dims)); + OP_REQUIRES_OK( + context, + ConvBackpropComputeDimensionsV2( + "Conv3DBackpropFilterOp", /*num_spatial_dims=*/3, input_shape, + filter_shape, out_backprop_shape, dilation_, stride_, padding_, + /*explicit_paddings=*/{}, data_format_, &dims)); Tensor* filter_backprop; OP_REQUIRES_OK(context, diff --git a/tensorflow/core/kernels/conv_ops.cc b/tensorflow/core/kernels/conv_ops.cc index dfba15792d..a8138fd0a7 100644 --- a/tensorflow/core/kernels/conv_ops.cc +++ b/tensorflow/core/kernels/conv_ops.cc @@ -122,7 +122,8 @@ struct LaunchConv2DOp { void operator()(OpKernelContext* ctx, bool use_cudnn, bool cudnn_use_autotune, const Tensor& input, const Tensor& filter, int row_dilation, int col_dilation, int row_stride, int col_stride, - const Padding& padding, Tensor* output, + const Padding& padding, + const std::vector& explicit_paddings, Tensor* output, TensorFormat data_format) { if (data_format != FORMAT_NHWC) { ctx->SetStatus( @@ -130,6 +131,11 @@ struct LaunchConv2DOp { "NHWC tensor format for now.")); return; } + // TODO(reedwm): Enable explicit padding on the CPU. + OP_REQUIRES( + ctx, padding != Padding::EXPLICIT, + errors::Unimplemented("Generic conv implementation does not support " + "EXPLICIT padding yet.")); const int64 in_depth = GetTensorDim(input, data_format, 'C'); OP_REQUIRES(ctx, in_depth == filter.dim_size(2), errors::Unimplemented("Generic conv implementation does not " @@ -274,6 +280,10 @@ Status InitConv2DParameters(const OpKernelConstruction* context, TF_RETURN_IF_ERROR(context->GetAttr("dilations", ¶ms->dilations)); TF_RETURN_IF_ERROR(context->GetAttr("strides", ¶ms->strides)); TF_RETURN_IF_ERROR(context->GetAttr("padding", ¶ms->padding)); + if (context->HasAttr("explicit_paddings")) { + TF_RETURN_IF_ERROR( + context->GetAttr("explicit_paddings", ¶ms->explicit_paddings)); + } string data_format_string; TF_RETURN_IF_ERROR(context->GetAttr("data_format", &data_format_string)); TF_REQUIRES(FormatFromString(data_format_string, ¶ms->data_format), @@ -313,6 +323,10 @@ Status InitConv2DParameters(const OpKernelConstruction* context, dilation_h > 0 && dilation_w > 0, errors::InvalidArgument("Dilated rates should be larger than 0.")); + TF_RETURN_IF_ERROR(CheckValidPadding(params->padding, + params->explicit_paddings, + /*num_dims=*/4, data_format)); + return Status::OK(); } @@ -381,14 +395,22 @@ Status ComputeConv2DDimension(const Conv2DParameters& params, const int dilation_cols = GetTensorDim(params.dilations, params.data_format, 'W'); + int64 pad_rows_before, pad_rows_after, pad_cols_before, pad_cols_after; + if (params.padding == Padding::EXPLICIT) { + GetExplicitPaddingForDim(params.explicit_paddings, params.data_format, 'H', + &pad_rows_before, &pad_rows_after); + GetExplicitPaddingForDim(params.explicit_paddings, params.data_format, 'W', + &pad_cols_before, &pad_cols_after); + } + // Compute windowed output sizes for rows and columns. - int64 out_rows = 0, out_cols = 0, pad_rows = 0, pad_cols = 0; - TF_RETURN_IF_ERROR(GetWindowedOutputSizeV2( + int64 out_rows = 0, out_cols = 0; + TF_RETURN_IF_ERROR(GetWindowedOutputSizeVerboseV2( input_rows, filter_rows, dilation_rows, stride_rows, params.padding, - &out_rows, &pad_rows)); - TF_RETURN_IF_ERROR(GetWindowedOutputSizeV2( + &out_rows, &pad_rows_before, &pad_rows_after)); + TF_RETURN_IF_ERROR(GetWindowedOutputSizeVerboseV2( input_cols, filter_cols, dilation_cols, stride_cols, params.padding, - &out_cols, &pad_cols)); + &out_cols, &pad_cols_before, &pad_cols_after)); dimensions->batch = batch; dimensions->input_rows = input_rows; @@ -404,8 +426,10 @@ Status ComputeConv2DDimension(const Conv2DParameters& params, dimensions->dilation_cols = dilation_cols; dimensions->out_rows = out_rows; dimensions->out_cols = out_cols; - dimensions->pad_rows = pad_rows; - dimensions->pad_cols = pad_cols; + dimensions->pad_rows_before = pad_rows_before; + dimensions->pad_rows_after = pad_rows_after; + dimensions->pad_cols_before = pad_cols_before; + dimensions->pad_cols_after = pad_cols_after; return Status::OK(); } @@ -463,33 +487,35 @@ class Conv2DOp : public BinaryOp { } #ifdef TENSORFLOW_USE_LIBXSMM_CONVOLUTIONS - if (LaunchXsmmConvOp::Run( + if (params_.padding != EXPLICIT && + LaunchXsmmConvOp::Run( context, input, filter, dimensions.batch, dimensions.input_rows, dimensions.input_cols, dimensions.in_depth, dimensions.filter_rows, - dimensions.filter_cols, dimensions.pad_rows, dimensions.pad_cols, - dimensions.out_rows, dimensions.out_cols, dimensions.out_depth, - dimensions.dilation_rows, dimensions.dilation_cols, - dimensions.stride_rows, dimensions.stride_cols, output, - params_.data_format)) { + dimensions.filter_cols, dimensions.pad_rows_before, + dimensions.pad_cols_before, dimensions.out_rows, + dimensions.out_cols, dimensions.out_depth, dimensions.dilation_rows, + dimensions.dilation_cols, dimensions.stride_rows, + dimensions.stride_cols, output, params_.data_format)) { return; } #endif - if (LaunchDeepConvOp::Run( + if (params_.padding != EXPLICIT && + LaunchDeepConvOp::Run( context, input, filter, dimensions.batch, dimensions.input_rows, dimensions.input_cols, dimensions.in_depth, dimensions.filter_rows, - dimensions.filter_cols, dimensions.pad_rows, dimensions.pad_cols, - dimensions.out_rows, dimensions.out_cols, dimensions.out_depth, - dimensions.dilation_rows, dimensions.dilation_cols, - dimensions.stride_rows, dimensions.stride_cols, output, - params_.data_format)) { + dimensions.filter_cols, dimensions.pad_rows_before, + dimensions.pad_cols_before, dimensions.out_rows, + dimensions.out_cols, dimensions.out_depth, dimensions.dilation_rows, + dimensions.dilation_cols, dimensions.stride_rows, + dimensions.stride_cols, output, params_.data_format)) { return; } launcher_(context, use_cudnn_, cudnn_use_autotune_, input, filter, dimensions.dilation_rows, dimensions.dilation_cols, dimensions.stride_rows, dimensions.stride_cols, params_.padding, - output, params_.data_format); + params_.explicit_paddings, output, params_.data_format); } private: @@ -551,7 +577,8 @@ void LaunchConv2DOp::operator()( OpKernelContext* ctx, bool use_cudnn, bool cudnn_use_autotune, const Tensor& input_param, const Tensor& filter, int row_dilation, int col_dilation, int row_stride, int col_stride, const Padding& padding, - Tensor* output, TensorFormat data_format) { + const std::vector& explicit_paddings, Tensor* output, + TensorFormat data_format) { using se::dnn::AlgorithmConfig; using se::dnn::AlgorithmDesc; using se::dnn::ProfileResult; @@ -580,7 +607,8 @@ void LaunchConv2DOp::operator()( bool is_grouped_convolution = patch_depths != in_depths; if (patch_rows == 1 && patch_cols == 1 && !is_grouped_convolution && row_dilation == 1 && col_dilation == 1 && row_stride == 1 && - col_stride == 1 && data_format == FORMAT_NHWC) { + col_stride == 1 && data_format == FORMAT_NHWC && + (padding == VALID || padding == SAME)) { // 1x1 filter, so call cublas directly. const uint64 m = in_batch * in_rows * in_cols; const uint64 k = patch_depths; @@ -634,49 +662,78 @@ void LaunchConv2DOp::operator()( return; } - int padding_rows = 0; - int padding_cols = 0; const int64 out_batch = GetTensorDim(*output, data_format, 'N'); const int64 out_rows = GetTensorDim(*output, data_format, 'H'); const int64 out_cols = GetTensorDim(*output, data_format, 'W'); const int64 out_depths = GetTensorDim(*output, data_format, 'C'); - if (padding == SAME) { - // Total padding on rows and cols is - // Pr = (R' - 1) * S + (Kr - 1) * Dr + 1 - R - // Pc = (C' - 1) * S + (Kc - 1) * Dc + 1 - C - // where (R', C') are output dimensions, (R, C) are input dimensions, S - // is stride, (Dr, Dc) are dilations, (Kr, Kc) are filter dimensions. - // We pad Pr/2 on the left and Pr - Pr/2 on the right, Pc/2 on the top - // and Pc - Pc/2 on the bottom. When Pr or Pc is odd, this means - // we pad more on the right and bottom than on the top and left. - padding_rows = - std::max(0, (out_rows - 1) * row_stride + - (patch_rows - 1) * row_dilation + 1 - in_rows); - padding_cols = - std::max(0, (out_cols - 1) * col_stride + - (patch_cols - 1) * col_dilation + 1 - in_cols); - const bool rows_odd = (padding_rows % 2 != 0); - const bool cols_odd = (padding_cols % 2 != 0); - if (rows_odd || cols_odd) { - Tensor transformed_input; - int64 new_in_rows = in_rows + rows_odd; - int64 new_in_cols = in_cols + cols_odd; - OP_REQUIRES_OK( - ctx, - ctx->allocate_temp(DataTypeToEnum::value, - ShapeFromFormat(data_format, in_batch, new_in_rows, - new_in_cols, in_depths), - &transformed_input)); - - functor::PadInput()( - ctx->eigen_device(), To32Bit(input_param.tensor()), - {{0, 0}}, {{rows_odd, cols_odd}}, - To32Bit(transformed_input.tensor()), data_format); - - input = transformed_input; - in_rows = new_in_rows; - in_cols = new_in_cols; + int64 padding_top = -1, padding_bottom = -1; + int64 padding_left = -1, padding_right = -1; + if (padding == EXPLICIT) { + GetExplicitPaddingForDim(explicit_paddings, data_format, 'H', &padding_top, + &padding_bottom); + GetExplicitPaddingForDim(explicit_paddings, data_format, 'W', &padding_left, + &padding_right); + } + int64 out_rows_check, out_cols_check; + Status status = GetWindowedOutputSizeVerboseV2( + in_rows, patch_rows, row_dilation, row_stride, padding, &out_rows_check, + &padding_top, &padding_bottom); + // The status is guaranteed to be OK because we checked the output and padding + // was valid earlier. + TF_CHECK_OK(status); + DCHECK_EQ(out_rows, out_rows_check); + status = GetWindowedOutputSizeVerboseV2(in_cols, patch_cols, col_dilation, + col_stride, padding, &out_cols_check, + &padding_left, &padding_right); + TF_CHECK_OK(status); + DCHECK_EQ(out_cols, out_cols_check); + + const int64 common_padding_rows = std::min(padding_top, padding_bottom); + const int64 common_padding_cols = std::min(padding_left, padding_right); + if (padding_top != padding_bottom || padding_left != padding_right) { + // cuDNN only supports padding the same amount on the left and right sides, + // and on the top and bottom sides. So we manually create a new padded + // input tensor such that we can pass it to cuDNN. + + // TODO(reedwm): In some cases, we can avoid an allocation even if the two + // padding sides are different. For example, if the input is 2x2, the filter + // is 1x1, the stride is 2, and the padding is (1, 0, 1, 0), the result is + // equivalent to as if the padding is (1, 1, 1, 1). Changing the padding in + // such a way would allow us to avoid the allocation. + Tensor transformed_input; + const int64 padding_rows_diff = std::abs(padding_bottom - padding_top); + const int64 padding_cols_diff = std::abs(padding_right - padding_left); + const int64 new_in_rows = in_rows + padding_rows_diff; + const int64 new_in_cols = in_cols + padding_cols_diff; + OP_REQUIRES_OK(ctx, ctx->allocate_temp( + DataTypeToEnum::value, + ShapeFromFormat(data_format, in_batch, new_in_rows, + new_in_cols, in_depths), + &transformed_input)); + + const int64 input_pad_top = padding_top - common_padding_rows; + const int64 input_pad_bottom = padding_bottom - common_padding_rows; + const int64 input_pad_left = padding_left - common_padding_cols; + const int64 input_pad_right = padding_right - common_padding_cols; + bool in_bounds = + FastBoundsCheck(input_pad_top, std::numeric_limits::max()) && + FastBoundsCheck(input_pad_bottom, std::numeric_limits::max()) && + FastBoundsCheck(input_pad_left, std::numeric_limits::max()) && + FastBoundsCheck(input_pad_right, std::numeric_limits::max()); + if (!in_bounds) { + ctx->SetStatus(errors::InvalidArgument("Padding is too large.")); + return; } + functor::PadInput()( + ctx->eigen_device(), To32Bit(input_param.tensor()), + {{static_cast(input_pad_top), static_cast(input_pad_left)}}, + {{static_cast(input_pad_bottom), + static_cast(input_pad_right)}}, + To32Bit(transformed_input.tensor()), data_format); + + input = transformed_input; + in_rows = new_in_rows; + in_cols = new_in_cols; } if (data_format == FORMAT_NHWC) { @@ -698,9 +755,9 @@ void LaunchConv2DOp::operator()( } } - CHECK(padding_rows >= 0 && padding_cols >= 0) - << "Negative row or col paddings: (" << padding_rows << ", " - << padding_cols << ")"; + CHECK(common_padding_rows >= 0 && common_padding_cols >= 0) // Crash OK + << "Negative row or col paddings: (" << common_padding_rows << ", " + << common_padding_cols << ")"; se::dnn::BatchDescriptor input_desc; input_desc.set_count(in_batch) .set_feature_map_count(in_depths) @@ -723,8 +780,8 @@ void LaunchConv2DOp::operator()( .set_horizontal_dilation_rate(col_dilation) .set_vertical_filter_stride(row_stride) .set_horizontal_filter_stride(col_stride) - .set_zero_padding_height(padding_rows / 2) - .set_zero_padding_width(padding_cols / 2) + .set_zero_padding_height(common_padding_rows) + .set_zero_padding_width(common_padding_cols) .set_group_count(in_depths / patch_depths); Tensor transformed_filter; @@ -767,23 +824,23 @@ void LaunchConv2DOp::operator()( int device_id = stream->parent()->device_ordinal(); DataType dtype = input.dtype(); ConvParameters conv_parameters = { - in_batch, // batch - in_depths, // in_depths - {{in_rows, // in_rows - in_cols}}, // in_cols - FORMAT_NCHW, // compute_data_format - out_depths, // out_depths - {{patch_rows, // filter_rows - patch_cols, // filter_cols - patch_depths}}, // filter_depths - {{row_dilation, // dilation_rows - col_dilation}}, // dilation_cols - {{row_stride, // stride_rows - col_stride}}, // stride_cols - {{padding_rows, // padding_rows - padding_cols}}, // padding_cols - dtype, // tensor datatype - device_id, // device_id + in_batch, // batch + in_depths, // in_depths + {{in_rows, // in_rows + in_cols}}, // in_cols + FORMAT_NCHW, // compute_data_format + out_depths, // out_depths + {{patch_rows, // filter_rows + patch_cols, // filter_cols + patch_depths}}, // filter_depths + {{row_dilation, // dilation_rows + col_dilation}}, // dilation_cols + {{row_stride, // stride_rows + col_stride}}, // stride_cols + {{common_padding_rows, // padding_rows + common_padding_cols}}, // padding_cols + dtype, // tensor datatype + device_id, // device_id }; AlgorithmConfig algorithm_config; if (cudnn_use_autotune && diff --git a/tensorflow/core/kernels/conv_ops.h b/tensorflow/core/kernels/conv_ops.h index 7ccbaf4bf2..105a4b1b82 100644 --- a/tensorflow/core/kernels/conv_ops.h +++ b/tensorflow/core/kernels/conv_ops.h @@ -36,7 +36,8 @@ struct LaunchConv2DOp { void operator()(OpKernelContext* ctx, bool use_cudnn, bool cudnn_use_autotune, const Tensor& input, const Tensor& filter, int row_dilation, int col_dilation, int row_stride, int col_stride, - const Padding& padding, Tensor* output, + const Padding& padding, + const std::vector& explicit_paddings, Tensor* output, TensorFormat data_format); }; @@ -46,7 +47,8 @@ struct LaunchConv2DOp { void operator()(OpKernelContext* ctx, bool use_cudnn, bool cudnn_use_autotune, const Tensor& input, const Tensor& filter, int row_dilation, int col_dilation, int row_stride, int col_stride, - const Padding& padding, Tensor* output, + const Padding& padding, + const std::vector& explicit_paddings, Tensor* output, TensorFormat data_format); }; #endif // GOOGLE_CUDA @@ -72,6 +74,7 @@ struct Conv2DParameters { std::vector strides; Padding padding; TensorFormat data_format; + std::vector explicit_paddings; }; // Convolution dimensions inferred from parameters, input and filter tensors. @@ -94,8 +97,10 @@ struct Conv2DDimensions { int64 out_rows; int64 out_cols; - int64 pad_rows; - int64 pad_cols; + int64 pad_rows_before; + int64 pad_rows_after; + int64 pad_cols_before; + int64 pad_cols_after; }; // Initializes and validates Conv2D parameters configured by OpKernel diff --git a/tensorflow/core/kernels/depthwise_conv_grad_op.cc b/tensorflow/core/kernels/depthwise_conv_grad_op.cc index da3bdb475e..c152f2b7e4 100644 --- a/tensorflow/core/kernels/depthwise_conv_grad_op.cc +++ b/tensorflow/core/kernels/depthwise_conv_grad_op.cc @@ -633,7 +633,8 @@ class DepthwiseConv2dNativeBackpropInputOp : public OpKernel { // conv is supported. launcher_(context, use_cudnn_, cudnn_use_autotune_, out_backprop, reshaped_filter, /*row_dilation=*/1, /*col_dilation=*/1, - stride_, stride_, padding_, in_backprop, data_format_); + stride_, stride_, padding_, /*explicit_paddings=*/{}, + in_backprop, data_format_); return; } @@ -1115,7 +1116,8 @@ class DepthwiseConv2dNativeBackpropFilterOp : public OpKernel { // conv is supported. launcher_(context, use_cudnn_, cudnn_use_autotune_, out_backprop, input, /*row_dilation=*/1, /*col_dilation=*/1, stride_, stride_, - padding_, &reshaped_filter, data_format_); + padding_, /*explicit_paddings=*/{}, &reshaped_filter, + data_format_); return; } diff --git a/tensorflow/core/kernels/depthwise_conv_op.cc b/tensorflow/core/kernels/depthwise_conv_op.cc index f0902fdba6..dacd3cfea8 100644 --- a/tensorflow/core/kernels/depthwise_conv_op.cc +++ b/tensorflow/core/kernels/depthwise_conv_op.cc @@ -404,7 +404,8 @@ class DepthwiseConv2dNativeOp : public BinaryOp { // conv is supported. launcher_(context, use_cudnn_, cudnn_use_autotune_, input, reshaped_filter, /*row_dilation=*/1, /*col_dilation=*/1, - stride_, stride_, padding_, output, data_format_); + stride_, stride_, padding_, /*explicit_paddings=*/{}, output, + data_format_); return; } diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index 0e94259f75..0f4f725937 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -269,10 +269,11 @@ REGISTER_OP("Conv2D") .Attr("T: {half, bfloat16, float, double}") .Attr("strides: list(int)") .Attr("use_cudnn_on_gpu: bool = true") - .Attr(GetPaddingAttrString()) + .Attr(GetPaddingAttrStringWithExplicit()) + .Attr(GetExplicitPaddingsAttrString()) .Attr(GetConvnetDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") - .SetShapeFn(shape_inference::Conv2DShape); + .SetShapeFn(shape_inference::Conv2DShapeWithExplicitPadding); REGISTER_OP("Conv2DBackpropInput") .Input("input_sizes: int32") @@ -282,7 +283,8 @@ REGISTER_OP("Conv2DBackpropInput") .Attr("T: {half, bfloat16, float, double}") .Attr("strides: list(int)") .Attr("use_cudnn_on_gpu: bool = true") - .Attr(GetPaddingAttrString()) + .Attr(GetPaddingAttrStringWithExplicit()) + .Attr(GetExplicitPaddingsAttrString()) .Attr(GetConvnetDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { @@ -304,7 +306,8 @@ REGISTER_OP("Conv2DBackpropFilter") .Attr("T: {half, bfloat16, float, double}") .Attr("strides: list(int)") .Attr("use_cudnn_on_gpu: bool = true") - .Attr(GetPaddingAttrString()) + .Attr(GetPaddingAttrStringWithExplicit()) + .Attr(GetExplicitPaddingsAttrString()) .Attr(GetConvnetDataFormatAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") .SetShapeFn([](InferenceContext* c) { diff --git a/tensorflow/core/util/padding.cc b/tensorflow/core/util/padding.cc index 117de5ee4b..9e7fb8489e 100644 --- a/tensorflow/core/util/padding.cc +++ b/tensorflow/core/util/padding.cc @@ -29,12 +29,55 @@ Status GetNodeAttr(const NodeDef& node_def, StringPiece attr_name, *value = SAME; } else if (str_value == "VALID") { *value = VALID; + } else if (str_value == "EXPLICIT") { + *value = EXPLICIT; } else { return errors::NotFound(str_value, " is not an allowed padding type"); } return Status::OK(); } +Status CheckValidPadding(Padding padding_type, + const std::vector& explicit_paddings, + int num_dims, TensorFormat data_format) { + if (padding_type == Padding::EXPLICIT) { + if (explicit_paddings.size() != 2 * num_dims) { + return errors::InvalidArgument( + "explicit_paddings attribute must contain ", 2 * num_dims, + " values, but got: ", explicit_paddings.size()); + } + for (int64 padding_value : explicit_paddings) { + if (padding_value < 0) { + return errors::InvalidArgument( + "All elements of explicit_paddings must be nonnegative"); + } + } + const int32 batch_index = GetTensorBatchDimIndex(num_dims, data_format); + const int32 depth_index = GetTensorFeatureDimIndex(num_dims, data_format); + if (explicit_paddings[2 * batch_index] != 0 || + explicit_paddings[2 * batch_index + 1] != 0 || + explicit_paddings[2 * depth_index] != 0 || + explicit_paddings[2 * depth_index + 1] != 0) { + return errors::InvalidArgument( + "Nonzero explicit padding in the batch or depth dimensions is not " + "supported"); + } + } else if (!explicit_paddings.empty()) { + return errors::InvalidArgument( + "explicit_paddings attribute must be empty if the padding attribute is " + "not EXPLICIT"); + } + return Status::OK(); +} + string GetPaddingAttrString() { return "padding: {'SAME', 'VALID'}"; } +string GetPaddingAttrStringWithExplicit() { + return "padding: {'SAME', 'VALID', 'EXPLICIT'}"; +} + +string GetExplicitPaddingsAttrString() { + return "explicit_paddings: list(int) = []"; +} + } // end namespace tensorflow diff --git a/tensorflow/core/util/padding.h b/tensorflow/core/util/padding.h index 76f9b4dd9a..a1dd1c0bd9 100644 --- a/tensorflow/core/util/padding.h +++ b/tensorflow/core/util/padding.h @@ -20,8 +20,10 @@ limitations under the License. // kernels. #include +#include #include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/util/tensor_format.h" namespace tensorflow { @@ -34,16 +36,29 @@ class NodeDef; // VALID: No padding is carried out. // SAME: The pad value is computed so that the output will have the same // dimensions as the input. +// EXPLICIT: The user specifies the pad values in the explicit_padding +// attribute. // The padded area is zero-filled. enum Padding { - VALID = 1, // No padding. - SAME = 2, // Input and output layers have the same size. + VALID = 1, // No padding. + SAME = 2, // Input and output layers have the same size. + EXPLICIT = 3, // Padding is explicitly specified }; +// Returns an error if the padding attributes are invalid. +Status CheckValidPadding(Padding padding_type, + const std::vector& explicit_paddings, + int num_dims, TensorFormat data_format); + // Return the string containing the list of valid padding types, that can be // used as an Attr() in REGISTER_OP. string GetPaddingAttrString(); +// Like GetPaddingAttrString(), but also includes EXPLICIT. +string GetPaddingAttrStringWithExplicit(); + +string GetExplicitPaddingsAttrString(); + // Specialization to parse an attribute directly into a Padding enum. Status GetNodeAttr(const NodeDef& node_def, StringPiece attr_name, Padding* value); diff --git a/tensorflow/core/util/tensor_format.h b/tensorflow/core/util/tensor_format.h index a296fb447e..643e14e0b5 100644 --- a/tensorflow/core/util/tensor_format.h +++ b/tensorflow/core/util/tensor_format.h @@ -408,18 +408,24 @@ inline int32 GetTensorDimIndex(TensorFormat format, char dimension) { return GetTensorDimIndex<2>(format, dimension); } +inline int32 GetTensorDimIndex(TensorFormat format, char dimension, + int num_total_dims) { + int32 index = (GetTensorSpatialDims(num_total_dims, format) == 3) + ? GetTensorDimIndex<3>(format, dimension) + : GetTensorDimIndex<2>(format, dimension); + CHECK(index >= 0 && index < num_total_dims) // Crash OK. + << "Invalid index from the dimension: " << index << ", " << format << ", " + << dimension; + return index; +} + // Return the element from 'dimension_attributes' that corresponds to the // specified 'dimension' according to 'tensor_format'. template T GetTensorDim(gtl::ArraySlice dimension_attributes, TensorFormat tensor_format, char dimension) { int index = - (GetTensorSpatialDims(dimension_attributes.size(), tensor_format) == 3) - ? GetTensorDimIndex<3>(tensor_format, dimension) - : GetTensorDimIndex<2>(tensor_format, dimension); - CHECK(index >= 0 && index < dimension_attributes.size()) - << "Invalid index from the dimension: " << index << ", " << tensor_format - << ", " << dimension; + GetTensorDimIndex(tensor_format, dimension, dimension_attributes.size()); return dimension_attributes[index]; } @@ -476,6 +482,15 @@ inline int64 GetFilterDim(const Tensor& tensor, return GetFilterDim(tensor.shape(), filter_tensor_format, dimension); } +inline void GetExplicitPaddingForDim( + const std::vector& explicit_paddings, TensorFormat tensor_format, + char dimension, int64* padding_before, int64* padding_after) { + int index = + GetTensorDimIndex(tensor_format, dimension, explicit_paddings.size() / 2); + *padding_before = explicit_paddings[2 * index]; + *padding_after = explicit_paddings[2 * index + 1]; +} + // Return the string that specifies the data format for convnet operations. string GetConvnetDataFormatAttrString(); string GetConvnet3dDataFormatAttrString(); diff --git a/tensorflow/python/keras/layers/convolutional.py b/tensorflow/python/keras/layers/convolutional.py index 80ec55f410..30b919cc0a 100644 --- a/tensorflow/python/keras/layers/convolutional.py +++ b/tensorflow/python/keras/layers/convolutional.py @@ -180,12 +180,14 @@ class Conv(Layer): op_padding = 'valid' else: op_padding = self.padding + if not isinstance(op_padding, (list, tuple)): + op_padding = op_padding.upper() self._convolution_op = nn_ops.Convolution( input_shape, filter_shape=self.kernel.get_shape(), dilation_rate=self.dilation_rate, strides=self.strides, - padding=op_padding.upper(), + padding=op_padding, data_format=conv_utils.convert_data_format(self.data_format, self.rank + 2)) self.built = True diff --git a/tensorflow/python/keras/utils/conv_utils.py b/tensorflow/python/keras/utils/conv_utils.py index f486e631e5..ea7427f61a 100644 --- a/tensorflow/python/keras/utils/conv_utils.py +++ b/tensorflow/python/keras/utils/conv_utils.py @@ -194,9 +194,11 @@ def normalize_data_format(value): def normalize_padding(value): + if isinstance(value, (list, tuple)): + return value padding = value.lower() if padding not in {'valid', 'same', 'causal'}: - raise ValueError('The `padding` argument must be one of ' + raise ValueError('The `padding` argument must be a list/tuple or one of ' '"valid", "same" (or "causal", only for `Conv1D). ' 'Received: ' + str(padding)) return padding diff --git a/tensorflow/python/kernel_tests/conv_ops_test.py b/tensorflow/python/kernel_tests/conv_ops_test.py index 2f6f3bb383..7ff1a61e47 100644 --- a/tensorflow/python/kernel_tests/conv_ops_test.py +++ b/tensorflow/python/kernel_tests/conv_ops_test.py @@ -26,13 +26,18 @@ import numpy as np from six.moves import xrange # pylint: disable=redefined-builtin from tensorflow.contrib import layers +from tensorflow.core.protobuf import config_pb2 +from tensorflow.core.protobuf import rewriter_config_pb2 from tensorflow.python.client import session as session_lib +from tensorflow.python.eager import backprop +from tensorflow.python.eager import context from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors_impl from tensorflow.python.framework import ops from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops +from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import gradient_checker from tensorflow.python.ops import gradients_impl from tensorflow.python.ops import nn_impl @@ -165,6 +170,12 @@ class Conv2DTest(test.TestCase): # as we will be using its gradients as reference for fp16 gradients. return [dtypes.float32, dtypes.float16, dtypes.float64] + def _CreateNumpyTensor(self, shape): + total_size = 1 + for s in shape: + total_size *= s + return np.arange(1, total_size + 1, dtype=np.float32).reshape(shape) + def _SetupValuesForDevice(self, tensor_in_sizes, filter_in_sizes, dilations, strides, padding, data_format, dtype, use_gpu): """Verifies the output values of the convolution function. @@ -183,26 +194,22 @@ class Conv2DTest(test.TestCase): Returns: Symbolic tensor value that can be used to execute the computation """ - total_size_1 = 1 - total_size_2 = 1 - for s in tensor_in_sizes: - total_size_1 *= s - for s in filter_in_sizes: - total_size_2 *= s - # Initializes the input tensor with array containing incrementing - # numbers from 1. - x1 = [f * 1.0 for f in range(1, total_size_1 + 1)] - x2 = [f * 1.0 for f in range(1, total_size_2 + 1)] + x1 = self._CreateNumpyTensor(tensor_in_sizes) + x2 = self._CreateNumpyTensor(filter_in_sizes) with test_util.device(use_gpu): t1 = constant_op.constant(x1, shape=tensor_in_sizes, dtype=dtype) t2 = constant_op.constant(x2, shape=filter_in_sizes, dtype=dtype) strides = [1] + strides + [1] dilations = [1] + dilations + [1] + if isinstance(padding, (list, tuple)): + padding = [(0, 0)] + padding + [(0, 0)] if data_format == "NCHW": t1 = test_util.NHWCToNCHW(t1) strides = test_util.NHWCToNCHW(strides) dilations = test_util.NHWCToNCHW(dilations) + if isinstance(padding, (list, tuple)): + padding = test_util.NHWCToNCHW(padding) conv = nn_ops.conv2d( t1, t2, @@ -254,17 +261,8 @@ class Conv2DTest(test.TestCase): def _ComputeReferenceDilatedConv(self, tensor_in_sizes, filter_in_sizes, stride, dilation, padding, data_format, use_gpu): - total_size_1 = 1 - total_size_2 = 1 - for s in tensor_in_sizes: - total_size_1 *= s - for s in filter_in_sizes: - total_size_2 *= s - - # Initializes the input tensor with array containing incrementing - # numbers from 1. - x1 = [f * 1.0 for f in range(1, total_size_1 + 1)] - x2 = [f * 1.0 for f in range(1, total_size_2 + 1)] + x1 = self._CreateNumpyTensor(tensor_in_sizes) + x2 = self._CreateNumpyTensor(filter_in_sizes) with test_util.device(use_gpu): t1 = constant_op.constant(x1, shape=tensor_in_sizes) t2 = constant_op.constant(x2, shape=filter_in_sizes) @@ -312,16 +310,29 @@ class Conv2DTest(test.TestCase): expected_values = self.evaluate(expected_results) computed_values = self.evaluate(computed_results) for e_value, c_value in zip(expected_values, computed_values): - tf_logging.info("expected = ", e_value) - tf_logging.info("actual = ", c_value) + tf_logging.debug("expected = %s", e_value) + tf_logging.debug("actual = %s", c_value) self.assertAllClose( e_value.flatten(), c_value.flatten(), atol=tolerance, rtol=1e-4) - def _VerifyValues(self, tensor_in_sizes, filter_in_sizes, strides, padding, - expected): + def _VerifyValues(self, + tensor_in_sizes, + filter_in_sizes, + strides, + padding, + expected, + dilations=(1, 1), + gpu_only=False, + test_grappler_layout_optimizer=False, + tol=1e-5, + fp16_tol=1e-3): + if gpu_only and not test.is_gpu_available(cuda_only=True): + return tensors = [] - dilations = [1, 1] + dilations = list(dilations) for (data_format, use_gpu) in GetTestConfigs(): + if gpu_only and not use_gpu: + continue for dtype in self._DtypesToTest(use_gpu): result = self._SetupValuesForDevice( tensor_in_sizes, @@ -332,19 +343,71 @@ class Conv2DTest(test.TestCase): data_format, dtype, use_gpu=use_gpu) + if test_grappler_layout_optimizer and data_format == "NHWC" and use_gpu: + # Grappler's layout optimizer will not optimize a fetch node, so + # this identity allows Grappler to optimize the Conv2D node. + result = array_ops.identity(result) tensors.append(result) values = self.evaluate(tensors) for i in range(len(tensors)): conv = tensors[i] value = values[i] - tf_logging.info("expected = ", expected) - tf_logging.info("actual = ", value) - tol = 1e-5 - if value.dtype == np.float16: - tol = 1e-3 - self.assertAllClose(expected, np.ravel(value), atol=tol, rtol=tol) + tf_logging.debug("expected = %s", expected) + tf_logging.debug("actual = %s", value) + tol_to_use = fp16_tol if value.dtype == np.float16 else tol + self.assertAllClose(expected, np.ravel(value), atol=tol_to_use, + rtol=tol_to_use) self.assertShapeEqual(value, conv) + def _VerifyExplicitPaddings(self, + tensor_in_sizes, + filter_in_sizes, + strides, + padding, + dilations=(1, 1), + test_grappler_layout_optimizer=False, + tol=1e-5, + fp16_tol=1e-3): + """Verifies Conv2D with explicit padding generates correct values. + + It does this by comparing with Conv2D without explicit padding. This + function assumes Conv2D without explicit padding works correctly. + + Args: + tensor_in_sizes: Input tensor dimensions in [batch, input_rows, + input_cols, input_depth]. + filter_in_sizes: Filter tensor dimensions in [kernel_rows, kernel_cols, + input_depth, output_depth]. + strides: [row_stride, col_stride] for the convolution; + padding: Explicit padding amounts. + dilations: Dilation values + test_grappler_layout_optimizer: If True, allow the Grappler layout + optimizer to run, which turns NHWC Conv2Ds on the GPU to NCHW Conv2Ds. + tol: The absolute and relative tolerance for non-fp16 dtypes. + fp16_tol: The absolute and relative tolerance for fp16. + """ + input_tensor = self._CreateNumpyTensor(tensor_in_sizes) + filter_tensor = self._CreateNumpyTensor(filter_in_sizes) + input_tensor = array_ops.pad(input_tensor, [(0, 0)] + padding + [(0, 0)]) + dilations = list(dilations) + conv2d_result = nn_ops.conv2d( + input_tensor, + filter_tensor, [1] + list(strides) + [1], + "VALID", + dilations=[1] + dilations + [1]) + expected = list(self.evaluate(array_ops.reshape(conv2d_result, [-1]))) + self._VerifyValues( + tensor_in_sizes, + filter_in_sizes, + strides, + padding, + expected, + dilations, + gpu_only=True, + test_grappler_layout_optimizer=test_grappler_layout_optimizer, + tol=tol, + fp16_tol=fp16_tol) + @test_util.run_in_graph_and_eager_modes def testConv2D1x1Filter(self): expected_output = [ @@ -510,6 +573,126 @@ class Conv2DTest(test.TestCase): dilations=[2, 2], padding="VALID") + @test_util.run_in_graph_and_eager_modes() + def testConv2D0x0Padding(self): + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 3, 3], + filter_in_sizes=[2, 2, 3, 3], + strides=[1, 1], + padding=[[0, 0], [0, 0]]) + + self._VerifyExplicitPaddings( + tensor_in_sizes=[3, 4, 3, 2], + filter_in_sizes=[1, 1, 2, 1], + strides=[2, 2], + padding=[[0, 0], [0, 0]]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D1x1Padding(self): + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 3, 2], + filter_in_sizes=[2, 2, 2, 2], + strides=[1, 1], + padding=[[1, 1], [1, 1]]) + + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 2, 1], + filter_in_sizes=[1, 1, 1, 2], + strides=[1, 1], + padding=[[1, 1], [1, 1]]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Padding(self): + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 1, 2], + filter_in_sizes=[2, 1, 2, 1], + strides=[1, 1], + padding=[[2, 2], [2, 2]]) + + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 1, 2], + filter_in_sizes=[1, 1, 2, 1], + strides=[2, 1], + padding=[[2, 2], [2, 2]]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2DOnlyBottomPadding(self): + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 3, 3], + filter_in_sizes=[2, 2, 3, 2], + strides=[1, 1], + padding=[[0, 3], [0, 0]], tol=2e-5) + + self._VerifyExplicitPaddings( + tensor_in_sizes=[2, 2, 4, 3], + filter_in_sizes=[1, 2, 3, 2], + strides=[2, 2], + padding=[[0, 3], [0, 0]]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2DOnlyTopRightPadding(self): + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 3, 3], + filter_in_sizes=[2, 2, 3, 2], + strides=[1, 1], + padding=[[1, 0], [0, 2]], + tol=5e-5) + + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 4, 2], + filter_in_sizes=[2, 2, 2, 2], + strides=[1, 3], + padding=[[1, 0], [0, 2]]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2DLotsPadding(self): + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 1, 1, 3], + filter_in_sizes=[2, 2, 3, 3], + strides=[1, 1], + padding=[[3, 4], [4, 2]]) + + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 1, 1], + filter_in_sizes=[2, 2, 1, 3], + strides=[2, 1], + padding=[[3, 4], [4, 2]]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2DExplicitPaddingWithDilations(self): + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 3, 2, 1], + filter_in_sizes=[1, 2, 1, 2], + strides=[1, 1], + padding=[[1, 0], [0, 1]], + dilations=[2, 1]) + + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 3, 2], + filter_in_sizes=[3, 2, 2, 1], + strides=[1, 1], + padding=[[2, 1], [1, 2]], + dilations=[2, 3]) + + def testConv2DExplicitPaddingWithLayoutOptimizer(self): + # Test with Grappler's layout optimizer, to ensure the layout optimizer + # handles explicit padding correctly. + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 3, 2, 1], + filter_in_sizes=[1, 2, 1, 2], + strides=[1, 1], + padding=[[1, 0], [0, 1]], + dilations=[2, 1], + test_grappler_layout_optimizer=True) + + self._VerifyExplicitPaddings( + tensor_in_sizes=[1, 2, 3, 2], + filter_in_sizes=[3, 2, 2, 1], + strides=[1, 1], + padding=[[2, 1], [1, 2]], + dilations=[2, 3], + test_grappler_layout_optimizer=True) + # TODO(yzhwang): this currently fails. # self._VerifyValues(tensor_in_sizes=[1, 8, 8, 1], # filter_in_sizes=[2, 2, 1, 1], @@ -517,19 +700,22 @@ class Conv2DTest(test.TestCase): # expected=[72, 112, 392, 432]) # Testing for backprops - def _RunAndVerifyBackpropInput(self, input_sizes, filter_sizes, output_sizes, - strides, padding, expected, data_format, - use_gpu, err): - total_output_size = 1 - total_filter_size = 1 - for s in output_sizes: - total_output_size *= s - for s in filter_sizes: - total_filter_size *= s - # Initializes the input tensor with array containing incrementing - # numbers from 1. - x1 = [f * 1.0 for f in range(1, total_filter_size + 1)] - x2 = [f * 1.0 for f in range(1, total_output_size + 1)] + def _RunAndVerifyBackpropInput(self, + input_sizes, + filter_sizes, + output_sizes, + strides, + padding, + expected, + data_format, + use_gpu, + err, + dilations=(1, 1)): + if use_gpu and not test.is_gpu_available(cuda_only=True): + return + x1 = self._CreateNumpyTensor(filter_sizes) + x2 = self._CreateNumpyTensor(output_sizes) + dilations = list(dilations) with test_util.device(use_gpu): if data_format == "NCHW": input_sizes = test_util.NHWCToNCHW(input_sizes) @@ -537,18 +723,30 @@ class Conv2DTest(test.TestCase): t1 = constant_op.constant(x1, shape=filter_sizes) t2 = constant_op.constant(x2, shape=output_sizes) strides = [1] + strides + [1] + dilations = [1] + dilations + [1] + if isinstance(padding, (list, tuple)): + padding = [(0, 0)] + padding + [(0, 0)] if data_format == "NCHW": t2 = test_util.NHWCToNCHW(t2) strides = test_util.NHWCToNCHW(strides) + dilations = test_util.NHWCToNCHW(dilations) + if isinstance(padding, (list, tuple)): + padding = test_util.NHWCToNCHW((padding)) conv = nn_ops.conv2d_backprop_input( - t0, t1, t2, strides=strides, padding=padding, data_format=data_format) + t0, + t1, + t2, + strides=strides, + padding=padding, + data_format=data_format, + dilations=dilations) if data_format == "NCHW": conv = test_util.NCHWToNHWC(conv) # "values" consists of two tensors for two backprops value = self.evaluate(conv) self.assertShapeEqual(value, conv) - tf_logging.info("expected = ", expected) - tf_logging.info("actual = ", value) + tf_logging.debug("expected = %s", expected) + tf_logging.debug("actual = %s", value) self.assertArrayNear(expected, value.flatten(), err) def _CompareBackpropInput(self, input_sizes, filter_sizes, output_sizes, @@ -691,41 +889,51 @@ class Conv2DTest(test.TestCase): err=1e-5) # Testing for backprops - def _RunAndVerifyBackpropFilter(self, input_sizes, filter_sizes, output_sizes, - strides, padding, expected, data_format, - use_gpu): - total_input_size = 1 - total_output_size = 1 - for s in input_sizes: - total_input_size *= s - for s in output_sizes: - total_output_size *= s - # Initializes the input tensor with array containing incrementing - # numbers from 1. - x0 = [f * 1.0 for f in range(1, total_input_size + 1)] - x2 = [f * 1.0 for f in range(1, total_output_size + 1)] + def _RunAndVerifyBackpropFilter(self, + input_sizes, + filter_sizes, + output_sizes, + strides, + padding, + expected, + data_format, + use_gpu, + dilations=(1, 1), + err=1e-5): + x0 = self._CreateNumpyTensor(input_sizes) + x2 = self._CreateNumpyTensor(output_sizes) + dilations = list(dilations) + explicit_strides = [1] + strides + [1] + new_padding = padding + new_dilations = [1] + dilations + [1] + if isinstance(new_padding, (list, tuple)): + new_padding = [(0, 0)] + new_padding + [(0, 0)] + if data_format == "NCHW": + explicit_strides = test_util.NHWCToNCHW(explicit_strides) + new_dilations = test_util.NHWCToNCHW(new_dilations) + if isinstance(padding, (list, tuple)): + new_padding = test_util.NHWCToNCHW(new_padding) for dtype in self._DtypesToTest(use_gpu=use_gpu): with test_util.device(use_gpu): t0 = constant_op.constant(x0, shape=input_sizes, dtype=dtype) t1 = constant_op.constant(filter_sizes, shape=[len(filter_sizes)]) t2 = constant_op.constant(x2, shape=output_sizes, dtype=dtype) - explicit_strides = [1] + strides + [1] if data_format == "NCHW": t0 = test_util.NHWCToNCHW(t0) t2 = test_util.NHWCToNCHW(t2) - explicit_strides = test_util.NHWCToNCHW(explicit_strides) conv = nn_ops.conv2d_backprop_filter( t0, t1, t2, strides=explicit_strides, - padding=padding, + padding=new_padding, + dilations=new_dilations, data_format=data_format) value = self.evaluate(conv) self.assertShapeEqual(value, conv) - tf_logging.info("expected = ", expected) - tf_logging.info("actual = ", value) - self.assertArrayNear(expected, value.flatten(), 1e-5) + tf_logging.debug("expected = %s", expected) + tf_logging.debug("actual = %s", value) + self.assertArrayNear(expected, value.flatten(), err) def _CompareBackFilter(self, input_sizes, filter_sizes, output_sizes, conv_strides, padding): @@ -866,16 +1074,8 @@ class Conv2DTest(test.TestCase): def _RunAndVerifyBackpropInputDilation(self, input_sizes, filter_sizes, output_sizes, strides, dilations, padding, data_format, use_gpu, err): - total_input_size = 1 - total_filter_size = 1 - for s in input_sizes: - total_input_size *= s - for s in filter_sizes: - total_filter_size *= s - # Initializes the input tensor with array containing incrementing - # numbers from 1. - x1 = [f * 1.0 for f in range(1, total_input_size + 1)] - x2 = [f * 1.0 for f in range(1, total_filter_size + 1)] + x1 = self._CreateNumpyTensor(input_sizes) + x2 = self._CreateNumpyTensor(filter_sizes) default_dilations = (dilations[0] == 1 and dilations[1] == 1) if default_dilations or use_gpu: with self.cached_session(use_gpu=use_gpu) as sess: @@ -912,24 +1112,16 @@ class Conv2DTest(test.TestCase): value_2 = self.evaluate(conv_2) self.assertShapeEqual(value, conv) self.assertShapeEqual(value_2, conv_2) - tf_logging.info("expected = ", value_2) - tf_logging.info("actual = ", value) + tf_logging.debug("expected = %s", value_2) + tf_logging.debug("actual = %s", value) self.assertArrayNear(value_2.flatten(), value.flatten(), err) # Testing for backprops def _RunAndVerifyBackpropFilterDilation(self, input_sizes, filter_sizes, output_sizes, strides, dilations, padding, data_format, use_gpu, err): - total_input_size = 1 - total_filter_size = 1 - for s in input_sizes: - total_input_size *= s - for s in filter_sizes: - total_filter_size *= s - # Initializes the input tensor with array containing incrementing - # numbers from 1. - x1 = [f * 1.0 for f in range(1, total_input_size + 1)] - x2 = [f * 1.0 for f in range(1, total_filter_size + 1)] + x1 = self._CreateNumpyTensor(input_sizes) + x2 = self._CreateNumpyTensor(filter_sizes) default_dilations = (dilations[0] == 1 and dilations[1] == 1) if default_dilations or use_gpu: with self.cached_session(use_gpu=use_gpu) as sess: @@ -965,8 +1157,8 @@ class Conv2DTest(test.TestCase): value_2 = self.evaluate(conv_2) self.assertShapeEqual(value, conv) self.assertShapeEqual(value_2, conv_2) - tf_logging.info("expected = ", value_2) - tf_logging.info("actual = ", value) + tf_logging.debug("expected = %s", value_2) + tf_logging.debug("actual = %s", value) self.assertArrayNear(value_2.flatten(), value.flatten(), err) def testConv2D2x2Depth3ValidBackpropFilterStride1x1Dilation2x1(self): @@ -1111,20 +1303,347 @@ class Conv2DTest(test.TestCase): use_gpu=use_gpu, err=1e-5) + def _RunAndVerifyBackpropInputExplicitPadding(self, + input_sizes, + filter_sizes, + output_sizes, + strides, + padding, + data_format, + dilations=(1, 1), + err=2e-5): + x1 = self._CreateNumpyTensor(filter_sizes) + x2 = self._CreateNumpyTensor(output_sizes) + dilations = list(dilations) + padded_input_sizes = input_sizes[:] + padded_input_sizes[1] += padding[0][0] + padding[0][1] + padded_input_sizes[2] += padding[1][0] + padding[1][1] + c = nn_ops.conv2d_backprop_input( + padded_input_sizes, + x1, + x2, + strides=[1] + strides + [1], + padding="VALID", + dilations=[1] + dilations + [1]) + c = c[:, padding[0][0]:(c.shape[1] - padding[0][1]), padding[1][0]:( + c.shape[2] - padding[1][1]), :] + expected = list(self.evaluate(array_ops.reshape(c, [-1]))) + self._RunAndVerifyBackpropInput( + input_sizes, + filter_sizes, + output_sizes, + strides, + padding, + expected, + data_format, + use_gpu=True, + err=err, + dilations=dilations) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding0x0BackpropInput(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 2, 3, 1], + filter_sizes=[2, 2, 1, 1], + output_sizes=[1, 1, 2, 1], + strides=[1, 1], + padding=[[0, 0], [0, 0]], + data_format=data_format) + + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 3, 4, 2], + filter_sizes=[2, 2, 2, 3], + output_sizes=[1, 1, 2, 3], + strides=[2, 2], + padding=[[0, 0], [0, 0]], + data_format=data_format) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding1x1BackpropInput(self): + if not test.is_gpu_available(cuda_only=True): + return + + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 2, 3, 1], + filter_sizes=[2, 2, 1, 2], + output_sizes=[1, 3, 4, 2], + strides=[1, 1], + padding=[[1, 1], [1, 1]], + data_format=data_format, err=1e-4) + + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 2, 3, 2], + filter_sizes=[1, 1, 2, 1], + output_sizes=[1, 4, 3, 1], + strides=[1, 2], + padding=[[1, 1], [1, 1]], + data_format=data_format) + + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 4, 3, 1], + filter_sizes=[2, 2, 1, 1], + output_sizes=[1, 4, 2, 1], + strides=[1, 2], + padding=[[1, 1], [1, 1]], + data_format=data_format, + dilations=[2, 2]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding2x2BackpropInput(self): + if not test.is_gpu_available(cuda_only=True): + return + + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[2, 3, 1, 1], + filter_sizes=[2, 1, 1, 1], + output_sizes=[2, 2, 5, 1], + strides=[3, 1], + padding=[[2, 2], [2, 2]], + data_format=data_format) + + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 3, 6, 1], + filter_sizes=[3, 2, 1, 1], + output_sizes=[1, 3, 4, 1], + strides=[1, 2], + padding=[[2, 2], [2, 2]], + data_format=data_format, + dilations=[2, 3]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding_1_8_4_1_BackpropInput(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 2, 3, 1], + filter_sizes=[2, 2, 1, 1], + output_sizes=[1, 10, 8, 1], + strides=[1, 1], + padding=[[1, 8], [4, 2]], + data_format=data_format, err=5e-5) + + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 5, 3, 1], + filter_sizes=[3, 2, 1, 1], + output_sizes=[1, 4, 8, 1], + strides=[3, 1], + padding=[[1, 8], [4, 2]], + data_format=data_format) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding_5_0_2_2_BackpropInput(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 3, 3, 1], + filter_sizes=[2, 1, 1, 1], + output_sizes=[1, 7, 7, 1], + strides=[1, 1], + padding=[[5, 0], [2, 2]], + data_format=data_format, + err=5e-5) + + self._RunAndVerifyBackpropInputExplicitPadding( + input_sizes=[1, 4, 2, 1], + filter_sizes=[3, 3, 1, 1], + output_sizes=[1, 5, 2, 1], + strides=[1, 2], + padding=[[5, 0], [2, 2]], + data_format=data_format, + dilations=[2, 1]) + + def _RunAndVerifyBackpropFilterExplicitPadding(self, + input_sizes, + filter_sizes, + output_sizes, + strides, + padding, + data_format, + dilations=(1, 1), + err=1e-5): + x0 = self._CreateNumpyTensor(input_sizes) + x2 = self._CreateNumpyTensor(output_sizes) + dilations = list(dilations) + + x0 = np.pad(x0, [(0, 0)] + padding + [(0, 0)], "constant") + c = nn_ops.conv2d_backprop_filter( + x0, + filter_sizes, + x2, + strides=[1] + strides + [1], + padding="VALID", + dilations=[1] + dilations + [1]) + expected = list(self.evaluate(array_ops.reshape(c, [-1]))) + self._RunAndVerifyBackpropFilter( + input_sizes, + filter_sizes, + output_sizes, + strides, + padding, + expected, + data_format, + use_gpu=True, + dilations=dilations, + err=err) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding0x0BackpropFilter(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 2, 3, 1], + filter_sizes=[2, 2, 1, 1], + output_sizes=[1, 1, 2, 1], + strides=[1, 1], + padding=[[0, 0], [0, 0]], + data_format=data_format) + + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 3, 4, 2], + filter_sizes=[2, 2, 2, 3], + output_sizes=[1, 1, 2, 3], + strides=[2, 2], + padding=[[0, 0], [0, 0]], + data_format=data_format) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding1x1BackpropFilter(self): + if not test.is_gpu_available(cuda_only=True): + return + + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 2, 3, 1], + filter_sizes=[2, 2, 1, 2], + output_sizes=[1, 3, 4, 2], + strides=[1, 1], + padding=[[1, 1], [1, 1]], + data_format=data_format, + err=5e-5) + + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 2, 3, 2], + filter_sizes=[1, 1, 2, 1], + output_sizes=[1, 4, 3, 1], + strides=[1, 2], + padding=[[1, 1], [1, 1]], + data_format=data_format) + + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 4, 3, 1], + filter_sizes=[2, 2, 1, 1], + output_sizes=[1, 4, 2, 1], + strides=[1, 2], + padding=[[1, 1], [1, 1]], + data_format=data_format, + dilations=[2, 2]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding2x2BackpropFilter(self): + if not test.is_gpu_available(cuda_only=True): + return + + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[2, 3, 1, 1], + filter_sizes=[2, 1, 1, 1], + output_sizes=[2, 2, 5, 1], + strides=[3, 1], + padding=[[2, 2], [2, 2]], + data_format=data_format) + + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 3, 6, 1], + filter_sizes=[3, 2, 1, 1], + output_sizes=[1, 3, 4, 1], + strides=[1, 2], + padding=[[2, 2], [2, 2]], + data_format=data_format, + dilations=[2, 3]) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding_1_8_4_1_BackpropFilter(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 2, 3, 1], + filter_sizes=[2, 2, 1, 1], + output_sizes=[1, 10, 8, 1], + strides=[1, 1], + padding=[[1, 8], [4, 2]], + data_format=data_format, + err=1e-4) + + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 5, 3, 1], + filter_sizes=[3, 2, 1, 1], + output_sizes=[1, 4, 8, 1], + strides=[3, 1], + padding=[[1, 8], [4, 2]], + data_format=data_format) + + @test_util.run_in_graph_and_eager_modes() + def testConv2D2x2Depth1Padding_5_0_2_2_BackpropFilter(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 3, 3, 1], + filter_sizes=[2, 1, 1, 1], + output_sizes=[1, 7, 7, 1], + strides=[1, 1], + padding=[[5, 0], [2, 2]], + data_format=data_format, + err=1e-4) + + self._RunAndVerifyBackpropFilterExplicitPadding( + input_sizes=[1, 4, 2, 1], + filter_sizes=[3, 3, 1, 1], + output_sizes=[1, 5, 2, 1], + strides=[1, 2], + padding=[[5, 0], [2, 2]], + data_format=data_format, + dilations=[2, 1]) + # Gradient checkers def ConstructAndTestGradient(self, batch, input_rows, input_cols, filter_rows, filter_cols, in_depth, out_depth, stride_rows, stride_cols, padding, test_input, data_format, - use_gpu): + use_gpu, max_err=0.002): input_shape = [batch, input_rows, input_cols, in_depth] filter_shape = [filter_rows, filter_cols, in_depth, out_depth] # TODO(yangke): re-factor the computation of output shape. if padding == "VALID": output_rows = (input_rows - filter_rows + stride_rows) // stride_rows output_cols = (input_cols - filter_cols + stride_cols) // stride_cols - else: + elif padding == "SAME": output_rows = (input_rows + stride_rows - 1) // stride_rows output_cols = (input_cols + stride_cols - 1) // stride_cols + else: + self.assertIsInstance(padding, (list, tuple)) + output_rows = (input_rows + padding[1][0] + padding[1][1] - filter_rows + + stride_rows) // stride_rows + output_cols = (input_cols + padding[2][0] + padding[2][1] - filter_cols + + stride_cols) // stride_cols output_shape = [batch, output_rows, output_cols, out_depth] input_size = 1 for x in input_shape: @@ -1145,16 +1664,19 @@ class Conv2DTest(test.TestCase): filter_tensor = constant_op.constant( filter_data, shape=filter_shape, dtype=dtype, name="filter") strides = [1, stride_rows, stride_cols, 1] + new_padding = padding if data_format == "NCHW": new_input_tensor = test_util.NHWCToNCHW(input_tensor) strides = test_util.NHWCToNCHW(strides) + if isinstance(padding, (list, tuple)): + new_padding = test_util.NHWCToNCHW(padding) else: new_input_tensor = input_tensor conv = nn_ops.conv2d( new_input_tensor, filter_tensor, strides, - padding, + new_padding, data_format=data_format, name="conv") if data_format == "NCHW": @@ -1178,8 +1700,8 @@ class Conv2DTest(test.TestCase): # since fp16 numerical gradients are too imprecise. err = np.fabs(jacob_t - reference_jacob_t).max() - tf_logging.info("conv_2d gradient error = ", err) - self.assertLess(err, 0.002) + tf_logging.debug("conv_2d gradient error = %s", err) + self.assertLess(err, max_err) def testInputGradientValidPaddingStrideOne(self): for (data_format, use_gpu) in GetTestConfigs(): @@ -1436,6 +1958,248 @@ class Conv2DTest(test.TestCase): data_format=data_format, use_gpu=use_gpu) + def testInputGradient1x1PaddingStrideOne(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=5, + input_cols=4, + filter_rows=3, + filter_cols=3, + in_depth=2, + out_depth=3, + stride_rows=1, + stride_cols=1, + padding=[[0, 0], [1, 1], [1, 1], [0, 0]], + test_input=True, + data_format=data_format, + use_gpu=use_gpu, + max_err=0.0025) + + def testFilterGradient1x1PaddingStrideOne(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=5, + input_cols=4, + filter_rows=3, + filter_cols=3, + in_depth=2, + out_depth=3, + stride_rows=1, + stride_cols=1, + padding=[[0, 0], [1, 1], [1, 1], [0, 0]], + test_input=False, + data_format=data_format, + use_gpu=use_gpu) + + def testInputGradient1x1PaddingStrideTwo(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=4, + input_cols=5, + filter_rows=3, + filter_cols=3, + in_depth=2, + out_depth=3, + stride_rows=2, + stride_cols=2, + padding=[[0, 0], [1, 1], [1, 1], [0, 0]], + test_input=True, + data_format=data_format, + use_gpu=use_gpu) + + def testFilterGradient1x1PaddingStrideTwo(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=4, + input_cols=5, + filter_rows=3, + filter_cols=3, + in_depth=2, + out_depth=3, + stride_rows=2, + stride_cols=2, + padding=[[0, 0], [1, 1], [1, 1], [0, 0]], + test_input=False, + data_format=data_format, + use_gpu=use_gpu) + + def testInputGradient2x2PaddingStrideOne(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=5, + input_cols=4, + filter_rows=3, + filter_cols=3, + in_depth=2, + out_depth=3, + stride_rows=1, + stride_cols=1, + padding=[[0, 0], [2, 2], [2, 2], [0, 0]], + test_input=True, + data_format=data_format, + use_gpu=use_gpu) + + def testFilterGradient2x2PaddingStrideOne(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=5, + input_cols=4, + filter_rows=3, + filter_cols=3, + in_depth=2, + out_depth=3, + stride_rows=1, + stride_cols=1, + padding=[[0, 0], [2, 2], [2, 2], [0, 0]], + test_input=False, + data_format=data_format, + use_gpu=use_gpu, + max_err=0.003) + + def testInputGradient1_2_3_4PaddingStride3x2(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=8, + input_cols=5, + filter_rows=4, + filter_cols=2, + in_depth=3, + out_depth=2, + stride_rows=3, + stride_cols=2, + padding=[[0, 0], [1, 2], [3, 4], [0, 0]], + test_input=True, + data_format=data_format, + use_gpu=use_gpu) + + def testFilterGradient1_2_3_4PaddingStride3x2(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=8, + input_cols=5, + filter_rows=4, + filter_cols=2, + in_depth=3, + out_depth=2, + stride_rows=3, + stride_cols=2, + padding=[[0, 0], [1, 2], [3, 4], [0, 0]], + test_input=False, + data_format=data_format, + use_gpu=use_gpu) + + def testInputGradient4_3_2_1PaddingStride2x1(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=3, + input_rows=5, + input_cols=7, + filter_rows=3, + filter_cols=2, + in_depth=1, + out_depth=2, + stride_rows=2, + stride_cols=1, + padding=[[0, 0], [4, 3], [2, 1], [0, 0]], + test_input=True, + data_format=data_format, + use_gpu=use_gpu) + + def testFilterGradient4_3_2_1PaddingStride2x1(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=3, + input_rows=5, + input_cols=7, + filter_rows=3, + filter_cols=2, + in_depth=1, + out_depth=2, + stride_rows=2, + stride_cols=1, + padding=[[0, 0], [4, 3], [2, 1], [0, 0]], + test_input=False, + data_format=data_format, + use_gpu=use_gpu) + + def testInputGradient0_0_0_5PaddingStride1x2(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=6, + input_cols=7, + filter_rows=3, + filter_cols=4, + in_depth=3, + out_depth=2, + stride_rows=1, + stride_cols=2, + padding=[[0, 0], [0, 0], [0, 5], [0, 0]], + test_input=True, + data_format=data_format, + use_gpu=use_gpu) + + def testFilterGradient0_0_0_5PaddingStride1x2(self): + if not test.is_gpu_available(cuda_only=True): + return + for (data_format, use_gpu) in GetTestConfigs(): + if use_gpu: + self.ConstructAndTestGradient( + batch=2, + input_rows=6, + input_cols=7, + filter_rows=3, + filter_cols=4, + in_depth=3, + out_depth=2, + stride_rows=1, + stride_cols=2, + padding=[[0, 0], [0, 0], [0, 5], [0, 0]], + test_input=False, + data_format=data_format, + use_gpu=use_gpu) + def testShapeFunctionEdgeCases(self): # All shapes unknown. c1 = nn_ops.conv2d( @@ -1473,6 +2237,55 @@ class Conv2DTest(test.TestCase): strides=[1, 1, 1, 1], padding="SAME") + # Negative padding. + with self.assertRaises(ValueError): + nn_ops.conv2d( + array_ops.placeholder(dtypes.float32), + array_ops.placeholder(dtypes.float32), + strides=[1, 1, 1, 1], + padding=[[0, 0], [0, -1], [1, 2], [0, 0]]) + + # Nonzero padding in nonspatial dimension. + with self.assertRaises(ValueError): + nn_ops.conv2d( + array_ops.placeholder(dtypes.float32), + array_ops.placeholder(dtypes.float32), + strides=[1, 1, 1, 1], + padding=[[1, 0], [0, 0], [0, 0], [0, 0]]) + + # Nonzero NCHW padding in nonspatial dimension. + with self.assertRaises(ValueError): + nn_ops.conv2d( + array_ops.placeholder(dtypes.float32), + array_ops.placeholder(dtypes.float32), + strides=[1, 1, 1, 1], + padding=[[0, 0], [0, 1], [0, 0], [0, 0]], + data_format="NCHW") + + # Wrong amount of padding + with self.assertRaises(ValueError): + nn_ops.conv2d( + array_ops.placeholder(dtypes.float32), + array_ops.placeholder(dtypes.float32), + strides=[1, 1, 1, 1], + padding=[[0, 0], [0, 0], [0, 0]]) + + # Only specify one padding amount per dimension + with self.assertRaises(ValueError): + nn_ops.conv2d( + array_ops.placeholder(dtypes.float32), + array_ops.placeholder(dtypes.float32), + strides=[1, 1, 1, 1], + padding=[[0], [0], [0], [0]]) + + # Explicit padding elements are not lists + with self.assertRaises(ValueError): + nn_ops.conv2d( + array_ops.placeholder(dtypes.float32), + array_ops.placeholder(dtypes.float32), + strides=[1, 1, 1, 1], + padding=[0, 0, 0, 0]) + def testOpEdgeCases(self): with self.cached_session() as sess: # Illegal strides. @@ -1513,6 +2326,41 @@ class Conv2DTest(test.TestCase): strides=[1, 1, 1, 1], padding="VALID")) + # Filter larger than input + padding. + with self.assertRaisesRegexp(ValueError, "Negative dimension size"): + sess.run( + nn_ops.conv2d( + array_ops.placeholder(dtypes.float32, shape=[32, 20, 20, 3]), + array_ops.placeholder(dtypes.float32, shape=[24, 25, 3, 2]), + strides=[1, 1, 1, 1], + padding=[[0, 0], [2, 2], [2, 2], [0, 0]])) + + if test.is_gpu_available(cuda_only=True): + with self.test_session(use_gpu=True): + # Negative padding during backprop. + with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, + "nonnegative"): + sess.run( + nn_ops.conv2d_backprop_input([32, 20, 20, 3], + array_ops.placeholder( + dtypes.float32, + shape=[18, 18, 3, 2]), + array_ops.placeholder( + dtypes.float32, + shape=[32, 3, 2, 2]), + strides=[1, 1, 1, 1], + padding=[[0, 0], [-1, 0], [0, 0], + [0, 0]])) + with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, + "nonnegative"): + sess.run( + nn_ops.conv2d_backprop_filter( + array_ops.placeholder(dtypes.float32, shape=[32, 20, 20, 3]), + [18, 18, 3, 2], + array_ops.placeholder(dtypes.float32, shape=[32, 3, 2, 2]), + strides=[1, 1, 1, 1], + padding=[[0, 0], [-1, 0], [0, 0], [0, 0]])) + class DepthwiseConv2DTest(test.TestCase): @@ -1546,7 +2394,7 @@ class DepthwiseConv2DTest(test.TestCase): conv = nn_impl.depthwise_conv2d( t1, t2, strides=[1, stride, stride, 1], padding=padding) value = self.evaluate(conv) - tf_logging.info("value = ", value) + tf_logging.debug("value = %s", value) self.assertArrayNear(expected, np.ravel(value), 1e-5) self.assertShapeEqual(value, conv) @@ -1668,7 +2516,7 @@ class SeparableConv2DTest(test.TestCase): conv = array_ops.transpose(conv, [0, 2, 3, 1]) value = self.evaluate(conv) - tf_logging.info("value = ", value) + tf_logging.debug("value = %s", value) self.assertArrayNear(expected, np.ravel(value), 1e-3) self.assertShapeEqual(value, conv) @@ -1828,6 +2676,194 @@ class Conv2DBenchmark(test.Benchmark): name="conv_stack_iter_%d" % iter_index, wall_time=wall_time) tf_logging.info("conv_stack_iter_%d: %.4f" % (iter_index, wall_time)) + def _bench_op(self, name, op, burn_iters, num_iters): + config = config_pb2.ConfigProto() + # Prevent Grappler from optimizing away the entire graph. + config.graph_options.rewrite_options.dependency_optimization = ( + rewriter_config_pb2.RewriterConfig.OFF) + with session_lib.Session(config=config) as session: + variables.global_variables_initializer().run() + self.run_op_benchmark( + session, op, burn_iters=burn_iters, min_iters=num_iters, name=name) + + def benchmarkExplicitVsManualPadding(self): + """Compare performance of EXPLICIT padding and calling tf.pad. + + A Conv2D op with EXPLICIT padding is benchmarked, and a tf.pad with the same + padding followed by an equivalent Conv2D op is benchmarked. + """ + if not test.is_gpu_available(): + return + + with ops.Graph().as_default(): + burn_iters = 15 + num_iters = 300 + batch_size = 64 + # The input and filter correspond to the first layer of Resnet50. + input = variables.Variable( # pylint: disable=redefined-builtin + random_ops.random_uniform([ + batch_size, + 3, + 224, + 224 + ])) + filter = variables.Variable(random_ops.random_uniform([7, 7, 3, 64])) # pylint: disable=redefined-builtin + strides = [1, 1, 2, 2] + padding = [(0, 0), (0, 0), (3, 3), (3, 3)] + output_explicit_pad = nn_ops.conv2d( + input, filter, strides, padding=padding, data_format="NCHW") + input_padded = array_ops.pad(input, padding) + output_manual_pad = nn_ops.conv2d( + input_padded, filter, strides, padding="VALID", data_format="NCHW") + # Benchmark just the forward pass. + self._bench_op("explicit_pad_forward", output_explicit_pad.op, burn_iters, + num_iters) + self._bench_op("manual_pad_forward", output_manual_pad.op, burn_iters, + num_iters) + + # Benchmark both the forward and backwards passes. + input_grad_explicit_pad, filter_grad_explicit_pad = ( + gradients_impl.gradients(output_explicit_pad, [input, filter])) + self._bench_op( + "explicit_pad_backward", + control_flow_ops.group(input_grad_explicit_pad, + filter_grad_explicit_pad), burn_iters, + num_iters) + input_grad_manual_pad, filter_grad_manual_pad = gradients_impl.gradients( + output_manual_pad, [input, filter]) + self._bench_op( + "manual_pad_backward", + control_flow_ops.group(input_grad_manual_pad, filter_grad_manual_pad), + burn_iters, num_iters) + + def benchmarkExplicitVsSamePaddingGraph(self): + """Compare performance of EXPLICIT and SAME padding in graph mode. + + A Conv2D op with SAME padding is benchmarked, and an equivalent Conv2D op + with explicit padding is benchmarked, where the padding is the same as in + the SAME case. The purpose is to ensure EXPLICIT padding is just as + efficient as the SAME case + """ + if not test.is_gpu_available(): + return + + with ops.Graph().as_default(): + burn_iters = 15 + num_convs = 20 + num_iters = 50 + batch_size = 64 + # The input and filter correspond to a middle layer of Resnet50. + input = variables.Variable( # pylint: disable=redefined-builtin + random_ops.random_uniform([ + batch_size, + 256, + 14, + 14 + ])) + filter = variables.Variable(random_ops.random_uniform([3, 3, 256, 256])) # pylint: disable=redefined-builtin + strides = [1, 1, 1, 1] + padding = [(0, 0), (0, 0), (1, 1), (1, 1)] + output_explicit_pad = input + output_same_pad = input + + for _ in range(num_convs): + output_explicit_pad = nn_ops.conv2d( + output_explicit_pad, + filter, + strides, + padding=padding, + data_format="NCHW") + output_same_pad = nn_ops.conv2d( + output_same_pad, + filter, + strides, + padding="SAME", + data_format="NCHW") + grad_explicit_pad, = gradients_impl.gradients(output_explicit_pad, filter) + grad_same_pad, = gradients_impl.gradients(output_same_pad, filter) + self._bench_op("graph_explicit_pad", grad_explicit_pad.op, burn_iters, + num_iters) + self._bench_op("graph_same_pad", grad_same_pad.op, burn_iters, num_iters) + + def benchmarkExplicitVsSamePaddingEager(self): + """Compare performance of EXPLICIT and SAME padding in eager mode. + + A Conv2D op with SAME padding is benchmarked, and an equivalent Conv2D op + with explicit padding is benchmarked, where the padding is the same as in + the SAME case. Currently, EXPLICIT padding is slightly slower, due to the + fact the Python padding list must be checked and processed before the Conv2D + op can run. + """ + # TODO(reedwm): Make EXPLICIT padding as fast as SAME padding. + if not test.is_gpu_available(): + return + + with context.eager_mode(): + burn_iters = 15 + num_convs = 20 + num_iters = 50 + batch_size = 64 + # The input and filter correspond to a middle layer of Resnet50. + input = variables.Variable( # pylint: disable=redefined-builtin + random_ops.random_uniform([ + batch_size, + 256, + 14, + 14 + ])) + filter = variables.Variable(random_ops.random_uniform([3, 3, 256, 256])) # pylint: disable=redefined-builtin + strides = [1, 1, 1, 1] + padding = [(0, 0), (0, 0), (1, 1), (1, 1)] + output_explicit_pad = input + output_same_pad = input + for _ in range(burn_iters): + output_explicit_pad = nn_ops.conv2d( + output_explicit_pad, + filter, + strides, + padding=padding, + data_format="NCHW") + output_same_pad = nn_ops.conv2d( + output_same_pad, + filter, + strides, + padding="SAME", + data_format="NCHW") + + start = time.time() + for _ in range(num_iters): + with backprop.GradientTape() as tape: + for _ in range(num_convs): + output_explicit_pad = nn_ops.conv2d( + output_explicit_pad, + filter, + strides, + padding=padding, + data_format="NCHW") + tape.gradient(output_explicit_pad, filter) + end = time.time() + self.report_benchmark( + name="eager_explicit_pad", + wall_time=(end - start) / num_iters, + iters=num_iters) + + start = time.time() + for _ in range(num_iters): + with backprop.GradientTape() as tape: + for _ in range(num_convs): + output_same_pad = nn_ops.conv2d( + output_same_pad, + filter, + strides, + padding="SAME", + data_format="NCHW") + tape.gradient(output_same_pad, filter) + end = time.time() + self.report_benchmark( + name="eager_same_pad", + wall_time=(end - start) / num_iters, + iters=num_iters) + def GetInceptionFwdTest(input_size, filter_size, stride, padding, gpu_only=False): diff --git a/tensorflow/python/ops/nn_grad.py b/tensorflow/python/ops/nn_grad.py index 7131e4abc4..6ca2b2aafe 100644 --- a/tensorflow/python/ops/nn_grad.py +++ b/tensorflow/python/ops/nn_grad.py @@ -514,29 +514,40 @@ def _SparseSoftmaxCrossEntropyWithLogitsGrad(op, grad_0, _): @ops.RegisterGradient("Conv2D") def _Conv2DGrad(op, grad): + """Gradient function for Conv2D.""" dilations = op.get_attr("dilations") strides = op.get_attr("strides") padding = op.get_attr("padding") + explicit_paddings = op.get_attr("explicit_paddings") use_cudnn_on_gpu = op.get_attr("use_cudnn_on_gpu") data_format = op.get_attr("data_format") shape_0, shape_1 = array_ops.shape_n([op.inputs[0], op.inputs[1]]) + + # We call the gen_nn_ops backprop functions instead of nn_ops backprop + # functions for performance reasons in Eager mode. gen_nn_ops functions take a + # `explicit_paddings` parameter, but nn_ops functions do not. So if were were + # to use the nn_ops functions, we would have to convert `padding` and + # `explicit_paddings` into a single `padding` parameter, increasing overhead + # in Eager mode. return [ - nn_ops.conv2d_backprop_input( + gen_nn_ops.conv2d_backprop_input( shape_0, op.inputs[1], grad, dilations=dilations, strides=strides, padding=padding, + explicit_paddings=explicit_paddings, use_cudnn_on_gpu=use_cudnn_on_gpu, data_format=data_format), - nn_ops.conv2d_backprop_filter( + gen_nn_ops.conv2d_backprop_filter( op.inputs[0], shape_1, grad, dilations=dilations, strides=strides, padding=padding, + explicit_paddings=explicit_paddings, use_cudnn_on_gpu=use_cudnn_on_gpu, data_format=data_format) ] diff --git a/tensorflow/python/ops/nn_ops.py b/tensorflow/python/ops/nn_ops.py index 6f2d2c15bd..f71fcef130 100644 --- a/tensorflow/python/ops/nn_ops.py +++ b/tensorflow/python/ops/nn_ops.py @@ -171,7 +171,7 @@ class _NonAtrousConvolution(object): raise ValueError("data_format must be \"NHWC\" or \"NCHW\".") self.strides = strides self.data_format = data_format - self.conv_op = gen_nn_ops.conv2d + self.conv_op = conv2d elif conv_dims == 3: if data_format is None or data_format == "NDHWC": strides = [1] + list(strides) + [1] @@ -1373,6 +1373,44 @@ def atrous_conv2d(value, filters, rate, padding, name=None): name=name) +def _convert_padding(padding): + """Converts Python padding to C++ padding for ops which take EXPLICIT padding. + + Args: + padding: the `padding` argument for a Python op which supports EXPLICIT + padding. + + Returns: + (padding, explicit_paddings) pair, which should be passed as attributes to a + C++ op. + + Raises: + ValueError: If padding is invalid. + """ + explicit_paddings = [] + if padding == "EXPLICIT": + # Give a better error message if EXPLICIT is passed. + raise ValueError('"EXPLICIT" is not a valid value for the padding ' + "parameter. To use explicit padding, the padding " + "parameter must be a list.") + if isinstance(padding, (list, tuple)): + for i, dim_paddings in enumerate(padding): + if not isinstance(dim_paddings, (list, tuple)): + raise ValueError("When padding is a list, each element of padding must " + "be a list/tuple of size 2. Element with index %d of " + "padding is not a list/tuple" % i) + if len(dim_paddings) != 2: + raise ValueError("When padding is a list, each element of padding must " + "be a list/tuple of size 2. Element with index %d of " + "padding has size %d" % (i, len(dim_paddings))) + explicit_paddings.extend(dim_paddings) + if len(padding) != 4: + raise ValueError("When padding is a list, it must be of size 4. Got " + "padding of size: %d" % len(padding)) + padding = "EXPLICIT" + return padding, explicit_paddings + + @tf_export("nn.conv2d", v1=[]) def conv2d_v2(input, # pylint: disable=redefined-builtin filters, @@ -1418,8 +1456,13 @@ def conv2d_v2(input, # pylint: disable=redefined-builtin 1-D tensor of length 4. The stride of the sliding window for each dimension of `input`. The dimension order is determined by the value of `data_format`, see below for details. - padding: A `string` from: `"SAME", "VALID"`. - The type of padding algorithm to use. + padding: Either the `string `"SAME"` or `"VALID"` indicating the type of + padding algorithm to use, or a list indicating the explicit paddings at + the start and end of each dimension. When explicit padding is used and + data_format is `"NHWC"`, this should be in the form `[[0, 0], [pad_top, + pad_bottom], [pad_left, pad_right], [0, 0]]`. When explicit padding used + and data_format is `"NCHW"`, this should be in the form `[[0, 0], [0, 0], + [pad_top, pad_bottom], [pad_left, pad_right]]`. data_format: An optional `string` from: `"NHWC", "NCHW"`. Defaults to `"NHWC"`. Specify the data format of the input and output data. With the @@ -1441,15 +1484,98 @@ def conv2d_v2(input, # pylint: disable=redefined-builtin # pylint: enable=line-too-long if dilations is None: dilations = [1, 1, 1, 1] + return conv2d(input, # pylint: disable=redefined-builtin + filters, + strides, + padding, + use_cudnn_on_gpu=True, + data_format=data_format, + dilations=dilations, + name=name) + + +@tf_export(v1=["nn.conv2d"]) +def conv2d( # pylint: disable=redefined-builtin,dangerous-default-value + input, + filter, + strides, + padding, + use_cudnn_on_gpu=True, + data_format="NHWC", + dilations=[1, 1, 1, 1], + name=None): + r"""Computes a 2-D convolution given 4-D `input` and `filter` tensors. + + Given an input tensor of shape `[batch, in_height, in_width, in_channels]` + and a filter / kernel tensor of shape + `[filter_height, filter_width, in_channels, out_channels]`, this op + performs the following: + + 1. Flattens the filter to a 2-D matrix with shape + `[filter_height * filter_width * in_channels, output_channels]`. + 2. Extracts image patches from the input tensor to form a *virtual* + tensor of shape `[batch, out_height, out_width, + filter_height * filter_width * in_channels]`. + 3. For each patch, right-multiplies the filter matrix and the image patch + vector. + + In detail, with the default NHWC format, + + output[b, i, j, k] = + sum_{di, dj, q} input[b, strides[1] * i + di, strides[2] * j + dj, q] + * filter[di, dj, q, k] + + Must have `strides[0] = strides[3] = 1`. For the most common case of the same + horizontal and vertices strides, `strides = [1, stride, stride, 1]`. + + Args: + input: A `Tensor`. Must be one of the following types: + `half`, `bfloat16`, `float32`, `float64`. + A 4-D tensor. The dimension order is interpreted according to the value + of `data_format`, see below for details. + filter: A `Tensor`. Must have the same type as `input`. + A 4-D tensor of shape + `[filter_height, filter_width, in_channels, out_channels]` + strides: A list of `ints`. + 1-D tensor of length 4. The stride of the sliding window for each + dimension of `input`. The dimension order is determined by the value of + `data_format`, see below for details. + padding: Either the `string `"SAME"` or `"VALID"` indicating the type of + padding algorithm to use, or a list indicating the explicit paddings at + the start and end of each dimension. When explicit padding is used and + data_format is `"NHWC"`, this should be in the form `[[0, 0], [pad_top, + pad_bottom], [pad_left, pad_right], [0, 0]]`. When explicit padding used + and data_format is `"NCHW"`, this should be in the form `[[0, 0], [0, 0], + [pad_top, pad_bottom], [pad_left, pad_right]]`. + use_cudnn_on_gpu: An optional `bool`. Defaults to `True`. + data_format: An optional `string` from: `"NHWC", "NCHW"`. + Defaults to `"NHWC"`. + Specify the data format of the input and output data. With the + default format "NHWC", the data is stored in the order of: + [batch, height, width, channels]. + Alternatively, the format could be "NCHW", the data storage order of: + [batch, channels, height, width]. + dilations: An optional list of `ints`. Defaults to `[1, 1, 1, 1]`. + 1-D tensor of length 4. The dilation factor for each dimension of + `input`. If set to k > 1, there will be k-1 skipped cells between each + filter element on that dimension. The dimension order is determined by the + value of `data_format`, see above for details. Dilations in the batch and + depth dimensions must be 1. + name: A name for the operation (optional). + + Returns: + A `Tensor`. Has the same type as `input`. + """ + padding, explicit_paddings = _convert_padding(padding) return gen_nn_ops.conv2d(input, # pylint: disable=redefined-builtin - filters, + filter, strides, padding, - use_cudnn_on_gpu=True, + use_cudnn_on_gpu=use_cudnn_on_gpu, + explicit_paddings=explicit_paddings, data_format=data_format, dilations=dilations, name=name) -tf_export(v1=["nn.conv2d"])(gen_nn_ops.conv2d) @tf_export("nn.conv2d_backprop_filter", v1=[]) @@ -1478,8 +1604,13 @@ def conv2d_backprop_filter_v2(input, # pylint: disable=redefined-builtin The stride of the sliding window for each dimension of the input of the convolution. Must be in the same order as the dimension specified with format. - padding: A `string` from: `"SAME", "VALID"`. - The type of padding algorithm to use. + padding: Either the `string `"SAME"` or `"VALID"` indicating the type of + padding algorithm to use, or a list indicating the explicit paddings at + the start and end of each dimension. When explicit padding is used and + data_format is `"NHWC"`, this should be in the form `[[0, 0], [pad_top, + pad_bottom], [pad_left, pad_right], [0, 0]]`. When explicit padding used + and data_format is `"NCHW"`, this should be in the form `[[0, 0], [0, 0], + [pad_top, pad_bottom], [pad_left, pad_right]]`. data_format: An optional `string` from: `"NHWC", "NCHW"`. Defaults to `"NHWC"`. Specify the data format of the input and output data. With the @@ -1500,17 +1631,75 @@ def conv2d_backprop_filter_v2(input, # pylint: disable=redefined-builtin """ if dilations is None: dilations = [1, 1, 1, 1] - return gen_nn_ops.conv2d_backprop_filter(input, # pylint: disable=redefined-builtin - filter_sizes, - out_backprop, - strides, - padding, - use_cudnn_on_gpu=True, - data_format=data_format, - dilations=dilations, - name=name) -tf_export(v1=["nn.conv2d_backprop_filter"])( - gen_nn_ops.conv2d_backprop_filter) + return conv2d_backprop_filter(input, # pylint: disable=redefined-builtin + filter_sizes, + out_backprop, + strides, + padding, + use_cudnn_on_gpu=True, + data_format=data_format, + dilations=dilations, + name=name) + + +@tf_export(v1=["nn.conv2d_backprop_filter"]) +def conv2d_backprop_filter( # pylint: disable=redefined-builtin,dangerous-default-value + input, + filter_sizes, + out_backprop, + strides, + padding, + use_cudnn_on_gpu=True, + data_format="NHWC", + dilations=[1, 1, 1, 1], + name=None): + r"""Computes the gradients of convolution with respect to the filter. + + Args: + input: A `Tensor`. Must be one of the following types: + `half`, `bfloat16`, `float32`, `float64`. + 4-D with shape `[batch, in_height, in_width, in_channels]`. + filter_sizes: A `Tensor` of type `int32`. + An integer vector representing the tensor shape of `filter`, + where `filter` is a 4-D + `[filter_height, filter_width, in_channels, out_channels]` tensor. + out_backprop: A `Tensor`. Must have the same type as `input`. + 4-D with shape `[batch, out_height, out_width, out_channels]`. + Gradients w.r.t. the output of the convolution. + strides: A list of `ints`. + The stride of the sliding window for each dimension of the input + of the convolution. Must be in the same order as the dimension specified + with format. + padding: Either the `string `"SAME"` or `"VALID"` indicating the type of + padding algorithm to use, or a list indicating the explicit paddings at + the start and end of each dimension. When explicit padding is used and + data_format is `"NHWC"`, this should be in the form `[[0, 0], [pad_top, + pad_bottom], [pad_left, pad_right], [0, 0]]`. When explicit padding used + and data_format is `"NCHW"`, this should be in the form `[[0, 0], [0, 0], + [pad_top, pad_bottom], [pad_left, pad_right]]`. + use_cudnn_on_gpu: An optional `bool`. Defaults to `True`. + data_format: An optional `string` from: `"NHWC", "NCHW"`. + Defaults to `"NHWC"`. + Specify the data format of the input and output data. With the + default format "NHWC", the data is stored in the order of: + [batch, in_height, in_width, in_channels]. + Alternatively, the format could be "NCHW", the data storage order of: + [batch, in_channels, in_height, in_width]. + dilations: An optional list of `ints`. Defaults to `[1, 1, 1, 1]`. + 1-D tensor of length 4. The dilation factor for each dimension of + `input`. If set to k > 1, there will be k-1 skipped cells between each + filter element on that dimension. The dimension order is determined by + the value of `data_format`, see above for details. Dilations in the batch + and depth dimensions must be 1. + name: A name for the operation (optional). + + Returns: + A `Tensor`. Has the same type as `input`. + """ + padding, explicit_paddings = _convert_padding(padding) + return gen_nn_ops.conv2d_backprop_filter( + input, filter_sizes, out_backprop, strides, padding, use_cudnn_on_gpu, + explicit_paddings, data_format, dilations, name) @tf_export("nn.conv2d_backprop_input", v1=[]) @@ -1539,8 +1728,13 @@ def conv2d_backprop_input_v2(input_sizes, The stride of the sliding window for each dimension of the input of the convolution. Must be in the same order as the dimension specified with format. - padding: A `string` from: `"SAME", "VALID"`. - The type of padding algorithm to use. + padding: Either the `string `"SAME"` or `"VALID"` indicating the type of + padding algorithm to use, or a list indicating the explicit paddings at + the start and end of each dimension. When explicit padding is used and + data_format is `"NHWC"`, this should be in the form `[[0, 0], [pad_top, + pad_bottom], [pad_left, pad_right], [0, 0]]`. When explicit padding used + and data_format is `"NCHW"`, this should be in the form `[[0, 0], [0, 0], + [pad_top, pad_bottom], [pad_left, pad_right]]`. data_format: An optional `string` from: `"NHWC", "NCHW"`. Defaults to `"NHWC"`. Specify the data format of the input and output data. With the @@ -1561,17 +1755,75 @@ def conv2d_backprop_input_v2(input_sizes, """ if dilations is None: dilations = [1, 1, 1, 1] - return gen_nn_ops.conv2d_backprop_input(input_sizes, - filters, - out_backprop, - strides, - padding, - use_cudnn_on_gpu=True, - data_format=data_format, - dilations=dilations, - name=name) -tf_export(v1=["nn.conv2d_backprop_input"])( - gen_nn_ops.conv2d_backprop_input) + return conv2d_backprop_input(input_sizes, + filters, + out_backprop, + strides, + padding, + use_cudnn_on_gpu=True, + data_format=data_format, + dilations=dilations, + name=name) + + +@tf_export(v1=["nn.conv2d_backprop_input"]) +def conv2d_backprop_input( # pylint: disable=redefined-builtin,dangerous-default-value + input_sizes, + filter, + out_backprop, + strides, + padding, + use_cudnn_on_gpu=True, + data_format="NHWC", + dilations=[1, 1, 1, 1], + name=None): + r"""Computes the gradients of convolution with respect to the input. + + Args: + input_sizes: A `Tensor` of type `int32`. + An integer vector representing the shape of `input`, + where `input` is a 4-D `[batch, height, width, channels]` tensor. + filter: A `Tensor`. Must be one of the following types: + `half`, `bfloat16`, `float32`, `float64`. + 4-D with shape + `[filter_height, filter_width, in_channels, out_channels]`. + out_backprop: A `Tensor`. Must have the same type as `filter`. + 4-D with shape `[batch, out_height, out_width, out_channels]`. + Gradients w.r.t. the output of the convolution. + strides: A list of `ints`. + The stride of the sliding window for each dimension of the input + of the convolution. Must be in the same order as the dimension specified + with format. + padding: Either the `string `"SAME"` or `"VALID"` indicating the type of + padding algorithm to use, or a list indicating the explicit paddings at + the start and end of each dimension. When explicit padding is used and + data_format is `"NHWC"`, this should be in the form `[[0, 0], [pad_top, + pad_bottom], [pad_left, pad_right], [0, 0]]`. When explicit padding used + and data_format is `"NCHW"`, this should be in the form `[[0, 0], [0, 0], + [pad_top, pad_bottom], [pad_left, pad_right]]`. + use_cudnn_on_gpu: An optional `bool`. Defaults to `True`. + data_format: An optional `string` from: `"NHWC", "NCHW"`. + Defaults to `"NHWC"`. + Specify the data format of the input and output data. With the + default format "NHWC", the data is stored in the order of: + [batch, in_height, in_width, in_channels]. + Alternatively, the format could be "NCHW", the data storage order of: + [batch, in_channels, in_height, in_width]. + dilations: An optional list of `ints`. Defaults to `[1, 1, 1, 1]`. + 1-D tensor of length 4. The dilation factor for each dimension of + `input`. If set to k > 1, there will be k-1 skipped cells between each + filter element on that dimension. The dimension order is determined by + the value of `data_format`, see above for details. Dilations in the batch + and depth dimensions must be 1. + name: A name for the operation (optional). + + Returns: + A `Tensor`. Has the same type as `filter`. + """ + padding, explicit_paddings = _convert_padding(padding) + return gen_nn_ops.conv2d_backprop_input( + input_sizes, filter, out_backprop, strides, padding, use_cudnn_on_gpu, + explicit_paddings, data_format, dilations, name) @tf_export(v1=["nn.conv2d_transpose"]) -- GitLab From e3b8fedc923d03fd5f388daf444b42b22dd909b8 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Tue, 8 Jan 2019 18:56:15 -0800 Subject: [PATCH 0398/2345] [XLA] Propagate compiler specific ShapeSizeFunction into TargetVerifierMetadata. PiperOrigin-RevId: 228440043 --- .../compiler/xla/service/hlo_verifier.h | 33 ++++++++++++------- .../compiler/xla/tests/hlo_test_base.cc | 6 ++-- tensorflow/compiler/xla/tests/hlo_test_base.h | 8 +++-- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/tensorflow/compiler/xla/service/hlo_verifier.h b/tensorflow/compiler/xla/service/hlo_verifier.h index a1a6aba972..479905b317 100644 --- a/tensorflow/compiler/xla/service/hlo_verifier.h +++ b/tensorflow/compiler/xla/service/hlo_verifier.h @@ -168,8 +168,13 @@ class ShapeVerifier : public DfsHloVisitor { // An interface used to encapsulate target-specific verification quirks. class TargetVerifierMetadata { public: + TargetVerifierMetadata(std::function shape_size_function) + : shape_size_function_(shape_size_function) {} + // Returns a target-specific shape size. - virtual int64 ShapeSize(const Shape& shape) const = 0; + int64 ShapeSize(const Shape& shape) const { + return shape_size_function_(shape); + } virtual std::unique_ptr GetVerifier() const = 0; @@ -178,20 +183,23 @@ class TargetVerifierMetadata { TargetVerifierMetadata(const TargetVerifierMetadata&) = delete; TargetVerifierMetadata& operator=(const TargetVerifierMetadata&) = delete; + + private: + // Returns a target-specific shape size. + std::function shape_size_function_; }; // The default implementation of TargetVerifierMetadata, used unless the target // needs to override it. class DefaultVerifierMetadata : public TargetVerifierMetadata { public: - DefaultVerifierMetadata(bool layout_sensitive, bool allow_mixed_precision) - : layout_sensitive_(layout_sensitive), + DefaultVerifierMetadata( + bool layout_sensitive, bool allow_mixed_precision, + std::function shape_size_function) + : TargetVerifierMetadata(shape_size_function), + layout_sensitive_(layout_sensitive), allow_mixed_precision_(allow_mixed_precision) {} - int64 ShapeSize(const Shape& shape) const override { - return ShapeUtil::ByteSizeOf(shape); - } - // Creates a ShapeVerifier that checks that shapes match inferred // expectations. This creates a new verifier every time because ShapeVerifier, // being a DfsHloVisitor, is stateful. We want a clean object for each run of @@ -210,11 +218,14 @@ class DefaultVerifierMetadata : public TargetVerifierMetadata { // the module. class HloVerifier : public HloModulePass { public: - explicit HloVerifier(bool layout_sensitive, bool allow_mixed_precision, - std::function - instruction_can_change_layout_func = {}) + explicit HloVerifier( + bool layout_sensitive, bool allow_mixed_precision, + std::function + instruction_can_change_layout_func = {}, + std::function shape_size_func = + [](const Shape& shape) { return ShapeUtil::ByteSizeOf(shape); }) : target_metadata_(absl::make_unique( - layout_sensitive, allow_mixed_precision)), + layout_sensitive, allow_mixed_precision, shape_size_func)), instruction_can_change_layout_func_( std::move(instruction_can_change_layout_func)) { CHECK(instruction_can_change_layout_func_ == nullptr || layout_sensitive); diff --git a/tensorflow/compiler/xla/tests/hlo_test_base.cc b/tensorflow/compiler/xla/tests/hlo_test_base.cc index d57846e19b..66f72ba8d2 100644 --- a/tensorflow/compiler/xla/tests/hlo_test_base.cc +++ b/tensorflow/compiler/xla/tests/hlo_test_base.cc @@ -139,7 +139,8 @@ std::unique_ptr HloTestBase::CreateNewVerifiedModule( const string& name) { return absl::make_unique( name, GetModuleConfigForTest(), verifier_layout_sensitive_, - allow_mixed_precision_in_hlo_verifier_); + allow_mixed_precision_in_hlo_verifier_, + backend().compiler()->ShapeSizeBytesFunction()); } StatusOr> @@ -147,7 +148,8 @@ HloTestBase::ParseAndReturnVerifiedModule(absl::string_view hlo_text, const HloModuleConfig& config) { auto module = absl::make_unique( TestName(), config, verifier_layout_sensitive_, - allow_mixed_precision_in_hlo_verifier_); + allow_mixed_precision_in_hlo_verifier_, + backend().compiler()->ShapeSizeBytesFunction()); TF_RETURN_IF_ERROR(ParseHloString(hlo_text, module.get())); TF_RETURN_IF_ERROR(module->Verify()); return std::move(module); diff --git a/tensorflow/compiler/xla/tests/hlo_test_base.h b/tensorflow/compiler/xla/tests/hlo_test_base.h index 1d1e7f4372..69a4f96288 100644 --- a/tensorflow/compiler/xla/tests/hlo_test_base.h +++ b/tensorflow/compiler/xla/tests/hlo_test_base.h @@ -46,10 +46,12 @@ class VerifiedHloModule : public HloModule { public: VerifiedHloModule(const string& name, const HloModuleConfig& config, bool verifier_layout_sensitive, - bool allow_mixed_precision_in_hlo_verifier) + bool allow_mixed_precision_in_hlo_verifier, + std::function shape_size_function) : HloModule(name, config), - verifier_(verifier_layout_sensitive, - allow_mixed_precision_in_hlo_verifier) {} + verifier_( + verifier_layout_sensitive, allow_mixed_precision_in_hlo_verifier, + /*instruction_can_change_layout_func=*/{}, shape_size_function) {} ~VerifiedHloModule() override { VerifyOrAddFailure("in destructor"); } -- GitLab From 6522d752cf3e5d26a507d9c4c517ee7633ae4c57 Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Tue, 8 Jan 2019 19:41:27 -0800 Subject: [PATCH 0399/2345] Removes the hack that mutates an HloInstruction in order to auto-tune the convolution for a specific algorithm. Redemption. PiperOrigin-RevId: 228443963 --- .../service/gpu/cudnn_conv_algorithm_picker.cc | 12 +++++++----- .../service/gpu/cudnn_conv_algorithm_picker.h | 3 ++- .../xla/service/gpu/cudnn_conv_runner.cc | 16 ++++++++++------ .../compiler/xla/service/gpu/cudnn_conv_runner.h | 12 ++++++++++-- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.cc b/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.cc index 6d6780fa1c..057d0d2364 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.cc +++ b/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.cc @@ -146,7 +146,8 @@ tensorflow::mutex_lock LockGpu(const se::StreamExecutor* stream_exec) { // trouble, but we may want to revisit this if we ever find a model where // caching would speed up compilation a lot. StatusOr -CudnnConvAlgorithmPicker::PickBestAlgorithm(HloCustomCallInstruction* instr) { +CudnnConvAlgorithmPicker::PickBestAlgorithm( + const HloCustomCallInstruction* instr) { // TODO(timshen): for now only check fp16. It can be expanded to other types, // with some work on the HLO routines. const bool cross_check_enabled = @@ -249,12 +250,13 @@ CudnnConvAlgorithmPicker::PickBestAlgorithm(HloCustomCallInstruction* instr) { VLOG(3) << "Trying algorithm " << AlgorithmToString(alg) << " for " << instr->ToString(); - backend_config.set_algorithm(alg.algo_id()); - backend_config.set_tensor_ops_enabled(alg.tensor_ops_enabled()); - TF_RETURN_IF_ERROR(instr->set_backend_config(backend_config)); + // Use assignment insetad of brace-list to make GCC 4.9 happy. + RunConvOptions options; + options.profile_result = &profile_result; + options.algo_override = alg; bool launch_ok = RunCudnnConv(instr, absl::MakeSpan(operand_buffers), result_buffer, - &scratch_allocator, &stream, &profile_result) + &scratch_allocator, &stream, options) .ok(); if (launch_ok && profile_result.is_valid()) { diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.h b/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.h index 642af787af..4991db0948 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.h +++ b/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.h @@ -56,7 +56,8 @@ class CudnnConvAlgorithmPicker : public HloModulePass { StatusOr RunOnComputation(HloComputation* computation); StatusOr RunOnInstruction(HloInstruction* instr); - StatusOr PickBestAlgorithm(HloCustomCallInstruction* instr); + StatusOr PickBestAlgorithm( + const HloCustomCallInstruction* instr); se::StreamExecutor* stream_exec_; // never null DeviceMemoryAllocator* allocator_; // may be null diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.cc b/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.cc index 3425e1b494..b628f27f4b 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.cc +++ b/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.cc @@ -395,32 +395,36 @@ Status RunCudnnConv(const HloCustomCallInstruction* conv, absl::Span operand_buffers, se::DeviceMemoryBase result_buffer, se::DeviceMemoryBase scratch_buf, se::Stream* stream, - se::dnn::ProfileResult* profile_result) { + RunConvOptions options) { ScratchBufAllocator scratch_allocator(scratch_buf); return RunCudnnConv(conv, operand_buffers, result_buffer, &scratch_allocator, - stream, profile_result); + stream, options); } Status RunCudnnConv(const HloCustomCallInstruction* conv, absl::Span operand_buffers, se::DeviceMemoryBase result_buffer, se::ScratchAllocator* scratch_allocator, se::Stream* stream, - se::dnn::ProfileResult* profile_result) { + RunConvOptions options) { TF_ASSIGN_OR_RETURN(CudnnConvParams params, GetCudnnConvParams(conv, operand_buffers, result_buffer)); + if (options.algo_override) { + params.algorithm = AlgorithmConfig(*options.algo_override); + } + PrimitiveType output_primitive_type = conv->shape().tuple_shapes(0).element_type(); switch (output_primitive_type) { case F16: return RunCudnnConvImpl(params, scratch_allocator, stream, - profile_result); + options.profile_result); case F32: return RunCudnnConvImpl(params, scratch_allocator, stream, - profile_result); + options.profile_result); case F64: return RunCudnnConvImpl(params, scratch_allocator, stream, - profile_result); + options.profile_result); default: LOG(FATAL) << ShapeUtil::HumanString(*params.output_shape); } diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.h b/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.h index edbc75a94a..7cc325bce3 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.h +++ b/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.h @@ -28,6 +28,14 @@ limitations under the License. namespace xla { namespace gpu { +struct RunConvOptions { + // Nullable output-parameter pointer for profiling results. + se::dnn::ProfileResult* profile_result = nullptr; + + // Use this algorithm, instead the one from the instrcution. + absl::optional algo_override; +}; + // This file contains low-level routines for running cudnn convolutions. // Calls into cudnn to run the specified convolution. @@ -46,13 +54,13 @@ Status RunCudnnConv(const HloCustomCallInstruction* conv, absl::Span operand_buffers, se::DeviceMemoryBase result_buffer, se::DeviceMemoryBase scratch_buf, se::Stream* stream, - se::dnn::ProfileResult* profile_result = nullptr); + RunConvOptions = {}); Status RunCudnnConv(const HloCustomCallInstruction* conv, absl::Span operand_buffers, se::DeviceMemoryBase result_buffer, se::ScratchAllocator* scratch_allocator, se::Stream* stream, - se::dnn::ProfileResult* profile_result = nullptr); + RunConvOptions = {}); } // namespace gpu } // namespace xla -- GitLab From 9a54ef2b7cd156ce9fb11d143cea61cc84aed3b3 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Tue, 8 Jan 2019 19:54:03 -0800 Subject: [PATCH 0400/2345] Automated rollback of commit f71e77471c140bac2f4dcb9a003139fb5c657302 PiperOrigin-RevId: 228444974 --- tensorflow/compiler/xla/service/cpu/BUILD | 2 + .../cpu/cpu_eigen_tensor_alignment_test.cc | 38 ++++++ .../xla/service/cpu/dot_op_emitter.cc | 117 ++++++------------ .../xla/service/cpu/dot_op_emitter_internal.h | 88 +++++++++++++ 4 files changed, 168 insertions(+), 77 deletions(-) create mode 100644 tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index a197bdddc8..de62aa60aa 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -385,6 +385,7 @@ cc_library( srcs = ["dot_op_emitter.cc"], hdrs = [ "dot_op_emitter.h", + "dot_op_emitter_internal.h", ], deps = [ ":cpu_options", @@ -1028,6 +1029,7 @@ tf_cc_test( size = "small", srcs = ["cpu_eigen_tensor_alignment_test.cc"], deps = [ + ":dot_op_emitter", ":ir_emission_utils", ":target_machine_features_fake", "//tensorflow/compiler/xla:test", diff --git a/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc b/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc index 485769a373..823bdf259c 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#include "tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h" #include "tensorflow/compiler/xla/service/cpu/ir_emission_utils.h" #include "tensorflow/compiler/xla/service/cpu/target_machine_features_fake.h" #include "tensorflow/compiler/xla/service/hlo_parser.h" @@ -22,11 +23,48 @@ namespace xla { namespace cpu { namespace { +using internal::DotImplementationStrategy; +using internal::DotInfo; +using internal::GetDotImplementationStrategy; + // Test that we don't call into Eigen with tensors too small to be aligned // reliably. class CpuEigenTensorAlignmentTest : public ::testing::Test {}; +TEST_F(CpuEigenTensorAlignmentTest, EigenDotAlignment) { + string hlo_string = R"( +HloModule DotOperation + +ENTRY DotOperation { + arg0 = f32[5,256] parameter(0) + arg1 = f32[256,1024] parameter(1) + ROOT dot = f32[5,1024] dot(arg0, arg1), lhs_contracting_dims={1}, rhs_contracting_dims={0} +} +)"; + + TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr module, + ParseHloString(hlo_string)); + + HloInstruction* dot = module->entry_computation()->root_instruction(); + + TargetMachineFeaturesWithFakeAlignmentLogic target_machine_with_no_alignment( + [](int64 size) { return 1; }); + + EXPECT_EQ(GetDotImplementationStrategy(HloModuleConfig{}, DotInfo(*dot), + target_machine_with_no_alignment), + DotImplementationStrategy::kNaiveLlvmIr); + + TargetMachineFeaturesWithFakeAlignmentLogic + target_machine_with_full_alignment([](int64 size) { + return TargetMachineFeatures::kEigenExpectedTensorAlignment; + }); + + EXPECT_NE(GetDotImplementationStrategy(HloModuleConfig{}, DotInfo(*dot), + target_machine_with_full_alignment), + DotImplementationStrategy::kNaiveLlvmIr); +} + TEST_F(CpuEigenTensorAlignmentTest, EigenConvAlignment) { string hlo_string = R"( HloModule ConvOperation diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc index b018e0cd46..e8a84ebe6b 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/compiler/xla/service/cpu/dot_op_emitter.h" +#include "tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h" #include #include @@ -42,63 +43,17 @@ namespace xla { using llvm_ir::SetToFirstInsertPoint; namespace cpu { + +using internal::DotImplementationStrategy; +using internal::DotInfo; +using internal::GetDotImplementationStrategy; + namespace { // Returns true if we should call into multi-threaded Eigen routines. bool ShouldUseMultiThreadedEigen(const HloModuleConfig& config) { return config.debug_options().xla_cpu_multi_thread_eigen(); } -// Represents a dot operation. We use this in lieu of an `HloInstruction` -// because we want to be able to create this for the "inner" dot operation in a -// batch dot, for which there is no separate HLO instruction. -struct DotInfo { - Shape lhs_shape; - Shape rhs_shape; - Shape result_shape; - DotDimensionNumbers dim_nums; - - explicit DotInfo(const HloInstruction& instr) { - CHECK_EQ(instr.opcode(), HloOpcode::kDot); - lhs_shape = instr.operand(0)->shape(); - rhs_shape = instr.operand(1)->shape(); - result_shape = instr.shape(); - dim_nums = instr.dot_dimension_numbers(); - } -}; - -// Dictates how a dot operation is implemented. -enum class DotImplementationStrategy { - // The dot operation is lowered into LLVM IR that implements a naive nested - // loop that computes the result one element at a time. This is our - // "fallback"; we don't really want this to kick in for any non-trival dot - // operation. - kNaiveLlvmIr, - - // The dot operation is lowered into LLVM IR that implements a tiled - // Matrix*Vector operation. This strategy also allows fusing in a bias add - // into the dot. The matrix can be row major or column major, both are - // supported. - kTiledLlvmIrGemv, - - // The dot operation is lowered into LLVM IR that implemetns a tiled - // Matrix*Matrix operation. No fusions are supported. The two inputs - // and the output have to be row major. - kTiledLlvmIrGemm, - - // The dot operation is lowered into a call into an Eigen routine. No fusions - // are supported today. The two inputs and the output have to be row major. - // However, we do allow transposing either the LHS or the RHS as part of the - // GEMM -- we expose this flexibility as flexibility in the contraction - // dimensions, but we can also see this as flexibility in the input layouts. - kEigen, -}; - -// Returns the implementation strategy for a dot with the configuration -// `dot_info`. -DotImplementationStrategy GetDotImplementationStrategy( - const HloModuleConfig& config, const DotInfo& dot_info, - const TargetMachineFeatures& target_machine_features); - // Helper class for emitting LLVM IR to perform the dot operation. class DotOpEmitter { public: @@ -235,8 +190,9 @@ void DotOpEmitter::EmitTiledLlvmIrGemm() { } int64 size_bytes = m * n * ShapeUtil::ByteSizeOfPrimitiveType(primitive_type); - b_->CreateMemSet(target, b_->getInt8(0), /*Size=*/size_bytes, - /*Align=*/1); + b_->CreateMemSet( + target, b_->getInt8(0), size_bytes, + target_machine_features_.minimum_alignment_for_allocation(size_bytes)); int64 max_target_vector_width = target_machine_features_.vector_register_num_elements( @@ -740,34 +696,40 @@ absl::optional ProfitableToMakeDotOperandColumnMajor( return {}; } +namespace internal { namespace { // Return whether the given shape is rank 2. bool IsRank2(const Shape& shape) { return shape.rank() == 2; } -bool IsSimpleLayout(const Layout& layout) { - return layout.tiles().empty() && layout.format() == DENSE; -} - // In a gemm operation where output = lhs * rhs, check whether the given shapes // are valid for the operation. -bool AreGemmShapes(const Shape& lhs_shape, const Shape& rhs_shape, - const Shape& output_shape, - const TargetMachineFeatures& target_machine_features) { - CHECK(!lhs_shape.has_layout() || IsSimpleLayout(lhs_shape.layout())) - << lhs_shape.DebugString(); - CHECK(!rhs_shape.has_layout() || IsSimpleLayout(rhs_shape.layout())) - << rhs_shape.DebugString(); - CHECK(!output_shape.has_layout() || IsSimpleLayout(output_shape.layout())) - << output_shape.DebugString(); - - switch (output_shape.element_type()) { - case F64: - case F32: - case F16: - return IsRank2(lhs_shape) && IsRank2(rhs_shape) && IsRank2(output_shape); - default: - return false; +bool AreAlignedGemmShapes( + const Shape& lhs_shape, const Shape& rhs_shape, const Shape& output_shape, + const TargetMachineFeatures& target_machine_features) { + // The inputs and the output must + // 1) be matrices with no padding, and + // 2) have an allowed element type. + PrimitiveType output_primitive_type = output_shape.element_type(); + if (!(output_primitive_type == F64 || output_primitive_type == F32 || + output_primitive_type == F16)) { + return false; + } + + if (!(IsRank2(lhs_shape) && IsRank2(rhs_shape) && IsRank2(output_shape))) { + return false; + } + + auto is_aligned = [&](const Shape& shape) { + return GetMinimumAlignmentForArray(shape, target_machine_features) >= + TargetMachineFeatures::kEigenExpectedTensorAlignment; + }; + + if (!is_aligned(lhs_shape) || !is_aligned(rhs_shape) || + !is_aligned(output_shape)) { + return false; } + + return true; } bool IsAlignedGemm(const DotInfo& dot_info, @@ -777,8 +739,8 @@ bool IsAlignedGemm(const DotInfo& dot_info, return false; } - return AreGemmShapes(dot_info.lhs_shape, dot_info.rhs_shape, - dot_info.result_shape, target_machine_features); + return AreAlignedGemmShapes(dot_info.lhs_shape, dot_info.rhs_shape, + dot_info.result_shape, target_machine_features); } bool CanEmitTiledLlvmIrGemm( @@ -819,6 +781,7 @@ bool CanEmitTiledLlvmIrGemm( return true; } +} // namespace DotImplementationStrategy GetDotImplementationStrategy( const HloModuleConfig& config, const DotInfo& dot_info, @@ -843,7 +806,7 @@ DotImplementationStrategy GetDotImplementationStrategy( return DotImplementationStrategy::kNaiveLlvmIr; } -} // namespace +} // namespace internal bool DotImplementationCanHandleTranspose( const HloInstruction& dot_instr, diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h b/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h new file mode 100644 index 0000000000..cc28918ed6 --- /dev/null +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h @@ -0,0 +1,88 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_COMPILER_XLA_SERVICE_CPU_DOT_OP_EMITTER_INTERNAL_H_ +#define TENSORFLOW_COMPILER_XLA_SERVICE_CPU_DOT_OP_EMITTER_INTERNAL_H_ + +#include "tensorflow/compiler/xla/service/cpu/target_machine_features.h" +#include "tensorflow/compiler/xla/service/hlo_instruction.h" +#include "tensorflow/compiler/xla/service/hlo_module.h" + +// ----------------------------------------------------------------------------- +// INTERNAL HEADER. +// +// This file exposes internal implementation details from dot_op_emitter.cc for +// unit tests. Please do not depend on this! +// +// ----------------------------------------------------------------------------- + +namespace xla { +namespace cpu { +namespace internal { + +// Represents a dot operation. We use this in lieu of an `HloInstruction` +// because we want to be able to create this for the "inner" dot operation in a +// batch dot, for which there is no separate HLO instruction. +struct DotInfo { + Shape lhs_shape; + Shape rhs_shape; + Shape result_shape; + DotDimensionNumbers dim_nums; + + explicit DotInfo(const HloInstruction& instr) { + CHECK_EQ(instr.opcode(), HloOpcode::kDot); + lhs_shape = instr.operand(0)->shape(); + rhs_shape = instr.operand(1)->shape(); + result_shape = instr.shape(); + dim_nums = instr.dot_dimension_numbers(); + } +}; + +// Dictates how a dot operation is implemented. +enum class DotImplementationStrategy { + // The dot operation is lowered into LLVM IR that implements a naive nested + // loop that computes the result one element at a time. This is our + // "fallback"; we don't really want this to kick in for any non-trival dot + // operation. + kNaiveLlvmIr, + + // The dot operation is lowered into LLVM IR that implements a tiled + // Matrix*Vector operation. This strategy also allows fusing in a bias add + // into the dot. The matrix can be row major or column major, both are + // supported. + kTiledLlvmIrGemv, + + // The dot operation is lowered into LLVM IR that implemetns a tiled + // Matrix*Matrix operation. No fusions are supported. The two inputs + // and the output have to be row major. + kTiledLlvmIrGemm, + + // The dot operation is lowered into a call into an Eigen routine. No fusions + // are supported today. The two inputs and the output have to be row major. + // However, we do allow transposing either the LHS or the RHS as part of the + // GEMM -- we expose this flexibility as flexibility in the contraction + // dimensions, but we can also see this as flexibility in the input layouts. + kEigen, +}; + +// Returns the implementation strategy for a dot with the configuration +// `dot_info`. +DotImplementationStrategy GetDotImplementationStrategy( + const HloModuleConfig& config, const DotInfo& dot_info, + const TargetMachineFeatures& target_machine_features); +} // namespace internal +} // namespace cpu +} // namespace xla + +#endif // TENSORFLOW_COMPILER_XLA_SERVICE_CPU_DOT_OP_EMITTER_INTERNAL_H_ -- GitLab From 8d4893d6efa971b35db9f4eb654adb1559b7186c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 8 Jan 2019 20:19:22 -0800 Subject: [PATCH 0401/2345] Update ops-related pbtxt files. PiperOrigin-RevId: 228447178 --- .../core/ops/compat/ops_history.v1.pbtxt | 254 ++++++++++++++++++ tensorflow/core/ops/ops.pbtxt | 27 ++ 2 files changed, 281 insertions(+) diff --git a/tensorflow/core/ops/compat/ops_history.v1.pbtxt b/tensorflow/core/ops/compat/ops_history.v1.pbtxt index 28d085b2d2..9698673dfe 100644 --- a/tensorflow/core/ops/compat/ops_history.v1.pbtxt +++ b/tensorflow/core/ops/compat/ops_history.v1.pbtxt @@ -13631,6 +13631,151 @@ op { } } } +op { + name: "Conv2D" + input_arg { + name: "input" + type_attr: "T" + } + input_arg { + name: "filter" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "strides" + type: "list(int)" + } + attr { + name: "use_cudnn_on_gpu" + type: "bool" + default_value { + b: true + } + } + attr { + name: "padding" + type: "string" + allowed_values { + list { + s: "SAME" + s: "VALID" + s: "EXPLICIT" + } + } + } + attr { + name: "explicit_paddings" + type: "list(int)" + default_value { + list { + } + } + } + attr { + name: "data_format" + type: "string" + default_value { + s: "NHWC" + } + allowed_values { + list { + s: "NHWC" + s: "NCHW" + } + } + } + attr { + name: "dilations" + type: "list(int)" + default_value { + list { + i: 1 + i: 1 + i: 1 + i: 1 + } + } + } +} +op { + name: "Conv2DBackpropFilter" + input_arg { + name: "input" + type_attr: "T" + } + input_arg { + name: "filter_sizes" + type: DT_INT32 + } + input_arg { + name: "out_backprop" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_HALF + type: DT_FLOAT + } + } + } + attr { + name: "strides" + type: "list(int)" + } + attr { + name: "use_cudnn_on_gpu" + type: "bool" + default_value { + b: true + } + } + attr { + name: "padding" + type: "string" + allowed_values { + list { + s: "SAME" + s: "VALID" + } + } + } + attr { + name: "data_format" + type: "string" + default_value { + s: "NHWC" + } + allowed_values { + list { + s: "NHWC" + s: "NCHW" + } + } + } +} op { name: "Conv2DBackpropFilter" input_arg { @@ -13655,6 +13800,7 @@ op { allowed_values { list { type: DT_HALF + type: DT_BFLOAT16 type: DT_FLOAT } } @@ -13693,6 +13839,18 @@ op { } } } + attr { + name: "dilations" + type: "list(int)" + default_value { + list { + i: 1 + i: 1 + i: 1 + i: 1 + } + } + } } op { name: "Conv2DBackpropFilter" @@ -13720,6 +13878,7 @@ op { type: DT_HALF type: DT_BFLOAT16 type: DT_FLOAT + type: DT_DOUBLE } } } @@ -13818,6 +13977,15 @@ op { list { s: "SAME" s: "VALID" + s: "EXPLICIT" + } + } + } + attr { + name: "explicit_paddings" + type: "list(int)" + default_value { + list { } } } @@ -14063,6 +14231,92 @@ op { } } } +op { + name: "Conv2DBackpropInput" + input_arg { + name: "input_sizes" + type: DT_INT32 + } + input_arg { + name: "filter" + type_attr: "T" + } + input_arg { + name: "out_backprop" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "strides" + type: "list(int)" + } + attr { + name: "use_cudnn_on_gpu" + type: "bool" + default_value { + b: true + } + } + attr { + name: "padding" + type: "string" + allowed_values { + list { + s: "SAME" + s: "VALID" + s: "EXPLICIT" + } + } + } + attr { + name: "explicit_paddings" + type: "list(int)" + default_value { + list { + } + } + } + attr { + name: "data_format" + type: "string" + default_value { + s: "NHWC" + } + allowed_values { + list { + s: "NHWC" + s: "NCHW" + } + } + } + attr { + name: "dilations" + type: "list(int)" + default_value { + list { + i: 1 + i: 1 + i: 1 + i: 1 + } + } + } +} op { name: "Conv3D" input_arg { diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt index b4767d35d2..4a1b3a6f2d 100644 --- a/tensorflow/core/ops/ops.pbtxt +++ b/tensorflow/core/ops/ops.pbtxt @@ -5876,6 +5876,15 @@ op { list { s: "SAME" s: "VALID" + s: "EXPLICIT" + } + } + } + attr { + name: "explicit_paddings" + type: "list(int)" + default_value { + list { } } } @@ -5953,6 +5962,15 @@ op { list { s: "SAME" s: "VALID" + s: "EXPLICIT" + } + } + } + attr { + name: "explicit_paddings" + type: "list(int)" + default_value { + list { } } } @@ -6030,6 +6048,15 @@ op { list { s: "SAME" s: "VALID" + s: "EXPLICIT" + } + } + } + attr { + name: "explicit_paddings" + type: "list(int)" + default_value { + list { } } } -- GitLab From 283bd7dc2914f36daf5d6604ffa25d939967bd7d Mon Sep 17 00:00:00 2001 From: Pariksheet Pinjari Date: Wed, 9 Jan 2019 10:33:43 +0530 Subject: [PATCH 0402/2345] Update api_def_StaticRegexReplace.pbtxt Documentation error fixed --- .../core/api_def/base_api/api_def_StaticRegexReplace.pbtxt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/api_def/base_api/api_def_StaticRegexReplace.pbtxt b/tensorflow/core/api_def/base_api/api_def_StaticRegexReplace.pbtxt index e382bcec81..8bb88f491a 100644 --- a/tensorflow/core/api_def/base_api/api_def_StaticRegexReplace.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_StaticRegexReplace.pbtxt @@ -14,7 +14,7 @@ op { } attr { name: "rewrite" - description: "The rewrite to be applied to the matched expresion." + description: "The rewrite to be applied to the matched expression." } attr { name: "replace_global" -- GitLab From ae2aceda4f6630dbdaa6c9a4da6ba690f34f40de Mon Sep 17 00:00:00 2001 From: Ilango R Date: Wed, 9 Jan 2019 13:10:52 +0530 Subject: [PATCH 0403/2345] Fix astor url to pypi.python.org --- tensorflow/workspace.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 5b0f6d4c84..a7f3665a31 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -285,7 +285,7 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): system_build_file = clean_dep("//third_party/systemlibs:astor.BUILD"), urls = [ "https://mirror.bazel.build/pypi.python.org/packages/99/80/f9482277c919d28bebd85813c0a70117214149a96b08981b72b63240b84c/astor-0.7.1.tar.gz", - "https://files.pythonhosted.org/packages/99/80/f9482277c919d28bebd85813c0a70117214149a96b08981b72b63240b84c/astor-0.7.1.tar.gz", + "https://pypi.python.org/packages/99/80/f9482277c919d28bebd85813c0a70117214149a96b08981b72b63240b84c/astor-0.7.1.tar.gz", ], ) -- GitLab From 746a9750f49d98b975a4cd0ee8c0f353426ffbc0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 01:02:25 -0800 Subject: [PATCH 0404/2345] compat: Update forward compatibility horizon to 2019-01-09 PiperOrigin-RevId: 228471346 --- 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 5008741d55..dfee972cab 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, 1, 8) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 9) @tf_export("compat.forward_compatible") -- GitLab From 4d594fec0345b34de40842a0b6e39b7771dad7ec Mon Sep 17 00:00:00 2001 From: Dayananda V Date: Mon, 7 Jan 2019 15:59:12 +0530 Subject: [PATCH 0405/2345] 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 --- tensorflow/lite/examples/android/app/build.gradle | 4 ++-- .../lite/examples/android/app/src/main/AndroidManifest.xml | 4 ---- tensorflow/lite/java/AndroidManifest.xml | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/tensorflow/lite/examples/android/app/build.gradle b/tensorflow/lite/examples/android/app/build.gradle index e5f5c7efd1..53e502978b 100644 --- a/tensorflow/lite/examples/android/app/build.gradle +++ b/tensorflow/lite/examples/android/app/build.gradle @@ -45,6 +45,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/src/main/AndroidManifest.xml b/tensorflow/lite/examples/android/app/src/main/AndroidManifest.xml index bc9574d646..e63c5d267e 100644 --- a/tensorflow/lite/examples/android/app/src/main/AndroidManifest.xml +++ b/tensorflow/lite/examples/android/app/src/main/AndroidManifest.xml @@ -24,10 +24,6 @@ - - -- GitLab From 1bc98108a3e835221318e019fef1642914dd5720 Mon Sep 17 00:00:00 2001 From: Adrian Kuegel Date: Wed, 9 Jan 2019 02:37:03 -0800 Subject: [PATCH 0406/2345] Allow adding extra parameters to a cloned computation. There is currently no way to clone a computation and add additional parameters to it, except if it is a fusion computation. In a future change this will be necessary, so we add the possibility to add such parameters via CloneWithReplacements(). PiperOrigin-RevId: 228483852 --- .../compiler/xla/service/hlo_computation.cc | 18 +++++++-- .../compiler/xla/service/hlo_computation.h | 4 ++ .../xla/service/hlo_computation_test.cc | 38 +++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/tensorflow/compiler/xla/service/hlo_computation.cc b/tensorflow/compiler/xla/service/hlo_computation.cc index 9e15722633..f9b64d12ae 100644 --- a/tensorflow/compiler/xla/service/hlo_computation.cc +++ b/tensorflow/compiler/xla/service/hlo_computation.cc @@ -846,7 +846,7 @@ std::unique_ptr HloComputation::Clone( return CloneWithReplacements( /*replacements=*/std::unordered_map>(), - context, suffix); + /*extra_parameters=*/{}, context, suffix); } std::unique_ptr HloComputation::CloneWithReplacementPairs( @@ -855,7 +855,8 @@ std::unique_ptr HloComputation::CloneWithReplacementPairs( std::unordered_map> replacements; replacements.emplace(std::move(r1)); - return CloneWithReplacements(std::move(replacements), context, suffix); + return CloneWithReplacements(std::move(replacements), /*extra_parameters=*/{}, + context, suffix); } std::unique_ptr HloComputation::CloneWithReplacementPairs( @@ -866,7 +867,8 @@ std::unique_ptr HloComputation::CloneWithReplacementPairs( replacements; replacements.emplace(std::move(r1)); replacements.emplace(std::move(r2)); - return CloneWithReplacements(std::move(replacements), context, suffix); + return CloneWithReplacements(std::move(replacements), /*extra_parameters=*/{}, + context, suffix); } std::unique_ptr HloComputation::CloneWithReplacementPairs( @@ -879,12 +881,14 @@ std::unique_ptr HloComputation::CloneWithReplacementPairs( replacements.emplace(std::move(r1)); replacements.emplace(std::move(r2)); replacements.emplace(std::move(r3)); - return CloneWithReplacements(std::move(replacements), context, suffix); + return CloneWithReplacements(std::move(replacements), /*extra_parameters=*/{}, + context, suffix); } std::unique_ptr HloComputation::CloneWithReplacements( std::unordered_map> replacements, + absl::Span extra_parameters, HloCloneContext* context, const string& suffix) { std::unique_ptr context_ptr; if (context == nullptr) { @@ -950,6 +954,12 @@ std::unique_ptr HloComputation::CloneWithReplacements( } std::vector> instructions; + // First add the extra parameters to 'instructions'. + for (const auto& instr : extra_parameters) { + CHECK_EQ(instr->opcode(), HloOpcode::kParameter) + << "Only parameter instructions are allowed in 'extra_parameters'"; + instructions.emplace_back(instr->Clone()); + } for (auto instr : postorder) { std::vector new_operands; for (auto operand : instr->operands()) { diff --git a/tensorflow/compiler/xla/service/hlo_computation.h b/tensorflow/compiler/xla/service/hlo_computation.h index a0ccbc583f..e6a1eb89cf 100644 --- a/tensorflow/compiler/xla/service/hlo_computation.h +++ b/tensorflow/compiler/xla/service/hlo_computation.h @@ -323,11 +323,15 @@ class HloComputation { // that's not already in the computation, it's cloned and added to the new // computation. // + // 'extra_parameters' allows to specify additional parameters that should be + // added to the computation. + // // All relevant instructions are cloned, *including* unique_ptr in the // `replacements` map. std::unique_ptr CloneWithReplacements( std::unordered_map> replacements, + absl::Span extra_parameters = {}, HloCloneContext* context = nullptr, const string& suffix = "clone"); // Convenience overloads for CloneWithReplacements. You want to do diff --git a/tensorflow/compiler/xla/service/hlo_computation_test.cc b/tensorflow/compiler/xla/service/hlo_computation_test.cc index 216d56b868..251c7bbec4 100644 --- a/tensorflow/compiler/xla/service/hlo_computation_test.cc +++ b/tensorflow/compiler/xla/service/hlo_computation_test.cc @@ -15,7 +15,10 @@ limitations under the License. #include "tensorflow/compiler/xla/service/hlo_computation.h" +#include #include +#include +#include #include "absl/container/flat_hash_set.h" #include "tensorflow/compiler/xla/literal.h" @@ -492,6 +495,41 @@ TEST_F(HloComputationTest, CloneWithControlDependency) { EXPECT_THAT(successors, ::testing::ElementsAre(cloned_add)); } +TEST_F(HloComputationTest, CloneWithReplacements) { + auto builder = HloComputation::Builder(TestName()); + Shape r0s64 = ShapeUtil::MakeShape(S64, {}); + Shape r0s32 = ShapeUtil::MakeShape(S32, {}); + Shape r0u32 = ShapeUtil::MakeShape(U32, {}); + auto param0 = builder.AddInstruction( + HloInstruction::CreateParameter(0, r0f32_, "p.0.lhs")); + auto param1 = builder.AddInstruction( + HloInstruction::CreateParameter(1, r0f32_, "p.0.rhs")); + auto param2 = + builder.AddInstruction(HloInstruction::CreateParameter(2, r0s64, "p.1")); + auto lt = builder.AddInstruction(HloInstruction::CreateBinary( + ShapeUtil::MakeShape(PRED, {}), HloOpcode::kLt, param0, param1)); + auto module = CreateNewVerifiedModule(); + auto computation = + module->AddEntryComputation(builder.Build(/*root_instruction=*/lt)); + std::unordered_map> + replacements; + replacements.emplace(param2, + HloInstruction::CreateParameter(2, r0s32, "p.1")); + auto param3 = HloInstruction::CreateParameter(3, r0u32, "p.2"); + std::vector extra_parameters{param3.get()}; + auto clone = computation->CloneWithReplacements(std::move(replacements), + extra_parameters); + ASSERT_EQ(clone->num_parameters(), 4); + EXPECT_TRUE( + ShapeUtil::Equal(clone->parameter_instruction(0)->shape(), r0f32_)); + EXPECT_TRUE( + ShapeUtil::Equal(clone->parameter_instruction(1)->shape(), r0f32_)); + EXPECT_TRUE( + ShapeUtil::Equal(clone->parameter_instruction(2)->shape(), r0s32)); + EXPECT_TRUE( + ShapeUtil::Equal(clone->parameter_instruction(3)->shape(), r0u32)); +} + TEST_F(HloComputationTest, Stringification) { const Shape s1 = ShapeUtil::MakeShape(F32, {5, 10}); const Shape s2 = ShapeUtil::MakeShape(F32, {20, 10}); -- GitLab From 94548dbeca41c3a94287bf9e506f876272904227 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 02:49:25 -0800 Subject: [PATCH 0407/2345] Object based saved model: Wire __call__ attribute as a method in each user object class. PiperOrigin-RevId: 228485119 --- tensorflow/python/saved_model/load.py | 20 +++++++++- tensorflow/python/saved_model/load_test.py | 43 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/saved_model/load.py b/tensorflow/python/saved_model/load.py index 1ee1b69b03..bbb1485612 100644 --- a/tensorflow/python/saved_model/load.py +++ b/tensorflow/python/saved_model/load.py @@ -85,11 +85,17 @@ class _Loader(object): raise ValueError("Can't convert node %s to tensor" % (type(obj))) def _load_all(self): + """Load all saved objects and wire their properties.""" self._nodes = [self._recreate(proto) for proto in self._proto.nodes] # After creating the objects, construct the edges between the objects. for obj, object_proto in zip(self._nodes, self._proto.nodes): for reference in object_proto.children: setattr(obj, reference.local_name, self._nodes[reference.node_id]) + # Note: if an object has an attribute `__call__` add a class method + # that allows `obj()` syntax to work. This is done per-instance to + # allow `callable` to be used to find out if an object is callable. + if reference.local_name == "__call__": + setattr(type(obj), "__call__", _call_attribute) def _restore_checkpoint(self): variables_path = saved_model_utils.get_variables_path(self._export_dir) @@ -113,8 +119,16 @@ class _Loader(object): return factory[kind]() def _recreate_user_object(self, proto): + """Instantiates a SavedUserObject.""" del proto - return tracking.Checkpointable() + + # Note: each user object has its own class. This allows to make each one + # individually callable by adding a `__call__` method to the classes of + # the objects instances that have a `__call__` property. + class _UserObject(tracking.Checkpointable): + pass + + return _UserObject() def _recreate_asset(self, proto): filename = os.path.join( @@ -132,6 +146,10 @@ class _Loader(object): return variables.Variable(dummy_value, trainable=proto.trainable) +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() diff --git a/tensorflow/python/saved_model/load_test.py b/tensorflow/python/saved_model/load_test.py index 35e384dbfc..f7020e4a45 100644 --- a/tensorflow/python/saved_model/load_test.py +++ b/tensorflow/python/saved_model/load_test.py @@ -269,6 +269,49 @@ class LoadTest(test.TestCase): grad = t.gradient(loss, [imported.weight, imported.bias]) self.assertAllClose(grad, [3.5, 1.0]) + def test_callable(self): + class M1(tracking.Checkpointable): + + @def_function.function( + input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) + def __call__(self, x): + return x + + root = tracking.Checkpointable() + root.m1 = M1() + root.m2 = tracking.Checkpointable() + root.m2.__call__ = def_function.function( + input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)])( + lambda x: x*3.0) + imported = self.cycle(root) + x = constant_op.constant(1.0) + + self.assertTrue(callable(imported.m1)) + self.assertAllEqual(root.m1(x), imported.m1(x)) + + # Note: `root.m2` was not callable since `__call__` attribute was set + # into the instance and not on the class. But after a serialization cycle + # that starts to work. + self.assertTrue(callable(imported.m2)) + self.assertAllEqual(root.m2.__call__(x), imported.m2(x)) + + # Verify that user objects without `__call__` attribute are not callable. + self.assertFalse(callable(imported)) + + def test_chain_callable(self): + func = def_function.function( + input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)])( + lambda x: x*3.0) + root = tracking.Checkpointable() + root.__call__ = tracking.Checkpointable() + root.__call__.__call__ = tracking.Checkpointable() + root.__call__.__call__.__call__ = func + + imported = self.cycle(root) + self.assertTrue(callable(imported)) + x = constant_op.constant(1.0) + self.assertAllEqual(imported(x).numpy(), 3.0) + if __name__ == "__main__": test.main() -- GitLab From 2db3c2ed12f7871322448c3265b82e21fa50e6aa Mon Sep 17 00:00:00 2001 From: Tamara Norman Date: Wed, 9 Jan 2019 02:50:05 -0800 Subject: [PATCH 0408/2345] Alter cast conversion so returns valid data types and allows a positional name arg PiperOrigin-RevId: 228485190 --- .../tools/compatibility/tf_upgrade_v2.py | 11 +++++++++ .../tools/compatibility/tf_upgrade_v2_test.py | 24 ++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2.py b/tensorflow/tools/compatibility/tf_upgrade_v2.py index 2dbbe27598..940b57891a 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2.py @@ -1213,10 +1213,21 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): # Find out the dtype to cast to from the function name dtype_str = name[3:] + # Special cases where the full dtype is not given + if dtype_str == "float": + dtype_str = "float32" + elif dtype_str == "double": + dtype_str = "float64" new_arg = ast.keyword(arg="dtype", value=ast.Attribute(value=ast.Name(id="tf", ctx=ast.Load()), attr=dtype_str, ctx=ast.Load())) + # Ensures a valid transformation when a positional name arg is given + if len(node.args) == 2: + name_arg = ast.keyword(arg="name", + value=node.args[-1]) + node.args = node.args[:-1] + node.keywords.append(name_arg) # Python3 ast requires the args for the Attribute, but codegen will mess up # the arg order if we just set them to 0. diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py index 80d86d7a2b..b2bdddf15e 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py @@ -861,9 +861,27 @@ tf.print('abc') self.assertIn(new_text, [expected_text1, expected_text2]) def testCast(self): - for dtype in ["int32", "int64", "float", "double", - "complex64", "complex128", "bfloat16"]: - text = "tf.to_%s(x, name='test')" % dtype + for (name, dtype) in [("int32", "int32"), + ("int64", "int64"), + ("float", "float32"), + ("double", "float64"), + ("complex64", "complex64"), + ("complex128", "complex128"), + ("bfloat16", "bfloat16")]: + text = "tf.to_%s(x, name='test')" % name + expected_text = "tf.cast(x, name='test', dtype=tf.%s)" % dtype + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) + + def testCastPositionalSecondArgument(self): + for (name, dtype) in [("int32", "int32"), + ("int64", "int64"), + ("float", "float32"), + ("double", "float64"), + ("complex64", "complex64"), + ("complex128", "complex128"), + ("bfloat16", "bfloat16")]: + text = "tf.to_%s(x, 'test')" % name expected_text = "tf.cast(x, name='test', dtype=tf.%s)" % dtype _, unused_report, unused_errors, new_text = self._upgrade(text) self.assertEqual(expected_text, new_text) -- GitLab From 41b0fb94457c0de1688ed595e5ff2ada068fc93f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 03:00:27 -0800 Subject: [PATCH 0409/2345] Optimize transpose_conv. PiperOrigin-RevId: 228486143 --- .../internal/optimized/optimized_ops.h | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h index 8f09fab4ca..ac68757b06 100644 --- a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h @@ -6079,7 +6079,27 @@ inline void TransposeConv( const float* filter_data, const RuntimeShape& output_shape, float* output_data, const RuntimeShape& im2col_shape, float* im2col_data) { gemmlowp::ScopedProfilingLabel label("TransposeConv"); - + // The complexity of the reference implementation is input.flat_size() * + // filter.flat_size() / in_channel. + // + // While the complexity of im2col->gemm + // implmentation is batch * output_height * output_width * + // (filter.flat_size() / out_channel)^2 * out_channel. + // + // so if input.flat_size() * out_channel^2 is much smaller than + // output.flat_size() * filter.size() * in_channel we should fall back to the + // reference implementation. + // + // TODO(b/122331966): optimize the intuitive implementation. + const int out_channel = output_shape.Dims(3); + const int in_channel = input_shape.Dims(3); + if ((input_shape.FlatSize() * out_channel * out_channel * 4) < + (filter_shape.FlatSize() * output_shape.FlatSize() * in_channel)) { + reference_ops::TransposeConv(params, input_shape, input_data, filter_shape, + filter_data, output_shape, output_data, + im2col_shape, im2col_data); + return; + } // Note we could use transposed weights with forward conv for unstrided // cases. But we are already getting good performance with this code as-is. TFLITE_DCHECK(im2col_data); -- GitLab From 964e3d0bd8bcfbfc8bcc1de16f32df8190cd7b75 Mon Sep 17 00:00:00 2001 From: Tamara Norman Date: Wed, 9 Jan 2019 03:29:29 -0800 Subject: [PATCH 0410/2345] Add conversion for tf.image.resize_* methods PiperOrigin-RevId: 228489020 --- tensorflow/tools/compatibility/renames_v2.py | 5 +- .../tools/compatibility/tf_upgrade_v2.py | 51 +++++++++++++++++++ .../tools/compatibility/tf_upgrade_v2_test.py | 18 +++++++ 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/tensorflow/tools/compatibility/renames_v2.py b/tensorflow/tools/compatibility/renames_v2.py index 5ff8a86b91..e9085e94b3 100644 --- a/tensorflow/tools/compatibility/renames_v2.py +++ b/tensorflow/tools/compatibility/renames_v2.py @@ -141,8 +141,8 @@ renames = { 'tf.diag': 'tf.linalg.tensor_diag', 'tf.diag_part': 'tf.linalg.tensor_diag_part', 'tf.digamma': 'tf.math.digamma', - 'tf.dimension_at_index': 'tf.compat.v1.dimension_at_index', - 'tf.dimension_value': 'tf.compat.v1.dimension_value', + 'tf.dimension_at_index': 'tf.compat.dimension_at_index', + 'tf.dimension_value': 'tf.compat.dimension_value', 'tf.disable_eager_execution': 'tf.compat.v1.disable_eager_execution', 'tf.disable_resource_variables': 'tf.compat.v1.disable_resource_variables', 'tf.disable_v2_batch_normalization': 'tf.compat.v1.disable_v2_batch_normalization', @@ -221,7 +221,6 @@ renames = { 'tf.image.resize_area': 'tf.compat.v1.image.resize_area', 'tf.image.resize_bicubic': 'tf.compat.v1.image.resize_bicubic', 'tf.image.resize_bilinear': 'tf.compat.v1.image.resize_bilinear', - 'tf.image.resize_images': 'tf.compat.v1.image.resize_images', 'tf.image.resize_nearest_neighbor': 'tf.compat.v1.image.resize_nearest_neighbor', 'tf.image.transpose_image': 'tf.compat.v1.image.transpose_image', 'tf.initialize_all_tables': 'tf.compat.v1.initialize_all_tables', diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2.py b/tensorflow/tools/compatibility/tf_upgrade_v2.py index 940b57891a..e93699fdbf 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2.py @@ -594,6 +594,8 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.compat.v1.initializers.random_normal", "tf.truncated_normal_initializer": "tf.compat.v1.initializers.truncated_normal", + "tf.image.resize_images": + "tf.image.resize", } # pylint: enable=line-too-long @@ -725,6 +727,11 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.to_int64": self._cast_transformer, "tf.nn.softmax_cross_entropy_with_logits": self._softmax_cross_entropy_with_logits_transformer, + "tf.image.resize_area": self._image_resize_transformer, + "tf.image.resize_bicubic": self._image_resize_transformer, + "tf.image.resize_bilinear": self._image_resize_transformer, + "tf.image.resize_nearest_neighbor": self._image_resize_transformer, + } decay_function_comment = ( @@ -1292,3 +1299,47 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): logs.append((node.lineno, node.col_offset, "Added keyword argument batch_dims=-1 to tf.batch_gather.")) return node + + @staticmethod + def _image_resize_transformer(parent, node, full_name, name, logs, errors): + """Transforms image.resize_* to image.resize(..., method=*, ...).""" + + resize_method = name[7:].upper() + new_arg = ast.keyword(arg="method", + value=ast.Attribute( + value=ast.Attribute( + value=ast.Attribute( + value=ast.Name(id="tf", ctx=ast.Load()), + attr="image", ctx=ast.Load()), + attr="ResizeMethod", ctx=ast.Load()), + attr=resize_method, ctx=ast.Load())) + + # Ensures a valid transformation when a positional name arg is given + if len(node.args) == 4: + pos_arg = ast.keyword(arg="preserve_aspect_ratio", + value=node.args[-1]) + node.args = node.args[:-1] + node.keywords.append(pos_arg) + if len(node.args) == 3: + pos_arg = ast.keyword(arg="align_corners", + value=node.args[-1]) + node.args = node.args[:-1] + node.keywords.append(pos_arg) + + # Python3 ast requires the args for the Attribute, but codegen will mess up + # the arg order if we just set them to 0. + new_arg.value.lineno = node.lineno + new_arg.value.col_offset = node.col_offset+100 + + node.keywords.append(new_arg) + if isinstance(node.func, ast.Attribute): + node.func.attr = "resize" + else: + assert isinstance(node.func, ast.Name) + node.func.id = "resize" + + logs.append((node.lineno, node.col_offset, + "Changed %s call to tf.image.resize(..., " + "method=tf.image.ResizeMethod.%s)." % (full_name, + resize_method))) + return node diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py index b2bdddf15e..c1882afd5c 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py @@ -886,6 +886,24 @@ tf.print('abc') _, unused_report, unused_errors, new_text = self._upgrade(text) self.assertEqual(expected_text, new_text) + def testImageResize(self): + for method in ["bilinear", "area", "bicubic", "nearest_neighbor"]: + text = "tf.image.resize_%s(i, s)" % method + expected_text = ("tf.image.resize(i, s, " + "method=tf.image.ResizeMethod.%s)" % method.upper()) + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) + + def testImageResizeExtraPositionalArgs(self): + for method in ["bilinear", "area", "bicubic", "nearest_neighbor"]: + text = "tf.image.resize_%s(i, s, a, p)" % method + expected_text = ["tf.image.resize(i, s, ", "align_corners=a, ", + "preserve_aspect_ratio=p, ", + "method=tf.image.ResizeMethod.%s)" % method.upper()] + _, unused_report, unused_errors, new_text = self._upgrade(text) + for s in expected_text: + self.assertIn(s, new_text) + class TestUpgradeFiles(test_util.TensorFlowTestCase): -- GitLab From 6e6d7aad7300294425933bd053afce55107ccd35 Mon Sep 17 00:00:00 2001 From: Tamara Norman Date: Wed, 9 Jan 2019 04:31:58 -0800 Subject: [PATCH 0411/2345] Make tf.cond remove the strict arg and emit a warning to say it has been set to true. Adds additional changes which ensure this warning is emitted if the original argument was positional. PiperOrigin-RevId: 228495150 --- tensorflow/tools/compatibility/ast_edits.py | 8 +++---- tensorflow/tools/compatibility/reorders_v2.py | 1 + .../tools/compatibility/tf_upgrade_v2.py | 21 ++++++++++++------- .../tools/compatibility/tf_upgrade_v2_test.py | 8 +++++++ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/tensorflow/tools/compatibility/ast_edits.py b/tensorflow/tools/compatibility/ast_edits.py index 859fd95314..2254c223ce 100644 --- a/tensorflow/tools/compatibility/ast_edits.py +++ b/tensorflow/tools/compatibility/ast_edits.py @@ -213,7 +213,7 @@ class _PastaEditVisitor(ast.NodeVisitor): return False def _maybe_add_call_warning(self, node, full_name, name): - """Print a warning when specific functions are called. + """Print a warning when specific functions are called with selected args. The function _print_warning_for_function matches the full name of the called function, e.g., tf.foo.bar(). This function matches the function name that @@ -241,13 +241,13 @@ class _PastaEditVisitor(ast.NodeVisitor): full_name, name) used_args = [kw.arg for kw in node.keywords] - for arg, warning in arg_warnings.items(): - if arg in used_args: + for (kwarg, arg), warning in arg_warnings.items(): + if kwarg in used_args or len(node.args) > arg: warned = True warning_message = warning.replace("", full_name or name) self.add_error(node.lineno, node.col_offset, "%s called with %s argument requires manual check: %s." % - (full_name or name, arg, warning_message)) + (full_name or name, kwarg, warning_message)) return warned diff --git a/tensorflow/tools/compatibility/reorders_v2.py b/tensorflow/tools/compatibility/reorders_v2.py index 5c11388516..f9b0e3f9d8 100644 --- a/tensorflow/tools/compatibility/reorders_v2.py +++ b/tensorflow/tools/compatibility/reorders_v2.py @@ -31,6 +31,7 @@ reorders = { 'tf.batch_gather': ['params', 'indices', 'name'], 'tf.batch_to_space': ['input', 'crops', 'block_size', 'name'], 'tf.boolean_mask': ['tensor', 'mask', 'name', 'axis'], + 'tf.cond': ['pred', 'true_fn', 'false_fn', 'strict', 'name', 'fn1', 'fn2'], 'tf.confusion_matrix': ['labels', 'predictions', 'num_classes', 'dtype', 'name', 'weights'], 'tf.convert_to_tensor': ['value', 'dtype', 'name', 'preferred_dtype'], 'tf.decode_csv': ['records', 'record_defaults', 'field_delim', 'use_quote_delim', 'name', 'na_value', 'select_cols'], diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2.py b/tensorflow/tools/compatibility/tf_upgrade_v2.py index e93699fdbf..2c56cbf36b 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2.py @@ -50,6 +50,11 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "*.compute_gradients": { "colocate_gradients_with_ops": None, }, + "tf.cond": { + "strict": None, + "fn1": "true_fn", + "fn2": "false_fn" + }, "tf.argmin": { "dimension": "axis", }, @@ -620,6 +625,7 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.argmin", "tf.batch_gather", "tf.batch_to_space", + "tf.cond", "tf.nn.space_to_batch", "tf.boolean_mask", "tf.convert_to_tensor", @@ -847,10 +853,6 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): assert_return_type_comment, "tf.assert_rank": assert_rank_comment, - "tf.cond": - "tf.cond no longer takes 'strict'. " - "Now 'strict' defaults to True." - "fn1/fn2 arguments are replaced by true_fn/false_fn.", "tf.debugging.assert_equal": assert_return_type_comment, "tf.debugging.assert_greater": @@ -1146,23 +1148,28 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): # Warnings that are emitted only if a specific arg is found. self.function_arg_warnings = { "tf.gradients": { - "colocate_gradients_with_ops": + ("colocate_gradients_with_ops", 4): "tf.gradients no longer takes " "'colocate_gradients_with_ops' argument, it behaves as if it " "was set to True.", }, "*.minimize": { - "colocate_gradients_with_ops": + ("colocate_gradients_with_ops", 5): "Optimizer.minimize no longer takes " "'colocate_gradients_with_ops' argument, it behaves as if it " "was set to True.", }, "*.compute_gradients": { - "colocate_gradients_with_ops": + ("colocate_gradients_with_ops", 4): "Optimizer.compute_gradients no " "longer takes 'colocate_gradients_with_ops' argument, it " "behaves as if it was set to True.", }, + "tf.cond": { + ("strict", 3): + "tf.cond no longer takes 'strict' argument, it behaves as " + "if was set to True." + }, } self.symbol_renames = { diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py index c1882afd5c..0a85cb39c8 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py @@ -904,6 +904,14 @@ tf.print('abc') for s in expected_text: self.assertIn(s, new_text) + def testCond(self): + text = "tf.cond(a, b, c, True)" + expected_text = "tf.cond(pred=a, true_fn=b, false_fn=c)" + _, unused_report, errors, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) + self.assertIn("tf.cond", errors[0]) + self.assertIn("requires manual check", errors[0]) + class TestUpgradeFiles(test_util.TensorFlowTestCase): -- GitLab From 7e175f8f732806256a739e55e8f71e59c645c130 Mon Sep 17 00:00:00 2001 From: Jia Qingtong Date: Wed, 9 Jan 2019 21:44:20 +0800 Subject: [PATCH 0412/2345] [typo] Fix a bazel url typo Signed-off-by: Jia Qingtong --- tensorflow/workspace.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 5b0f6d4c84..2ce70557fa 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -141,7 +141,7 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): sha256 = "9de38f2d162c51599b802f7c36d9f3773980d19ac908c61638f8344d2c10e1ca", strip_prefix = "eigen-eigen-88fc23324517", urls = [ - "https://mirror.bazel.build/bitbucket.org/eigen/eigen/get/88fc23324517..tar.gz", + "https://mirror.bazel.build/bitbucket.org/eigen/eigen/get/88fc23324517.tar.gz", "https://bitbucket.org/eigen/eigen/get/88fc23324517.tar.gz", ], ) -- GitLab From b5fdd811998d495950c4b736879454be39acf163 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 07:08:38 -0800 Subject: [PATCH 0413/2345] Stop using set for signatures comparison. Sets internal hashing mechanism makes it impossible to use any kind of structures (lists, etc.). Also do some refactoring. PiperOrigin-RevId: 228511638 --- tensorflow/python/eager/def_function.py | 16 +++++++++------ tensorflow/python/eager/function.py | 27 ++++++++++++++++++------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index aa4f20df49..4c22a12c1c 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -446,10 +446,12 @@ class PolymorphicFunction(object): @property def _cached_input_signatures(self): """All input signatures used to call this PolymorphicFunction.""" - seen = set() - # Preserves signature ordering rather than returning a set() so that we - # don't need to re-sort signatures later to work around Python 2's set - # nondeterminism. + seen = list() + # We are using a list so that: + # - the returned collection is deterministic, and + # - we can use a custom equality operator (is_same_structure). + # This is run only at serialization time on likely very small inputs so we + # are not concerned about O(n^2) runtime. # pylint: disable=protected-access concrete_functions = [] if self._stateful_fn: @@ -458,9 +460,11 @@ class PolymorphicFunction(object): concrete_functions.extend(self._stateless_fn._function_cache.values()) for concrete_function in concrete_functions: signature = concrete_function._python_call_signature - if signature not in seen: + equal_to_signature = functools.partial( + function_lib.is_same_structure, signature, check_values=True) + if not any(equal_to_signature(s) for s in seen): yield signature - seen.add(signature) + seen.append(signature) # pylint: enable=protected-access def get_concrete_function(self, *args, **kwargs): diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 05fccfbcd9..67c633726f 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -74,6 +74,24 @@ CacheKey = collections.namedtuple("CacheKey", [ ]) +def is_same_structure(structure1, + structure2, + check_values=False): + """Check two structures for equality, optionally of types and of values.""" + try: + nest.assert_same_structure(structure1, structure2) + except (ValueError, TypeError): + return False + if check_values: + flattened1 = nest.flatten(structure1) + flattened2 = nest.flatten(structure2) + # First check the types to avoid AttributeErrors. + if any(type(f1) != type(f2) for f1, f2 in zip(flattened1, flattened2)): + return False + return flattened1 == flattened2 + return True + + def _parse_func_attrs(attributes): """Convert the keyword arguments into function_def attributes. @@ -919,10 +937,7 @@ class FunctionSpec(object): else: assert not kwargs signature_relevant_inputs = inputs[:len(self.input_signature)] - try: - nest.assert_same_structure(self.input_signature, - signature_relevant_inputs) - except (ValueError, TypeError): + if not is_same_structure(self.input_signature, signature_relevant_inputs): raise ValueError("Structure of Python function inputs does not match " "input_signature.") signature_inputs_flat = nest.flatten(signature_relevant_inputs) @@ -1049,9 +1064,7 @@ class PolymorphicFunction(object): "input_signature is provided.") if args: # If args are provided, they must match the input signature. - try: - nest.assert_same_structure(self._input_signature, args) - except (ValueError, TypeError): + if not is_same_structure(self._input_signature, args): raise ValueError("Structure of Python function inputs does not match " "input_signature.") flat_inputs = nest.flatten(args) -- GitLab From 433d47e39ce5e1c8c478393e7f5d2917c7df0fa7 Mon Sep 17 00:00:00 2001 From: Shashi Shekhar Date: Wed, 9 Jan 2019 08:00:10 -0800 Subject: [PATCH 0414/2345] Add symmetric quantization support to the accuracy tool. PiperOrigin-RevId: 228517966 --- .../ilsvrc/imagenet_model_evaluator.cc | 26 ++++------ .../accuracy/ilsvrc/imagenet_topk_eval.cc | 9 +++- .../ilsvrc/inception_preprocessing.cc | 42 +++++++++------ .../accuracy/ilsvrc/inception_preprocessing.h | 52 ++++++++++++++----- .../ilsvrc/inception_preprocessing_test.cc | 41 ++++++++++++--- 5 files changed, 119 insertions(+), 51 deletions(-) diff --git a/tensorflow/lite/tools/accuracy/ilsvrc/imagenet_model_evaluator.cc b/tensorflow/lite/tools/accuracy/ilsvrc/imagenet_model_evaluator.cc index 9a74e221c1..129747fe4d 100644 --- a/tensorflow/lite/tools/accuracy/ilsvrc/imagenet_model_evaluator.cc +++ b/tensorflow/lite/tools/accuracy/ilsvrc/imagenet_model_evaluator.cc @@ -22,6 +22,12 @@ limitations under the License. #include "absl/memory/memory.h" #include "tensorflow/cc/framework/scope.h" +#include "tensorflow/core/lib/core/blocking_counter.h" +#include "tensorflow/core/lib/core/threadpool.h" +#include "tensorflow/core/platform/init_main.h" +#include "tensorflow/core/platform/mutex.h" +#include "tensorflow/core/public/session.h" +#include "tensorflow/core/util/command_line_flags.h" #include "tensorflow/lite/tools/accuracy/eval_pipeline.h" #include "tensorflow/lite/tools/accuracy/eval_pipeline_builder.h" #include "tensorflow/lite/tools/accuracy/file_reader_stage.h" @@ -29,12 +35,6 @@ limitations under the License. #include "tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.h" #include "tensorflow/lite/tools/accuracy/run_tflite_model_stage.h" #include "tensorflow/lite/tools/accuracy/utils.h" -#include "tensorflow/core/lib/core/blocking_counter.h" -#include "tensorflow/core/lib/core/threadpool.h" -#include "tensorflow/core/platform/init_main.h" -#include "tensorflow/core/platform/mutex.h" -#include "tensorflow/core/public/session.h" -#include "tensorflow/core/util/command_line_flags.h" namespace { using tensorflow::string; @@ -185,21 +185,17 @@ Status EvaluateModelForShard(const uint64_t shard_id, const TensorShape& input_shape = model_info.input_shapes[0]; const int image_height = input_shape.dim_size(1); const int image_width = input_shape.dim_size(2); - const bool is_quantized = (model_info.input_types[0] == DT_UINT8); RunTFLiteModelStage::Params tfl_model_params; tfl_model_params.model_file_path = params.model_file_path; - if (is_quantized) { - tfl_model_params.input_type = {DT_UINT8}; - tfl_model_params.output_type = {DT_UINT8}; - } else { - tfl_model_params.input_type = {DT_FLOAT}; - tfl_model_params.output_type = {DT_FLOAT}; - } + + tfl_model_params.input_type = {model_info.input_types[0]}; + tfl_model_params.output_type = {model_info.input_types[0]}; Scope root = Scope::NewRootScope(); FileReaderStage reader; - InceptionPreprocessingStage inc(image_height, image_width, is_quantized); + InceptionPreprocessingStage inc(image_height, image_width, + model_info.input_types[0]); RunTFLiteModelStage tfl_model_stage(tfl_model_params); EvalPipelineBuilder builder; diff --git a/tensorflow/lite/tools/accuracy/ilsvrc/imagenet_topk_eval.cc b/tensorflow/lite/tools/accuracy/ilsvrc/imagenet_topk_eval.cc index 2b086cdf70..f5642d52a8 100644 --- a/tensorflow/lite/tools/accuracy/ilsvrc/imagenet_topk_eval.cc +++ b/tensorflow/lite/tools/accuracy/ilsvrc/imagenet_topk_eval.cc @@ -67,11 +67,18 @@ Status ImagenetTopKAccuracy::ComputeEval( for (size_t i = 0; i < probs.size(); i++) { probabilities.push_back(probs(i)); } - } else { + } else if (output.dtype() == DT_UINT8) { auto probs = output.flat(); for (size_t i = 0; i < probs.size(); i++) { probabilities.push_back(probs(i)); } + } else if (output.dtype() == DT_INT8) { + auto probs = output.flat(); + for (size_t i = 0; i < probs.size(); i++) { + probabilities.push_back(probs(i)); + } + } else { + return errors::InvalidArgument("Invalid datatype"); } CHECK_EQ(kNumCategories, probabilities.size()); diff --git a/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.cc b/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.cc index 9a889f0dd8..04b6cb7558 100644 --- a/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.cc +++ b/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.cc @@ -57,23 +57,33 @@ void InceptionPreprocessingStage::AddToGraph(const Scope& scope, tensorflow::Output cropped_image; CentralCropImage(s, decoded_jpeg, params_.cropping_fraction, &cropped_image); auto dims_expander = ops::ExpandDims(s, cropped_image, 0); - auto resized_image = ops::ResizeBilinear( - s, dims_expander, - ops::Const(s.WithOpName("size"), {image_height_, image_width_})); - if (is_quantized_) { - this->stage_output_ = - ops::Cast(s.WithOpName(output_name()), resized_image, DT_UINT8); - } else { - auto squeezed_image = ops::Squeeze(s, resized_image); - auto normalized_image = - ops::Div(s, - ops::Sub(s, squeezed_image, - {params_.input_means[0], params_.input_means[1], - params_.input_means[2]}), - {params_.scale}); - this->stage_output_ = - ops::ExpandDims(s.WithOpName(output_name()), normalized_image, {0}); + auto resized_image = + ops::ResizeBilinear(s.WithOpName("resize"), dims_expander, + ops::Const(s, {image_height_, image_width_})); + + ::tensorflow::Output preprocessed_image = resized_image; + + if (!params_.input_means.empty()) { + preprocessed_image = + ops::Sub(s.WithOpName("sub"), preprocessed_image, + {params_.input_means[0], params_.input_means[1], + params_.input_means[2]}); + } + + if (std::abs(params_.scale) > 1e-7f) { + auto squeezed_image = ops::Squeeze(s, preprocessed_image); + preprocessed_image = ops::Div(s, squeezed_image, {params_.scale}); + preprocessed_image = ops::ExpandDims(s, preprocessed_image, {0}); } + + // Cast the output from float to output datatype. + if (output_datatype_ != DT_FLOAT) { + preprocessed_image = + ops::Cast(s.WithOpName("cast"), preprocessed_image, output_datatype_); + } + + this->stage_output_ = + ops::Identity(s.WithOpName(output_name()), preprocessed_image); } } // namespace metrics diff --git a/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.h b/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.h index 4a1d3ce476..371feb3e76 100644 --- a/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.h +++ b/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.h @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_TOOLS_ACCURACY_INCEPTION_PREPROCESSING_H_ -#define TENSORFLOW_LITE_TOOLS_ACCURACY_INCEPTION_PREPROCESSING_H_ +#ifndef TENSORFLOW_LITE_TOOLS_ACCURACY_ILSVRC_INCEPTION_PREPROCESSING_H_ +#define TENSORFLOW_LITE_TOOLS_ACCURACY_ILSVRC_INCEPTION_PREPROCESSING_H_ #include -#include "tensorflow/lite/tools/accuracy/stage.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/lib/core/status.h" +#include "tensorflow/lite/tools/accuracy/stage.h" namespace tensorflow { namespace metrics { @@ -31,28 +31,53 @@ namespace metrics { // shape {1, image_height, image_width, 3}, where 3 is the number of channels. class InceptionPreprocessingStage : public Stage { public: + // Preprocessing params that govern scaling and normalization of channels of + // the image. struct Params { + // Input means are subtracted from each channel. + // In case of an empty vector this is skipped. std::vector input_means; + // Scale is used to divide the input. + // A scale of 0 means divison is skipped. float scale; double cropping_fraction; }; - static Params DefaultParams() { - return {.input_means = {127.5, 127.5, 127.5}, - .scale = 127.5, - .cropping_fraction = 0.875}; + // Default preprocessing for inception stage based on |output_type| + static Params DefaultParamsForType(DataType output_type) { + const float kCroppingFraction = 0.875; + Params params = {}; + params.cropping_fraction = kCroppingFraction; + if (output_type == DT_UINT8) { + } else if (output_type == DT_INT8) { + params.input_means = {128.0, 128.0, 128.0}; + } else { + // Assume floating point preprocessing. + params.input_means = {127.5, 127.5, 127.5}; + params.scale = 127.5; + } + return params; + } + + // Creates a new preprocessing stage object with provided |image_width| + // |image_height| as the size of output image. + // |output_datatype| is the datatype of output of the stage. + InceptionPreprocessingStage(int image_width, int image_height, + DataType output_datatype) + : output_datatype_(output_datatype), + image_width_(image_width), + image_height_(image_height) { + params_ = DefaultParamsForType(output_datatype); } // Creates a new preprocessing stage object with provided |image_width| // |image_height| as the size of output image. - // If |is_quantized| is set to true then |params| is ignored since quantized - // images don't go through any preprocessing. + // |output_datatype| is the datatype of output of the stage. InceptionPreprocessingStage(int image_width, int image_height, - bool is_quantized, - Params params = DefaultParams()) - : image_width_(image_width), + DataType output_datatype, Params params) + : output_datatype_(output_datatype), + image_width_(image_width), image_height_(image_height), - is_quantized_(is_quantized), params_(std::move(params)) {} string name() const override { return "stage_inception_preprocess"; } @@ -63,6 +88,7 @@ class InceptionPreprocessingStage : public Stage { void AddToGraph(const Scope& scope, const Input& input) override; private: + DataType output_datatype_; int image_width_; int image_height_; bool is_quantized_; diff --git a/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing_test.cc b/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing_test.cc index 5d0e01d7d1..f88847035f 100644 --- a/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing_test.cc +++ b/tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing_test.cc @@ -17,10 +17,10 @@ limitations under the License. #include #include -#include "tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.h" #include "tensorflow/core/platform/init_main.h" #include "tensorflow/core/public/session.h" #include "tensorflow/core/util/command_line_flags.h" +#include "tensorflow/lite/tools/accuracy/ilsvrc/inception_preprocessing.h" namespace { tensorflow::string* g_test_image_file = nullptr; @@ -48,7 +48,7 @@ Status GetContents(const string& filename, string* output) { } } -TEST(InceptionPreprocessingTest, TestImagePreprocessQuantized) { +TEST(InceptionPreprocessingTest, TestImagePreprocessUInt8Quantized) { ASSERT_TRUE(g_test_image_file != nullptr); string image_contents; string image_path = *g_test_image_file; @@ -56,8 +56,8 @@ TEST(InceptionPreprocessingTest, TestImagePreprocessQuantized) { ASSERT_TRUE(status.ok()) << status.error_message(); const int width = 224; const int height = 224; - const bool is_quantized = true; - InceptionPreprocessingStage preprocess_stage(width, height, is_quantized); + auto params = InceptionPreprocessingStage::DefaultParamsForType(DT_UINT8); + InceptionPreprocessingStage preprocess_stage(width, height, DT_UINT8, params); Scope scope = Scope::NewRootScope(); preprocess_stage.AddToGraph(scope, image_contents); TF_CHECK_OK(scope.status()); @@ -77,6 +77,35 @@ TEST(InceptionPreprocessingTest, TestImagePreprocessQuantized) { EXPECT_TRUE(outputs[0].shape().IsSameSize({1, 224, 224, 3})); } +TEST(InceptionPreprocessingTest, TestImagePreprocessInt8Quantized) { + ASSERT_TRUE(g_test_image_file != nullptr); + string image_contents; + string image_path = *g_test_image_file; + auto status = GetContents(image_path, &image_contents); + ASSERT_TRUE(status.ok()) << status.error_message(); + const int width = 224; + const int height = 224; + auto params = InceptionPreprocessingStage::DefaultParamsForType(DT_INT8); + InceptionPreprocessingStage preprocess_stage(width, height, DT_INT8, params); + Scope scope = Scope::NewRootScope(); + preprocess_stage.AddToGraph(scope, image_contents); + TF_CHECK_OK(scope.status()); + + GraphDef graph_def; + TF_CHECK_OK(scope.ToGraphDef(&graph_def)); + std::unique_ptr session(NewSession(SessionOptions())); + TF_CHECK_OK(session->Create(graph_def)); + std::vector outputs; + auto run_status = + session->Run({}, /*inputs*/ + {preprocess_stage.output_name()}, {}, /*target node names */ + &outputs); + TF_CHECK_OK(run_status); + EXPECT_EQ(1, outputs.size()); + EXPECT_EQ(DT_INT8, outputs[0].dtype()); + EXPECT_TRUE(outputs[0].shape().IsSameSize({1, 224, 224, 3})); +} + TEST(InceptionPreprocessingTest, TestImagePreprocessFloat) { ASSERT_TRUE(g_test_image_file != nullptr); string image_contents; @@ -85,8 +114,8 @@ TEST(InceptionPreprocessingTest, TestImagePreprocessFloat) { ASSERT_TRUE(status.ok()) << status.error_message(); const int width = 224; const int height = 224; - const bool is_quantized = false; - InceptionPreprocessingStage preprocess_stage(width, height, is_quantized); + auto params = InceptionPreprocessingStage::DefaultParamsForType(DT_FLOAT); + InceptionPreprocessingStage preprocess_stage(width, height, DT_FLOAT, params); Scope scope = Scope::NewRootScope(); preprocess_stage.AddToGraph(scope, image_contents); TF_CHECK_OK(scope.status()); -- GitLab From eed1c1413ff2c45c82142d5d4dd8b6d51a2f5636 Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Wed, 9 Jan 2019 08:11:28 -0800 Subject: [PATCH 0415/2345] Automated rollback of commit 9f3acf3d89a4ef5f9313a6e2cb2d81e03177e8ef PiperOrigin-RevId: 228519835 --- tensorflow/lite/python/BUILD | 1 + tensorflow/lite/python/convert.py | 13 +++++-------- tensorflow/lite/toco/python/BUILD | 21 ++++++++++++++++++--- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/tensorflow/lite/python/BUILD b/tensorflow/lite/python/BUILD index 7bfd1a8996..54b925cc05 100644 --- a/tensorflow/lite/python/BUILD +++ b/tensorflow/lite/python/BUILD @@ -74,6 +74,7 @@ py_test( data = ["@tflite_mobilenet_ssd_quant_protobuf//:tflite_graph.pb"], srcs_version = "PY2AND3", tags = [ + "no_oss", "no_windows", ], deps = [ diff --git a/tensorflow/lite/python/convert.py b/tensorflow/lite/python/convert.py index 9f315fc874..9c60399871 100644 --- a/tensorflow/lite/python/convert.py +++ b/tensorflow/lite/python/convert.py @@ -37,14 +37,11 @@ from tensorflow.python.util.tf_export import tf_export as _tf_export # Lazy load since some of the performance benchmark skylark rules # break dependencies. -if lite_constants.EXPERIMENTAL_USE_TOCO_API_DIRECTLY: - _toco_python = LazyLoader( - "tensorflow_wrap_toco", globals(), - "tensorflow.lite.toco.python." - "tensorflow_wrap_toco") - del LazyLoader -else: - _toco_python = None +_toco_python = LazyLoader( + "tensorflow_wrap_toco", globals(), + "tensorflow.lite.toco.python." + "tensorflow_wrap_toco") +del LazyLoader # Find the toco_from_protos binary using the resource loader if using from # bazel, otherwise we are in a pip where console_scripts already has diff --git a/tensorflow/lite/toco/python/BUILD b/tensorflow/lite/toco/python/BUILD index f3ea5b7bfe..8a6e82ec46 100644 --- a/tensorflow/lite/toco/python/BUILD +++ b/tensorflow/lite/toco/python/BUILD @@ -10,20 +10,35 @@ load("//tensorflow:tensorflow.bzl", "tf_py_wrap_cc") load("//tensorflow:tensorflow.bzl", "tf_py_test") load("//tensorflow:tensorflow.bzl", "py_binary") +config_setting( + name = "tflite_convert_with_select_tf_ops", + define_values = {"tflite_convert_with_select_tf_ops": "true"}, + visibility = [ + "//tensorflow/contrib/lite:__subpackages__", + "//tensorflow/lite:__subpackages__", + ], +) + cc_library( name = "toco_python_api", srcs = ["toco_python_api.cc"], hdrs = ["toco_python_api.h"], deps = [ + "//third_party/python_runtime:headers", "//tensorflow/core:lib", - "//tensorflow/core:ops", "//tensorflow/lite/toco:model_flags_proto_cc", "//tensorflow/lite/toco:toco_flags_proto_cc", "//tensorflow/lite/toco:toco_graphviz_dump_options", "//tensorflow/lite/toco:toco_port", "//tensorflow/lite/toco:toco_tooling", - "//third_party/python_runtime:headers", - ], + ] + select({ + # This is required when running `tflite_convert` from `bazel`. + # It requires to link with TensorFlow Ops to get the op definitions. + ":tflite_convert_with_select_tf_ops": [ + "//tensorflow/core:ops", + ], + "//conditions:default": [], + }), ) tf_py_wrap_cc( -- GitLab From beae80428e5a7d67417dc18561b8ed74724fe4bf Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Wed, 9 Jan 2019 08:32:27 -0800 Subject: [PATCH 0416/2345] Move explicit template instantiations to namespace scope. Fixes compile error on mac os on attached bug causing 1.13 build problems. PiperOrigin-RevId: 228522576 --- tensorflow/lite/testing/tflite_driver.cc | 121 ++++++++++++----------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/tensorflow/lite/testing/tflite_driver.cc b/tensorflow/lite/testing/tflite_driver.cc index ffe296432a..a637dc86c0 100644 --- a/tensorflow/lite/testing/tflite_driver.cc +++ b/tensorflow/lite/testing/tflite_driver.cc @@ -79,32 +79,7 @@ class TfLiteDriver::Expectation { SetTensorData(values, &data_); } - template <> - void SetData(const string& csv_values) { - string s = absl::HexStringToBytes(csv_values); - data_.raw = new char[s.size()]; - memcpy(data_.raw, s.data(), s.size()); - } - - bool Check(bool verbose, const TfLiteTensor& tensor) { - switch (tensor.type) { - case kTfLiteFloat32: - return TypedCheck(verbose, tensor); - case kTfLiteInt32: - return TypedCheck(verbose, tensor); - case kTfLiteInt64: - return TypedCheck(verbose, tensor); - case kTfLiteUInt8: - return TypedCheck(verbose, tensor); - case kTfLiteBool: - return TypedCheck(verbose, tensor); - case kTfLiteString: - return TypedCheck(verbose, tensor); - default: - fprintf(stderr, "Unsupported type %d in Check\n", tensor.type); - return false; - } - } + bool Check(bool verbose, const TfLiteTensor& tensor); private: template @@ -146,49 +121,77 @@ class TfLiteDriver::Expectation { return good_output; } - template <> - bool TypedCheck(bool verbose, const TfLiteTensor& tensor) { - if (tensor.data.raw == nullptr) { + TfLitePtrUnion data_; + size_t num_elements_; +}; + +template <> +void TfLiteDriver::Expectation::SetData(const string& csv_values) { + string s = absl::HexStringToBytes(csv_values); + data_.raw = new char[s.size()]; + memcpy(data_.raw, s.data(), s.size()); +} + +template <> +bool TfLiteDriver::Expectation::TypedCheck(bool verbose, + const TfLiteTensor& tensor) { + if (tensor.data.raw == nullptr) { + if (verbose) { + std::cerr << " got empty string" << std::endl; + } + return false; + } + int expected_num_strings = GetStringCount(data_.raw); + int returned_num_strings = GetStringCount(tensor.data.raw); + if (expected_num_strings != returned_num_strings) { + if (verbose) { + std::cerr << " string count differ: got " << returned_num_strings + << ", but expected " << expected_num_strings << std::endl; + } + return false; + } + for (int i = 0; i < returned_num_strings; ++i) { + auto expected_ref = GetString(data_.raw, i); + auto returned_ref = GetString(tensor.data.raw, i); + if (expected_ref.len != returned_ref.len) { if (verbose) { - std::cerr << " got empty string" << std::endl; + std::cerr << " index " << i << ": got string of size " + << returned_ref.len << ", but expected size " + << expected_ref.len << std::endl; } return false; } - int expected_num_strings = GetStringCount(data_.raw); - int returned_num_strings = GetStringCount(tensor.data.raw); - if (expected_num_strings != returned_num_strings) { + if (strncmp(expected_ref.str, returned_ref.str, returned_ref.len) != 0) { if (verbose) { - std::cerr << " string count differ: got " << returned_num_strings - << ", but expected " << expected_num_strings << std::endl; + std::cerr << " index " << i << ": strings are different" << std::endl; } return false; } - for (int i = 0; i < returned_num_strings; ++i) { - auto expected_ref = GetString(data_.raw, i); - auto returned_ref = GetString(tensor.data.raw, i); - if (expected_ref.len != returned_ref.len) { - if (verbose) { - std::cerr << " index " << i << ": got string of size " - << returned_ref.len << ", but expected size " - << expected_ref.len << std::endl; - } - return false; - } - if (strncmp(expected_ref.str, returned_ref.str, returned_ref.len) != 0) { - if (verbose) { - std::cerr << " index " << i << ": strings are different" - << std::endl; - } - return false; - } - } - - return true; } - TfLitePtrUnion data_; - size_t num_elements_; -}; + return true; +} + +bool TfLiteDriver::Expectation::Check(bool verbose, + const TfLiteTensor& tensor) { + switch (tensor.type) { + case kTfLiteFloat32: + return TypedCheck(verbose, tensor); + case kTfLiteInt32: + return TypedCheck(verbose, tensor); + case kTfLiteInt64: + return TypedCheck(verbose, tensor); + case kTfLiteUInt8: + return TypedCheck(verbose, tensor); + case kTfLiteBool: + return TypedCheck(verbose, tensor); + case kTfLiteString: + return TypedCheck(verbose, tensor); + default: + fprintf(stderr, "Unsupported type %d in Check\n", tensor.type); + return false; + } +} TfLiteDriver::TfLiteDriver(bool use_nnapi, const string& delegate_name, bool reference_kernel) -- GitLab From 5922eee396da936af1451c22e0433071f909eeaa Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 09:15:17 -0800 Subject: [PATCH 0417/2345] Build file changes. PiperOrigin-RevId: 228528921 --- tensorflow/BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/BUILD b/tensorflow/BUILD index 0b4498ef12..29d71c323a 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -385,7 +385,6 @@ package_group( "-//third_party/tensorflow/python/estimator", "//learning/deepmind/...", "//learning/meta_rank/...", - "//learning/pathways/...", # While dataset C++ api requires internals "//tensorflow/...", "//tensorflow_estimator/contrib/...", "//tensorflow_fold/llgtm/...", -- GitLab From 1ee193a2563d51ee45b401f2cff91f6e480e21db Mon Sep 17 00:00:00 2001 From: Martin Wicke Date: Wed, 9 Jan 2019 09:33:54 -0800 Subject: [PATCH 0418/2345] Remove aux-bin from pip package, it's not needed any more. PiperOrigin-RevId: 228531942 --- tensorflow/tools/pip_package/MANIFEST.in | 1 - tensorflow/tools/pip_package/build_pip_package.sh | 3 --- 2 files changed, 4 deletions(-) diff --git a/tensorflow/tools/pip_package/MANIFEST.in b/tensorflow/tools/pip_package/MANIFEST.in index 272ff4735c..c304e8cf6e 100644 --- a/tensorflow/tools/pip_package/MANIFEST.in +++ b/tensorflow/tools/pip_package/MANIFEST.in @@ -6,7 +6,6 @@ recursive-include * *.so recursive-include * *.dll recursive-include * *.lib recursive-include * *.csv -recursive-include tensorflow/aux-bin * recursive-include tensorflow/include/tensorflow *.h recursive-include tensorflow/include/Eigen * recursive-include tensorflow/include/external * diff --git a/tensorflow/tools/pip_package/build_pip_package.sh b/tensorflow/tools/pip_package/build_pip_package.sh index 439b5428b3..27815491d2 100755 --- a/tensorflow/tools/pip_package/build_pip_package.sh +++ b/tensorflow/tools/pip_package/build_pip_package.sh @@ -118,9 +118,6 @@ function prepare_src() { fi fi fi - mkdir "${TMPDIR}/tensorflow/aux-bin" - # Install toco as a binary in aux-bin. - cp bazel-bin/tensorflow/lite/python/tflite_convert ${TMPDIR}/tensorflow/aux-bin/ fi # protobuf pip package doesn't ship with header files. Copy the headers -- GitLab From 2da8e8c60343d9e833f1c591c54ee2a27c240842 Mon Sep 17 00:00:00 2001 From: Tamara Norman Date: Wed, 9 Jan 2019 10:23:54 -0800 Subject: [PATCH 0419/2345] Change so that error message received if attempting to do + between a python literal and tensor with incompatible dtype to come from the op PiperOrigin-RevId: 228541386 --- tensorflow/python/ops/math_ops.py | 3 ++- tensorflow/python/ops/math_ops_test.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/ops/math_ops.py b/tensorflow/python/ops/math_ops.py index 5bccf5493f..248d092538 100644 --- a/tensorflow/python/ops/math_ops.py +++ b/tensorflow/python/ops/math_ops.py @@ -813,7 +813,8 @@ def _OverrideBinaryOperatorHelper(func, op_name, clazz_object=ops.Tensor): return func(x, y, name=name) elif not isinstance(y, sparse_tensor.SparseTensor): try: - y = ops.convert_to_tensor(y, dtype=x.dtype.base_dtype, name="y") + y = ops.convert_to_tensor_v2(y, dtype_hint=x.dtype.base_dtype, + name="y") except TypeError: # If the RHS is not a tensor, it might be a tensor aware object # that can implement the operator with knowledge of itself diff --git a/tensorflow/python/ops/math_ops_test.py b/tensorflow/python/ops/math_ops_test.py index b27cf7208c..b4832e09c0 100644 --- a/tensorflow/python/ops/math_ops_test.py +++ b/tensorflow/python/ops/math_ops_test.py @@ -22,6 +22,7 @@ import numpy as np from tensorflow.python.eager import context from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes +from tensorflow.python.framework import errors_impl from tensorflow.python.framework import ops from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops @@ -664,5 +665,22 @@ class NextAfterTest(test_util.TensorFlowTestCase): self.assertAllEqual(math_ops.nextafter(one, two) - one, eps_const) +class BinaryOpsTest(test_util.TensorFlowTestCase): + + @test_util.run_in_graph_and_eager_modes + def testErrorReceivedIfDtypeMismatchFromOp(self): + if context.executing_eagerly(): + error = errors_impl.InvalidArgumentError + error_message = ( + r"cannot compute Add as input #0\(zero-based\) was expected to be a " + r"float tensor but is a int32 tensor \[Op:Add\] name: add/") + else: + error = TypeError + error_message = ("Input 'y' of 'Add' Op has type float32 that does not " + "match type int32 of argument 'x'.") + with self.assertRaisesRegexp(error, error_message): + a = array_ops.ones([1], dtype=dtypes.int32) + 1.0 + self.evaluate(a) + if __name__ == "__main__": googletest.main() -- GitLab From 2c76c0a07fbf82d5bc8cfc81e688560d0b7e4537 Mon Sep 17 00:00:00 2001 From: Martin Wicke Date: Wed, 9 Jan 2019 10:24:39 -0800 Subject: [PATCH 0420/2345] Add dependency on google_pasta (freshly minted on pypi). PiperOrigin-RevId: 228541538 --- tensorflow/tools/pip_package/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index 9567e0aff7..55b7046e30 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -51,6 +51,7 @@ REQUIRED_PACKAGES = [ 'absl-py >= 0.1.6', 'astor >= 0.6.0', 'gast >= 0.2.0', + 'google_pasta >= 0.1.0', 'keras_applications >= 1.0.6', 'keras_preprocessing >= 1.0.5', 'numpy >= 1.13.3', -- GitLab From 864fe25ff7363730ef81e402d6a10cc758df72cd Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Wed, 9 Jan 2019 10:52:22 -0800 Subject: [PATCH 0421/2345] Align Android gradle build files Use Gradle 3.1.4 and modernize the TFLite example gradle files. PiperOrigin-RevId: 228547238 --- .../lite/examples/android/app/build.gradle | 6 ++--- tensorflow/lite/examples/android/build.gradle | 2 +- .../lite/g3doc/tfmobile/android_build.md | 4 +-- tensorflow/lite/g3doc/using_select_tf_ops.md | 2 +- tensorflow/lite/java/demo/app/build.gradle | 26 +++++++------------ tensorflow/lite/java/demo/build.gradle | 4 ++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../lite/java/ovic/demo/app/build.gradle | 21 ++++++--------- tensorflow/lite/java/ovic/demo/build.gradle | 4 ++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 10 files changed, 33 insertions(+), 40 deletions(-) diff --git a/tensorflow/lite/examples/android/app/build.gradle b/tensorflow/lite/examples/android/app/build.gradle index e5f5c7efd1..b372afae19 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 '27.0.3' defaultConfig { applicationId "org.tensorflow.lite.demo" minSdkVersion 15 @@ -45,6 +45,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/build.gradle b/tensorflow/lite/examples/android/build.gradle index 7c79358e45..7c038ddd46 100644 --- a/tensorflow/lite/examples/android/build.gradle +++ b/tensorflow/lite/examples/android/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/tensorflow/lite/g3doc/tfmobile/android_build.md b/tensorflow/lite/g3doc/tfmobile/android_build.md index 2eb776d10c..f8c0243298 100644 --- a/tensorflow/lite/g3doc/tfmobile/android_build.md +++ b/tensorflow/lite/g3doc/tfmobile/android_build.md @@ -91,10 +91,10 @@ following lines to your Gradle build file: repositories { jcenter() } - } + } dependencies { - compile 'org.tensorflow:tensorflow-android:+' + implementation 'org.tensorflow:tensorflow-android:+' } This automatically downloads the latest stable version of TensorFlow as an AAR diff --git a/tensorflow/lite/g3doc/using_select_tf_ops.md b/tensorflow/lite/g3doc/using_select_tf_ops.md index aa51f58baa..269774a4b1 100644 --- a/tensorflow/lite/g3doc/using_select_tf_ops.md +++ b/tensorflow/lite/g3doc/using_select_tf_ops.md @@ -130,7 +130,7 @@ allprojects { } dependencies { - compile 'org.tensorflow:tensorflow-lite-with-select-tf-ops:0.1.100' + implementation 'org.tensorflow:tensorflow-lite-with-select-tf-ops:0.1.100' } ``` diff --git a/tensorflow/lite/java/demo/app/build.gradle b/tensorflow/lite/java/demo/app/build.gradle index b8fc282cb1..8ea16a3417 100644 --- a/tensorflow/lite/java/demo/app/build.gradle +++ b/tensorflow/lite/java/demo/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 26 - buildToolsVersion "26.0.1" + buildToolsVersion "27.0.3" defaultConfig { applicationId "android.example.com.tflitecamerademo" // Required by Camera2 API. @@ -10,11 +10,6 @@ android { targetSdkVersion 26 versionCode 1 versionName "1.0" - - // Remove this block. - jackOptions { - enabled true - } } lintOptions { abortOnError false @@ -40,6 +35,7 @@ repositories { url 'https://google.bintray.com/tensorflow' } } + allprojects { repositories { // Uncomment if you want to use a local repo. @@ -48,20 +44,18 @@ allprojects { } } - - dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:25.2.0' - compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'com.android.support:design:25.2.0' - compile 'com.android.support:support-annotations:25.3.1' - compile 'com.android.support:support-v13:25.2.0' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:25.2.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'com.android.support:design:25.2.0' + implementation 'com.android.support:support-annotations:25.3.1' + implementation 'com.android.support:support-v13:25.2.0' // Build off of nightly TensorFlow Lite - compile 'org.tensorflow:tensorflow-lite:0.0.0-nightly' + implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly' // Use local TensorFlow library - // compile 'org.tensorflow:tensorflow-lite-local:0.0.0' + // implementation 'org.tensorflow:tensorflow-lite-local:0.0.0' } def targetFolder = "src/main/assets" diff --git a/tensorflow/lite/java/demo/build.gradle b/tensorflow/lite/java/demo/build.gradle index b78a0b86c9..a88b3fdc70 100644 --- a/tensorflow/lite/java/demo/build.gradle +++ b/tensorflow/lite/java/demo/build.gradle @@ -2,10 +2,11 @@ buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:3.1.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -14,6 +15,7 @@ buildscript { allprojects { repositories { + google() jcenter() } } diff --git a/tensorflow/lite/java/demo/gradle/wrapper/gradle-wrapper.properties b/tensorflow/lite/java/demo/gradle/wrapper/gradle-wrapper.properties index fa7a38a0e4..9ff32fe2bb 100644 --- a/tensorflow/lite/java/demo/gradle/wrapper/gradle-wrapper.properties +++ b/tensorflow/lite/java/demo/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/tensorflow/lite/java/ovic/demo/app/build.gradle b/tensorflow/lite/java/ovic/demo/app/build.gradle index 4f3a6cdb2f..77f568448a 100644 --- a/tensorflow/lite/java/ovic/demo/app/build.gradle +++ b/tensorflow/lite/java/ovic/demo/app/build.gradle @@ -2,18 +2,13 @@ apply plugin: 'com.android.application' android { compileSdkVersion 26 - buildToolsVersion "26.0.1" + buildToolsVersion "27.0.3" defaultConfig { applicationId "android.example.com.ovicbenchmarker" minSdkVersion 15 targetSdkVersion 26 versionCode 1 versionName "1.0" - - // Remove this block. - jackOptions { - enabled true - } } lintOptions { abortOnError false @@ -41,12 +36,12 @@ repositories { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:25.2.0' - compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'com.android.support:design:25.2.0' - compile 'com.android.support:support-annotations:25.3.1' - compile 'com.android.support:support-v13:25.2.0' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:25.2.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'com.android.support:design:25.2.0' + implementation 'com.android.support:support-annotations:25.3.1' + implementation 'com.android.support:support-v13:25.2.0' - compile 'org.tensorflow:tensorflow-lite:+' + implementation 'org.tensorflow:tensorflow-lite:+' } diff --git a/tensorflow/lite/java/ovic/demo/build.gradle b/tensorflow/lite/java/ovic/demo/build.gradle index b78a0b86c9..a88b3fdc70 100644 --- a/tensorflow/lite/java/ovic/demo/build.gradle +++ b/tensorflow/lite/java/ovic/demo/build.gradle @@ -2,10 +2,11 @@ buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:3.1.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -14,6 +15,7 @@ buildscript { allprojects { repositories { + google() jcenter() } } diff --git a/tensorflow/lite/java/ovic/demo/gradle/wrapper/gradle-wrapper.properties b/tensorflow/lite/java/ovic/demo/gradle/wrapper/gradle-wrapper.properties index fa7a38a0e4..9ff32fe2bb 100644 --- a/tensorflow/lite/java/ovic/demo/gradle/wrapper/gradle-wrapper.properties +++ b/tensorflow/lite/java/ovic/demo/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip -- GitLab From 65cfc6a54bc76912dd16c25b9e13610c4359a6d9 Mon Sep 17 00:00:00 2001 From: Blake Hechtman Date: Wed, 9 Jan 2019 11:13:04 -0800 Subject: [PATCH 0422/2345] [XLA] Fix Dot operation semantics. PiperOrigin-RevId: 228551710 --- tensorflow/compiler/xla/g3doc/operation_semantics.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tensorflow/compiler/xla/g3doc/operation_semantics.md b/tensorflow/compiler/xla/g3doc/operation_semantics.md index 7377ed729b..59ba9bb658 100644 --- a/tensorflow/compiler/xla/g3doc/operation_semantics.md +++ b/tensorflow/compiler/xla/g3doc/operation_semantics.md @@ -871,9 +871,7 @@ DotGeneral performs the sum of products over contracting dimensions specified in 'dimension_numbers'. Associated contracting dimension numbers from the 'lhs' and 'rhs' do not need -to be the same, but must be listed in the same order in both -'lhs/rhs_contracting_dimensions' arrays and have the same dimension sizes. -There must be exactly one contracting dimension on both 'lhs' and 'rhs'. +to be the same and but must have the same dimension sizes. Example with contracting dimension numbers: @@ -892,10 +890,8 @@ DotGeneral(lhs, rhs, dnums) -> { {6.0, 12.0}, {15.0, 30.0} } ``` -Associated batch dimension numbers from the 'lhs' and 'rhs' must have the same -dimension number, must be listed in the same order in both arrays, must -have the same dimension sizes, and must be ordered before contracting and -non-contracting/non-batch dimension numbers. +Associated batch dimension numbers from the 'lhs' and 'rhs' must +have the same dimension sizes. Example with batch dimension numbers (batch size 2, 2x2 matrices): -- GitLab From d987224e93ba153201a7c3958594396af56a255d Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Wed, 9 Jan 2019 11:24:20 -0800 Subject: [PATCH 0423/2345] Typo fix PiperOrigin-RevId: 228554042 --- tensorflow/core/grappler/costs/graph_properties.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/grappler/costs/graph_properties.cc b/tensorflow/core/grappler/costs/graph_properties.cc index cfbd340f08..1ed96ca12a 100644 --- a/tensorflow/core/grappler/costs/graph_properties.cc +++ b/tensorflow/core/grappler/costs/graph_properties.cc @@ -1463,9 +1463,9 @@ class SymbolicShapeRefiner { // Due to the cost of EvaluateNode(), we run it only for certain op types // (white listed) and small integer tensors. - const int max_elelment_size = 17; // Max up to 4x4 matrix or similar. + const int max_element_size = 17; // Max up to 4x4 matrix or similar. if (AllOutputValuesKnown(c) || !AllInputValuesKnown(c) || - !ShouldUpdateOutputValues(c, max_elelment_size)) { + !ShouldUpdateOutputValues(c, max_element_size)) { return Status::OK(); } UpdateOutputValues(node, c).IgnoreError(); // This is optional. -- GitLab From 9e4f493abffbcb1efc7ea2b1e9bce0afff8122ce Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Wed, 9 Jan 2019 11:31:52 -0800 Subject: [PATCH 0424/2345] Reduce max_workspace_size_bytes so the GPU memory allocation doesn't exceed the limit. PiperOrigin-RevId: 228555498 --- tensorflow/contrib/tensorrt/test/quantization_mnist_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/tensorrt/test/quantization_mnist_test.py b/tensorflow/contrib/tensorrt/test/quantization_mnist_test.py index e7d6ec4ad3..79dde2a6b5 100644 --- a/tensorflow/contrib/tensorrt/test/quantization_mnist_test.py +++ b/tensorflow/contrib/tensorrt/test/quantization_mnist_test.py @@ -144,7 +144,10 @@ class QuantizationAwareTrainingMNISTTest(test_util.TensorFlowTestCase): outputs=[OUTPUT_NODE_NAME], max_batch_size=max_batch_size, precision_mode='INT8', - max_workspace_size_bytes=4096 << 19, + # There is a 2GB GPU memory limit for each test, so we set + # max_workspace_size_bytes to 256MB to leave enough room for TF + # runtime to allocate GPU memory. + max_workspace_size_bytes=1 << 28, minimum_segment_size=2, use_calibration=False, ) -- GitLab From 06974e29b1e9f4b4b5120acca2889bfa3bcf055d Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Wed, 9 Jan 2019 11:39:26 -0800 Subject: [PATCH 0425/2345] Internal Cleanup. PiperOrigin-RevId: 228557086 --- tensorflow/python/keras/optimizer_v2/BUILD | 4 - tensorflow/python/keras/optimizer_v2/nadam.py | 3 +- .../keras/optimizer_v2/optimizer_v2_test.py | 115 ++++++++++-------- 3 files changed, 62 insertions(+), 60 deletions(-) diff --git a/tensorflow/python/keras/optimizer_v2/BUILD b/tensorflow/python/keras/optimizer_v2/BUILD index 2b990c9334..da757edc74 100644 --- a/tensorflow/python/keras/optimizer_v2/BUILD +++ b/tensorflow/python/keras/optimizer_v2/BUILD @@ -177,11 +177,7 @@ py_test( srcs = ["optimizer_v2_test.py"], shard_count = 8, tags = [ - "manual", - "no_gpu", - "no_oss", "no_windows", - "notap", ], deps = [ ":optimizer_v2", diff --git a/tensorflow/python/keras/optimizer_v2/nadam.py b/tensorflow/python/keras/optimizer_v2/nadam.py index 627258f455..7141c6a079 100644 --- a/tensorflow/python/keras/optimizer_v2/nadam.py +++ b/tensorflow/python/keras/optimizer_v2/nadam.py @@ -84,8 +84,7 @@ class Nadam(optimizer_v2.OptimizerV2): """ # Backwards compatiblity with keras NAdam optimizer. - if 'schedule_decay' in kwargs: - kwargs['decay'] = kwargs.pop('schedule_decay', 0.004) + kwargs['decay'] = kwargs.pop('schedule_decay', 0.004) super(Nadam, self).__init__(name, **kwargs) self._set_hyper('learning_rate', kwargs.get('lr', learning_rate)) self._set_hyper('decay', self._initial_decay) diff --git a/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py b/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py index b41e4aa064..93a6a7e85e 100644 --- a/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py +++ b/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py @@ -18,10 +18,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import os -import tempfile - -from absl.testing import parameterized import numpy as np from tensorflow.python import keras @@ -33,22 +29,25 @@ from tensorflow.python.framework import ops from tensorflow.python.framework import test_util from tensorflow.python.keras import backend from tensorflow.python.keras import callbacks +from tensorflow.python.keras import keras_parameterized from tensorflow.python.keras import optimizers from tensorflow.python.keras import testing_utils from tensorflow.python.keras.engine import input_layer -from tensorflow.python.keras.engine import saving from tensorflow.python.keras.engine import sequential from tensorflow.python.keras.engine import training from tensorflow.python.keras.layers import core +from tensorflow.python.keras.optimizer_v2 import adadelta +from tensorflow.python.keras.optimizer_v2 import adagrad from tensorflow.python.keras.optimizer_v2 import adam +from tensorflow.python.keras.optimizer_v2 import adamax from tensorflow.python.keras.optimizer_v2 import gradient_descent -from tensorflow.python.keras.optimizer_v2 import optimizer_v2 +from tensorflow.python.keras.optimizer_v2 import nadam +from tensorflow.python.keras.optimizer_v2 import rmsprop from tensorflow.python.ops import array_ops from tensorflow.python.ops import clip_ops from tensorflow.python.ops import resource_variable_ops from tensorflow.python.ops import state_ops from tensorflow.python.ops import variables -from tensorflow.python.platform import gfile from tensorflow.python.platform import test from tensorflow.python.training import momentum from tensorflow.python.training import training_util @@ -492,23 +491,20 @@ class OptimizerTest(test.TestCase): opt.iterations = global_step var = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtypes.float32) + self.evaluate(variables.global_variables_initializer()) + init_step_value = self.evaluate(global_step) loss = lambda: 3 * var opt_op = opt.minimize(loss, [var]) self.evaluate(variables.global_variables_initializer()) - init_step_value = self.evaluate(global_step) self.evaluate(opt_op) new_step_value = self.evaluate(global_step) self.assertEqual(new_step_value, init_step_value + 1) -class OptimizersCompatibilityTest(test.TestCase, parameterized.TestCase): +@keras_parameterized.run_with_all_model_types +class OptimizersCompatibilityTest(keras_parameterized.TestCase): - @parameterized.named_parameters( - ('adadelta', 'adadelta', True), ('adagrad', 'adagrad', True), - ('adam', 'adam', True), ('adamax', 'adamax', True), - ('nadam', 'nadam', True), ('momentum', 'momentum', True), - ('rmsprop', 'rmsprop', True), ('sgd', 'sgd', False)) - def testOptimizersCompatibility(self, opt_str, test_weights): + def _testOptimizersCompatibility(self, opt_v1, opt_v2, test_weights=True): np.random.seed(1331) with self.cached_session(): train_samples = 20 @@ -522,43 +518,63 @@ class OptimizersCompatibilityTest(test.TestCase, parameterized.TestCase): y = keras.utils.to_categorical(y) num_hidden = 5 - model = testing_utils.get_small_sequential_mlp( + model_v1 = testing_utils.get_small_sequential_mlp( num_hidden=num_hidden, num_classes=num_classes, input_dim=input_dim) + model_v1.compile(opt_v1, loss='categorical_crossentropy', metrics=[]) + model_v1.fit(x, y, batch_size=5, epochs=1) - old_mode = os.environ.get('TF2_BEHAVIOR', None) - # Disable tf2 to create V1 optimizer. - disable_tf2() - if opt_str == 'momentum': - opt_v1 = optimizers.SGD(momentum=0.9) - else: - opt_v1 = optimizers.get(opt_str) - - # Test compile and fit with v1 optimizer. - model.compile(opt_v1, loss='categorical_crossentropy', metrics=[]) - model.fit(x, y, batch_size=5, epochs=1) - model_dir = tempfile.mkdtemp() - gfile.MakeDirs(model_dir) - file_name = os.path.join(model_dir, 'model.h5') - model.save(file_name) - - enable_tf2() - # Test load and fit with v2 optimizer. - model_2 = saving.load_model(file_name) - opt_v2 = model_2.optimizer - self.assertIsInstance(opt_v2, optimizer_v2.OptimizerV2) - # set_weights is called inside load_model but exception is swallowed, - # this call checks the weights can be set correctly. + model_v2 = testing_utils.get_small_sequential_mlp( + num_hidden=num_hidden, num_classes=num_classes, input_dim=input_dim) + model_v2.set_weights(model_v1.get_weights()) + model_v2.compile(opt_v2, loss='categorical_crossentropy', metrics=[]) + model_v2._make_train_function() if test_weights: opt_v2.set_weights(opt_v1.get_weights()) - hist_1 = model.fit(x, y, batch_size=5, epochs=1, shuffle=False) - hist_2 = model_2.fit(x, y, batch_size=5, epochs=1, shuffle=False) - self.assertAllClose(model.get_weights(), model_2.get_weights()) - self.assertAllClose(model.get_weights(), model_2.get_weights()) + hist_1 = model_v1.fit(x, y, batch_size=5, epochs=1, shuffle=False) + hist_2 = model_v2.fit(x, y, batch_size=5, epochs=1, shuffle=False) + self.assertAllClose(model_v1.get_weights(), model_v2.get_weights()) self.assertAllClose(hist_1.history['loss'], hist_2.history['loss']) - if old_mode is not None: - os.environ['TF2_BEHAVIOR'] = old_mode + def testAdadeltaCompatibility(self): + opt_v1 = optimizers.Adadelta(lr=0.01) + opt_v2 = adadelta.Adadelta(learning_rate=0.01) + self._testOptimizersCompatibility(opt_v1, opt_v2) + + def testAdagradCompatibility(self): + opt_v1 = optimizers.Adagrad(lr=0.01) + opt_v2 = adagrad.Adagrad(learning_rate=0.01) + self._testOptimizersCompatibility(opt_v1, opt_v2) + + def testAdamCompatibility(self): + opt_v1 = optimizers.Adam() + opt_v2 = adam.Adam() + self._testOptimizersCompatibility(opt_v1, opt_v2) + + def testAdamaxCompatibility(self): + opt_v1 = optimizers.Adamax(lr=0.01) + opt_v2 = adamax.Adamax(learning_rate=0.01) + self._testOptimizersCompatibility(opt_v1, opt_v2) + + def testNadamCompatibility(self): + opt_v1 = optimizers.Nadam(lr=0.001) + opt_v2 = nadam.Nadam(learning_rate=0.001) + self._testOptimizersCompatibility(opt_v1, opt_v2) + + def testMomentumCompatibility(self): + opt_v1 = optimizers.SGD(lr=0.01, momentum=0.9) + opt_v2 = gradient_descent.SGD(learning_rate=0.01, momentum=0.9) + self._testOptimizersCompatibility(opt_v1, opt_v2) + + def testRMSpropCompatibility(self): + opt_v1 = optimizers.RMSprop() + opt_v2 = rmsprop.RMSprop() + self._testOptimizersCompatibility(opt_v1, opt_v2) + + def testSGDCompatibility(self): + opt_v1 = optimizers.SGD(lr=0.01) + opt_v2 = gradient_descent.SGD(learning_rate=0.01) + self._testOptimizersCompatibility(opt_v1, opt_v2, False) def testNumericEquivalenceForNesterovMomentum(self): np.random.seed(1331) @@ -636,15 +652,6 @@ class OptimizersCompatibilityTest(test.TestCase, parameterized.TestCase): self.assertAllClose(hist_k_v1.history['loss'], hist_k_v2.history['loss']) -def disable_tf2(): - if 'TF2_BEHAVIOR' in os.environ: - del os.environ['TF2_BEHAVIOR'] - - -def enable_tf2(): - os.environ['TF2_BEHAVIOR'] = 'enabled' - - # Note: These tests are kept in a separate class to avoid bugs in some # distributions of Python that break AutoGraph which is used by tf.function. class OptimizerWithFunctionTest(test.TestCase): -- GitLab From f5f079ca12c376eb2b86389973661f27054136af Mon Sep 17 00:00:00 2001 From: Karim Nosir Date: Wed, 9 Jan 2019 11:44:32 -0800 Subject: [PATCH 0426/2345] Add missing include needed for windows build. Fixes #24521 PiperOrigin-RevId: 228558062 --- tensorflow/core/util/stats_calculator.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/core/util/stats_calculator.h b/tensorflow/core/util/stats_calculator.h index e191737bb2..5005ee08a4 100644 --- a/tensorflow/core/util/stats_calculator.h +++ b/tensorflow/core/util/stats_calculator.h @@ -18,6 +18,7 @@ limitations under the License. #include +#include #include #include #include -- GitLab From 245925078f574983593e42154091e07c311e30bd Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Wed, 9 Jan 2019 11:45:24 -0800 Subject: [PATCH 0427/2345] Remove the implementation of TPU Strategy initialize and finalize PiperOrigin-RevId: 228558184 --- .../contrib/distribute/python/tpu_strategy.py | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/tensorflow/contrib/distribute/python/tpu_strategy.py b/tensorflow/contrib/distribute/python/tpu_strategy.py index f498f58b25..00360bf996 100644 --- a/tensorflow/contrib/distribute/python/tpu_strategy.py +++ b/tensorflow/contrib/distribute/python/tpu_strategy.py @@ -51,9 +51,6 @@ from tensorflow.python.platform import tf_logging as logging from tensorflow.python.util import nest -_TPU_INITIALIZE_SYSTEM_COLLECTION = "TPU_STRATEGY_INITIALIZE" - - def initialize_tpu_system(cluster_resolver=None): """Initialize the TPU devices in a separate session and graph. @@ -463,28 +460,6 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended): """ initialize_tpu_system(self._tpu_cluster_resolver) - def _initialize(self): - if context.executing_eagerly(): - # TODO(priyag): Add appopriate call here when eager is supported for TPUs. - raise NotImplementedError("Eager mode not supported in TPUStrategy.") - else: - # TODO(jhseu): We need this hack because DistributionStrategies must be - # pickleable for copy.deepcopy(). Remove when initialize_system goes away. - graph = ops.get_default_graph() - tpu_init = graph.get_collection(_TPU_INITIALIZE_SYSTEM_COLLECTION) - if tpu_init: - return tpu_init - graph.add_to_collection(_TPU_INITIALIZE_SYSTEM_COLLECTION, - tpu.initialize_system()) - return graph.get_collection(_TPU_INITIALIZE_SYSTEM_COLLECTION) - - def _finalize(self): - if context.executing_eagerly(): - # TODO(priyag): Add appopriate call here when eager is supported for TPUs. - raise NotImplementedError("Eager mode not supported in TPUStrategy.") - else: - return [] - def _create_variable(self, next_creator, *args, **kwargs): """Create a TPUMirroredVariable. See `DistributionStrategy.scope`.""" colocate_with = kwargs.pop("colocate_with", None) -- GitLab From bebf6b3d5d11e0ae09edab0a3700e0e4000ac842 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 11:57:48 -0800 Subject: [PATCH 0428/2345] Scope GBDT resource variables. PiperOrigin-RevId: 228560356 --- .../contrib/boosted_trees/python/ops/model_ops.py | 10 +++++----- .../python/training/functions/gbdt_batch.py | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tensorflow/contrib/boosted_trees/python/ops/model_ops.py b/tensorflow/contrib/boosted_trees/python/ops/model_ops.py index fca22c71a8..c3685b54e2 100644 --- a/tensorflow/contrib/boosted_trees/python/ops/model_ops.py +++ b/tensorflow/contrib/boosted_trees/python/ops/model_ops.py @@ -62,8 +62,8 @@ class TreeEnsembleVariableSavable(saver.BaseSaverBuilder.SaveableObject): saver.BaseSaverBuilder.SaveSpec(ensemble_config, slice_spec, name + "_config"), ] - super(TreeEnsembleVariableSavable, - self).__init__(tree_ensemble_handle, specs, name) + super(TreeEnsembleVariableSavable, self).__init__(tree_ensemble_handle, + specs, name) self._tree_ensemble_handle = tree_ensemble_handle self._create_op = create_op @@ -115,7 +115,7 @@ class TreeEnsembleVariable(tracking.TrackableResource): def _gather_saveables_for_checkpoint(self): return { - "tree_ensemble_variable": + self.resource_handle.op.name + "/tree_ensemble_variable": functools.partial( TreeEnsembleVariableSavable, tree_ensemble_handle=self.resource_handle, @@ -131,8 +131,8 @@ def tree_ensemble_variable(stamp_token, Args: stamp_token: The initial stamp token value for the ensemble resource. - tree_ensemble_config: A `Tensor` of type `string`. - Serialized proto of the tree ensemble. + tree_ensemble_config: A `Tensor` of type `string`. Serialized proto of the + tree ensemble. name: A name for the ensemble variable. container: An optional `string`. Defaults to `""`. diff --git a/tensorflow/contrib/boosted_trees/python/training/functions/gbdt_batch.py b/tensorflow/contrib/boosted_trees/python/training/functions/gbdt_batch.py index a5951fb737..e78ec476ab 100644 --- a/tensorflow/contrib/boosted_trees/python/training/functions/gbdt_batch.py +++ b/tensorflow/contrib/boosted_trees/python/training/functions/gbdt_batch.py @@ -566,9 +566,10 @@ class GradientBoostedDecisionTreeModel(object): # Determine if ensemble is colocated with the inputs. if self._ensemble_handle.device != input_deps[0].device: # Create a local ensemble and get its local stamp. - with ops.name_scope("local_ensemble", "TreeEnsembleVariable") as name: + with ops.name_scope("local_ensemble", "TreeEnsembleVariable"): local_ensemble_handle = ( - gen_model_ops.decision_tree_ensemble_resource_handle_op(name=name)) + gen_model_ops.decision_tree_ensemble_resource_handle_op( + self._ensemble_handle.op.name + "/local_ensemble")) create_op = gen_model_ops.create_tree_ensemble_variable( local_ensemble_handle, stamp_token=-1, tree_ensemble_config="") with ops.control_dependencies([create_op]): -- GitLab From 8d9872cebb57219acbfaad57e5203b3a407ac28e Mon Sep 17 00:00:00 2001 From: Adrian Kuegel Date: Wed, 9 Jan 2019 11:57:49 -0800 Subject: [PATCH 0429/2345] Fix typos in comments. PiperOrigin-RevId: 228560360 --- .../compiler/xla/service/gpu/cudnn_conv_algorithm_picker.cc | 2 +- tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.cc b/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.cc index 057d0d2364..309b0aca64 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.cc +++ b/tensorflow/compiler/xla/service/gpu/cudnn_conv_algorithm_picker.cc @@ -250,7 +250,7 @@ CudnnConvAlgorithmPicker::PickBestAlgorithm( VLOG(3) << "Trying algorithm " << AlgorithmToString(alg) << " for " << instr->ToString(); - // Use assignment insetad of brace-list to make GCC 4.9 happy. + // Use assignment instead of brace-list to make GCC 4.9 happy. RunConvOptions options; options.profile_result = &profile_result; options.algo_override = alg; diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.h b/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.h index 7cc325bce3..25b2461ca6 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.h +++ b/tensorflow/compiler/xla/service/gpu/cudnn_conv_runner.h @@ -32,7 +32,7 @@ struct RunConvOptions { // Nullable output-parameter pointer for profiling results. se::dnn::ProfileResult* profile_result = nullptr; - // Use this algorithm, instead the one from the instrcution. + // Use this algorithm, instead of the one from the instruction. absl::optional algo_override; }; -- GitLab From a14adaa2329fb46cb472b949ee52546c2516a21e Mon Sep 17 00:00:00 2001 From: Reed Wanderman-Milne Date: Wed, 9 Jan 2019 12:00:29 -0800 Subject: [PATCH 0430/2345] Automated rollback of changelist 205730535 PiperOrigin-RevId: 228560984 --- tensorflow/core/BUILD | 11 ---- .../core/common_runtime/gpu/gpu_device.cc | 51 +------------------ .../core/common_runtime/gpu/gpu_device.h | 11 ---- .../gpu/gpu_device_kernel_check.cu.cc | 37 -------------- .../gpu/gpu_device_kernel_check.h | 32 ------------ 5 files changed, 1 insertion(+), 141 deletions(-) delete mode 100644 tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.cu.cc delete mode 100644 tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 6a842a432f..e6af9211b5 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -87,7 +87,6 @@ load( "tf_gen_op_libs", "tf_generate_proto_text_sources", "tf_genrule_cmd_append_to_srcs", - "tf_gpu_kernel_library", "tf_opts_nortti_if_android", "transitive_hdrs", ) @@ -3163,15 +3162,6 @@ cc_library( ], ) -tf_gpu_kernel_library( - name = "gpu_device_kernel_check", - srcs = ["common_runtime/gpu/gpu_device_kernel_check.cu.cc"], - hdrs = ["common_runtime/gpu/gpu_device_kernel_check.h"], - deps = [ - "//tensorflow/core:stream_executor", - ], -) - GPU_RUNTIME_HEADERS = [ "common_runtime/gpu/cuda_host_allocator.h", "common_runtime/gpu/gpu_bfc_allocator.h", @@ -3210,7 +3200,6 @@ tf_cuda_library( ":core_cpu_lib", ":framework", ":framework_internal", - ":gpu_device_kernel_check", ":gpu_id_impl", ":gpu_init_impl", ":gpu_lib", diff --git a/tensorflow/core/common_runtime/gpu/gpu_device.cc b/tensorflow/core/common_runtime/gpu/gpu_device.cc index 891b6e0e2a..010fdff4e9 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_device.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_device.cc @@ -31,7 +31,6 @@ limitations under the License. #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/common_runtime/device_factory.h" -#include "tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h" #include "tensorflow/core/common_runtime/gpu/gpu_event_mgr.h" #include "tensorflow/core/common_runtime/gpu/gpu_id.h" #include "tensorflow/core/common_runtime/gpu/gpu_id_manager.h" @@ -389,7 +388,7 @@ Status BaseGPUDevice::Init(const SessionOptions& options) { } } - return CheckGPU(); + return Status::OK(); } bool BaseGPUDevice::RequiresRecordingAccessedTensors() const { @@ -908,54 +907,6 @@ Allocator* BaseGPUDevice::GetScopedAllocator(AllocatorAttributes attr, return gpu_allocator_; } -Status BaseGPUDevice::CheckGPU() { - se::Stream* stream = tensorflow_gpu_device_info()->stream; - TF_RETURN_IF_ERROR(stream->BlockHostUntilDone()); - Tensor device_tensor(gpu_allocator_, DT_FLOAT, {}); - if (!device_tensor.IsInitialized()) { - return errors::ResourceExhausted("Failed to allocate ", sizeof(float), - " bytes on the GPU for initialization " - "checks"); - } - float* val_dev = device_tensor.scalar().data(); - const cudaStream_t cu_stream = *reinterpret_cast( - stream->implementation()->GpuStreamMemberHack()); - { - se::cuda::ScopedActivateExecutorContext scoped_activation{stream->parent()}; - run_test_kernel(val_dev, cu_stream); - // We have to use the CUDA runtime function cudaPeekAtLastError here, - // because 'stream' does not provide a way to check if a kernel launch - // succeeds. Calling 'stream->BlockHostUntilDone()', which internally calls - // 'cuCtxSynchronize()', does not catch all kernel launch errors. - cudaError_t cuda_error = cudaPeekAtLastError(); - if (cuda_error == cudaSuccess) { - cuda_error = cudaDeviceSynchronize(); - } - TF_RETURN_IF_ERROR(CudaErrorToStatus(cuda_error, *stream)); - } - - float val_host = 0.; - stream->ThenMemcpy(&val_host, se::DeviceMemoryBase(val_dev, sizeof(float)), - sizeof(float)); - TF_RETURN_IF_ERROR(stream->BlockHostUntilDone()); - if (val_host != 12345.) { - return errors::Internal( - "GPU kernel for initialization returned wrong value: ", val_host); - } - return Status::OK(); -} - -Status BaseGPUDevice::CudaErrorToStatus(cudaError_t cuda_error, - const se::Stream& stream) { - if (cuda_error != cudaSuccess) { - return errors::Internal( - "Failed to run GPU kernel for the initialization check. Received " - "error ", - cudaGetErrorName(cuda_error), " after running GPU kernel."); - } - return Status::OK(); -} - const int BaseGPUDeviceFactory::InterconnectMap::kSameDeviceStrength = 1000; const int BaseGPUDeviceFactory::InterconnectMap::kStreamExecutorStrength = 1; diff --git a/tensorflow/core/common_runtime/gpu/gpu_device.h b/tensorflow/core/common_runtime/gpu/gpu_device.h index 86622dad49..d002d02c51 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_device.h +++ b/tensorflow/core/common_runtime/gpu/gpu_device.h @@ -26,7 +26,6 @@ limitations under the License. #include #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" -#include "cuda/include/cuda_runtime_api.h" #include "tensorflow/core/common_runtime/device_factory.h" #include "tensorflow/core/common_runtime/gpu/gpu_event_mgr.h" #include "tensorflow/core/common_runtime/gpu/gpu_id.h" @@ -122,12 +121,6 @@ class BaseGPUDevice : public LocalDevice { se::StreamExecutor* executor_; // not owned std::unique_ptr scoped_allocator_mgr_; - // Returns a Status corresponding to a cudaError_t. The CUDA error must have - // been obtained from a CUDA kernel launch used to check if the GPU is - // initialized properly. - virtual Status CudaErrorToStatus(cudaError_t cuda_error, - const se::Stream& stream); - private: struct StreamGroup { se::Stream* compute = nullptr; @@ -168,10 +161,6 @@ class BaseGPUDevice : public LocalDevice { Status MaybeCopyTensorToGPU(const AllocatorAttributes& alloc_attrs, const Tensor& from, Tensor* to, StatusCallback done); - - // Checks that the GPU is capable of doing work, by running a test kernel on - // it. - Status CheckGPU(); }; class BaseGPUDeviceFactory : public DeviceFactory { diff --git a/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.cu.cc b/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.cu.cc deleted file mode 100644 index 017565195b..0000000000 --- a/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.cu.cc +++ /dev/null @@ -1,37 +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. -==============================================================================*/ - -#if GOOGLE_CUDA - -#include "tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h" -#include "tensorflow/stream_executor/cuda/cuda_activation.h" - -namespace { -__global__ void test_kernel(float* val) { - if (blockIdx.x == 0 && threadIdx.x == 0) { - (*val) = 12345.; - } -} -} // namespace - -namespace tensorflow { - -void run_test_kernel(float* val, cudaStream_t cu_stream) { - test_kernel<<<1, 1, 0, cu_stream>>>(val); -} - -} // namespace tensorflow - -#endif // GOOGLE_CUDA diff --git a/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h b/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h deleted file mode 100644 index 064fb7a49f..0000000000 --- a/tensorflow/core/common_runtime/gpu/gpu_device_kernel_check.h +++ /dev/null @@ -1,32 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ - -#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_GPU_GPU_DEVICE_KERNEL_CHECK_H_ -#define TENSORFLOW_CORE_COMMON_RUNTIME_GPU_GPU_DEVICE_KERNEL_CHECK_H_ - -#if GOOGLE_CUDA - -#include "tensorflow/core/platform/stream_executor.h" - -namespace tensorflow { - -// Runs a GPU kernel to test that it functions correctly. Sets 'val' to 12345. -void run_test_kernel(float* val, cudaStream_t cu_stream); - -} // namespace tensorflow - -#endif // GOOGLE_CUDA - -#endif // TENSORFLOW_CORE_COMMON_RUNTIME_GPU_GPU_DEVICE_KERNEL_CHECK_H_ -- GitLab From 4fe7980e6b33d075a4a9fcfa40310af85f071cff Mon Sep 17 00:00:00 2001 From: Smit Hinsu Date: Wed, 9 Jan 2019 12:09:12 -0800 Subject: [PATCH 0431/2345] Automated rollback of commit 7009febfed6721cbf664b1ccfd44358392c3d5ca PiperOrigin-RevId: 228562832 --- tensorflow/contrib/tensorrt/BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index c17dea02f1..3d34b91a74 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -551,7 +551,6 @@ cuda_py_test( ], tags = [ "no_cuda_on_cpu_tap", - "no_oss", # TODO(b/121194394): re-enable in OSS after OOM is fixed. "no_pip", "no_tap", # It is not able to download the mnist data. "no_windows", -- GitLab From 57d4745d24195699f93d1674171a03345269d2c1 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 12:10:02 -0800 Subject: [PATCH 0432/2345] Compute reshape permutation statically. Aside from presumably being more efficient generally, this enables using the routines in this file inside of XLA. (which currently throws an error `Input 0 to InvertPermutation operator must be a compile-time constant.`) PiperOrigin-RevId: 228563007 --- tensorflow/python/ops/linalg/linear_operator_util.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/ops/linalg/linear_operator_util.py b/tensorflow/python/ops/linalg/linear_operator_util.py index 54d04e4a70..6c18943dab 100644 --- a/tensorflow/python/ops/linalg/linear_operator_util.py +++ b/tensorflow/python/ops/linalg/linear_operator_util.py @@ -481,9 +481,9 @@ def _reshape_for_efficiency(a, # Permutation to put the extra dims at the end. perm = ( - array_ops.concat( - (math_ops.range(b_extra_ndims, b.shape.ndims), - math_ops.range(0, b_extra_ndims)), 0)) + np.concatenate( + (np.arange(b_extra_ndims, b.shape.ndims), + np.arange(0, b_extra_ndims)), 0)) b_extra_on_end = array_ops.transpose(b, perm=perm) # Now squash this end into one long dim. @@ -497,7 +497,7 @@ def _reshape_for_efficiency(a, y_extra_shape = array_ops.concat( (array_ops.shape(y)[:-1], [b_main_sh[-1]], b_extra_sh), 0) y_extra_on_end = array_ops.reshape(y, y_extra_shape) - return array_ops.transpose( - y_extra_on_end, perm=array_ops.invert_permutation(perm)) + inverse_perm = np.argsort(perm) + return array_ops.transpose(y_extra_on_end, perm=inverse_perm) return a, b_squashed_end, reshape_inv, still_need_to_transpose -- GitLab From 01d9a34786ea0da087855f88ac32d0d7085c8266 Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Wed, 9 Jan 2019 12:16:00 -0800 Subject: [PATCH 0433/2345] Mark tf.summary v1 API classes and tf.train.summary_iterator() as v1-only The tf.summary.FileWriter and FileWriterCache classes are only for v1 summary writing; TF 2.0 provides the v2 create_file_writer() API instead. The tf.summary python protos are being removed as part of the overall effort to remove protos from the TF 2.0 python API. The recourse for users will be accessing these bindings from within TensorBoard - the exact package path is still TBD, but when we have one we can add a migration hint. As for tf.train.summary_iterator(), this can be replaced by just using tf.io.record_iterator() and parsing each record with Event.FromString() using the Event proto symbol, wherever in TensorBoard it ends up. PiperOrigin-RevId: 228564320 --- tensorflow/python/__init__.py | 10 +- tensorflow/python/summary/summary.py | 5 +- tensorflow/python/summary/summary_iterator.py | 2 +- tensorflow/python/summary/writer/writer.py | 2 +- .../python/summary/writer/writer_cache.py | 2 +- .../python/tools/api/generator/doc_srcs.py | 1 + .../api/golden/v2/tensorflow.-event.pbtxt | 74 --------- ...rflow.-summary-metadata.-plugin-data.pbtxt | 18 --- .../v2/tensorflow.-summary-metadata.pbtxt | 40 ----- .../v2/tensorflow.-summary.-audio.pbtxt | 36 ----- .../v2/tensorflow.-summary.-image.pbtxt | 30 ---- .../v2/tensorflow.-summary.-value.pbtxt | 74 --------- .../api/golden/v2/tensorflow.-summary.pbtxt | 144 ------------------ .../tools/api/golden/v2/tensorflow.pbtxt | 12 -- .../golden/v2/tensorflow.summary.-event.pbtxt | 74 --------- ...ensorflow.summary.-file-writer-cache.pbtxt | 16 -- .../v2/tensorflow.summary.-file-writer.pbtxt | 50 ------ ...sorflow.summary.-summary-description.pbtxt | 12 -- .../tensorflow.summary.-summary.-audio.pbtxt | 36 ----- .../tensorflow.summary.-summary.-image.pbtxt | 30 ---- .../tensorflow.summary.-summary.-value.pbtxt | 74 --------- .../v2/tensorflow.summary.-summary.pbtxt | 144 ------------------ ...sorflow.summary.-tagged-run-metadata.pbtxt | 18 --- .../api/golden/v2/tensorflow.summary.pbtxt | 24 --- .../api/golden/v2/tensorflow.train.pbtxt | 4 - tensorflow/tools/compatibility/renames_v2.py | 10 ++ 26 files changed, 22 insertions(+), 920 deletions(-) delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-event.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-summary-metadata.-plugin-data.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-summary-metadata.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-summary.-audio.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-summary.-image.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-summary.-value.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-summary.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.-event.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.-file-writer-cache.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.-file-writer.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.-summary-description.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-audio.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-image.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-value.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.-tagged-run-metadata.pbtxt diff --git a/tensorflow/python/__init__.py b/tensorflow/python/__init__.py index 207123cb14..31f9dce8e8 100644 --- a/tensorflow/python/__init__.py +++ b/tensorflow/python/__init__.py @@ -150,7 +150,7 @@ nn.rnn_cell = rnn_cell # pylint: disable=undefined-variable tf_export(v1=['AttrValue'])(AttrValue) tf_export(v1=['ConfigProto'])(ConfigProto) -tf_export('Event', 'summary.Event')(Event) +tf_export(v1=['Event', 'summary.Event'])(Event) tf_export(v1=['GPUOptions'])(GPUOptions) tf_export(v1=['GraphDef'])(GraphDef) tf_export(v1=['GraphOptions'])(GraphOptions) @@ -163,10 +163,10 @@ tf_export(v1=['OptimizerOptions'])(OptimizerOptions) tf_export(v1=['RunMetadata'])(RunMetadata) tf_export(v1=['RunOptions'])(RunOptions) tf_export(v1=['SessionLog', 'summary.SessionLog'])(SessionLog) -tf_export('Summary', 'summary.Summary')(Summary) -tf_export('summary.SummaryDescription')(SummaryDescription) -tf_export('SummaryMetadata')(SummaryMetadata) -tf_export('summary.TaggedRunMetadata')(TaggedRunMetadata) +tf_export(v1=['Summary', 'summary.Summary'])(Summary) +tf_export(v1=['summary.SummaryDescription'])(SummaryDescription) +tf_export(v1=['SummaryMetadata'])(SummaryMetadata) +tf_export(v1=['summary.TaggedRunMetadata'])(TaggedRunMetadata) tf_export(v1=['TensorInfo'])(TensorInfo) # pylint: enable=undefined-variable diff --git a/tensorflow/python/summary/summary.py b/tensorflow/python/summary/summary.py index 0c13016712..a01feb3dde 100644 --- a/tensorflow/python/summary/summary.py +++ b/tensorflow/python/summary/summary.py @@ -13,9 +13,10 @@ # limitations under the License. # ============================================================================== -"""Tensor summaries for exporting information about a model. +"""Operations for writing summary data, for use in analysis and visualization. -See the [Summary](https://tensorflow.org/api_guides/python/summary) guide. +See the [Summaries and +TensorBoard](https://www.tensorflow.org/guide/summaries_and_tensorboard) guide. """ from __future__ import absolute_import diff --git a/tensorflow/python/summary/summary_iterator.py b/tensorflow/python/summary/summary_iterator.py index 321b11ffb7..3675c235cf 100644 --- a/tensorflow/python/summary/summary_iterator.py +++ b/tensorflow/python/summary/summary_iterator.py @@ -24,7 +24,7 @@ from tensorflow.python.lib.io import tf_record from tensorflow.python.util.tf_export import tf_export -@tf_export('train.summary_iterator') +@tf_export(v1=['train.summary_iterator']) def summary_iterator(path): # pylint: disable=line-too-long """An iterator for reading `Event` protocol buffers from an event file. diff --git a/tensorflow/python/summary/writer/writer.py b/tensorflow/python/summary/writer/writer.py index 78217b503f..a66be4f833 100644 --- a/tensorflow/python/summary/writer/writer.py +++ b/tensorflow/python/summary/writer/writer.py @@ -279,7 +279,7 @@ class SummaryToEventTransformer(object): self.event_writer.add_event(event) -@tf_export("summary.FileWriter") +@tf_export(v1=["summary.FileWriter"]) class FileWriter(SummaryToEventTransformer): """Writes `Summary` protocol buffers to event files. diff --git a/tensorflow/python/summary/writer/writer_cache.py b/tensorflow/python/summary/writer/writer_cache.py index 645fa28a37..c62a7ce1a3 100644 --- a/tensorflow/python/summary/writer/writer_cache.py +++ b/tensorflow/python/summary/writer/writer_cache.py @@ -25,7 +25,7 @@ from tensorflow.python.summary.writer.writer import FileWriter from tensorflow.python.util.tf_export import tf_export -@tf_export('summary.FileWriterCache') +@tf_export(v1=['summary.FileWriterCache']) class FileWriterCache(object): """Cache for file writers. diff --git a/tensorflow/python/tools/api/generator/doc_srcs.py b/tensorflow/python/tools/api/generator/doc_srcs.py index b567eead3d..28bf0e9d01 100644 --- a/tensorflow/python/tools/api/generator/doc_srcs.py +++ b/tensorflow/python/tools/api/generator/doc_srcs.py @@ -61,6 +61,7 @@ _TENSORFLOW_DOC_SOURCES = { 'signal': DocSource(docstring_module_name='ops.signal.signal'), 'sparse': DocSource(docstring_module_name='ops.sparse_ops'), 'strings': DocSource(docstring_module_name='ops.string_ops'), + 'summary': DocSource(docstring_module_name='summary.summary'), 'sysconfig': DocSource(docstring_module_name='platform.sysconfig'), 'test': DocSource(docstring_module_name='platform.test'), 'train': DocSource(docstring_module_name='training.training'), diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-event.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-event.pbtxt deleted file mode 100644 index 3b75a1735b..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.-event.pbtxt +++ /dev/null @@ -1,74 +0,0 @@ -path: "tensorflow.Event" -tf_proto { - descriptor { - name: "Event" - field { - name: "wall_time" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_DOUBLE - } - field { - name: "step" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "file_version" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_STRING - oneof_index: 0 - } - field { - name: "graph_def" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - oneof_index: 0 - } - field { - name: "summary" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary" - oneof_index: 0 - } - field { - name: "log_message" - number: 6 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.LogMessage" - oneof_index: 0 - } - field { - name: "session_log" - number: 7 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.SessionLog" - oneof_index: 0 - } - field { - name: "tagged_run_metadata" - number: 8 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.TaggedRunMetadata" - oneof_index: 0 - } - field { - name: "meta_graph_def" - number: 9 - label: LABEL_OPTIONAL - type: TYPE_BYTES - oneof_index: 0 - } - oneof_decl { - name: "what" - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-summary-metadata.-plugin-data.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-summary-metadata.-plugin-data.pbtxt deleted file mode 100644 index a66b74b315..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.-summary-metadata.-plugin-data.pbtxt +++ /dev/null @@ -1,18 +0,0 @@ -path: "tensorflow.SummaryMetadata.PluginData" -tf_proto { - descriptor { - name: "PluginData" - field { - name: "plugin_name" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "content" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-summary-metadata.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-summary-metadata.pbtxt deleted file mode 100644 index c02575b962..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.-summary-metadata.pbtxt +++ /dev/null @@ -1,40 +0,0 @@ -path: "tensorflow.SummaryMetadata" -tf_proto { - descriptor { - name: "SummaryMetadata" - field { - name: "plugin_data" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.SummaryMetadata.PluginData" - } - field { - name: "display_name" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "summary_description" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - nested_type { - name: "PluginData" - field { - name: "plugin_name" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "content" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-summary.-audio.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-summary.-audio.pbtxt deleted file mode 100644 index 94f712073e..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.-summary.-audio.pbtxt +++ /dev/null @@ -1,36 +0,0 @@ -path: "tensorflow.Summary.Audio" -tf_proto { - descriptor { - name: "Audio" - field { - name: "sample_rate" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_FLOAT - } - field { - name: "num_channels" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "length_frames" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "encoded_audio_string" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - field { - name: "content_type" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-summary.-image.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-summary.-image.pbtxt deleted file mode 100644 index fc1acb483b..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.-summary.-image.pbtxt +++ /dev/null @@ -1,30 +0,0 @@ -path: "tensorflow.Summary.Image" -tf_proto { - descriptor { - name: "Image" - field { - name: "height" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "width" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "colorspace" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "encoded_image_string" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-summary.-value.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-summary.-value.pbtxt deleted file mode 100644 index feb84b6ee9..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.-summary.-value.pbtxt +++ /dev/null @@ -1,74 +0,0 @@ -path: "tensorflow.Summary.Value" -tf_proto { - descriptor { - name: "Value" - field { - name: "node_name" - number: 7 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "tag" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "metadata" - number: 9 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.SummaryMetadata" - } - field { - name: "simple_value" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_FLOAT - oneof_index: 0 - } - field { - name: "obsolete_old_style_histogram" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_BYTES - oneof_index: 0 - } - field { - name: "image" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Image" - oneof_index: 0 - } - field { - name: "histo" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.HistogramProto" - oneof_index: 0 - } - field { - name: "audio" - number: 6 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Audio" - oneof_index: 0 - } - field { - name: "tensor" - number: 8 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.TensorProto" - oneof_index: 0 - } - oneof_decl { - name: "value" - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-summary.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-summary.pbtxt deleted file mode 100644 index b2bdff7171..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.-summary.pbtxt +++ /dev/null @@ -1,144 +0,0 @@ -path: "tensorflow.Summary" -tf_proto { - descriptor { - name: "Summary" - field { - name: "value" - number: 1 - label: LABEL_REPEATED - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Value" - } - nested_type { - name: "Image" - field { - name: "height" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "width" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "colorspace" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "encoded_image_string" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - } - nested_type { - name: "Audio" - field { - name: "sample_rate" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_FLOAT - } - field { - name: "num_channels" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "length_frames" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "encoded_audio_string" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - field { - name: "content_type" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - } - nested_type { - name: "Value" - field { - name: "node_name" - number: 7 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "tag" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "metadata" - number: 9 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.SummaryMetadata" - } - field { - name: "simple_value" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_FLOAT - oneof_index: 0 - } - field { - name: "obsolete_old_style_histogram" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_BYTES - oneof_index: 0 - } - field { - name: "image" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Image" - oneof_index: 0 - } - field { - name: "histo" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.HistogramProto" - oneof_index: 0 - } - field { - name: "audio" - number: 6 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Audio" - oneof_index: 0 - } - field { - name: "tensor" - number: 8 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.TensorProto" - oneof_index: 0 - } - oneof_decl { - name: "value" - } - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt index 0cf1fe0e3c..7e5f86d7e6 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt @@ -8,10 +8,6 @@ tf_module { name: "DType" mtype: "" } - member { - name: "Event" - mtype: "" - } member { name: "GradientTape" mtype: "" @@ -40,14 +36,6 @@ tf_module { name: "SparseTensor" mtype: "" } - member { - name: "Summary" - mtype: "" - } - member { - name: "SummaryMetadata" - mtype: "" - } member { name: "Tensor" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-event.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-event.pbtxt deleted file mode 100644 index eb99d0f533..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-event.pbtxt +++ /dev/null @@ -1,74 +0,0 @@ -path: "tensorflow.summary.Event" -tf_proto { - descriptor { - name: "Event" - field { - name: "wall_time" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_DOUBLE - } - field { - name: "step" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "file_version" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_STRING - oneof_index: 0 - } - field { - name: "graph_def" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - oneof_index: 0 - } - field { - name: "summary" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary" - oneof_index: 0 - } - field { - name: "log_message" - number: 6 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.LogMessage" - oneof_index: 0 - } - field { - name: "session_log" - number: 7 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.SessionLog" - oneof_index: 0 - } - field { - name: "tagged_run_metadata" - number: 8 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.TaggedRunMetadata" - oneof_index: 0 - } - field { - name: "meta_graph_def" - number: 9 - label: LABEL_OPTIONAL - type: TYPE_BYTES - oneof_index: 0 - } - oneof_decl { - name: "what" - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-file-writer-cache.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-file-writer-cache.pbtxt deleted file mode 100644 index 2a5b63dcea..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-file-writer-cache.pbtxt +++ /dev/null @@ -1,16 +0,0 @@ -path: "tensorflow.summary.FileWriterCache" -tf_class { - is_instance: "" - is_instance: "" - member_method { - name: "__init__" - } - member_method { - name: "clear" - argspec: "args=[], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "get" - argspec: "args=[\'logdir\'], varargs=None, keywords=None, defaults=None" - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-file-writer.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-file-writer.pbtxt deleted file mode 100644 index 6b65b0ace3..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-file-writer.pbtxt +++ /dev/null @@ -1,50 +0,0 @@ -path: "tensorflow.summary.FileWriter" -tf_class { - is_instance: "" - is_instance: "" - is_instance: "" - member_method { - name: "__init__" - argspec: "args=[\'self\', \'logdir\', \'graph\', \'max_queue\', \'flush_secs\', \'graph_def\', \'filename_suffix\', \'session\'], varargs=None, keywords=None, defaults=[\'None\', \'10\', \'120\', \'None\', \'None\', \'None\'], " - } - member_method { - name: "add_event" - argspec: "args=[\'self\', \'event\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "add_graph" - argspec: "args=[\'self\', \'graph\', \'global_step\', \'graph_def\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " - } - member_method { - name: "add_meta_graph" - argspec: "args=[\'self\', \'meta_graph_def\', \'global_step\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "add_run_metadata" - argspec: "args=[\'self\', \'run_metadata\', \'tag\', \'global_step\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "add_session_log" - argspec: "args=[\'self\', \'session_log\', \'global_step\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "add_summary" - argspec: "args=[\'self\', \'summary\', \'global_step\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "close" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "flush" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "get_logdir" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "reopen" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary-description.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary-description.pbtxt deleted file mode 100644 index 4a8b59cf02..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary-description.pbtxt +++ /dev/null @@ -1,12 +0,0 @@ -path: "tensorflow.summary.SummaryDescription" -tf_proto { - descriptor { - name: "SummaryDescription" - field { - name: "type_hint" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-audio.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-audio.pbtxt deleted file mode 100644 index 8b271cf58f..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-audio.pbtxt +++ /dev/null @@ -1,36 +0,0 @@ -path: "tensorflow.summary.Summary.Audio" -tf_proto { - descriptor { - name: "Audio" - field { - name: "sample_rate" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_FLOAT - } - field { - name: "num_channels" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "length_frames" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "encoded_audio_string" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - field { - name: "content_type" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-image.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-image.pbtxt deleted file mode 100644 index dbbc02dd05..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-image.pbtxt +++ /dev/null @@ -1,30 +0,0 @@ -path: "tensorflow.summary.Summary.Image" -tf_proto { - descriptor { - name: "Image" - field { - name: "height" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "width" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "colorspace" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "encoded_image_string" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-value.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-value.pbtxt deleted file mode 100644 index 4176171cd9..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.-value.pbtxt +++ /dev/null @@ -1,74 +0,0 @@ -path: "tensorflow.summary.Summary.Value" -tf_proto { - descriptor { - name: "Value" - field { - name: "node_name" - number: 7 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "tag" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "metadata" - number: 9 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.SummaryMetadata" - } - field { - name: "simple_value" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_FLOAT - oneof_index: 0 - } - field { - name: "obsolete_old_style_histogram" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_BYTES - oneof_index: 0 - } - field { - name: "image" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Image" - oneof_index: 0 - } - field { - name: "histo" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.HistogramProto" - oneof_index: 0 - } - field { - name: "audio" - number: 6 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Audio" - oneof_index: 0 - } - field { - name: "tensor" - number: 8 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.TensorProto" - oneof_index: 0 - } - oneof_decl { - name: "value" - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.pbtxt deleted file mode 100644 index d6c5e3a87a..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary.pbtxt +++ /dev/null @@ -1,144 +0,0 @@ -path: "tensorflow.summary.Summary" -tf_proto { - descriptor { - name: "Summary" - field { - name: "value" - number: 1 - label: LABEL_REPEATED - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Value" - } - nested_type { - name: "Image" - field { - name: "height" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "width" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "colorspace" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_INT32 - } - field { - name: "encoded_image_string" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - } - nested_type { - name: "Audio" - field { - name: "sample_rate" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_FLOAT - } - field { - name: "num_channels" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "length_frames" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_INT64 - } - field { - name: "encoded_audio_string" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - field { - name: "content_type" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - } - nested_type { - name: "Value" - field { - name: "node_name" - number: 7 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "tag" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "metadata" - number: 9 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.SummaryMetadata" - } - field { - name: "simple_value" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_FLOAT - oneof_index: 0 - } - field { - name: "obsolete_old_style_histogram" - number: 3 - label: LABEL_OPTIONAL - type: TYPE_BYTES - oneof_index: 0 - } - field { - name: "image" - number: 4 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Image" - oneof_index: 0 - } - field { - name: "histo" - number: 5 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.HistogramProto" - oneof_index: 0 - } - field { - name: "audio" - number: 6 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.Summary.Audio" - oneof_index: 0 - } - field { - name: "tensor" - number: 8 - label: LABEL_OPTIONAL - type: TYPE_MESSAGE - type_name: ".tensorflow.TensorProto" - oneof_index: 0 - } - oneof_decl { - name: "value" - } - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-tagged-run-metadata.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-tagged-run-metadata.pbtxt deleted file mode 100644 index 27c8873320..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-tagged-run-metadata.pbtxt +++ /dev/null @@ -1,18 +0,0 @@ -path: "tensorflow.summary.TaggedRunMetadata" -tf_proto { - descriptor { - name: "TaggedRunMetadata" - field { - name: "tag" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - } - field { - name: "run_metadata" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_BYTES - } - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt index 61670bd151..c59f1b8474 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt @@ -1,33 +1,9 @@ path: "tensorflow.summary" tf_module { - member { - name: "Event" - mtype: "" - } - member { - name: "FileWriter" - mtype: "" - } - member { - name: "FileWriterCache" - mtype: "" - } - member { - name: "Summary" - mtype: "" - } - member { - name: "SummaryDescription" - mtype: "" - } member { name: "SummaryWriter" mtype: "" } - member { - name: "TaggedRunMetadata" - mtype: "" - } member_method { name: "create_file_writer" argspec: "args=[\'logdir\', \'max_queue\', \'flush_millis\', \'filename_suffix\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.train.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.train.pbtxt index c72564e598..a3ace15ca2 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.train.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.train.pbtxt @@ -140,8 +140,4 @@ tf_module { name: "sdca_shrink_l1" argspec: "args=[\'weights\', \'l1\', \'l2\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " } - member_method { - name: "summary_iterator" - argspec: "args=[\'path\'], varargs=None, keywords=None, defaults=None" - } } diff --git a/tensorflow/tools/compatibility/renames_v2.py b/tensorflow/tools/compatibility/renames_v2.py index e9085e94b3..b757699f63 100644 --- a/tensorflow/tools/compatibility/renames_v2.py +++ b/tensorflow/tools/compatibility/renames_v2.py @@ -34,6 +34,7 @@ renames = { 'tf.ConfigProto': 'tf.compat.v1.ConfigProto', 'tf.DeviceSpec': 'tf.compat.v1.DeviceSpec', 'tf.Dimension': 'tf.compat.v1.Dimension', + 'tf.Event': 'tf.compat.v1.Event', 'tf.FIFOQueue': 'tf.queue.FIFOQueue', 'tf.FixedLenFeature': 'tf.io.FixedLenFeature', 'tf.FixedLenSequenceFeature': 'tf.io.FixedLenSequenceFeature', @@ -73,6 +74,8 @@ renames = { 'tf.SparseConditionalAccumulator': 'tf.sparse.SparseConditionalAccumulator', 'tf.SparseFeature': 'tf.io.SparseFeature', 'tf.SparseTensorValue': 'tf.compat.v1.SparseTensorValue', + 'tf.Summary': 'tf.compat.v1.Summary', + 'tf.SummaryMetadata': 'tf.compat.v1.SummaryMetadata', 'tf.TFRecordReader': 'tf.compat.v1.TFRecordReader', 'tf.TensorInfo': 'tf.compat.v1.TensorInfo', 'tf.TextLineReader': 'tf.compat.v1.TextLineReader', @@ -608,8 +611,14 @@ renames = { 'tf.string_strip': 'tf.strings.strip', 'tf.string_to_hash_bucket_fast': 'tf.strings.to_hash_bucket_fast', 'tf.string_to_hash_bucket_strong': 'tf.strings.to_hash_bucket_strong', + 'tf.summary.Event': 'tf.compat.v1.summary.Event', + 'tf.summary.FileWriter': 'tf.compat.v1.summary.FileWriter', + 'tf.summary.FileWriterCache': 'tf.compat.v1.summary.FileWriterCache', 'tf.summary.SessionLog': 'tf.compat.v1.summary.SessionLog', 'tf.summary.audio': 'tf.compat.v1.summary.audio', + 'tf.summary.Summary': 'tf.compat.v1.summary.Summary', + 'tf.summary.SummaryDescription': 'tf.compat.v1.summary.SummaryDescription', + 'tf.summary.TaggedRunMetadata': 'tf.compat.v1.summary.TaggedRunMetadata', 'tf.summary.get_summary_description': 'tf.compat.v1.summary.get_summary_description', 'tf.summary.histogram': 'tf.compat.v1.summary.histogram', 'tf.summary.image': 'tf.compat.v1.summary.image', @@ -713,6 +722,7 @@ renames = { 'tf.train.slice_input_producer': 'tf.compat.v1.train.slice_input_producer', 'tf.train.start_queue_runners': 'tf.compat.v1.train.start_queue_runners', 'tf.train.string_input_producer': 'tf.compat.v1.train.string_input_producer', + 'tf.train.summary_iterator': 'tf.compat.v1.train.summary_iterator', 'tf.train.update_checkpoint_state': 'tf.compat.v1.train.update_checkpoint_state', 'tf.train.warm_start': 'tf.compat.v1.train.warm_start', 'tf.train.write_graph': 'tf.io.write_graph', -- GitLab From 9a5dcb4c6b7b60557e996106402c46475307c4cd Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 12:18:30 -0800 Subject: [PATCH 0434/2345] A new Eager C API: Profiler. The profiler starts profiling when it is created. It stops when it is destroyed. Its output format is tfprof proto. PiperOrigin-RevId: 228564754 --- tensorflow/c/eager/BUILD | 32 +++++++- tensorflow/c/eager/c_api.cc | 2 +- tensorflow/c/eager/c_api_experimental.cc | 23 ++++++ tensorflow/c/eager/c_api_experimental.h | 18 +++++ tensorflow/c/eager/c_api_experimental_test.cc | 79 +++++++++++++++++++ tensorflow/c/eager/c_api_internal.h | 8 ++ tensorflow/core/common_runtime/eager/BUILD | 28 +++++++ .../core/common_runtime/eager/context.cc | 32 +++++++- .../core/common_runtime/eager/context.h | 17 +++- .../core/common_runtime/eager/execute.cc | 2 +- .../core/common_runtime/eager/profiler.cc | 77 ++++++++++++++++++ .../core/common_runtime/eager/profiler.h | 63 +++++++++++++++ tensorflow/python/pywrap_tfe.i | 3 + 13 files changed, 375 insertions(+), 9 deletions(-) create mode 100644 tensorflow/c/eager/c_api_experimental_test.cc create mode 100644 tensorflow/core/common_runtime/eager/profiler.cc create mode 100644 tensorflow/core/common_runtime/eager/profiler.h diff --git a/tensorflow/c/eager/BUILD b/tensorflow/c/eager/BUILD index 9f09ad1fc3..3ea1724d1e 100644 --- a/tensorflow/c/eager/BUILD +++ b/tensorflow/c/eager/BUILD @@ -3,11 +3,10 @@ licenses(["notice"]) # Apache 2.0 load( "//tensorflow:tensorflow.bzl", - "tf_cuda_cc_test", - "tf_cc_test", "tf_copts", - "tfe_xla_copts", + "tf_cuda_cc_test", "tf_cuda_library", + "tfe_xla_copts", ) tf_cuda_library( @@ -29,6 +28,7 @@ tf_cuda_library( "//tensorflow/c:c_api_internal", "//tensorflow/core:core_cpu", "//tensorflow/core/common_runtime/eager:attr_builder", + "//tensorflow/core/common_runtime/eager:profiler", "//tensorflow/core/common_runtime/eager:context", "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:execute", @@ -89,6 +89,7 @@ tf_cuda_library( "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:eager_operation", "//tensorflow/core/common_runtime/eager:kernel_and_device", + "//tensorflow/core/common_runtime/eager:profiler", "//tensorflow/core/common_runtime/eager:tensor_handle", "//tensorflow/core/distributed_runtime:remote_device", "//tensorflow/core/distributed_runtime:server_lib", @@ -204,6 +205,31 @@ tf_cuda_library( ], ) +tf_cuda_cc_test( + name = "c_api_experimental_test", + size = "small", + srcs = [ + "c_api_experimental_test.cc", + ], + extra_copts = tfe_xla_copts(), + tags = [ + "guitar", + "multi_gpu", + ], + deps = [ + ":c_api_experimental", + ":c_api_test_util", + "//tensorflow/c:c_test_util", + "//tensorflow/cc/profiler", + "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core/profiler:protos_all_cc", + "@com_google_absl//absl/strings", + ], +) + cc_library( name = "tape", hdrs = ["tape.h"], diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index 027d752f42..d5a391a98d 100755 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -774,7 +774,7 @@ void TFE_ContextExportRunMetadata(TFE_Context* ctx, TF_Buffer* buf, if (!status->status.ok()) return; tensorflow::mutex_lock ml(*ctx->context.MetadataMu()); status->status = MessageToBuffer(*ctx->context.RunMetadataProto(), buf); - ctx->context.RunMetadataProto()->Clear(); + ctx->context.ClearRunMetadata(); } namespace { diff --git a/tensorflow/c/eager/c_api_experimental.cc b/tensorflow/c/eager/c_api_experimental.cc index 3461d81b93..1ce03fb226 100644 --- a/tensorflow/c/eager/c_api_experimental.cc +++ b/tensorflow/c/eager/c_api_experimental.cc @@ -18,6 +18,29 @@ limitations under the License. #include "tensorflow/c/c_api.h" #include "tensorflow/c/eager/c_api_internal.h" +using tensorflow::string; + void TFE_OpConsumeInput(TFE_Op* op, TFE_TensorHandle* h, TF_Status* status) { op->operation.ConsumeInput(h->handle); } + +TFE_Profiler* TFE_NewProfiler(TFE_Context* ctx) { + return new TFE_Profiler(ctx); +} + +void TFE_DeleteProfiler(TFE_Profiler* profiler) { delete profiler; } + +void TFE_ProfilerSerializeToString(TFE_Context* ctx, TFE_Profiler* profiler, + TF_Buffer* buf, TF_Status* status) { + TFE_ContextAsyncWait(ctx, status); + if (!status->status.ok()) return; + string content; + status->status = profiler->profiler->SerializeToString(&content); + void* data = tensorflow::port::Malloc(content.length()); + content.copy(static_cast(data), content.length(), 0); + buf->data = data; + buf->length = content.length(); + buf->data_deallocator = [](void* data, size_t length) { + tensorflow::port::Free(data); + }; +} diff --git a/tensorflow/c/eager/c_api_experimental.h b/tensorflow/c/eager/c_api_experimental.h index 4ee6c066ee..9eb80f5216 100644 --- a/tensorflow/c/eager/c_api_experimental.h +++ b/tensorflow/c/eager/c_api_experimental.h @@ -25,6 +25,24 @@ extern "C" { TF_CAPI_EXPORT extern void TFE_OpConsumeInput(TFE_Op* op, TFE_TensorHandle* h, TF_Status* status); +// A profiler which will start profiling when creating the object and will stop +// when the object is destroyed. It will profile all operations run under the +// given TFE_Context. Multiple instance of it can be created, but at most one +// of them will profile for each TFE_Context. +// Thread-safety: TFE_Profiler is thread-safe. +typedef struct TFE_Profiler TFE_Profiler; + +TF_CAPI_EXPORT extern TFE_Profiler* TFE_NewProfiler(TFE_Context* ctx); +TF_CAPI_EXPORT extern void TFE_DeleteProfiler(TFE_Profiler* profiler); + +// The output string is a binary string of tensorflow.tfprof.ProfileProto. +// User can write the string to file for offline analysis by tfprof command-line +// tools or graphical user interface. +TF_CAPI_EXPORT extern void TFE_ProfilerSerializeToString(TFE_Context* ctx, + TFE_Profiler* profiler, + TF_Buffer* buf, + TF_Status* status); + #ifdef __cplusplus } /* end extern "C" */ #endif diff --git a/tensorflow/c/eager/c_api_experimental_test.cc b/tensorflow/c/eager/c_api_experimental_test.cc new file mode 100644 index 0000000000..9b4fca9d45 --- /dev/null +++ b/tensorflow/c/eager/c_api_experimental_test.cc @@ -0,0 +1,79 @@ +/* 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/c/eager/c_api_experimental.h" + +#include +#include "absl/strings/match.h" +#include "tensorflow/c/eager/c_api_test_util.h" +#include "tensorflow/cc/profiler/profiler.h" +#include "tensorflow/core/platform/logging.h" +#include "tensorflow/core/platform/protobuf.h" +#include "tensorflow/core/platform/test.h" +#include "tensorflow/core/platform/test_benchmark.h" +#include "tensorflow/core/profiler/tfprof_log.pb.h" + +using tensorflow::string; + +namespace { + +void ExecuteWithProfiling(bool async) { + TF_Status* status = TF_NewStatus(); + TFE_ContextOptions* opts = TFE_NewContextOptions(); + TFE_ContextOptionsSetAsync(opts, static_cast(async)); + TFE_Context* ctx = TFE_NewContext(opts, status); + TFE_Profiler* profiler = TFE_NewProfiler(ctx); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_DeleteContextOptions(opts); + + TFE_TensorHandle* m = TestMatrixTensorHandle(); + TFE_Op* matmul = MatMulOp(ctx, m, m); + TFE_TensorHandle* retvals[1] = {nullptr}; + int num_retvals = 1; + TFE_Execute(matmul, &retvals[0], &num_retvals, status); + EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_DeleteOp(matmul); + TFE_DeleteTensorHandle(m); + + ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + ASSERT_EQ(1, num_retvals); + TF_Buffer* profiler_result = TF_NewBuffer(); + TFE_ProfilerSerializeToString(ctx, profiler, profiler_result, status); + TFE_DeleteProfiler(profiler); + ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + tensorflow::tfprof::ProfileProto profile_proto; + EXPECT_TRUE(profile_proto.ParseFromString( + {reinterpret_cast(profiler_result->data), + profiler_result->length})); + TF_DeleteBuffer(profiler_result); + + TF_Tensor* t = TFE_TensorHandleResolve(retvals[0], status); + TFE_DeleteTensorHandle(retvals[0]); + TFE_DeleteContext(ctx); + ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + float product[4] = {0}; + EXPECT_EQ(sizeof(product), TF_TensorByteSize(t)); + memcpy(&product[0], TF_TensorData(t), TF_TensorByteSize(t)); + TF_DeleteTensor(t); + EXPECT_EQ(7, product[0]); + EXPECT_EQ(10, product[1]); + EXPECT_EQ(15, product[2]); + EXPECT_EQ(22, product[3]); + TF_DeleteStatus(status); +} +TEST(CAPI, ExecuteWithTracing) { ExecuteWithProfiling(false); } +TEST(CAPI, ExecuteWithTracingAsync) { ExecuteWithProfiling(true); } + +} // namespace diff --git a/tensorflow/c/eager/c_api_internal.h b/tensorflow/c/eager/c_api_internal.h index 67bc1bcd24..7b1035f631 100644 --- a/tensorflow/c/eager/c_api_internal.h +++ b/tensorflow/c/eager/c_api_internal.h @@ -34,6 +34,7 @@ limitations under the License. #include "tensorflow/core/common_runtime/eager/eager_executor.h" #include "tensorflow/core/common_runtime/eager/eager_operation.h" #include "tensorflow/core/common_runtime/eager/kernel_and_device.h" +#include "tensorflow/core/common_runtime/eager/profiler.h" #include "tensorflow/core/common_runtime/eager/tensor_handle.h" #include "tensorflow/core/common_runtime/function.h" #include "tensorflow/core/common_runtime/rendezvous_mgr.h" @@ -100,6 +101,13 @@ struct TFE_Op { tensorflow::EagerOperation operation; }; +struct TFE_Profiler { + TFE_Profiler(TFE_Context* ctx) + : profiler(tensorflow::EagerProfiler::Create(&ctx->context)) {} + + std::unique_ptr profiler; +}; + namespace tensorflow { // Set an AttrValue on the op. Doesn't handle the list types. void SetOpAttrValueScalar(TFE_Context* ctx, TFE_Op* op, diff --git a/tensorflow/core/common_runtime/eager/BUILD b/tensorflow/core/common_runtime/eager/BUILD index 77e3246df0..cabbddb77d 100644 --- a/tensorflow/core/common_runtime/eager/BUILD +++ b/tensorflow/core/common_runtime/eager/BUILD @@ -88,6 +88,34 @@ tf_cuda_library( ], ) +tf_cuda_library( + name = "profiler", + srcs = [ + "profiler.cc", + ], + hdrs = [ + "profiler.h", + ], + visibility = ["//tensorflow:internal"], + deps = [ + ":context", + "//tensorflow/cc/profiler", + ] + select({ + "//tensorflow:android": [ + "//tensorflow/core:android_tensorflow_lib_lite", + ], + "//conditions:default": [ + "//tensorflow/core:core_cpu_lib", + "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:session_options", + ], + }), +) + tf_cuda_library( name = "tensor_handle", srcs = [ diff --git a/tensorflow/core/common_runtime/eager/context.cc b/tensorflow/core/common_runtime/eager/context.cc index 2212bda534..1d93d2bbe6 100644 --- a/tensorflow/core/common_runtime/eager/context.cc +++ b/tensorflow/core/common_runtime/eager/context.cc @@ -234,6 +234,29 @@ Status EagerContext::FindDeviceByName(const string& name, Device** result) { return Status::OK(); } +void EagerContext::ClearRunMetadata() { + if (metadata_listener_ != nullptr) { + metadata_listener_->BeforeClearRunMetadata(); + } + run_metadata_.Clear(); +} + +Status EagerContext::RegisterRunMetadataListener( + RunMetadataListener* listener) { + mutex_lock l(metadata_mu_); + if (metadata_listener_ != nullptr) { + return Status(error::Code::INVALID_ARGUMENT, + "Cannot run two eager profiler at the same time"); + } + metadata_listener_ = listener; + return Status::OK(); +} + +void EagerContext::ClearRunMetadataListener() { + mutex_lock l(metadata_mu_); + metadata_listener_ = nullptr; +} + void EagerContext::StartStep() { mutex_lock ml(metadata_mu_); num_active_steps_++; @@ -317,10 +340,15 @@ void EagerContext::AddKernelToCache(Fprint128 cache_key, gtl::InsertOrUpdate(&kernel_cache_, cache_key, kernel); } +bool EagerContext::ShouldStoreMetadata() { + mutex_lock ml(metadata_mu_); + return should_store_metadata_.load() || metadata_listener_ != nullptr; +} + void EagerContext::SetShouldStoreMetadata(bool value) { + mutex_lock ml(metadata_mu_); should_store_metadata_.store(value); - if (!value) { - mutex_lock ml(metadata_mu_); + if (!value || metadata_listener_ != nullptr) { run_metadata_.Clear(); } } diff --git a/tensorflow/core/common_runtime/eager/context.h b/tensorflow/core/common_runtime/eager/context.h index 5ff6b3ffbd..b83f5707da 100644 --- a/tensorflow/core/common_runtime/eager/context.h +++ b/tensorflow/core/common_runtime/eager/context.h @@ -29,6 +29,7 @@ limitations under the License. #include "tensorflow/core/common_runtime/eager/kernel_and_device.h" #include "tensorflow/core/common_runtime/function.h" #include "tensorflow/core/common_runtime/rendezvous_mgr.h" +#include "tensorflow/core/example/example.pb.h" #ifndef __ANDROID__ #include "tensorflow/core/distributed_runtime/eager/eager_client.h" #include "tensorflow/core/distributed_runtime/server_lib.h" @@ -66,6 +67,12 @@ enum ContextDevicePlacementPolicy { DEVICE_PLACEMENT_SILENT_FOR_INT32 = 3, }; +class RunMetadataListener { + public: + virtual ~RunMetadataListener() {} + virtual void BeforeClearRunMetadata() = 0; +}; + class EagerContext { public: // TODO: remove this constructor once we migrate all callers to the next one. @@ -172,10 +179,15 @@ class EagerContext { void ReleaseDeviceMgr() { local_device_manager_.release(); } // TODO(apassos) clean up RunMetadata storage. - mutex* MetadataMu() { return &metadata_mu_; } - bool ShouldStoreMetadata() { return should_store_metadata_.load(); } + mutex* MetadataMu() LOCK_RETURNED(metadata_mu_) { return &metadata_mu_; } + bool ShouldStoreMetadata() LOCKS_EXCLUDED(metadata_mu_); void SetShouldStoreMetadata(bool value); RunMetadata* RunMetadataProto() { return &run_metadata_; } + void ClearRunMetadata() EXCLUSIVE_LOCKS_REQUIRED(metadata_mu_); + + Status RegisterRunMetadataListener(RunMetadataListener* listener) + LOCKS_EXCLUDED(metadata_mu_); + void ClearRunMetadataListener() LOCKS_EXCLUDED(metadata_mu_); void StartStep(); void EndStep(); @@ -269,6 +281,7 @@ class EagerContext { std::atomic should_store_metadata_{false}; mutex metadata_mu_; RunMetadata run_metadata_ GUARDED_BY(metadata_mu_); + RunMetadataListener* metadata_listener_ GUARDED_BY(metadata_mu_) = nullptr; GraphCollector graph_collector_; const bool log_device_placement_; // EagerExecutor for async execution. diff --git a/tensorflow/core/common_runtime/eager/execute.cc b/tensorflow/core/common_runtime/eager/execute.cc index 79806c3c73..9d2401929d 100644 --- a/tensorflow/core/common_runtime/eager/execute.cc +++ b/tensorflow/core/common_runtime/eager/execute.cc @@ -752,8 +752,8 @@ Status EagerExecute(EagerContext* ctx, Device* device, maybe_stats->set_all_end_rel_micros(nanos / EnvTime::kMicrosToNanos - maybe_stats->all_start_micros()); maybe_stats->set_all_end_rel_nanos(nanos - maybe_stats->all_start_nanos()); - mutex_lock ml(*ctx->MetadataMu()); if (ctx->ShouldStoreMetadata()) { + mutex_lock ml(*ctx->MetadataMu()); { GraphCollector* collector = ctx->GetGraphCollector(); mutex_lock mll(collector->mu); diff --git a/tensorflow/core/common_runtime/eager/profiler.cc b/tensorflow/core/common_runtime/eager/profiler.cc new file mode 100644 index 0000000000..d670d6f344 --- /dev/null +++ b/tensorflow/core/common_runtime/eager/profiler.cc @@ -0,0 +1,77 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/common_runtime/eager/profiler.h" +#include "tensorflow/cc/profiler/profiler.h" +#include "tensorflow/core/framework/graph.pb.h" +#include "tensorflow/core/platform/mutex.h" +#include "tensorflow/core/protobuf/config.pb.h" + +namespace tensorflow { + +/*static*/ std::unique_ptr EagerProfiler::Create( + EagerContext* const context) { + return absl::WrapUnique(new EagerProfiler(context)); +} + +void EagerProfiler::BeforeClearRunMetadata() { + mutex_lock l(mutex_); + run_metadata_.MergeFrom(*context_->RunMetadataProto()); +} + +Status EagerProfiler::Status() { + mutex_lock l(mutex_); + return status_; +} + +Status EagerProfiler::SerializeToString(string* content) { + { + mutex_lock l(mutex_); + if (!status_.ok()) return status_; + } + RunMetadata metadata; + GetMergetRunMetadata(&metadata); + + // TODO(fishx): update tfprof to use a lighter representation instead of + // GraphDef. + GraphDef graph; + std::unique_ptr tfprof(new tfprof::Profiler(graph)); + tfprof->AddStep(0, metadata); + return tfprof->SerializeToString(content); +} + +EagerProfiler::EagerProfiler(EagerContext* const context) : context_(context) { + LOG(INFO) << "Eager Profiler started."; + + status_ = context_->RegisterRunMetadataListener(this); + if (!status_.ok()) { + LOG(INFO) << "Eager Profiler failed to start. Another profiler is running."; + return; + } +} + +EagerProfiler::~EagerProfiler() { + context_->ClearRunMetadataListener(); + LOG(INFO) << "Eager Profiler ended with status:" << status_; +} + +void EagerProfiler::GetMergetRunMetadata(RunMetadata* metadata) { + mutex_lock ml(*context_->MetadataMu()); + mutex_lock l(mutex_); + *metadata = run_metadata_; + metadata->MergeFrom(*context_->RunMetadataProto()); +} + +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/eager/profiler.h b/tensorflow/core/common_runtime/eager/profiler.h new file mode 100644 index 0000000000..cadbfdb498 --- /dev/null +++ b/tensorflow/core/common_runtime/eager/profiler.h @@ -0,0 +1,63 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_PROFILER_H_ +#define TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_PROFILER_H_ + +#include "tensorflow/core/common_runtime/eager/context.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/platform/mutex.h" +#include "tensorflow/core/protobuf/config.pb.h" + +namespace tensorflow { + +// A profiler which will start profiling when creating the object and will stop +// when the object is destroyed. It will profile all operations run under the +// given EagerContext. +// Multiple instances of it can be created, but at most one of them will profile +// for each EagerContext. Status() will return OK only for the instance that is +// profiling. +// Thread-safety: TFE_Profiler is thread-safe. +class EagerProfiler : RunMetadataListener { + public: + // Creates and EagerProfiler and starts profiling. + static std::unique_ptr Create(EagerContext* const context); + + // Deletes an exsiting Profiler and enables starting a new one. + ~EagerProfiler() override; + + void BeforeClearRunMetadata() override LOCKS_EXCLUDED(mutex_) + EXCLUSIVE_LOCKS_REQUIRED(context_->MetadataMu()); + tensorflow::Status Status() LOCKS_EXCLUDED(mutex_); + + tensorflow::Status SerializeToString(string* content) LOCKS_EXCLUDED(mutex_); + + private: + // Constructs an instance of the class and starts profiling + explicit EagerProfiler(EagerContext* const context); + + // Profiler is neither copyable or movable. + EagerProfiler(const EagerProfiler&) = delete; + EagerProfiler& operator=(const EagerProfiler&) = delete; + + void GetMergetRunMetadata(RunMetadata* metadata) LOCKS_EXCLUDED(mutex_); + + RunMetadata run_metadata_ GUARDED_BY(mutex_); + tensorflow::Status status_ GUARDED_BY(mutex_); + EagerContext* const context_; + mutex mutex_; +}; + +} // namespace tensorflow +#endif // TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_PROFILER_H_ diff --git a/tensorflow/python/pywrap_tfe.i b/tensorflow/python/pywrap_tfe.i index 733d471ca2..0620e0345c 100755 --- a/tensorflow/python/pywrap_tfe.i +++ b/tensorflow/python/pywrap_tfe.i @@ -32,6 +32,9 @@ limitations under the License. %rename("%s") TFE_ContextSetServerDef; %rename("%s") TFE_ContextAsyncWait; %rename("%s") TFE_ContextAsyncClearError; +%rename("%s") TFE_NewProfiler; +%rename("%s") TFE_DeleteProfiler; +%rename("%s") TFE_ProfilerSerializeToString; %rename("%s") TFE_OpNameGetAttrType; %rename("%s") TFE_Py_InitEagerTensor; %rename("%s") TFE_Py_SetEagerTensorProfiler; -- GitLab From 52718ef7adf0f0a292dcd43432587c818f895ce2 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Wed, 9 Jan 2019 12:18:32 -0800 Subject: [PATCH 0435/2345] Add `axis` property to CosineProximity loss and metric. Refactor cosine proximity tests for better clarity. PiperOrigin-RevId: 228564762 --- tensorflow/python/keras/losses.py | 26 ++++-- tensorflow/python/keras/losses_test.py | 93 ++++++++++++------- tensorflow/python/keras/metrics.py | 12 ++- tensorflow/python/keras/metrics_test.py | 58 ++++++++---- ...rflow.keras.losses.-cosine-proximity.pbtxt | 2 +- .../golden/v1/tensorflow.keras.losses.pbtxt | 4 +- ...flow.keras.metrics.-cosine-proximity.pbtxt | 2 +- .../golden/v1/tensorflow.keras.metrics.pbtxt | 4 +- ...rflow.keras.losses.-cosine-proximity.pbtxt | 2 +- .../golden/v2/tensorflow.keras.losses.pbtxt | 4 +- ...flow.keras.metrics.-cosine-proximity.pbtxt | 2 +- .../golden/v2/tensorflow.keras.metrics.pbtxt | 4 +- 12 files changed, 143 insertions(+), 70 deletions(-) diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index 5f9278823c..66780de0f0 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -821,15 +821,15 @@ def poisson(y_true, y_pred): 'keras.metrics.cosine', 'keras.losses.cosine_proximity', 'keras.losses.cosine') -def cosine_proximity(y_true, y_pred): - y_true = nn.l2_normalize(y_true, axis=-1) - y_pred = nn.l2_normalize(y_pred, axis=-1) - return -math_ops.reduce_sum(y_true * y_pred, axis=-1) +def cosine_proximity(y_true, y_pred, axis=-1): + 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) @keras_export('keras.losses.CosineProximity') class CosineProximity(Loss): - """Computes the cosine distance between `y_true` and `y_pred`. + """Computes the cosine proximity between `y_true` and `y_pred`. Usage: @@ -845,8 +845,22 @@ class CosineProximity(Loss): model = keras.models.Model(inputs, outputs) model.compile('sgd', loss=tf.losses.CosineProximity()) ``` + + Args: + axis: (Optional) Defaults to -1. The dimension along which the cosine + proximity is computed. + reduction: (Optional) Type of `tf.losses.Reduction` to apply to loss. + Default value is `SUM_OVER_BATCH_SIZE`. + name: Optional name for the op. """ + def __init__(self, + axis=-1, + reduction=losses_impl.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. @@ -859,7 +873,7 @@ class CosineProximity(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) + return cosine_proximity(y_true, y_pred, axis=self.axis) # Aliases. diff --git a/tensorflow/python/keras/losses_test.py b/tensorflow/python/keras/losses_test.py index 5d6cd1be55..004c30f84d 100644 --- a/tensorflow/python/keras/losses_test.py +++ b/tensorflow/python/keras/losses_test.py @@ -485,59 +485,88 @@ class MeanSquaredLogarithmicErrorTest(test.TestCase): @test_util.run_all_in_graph_and_eager_modes class CosineProximityTest(test.TestCase): + def l2_norm(self, x, axis): + epsilon = 1e-12 + square_sum = np.sum(np.square(x), axis=axis, keepdims=True) + x_inv_norm = 1 / np.sqrt(np.maximum(square_sum, epsilon)) + return np.multiply(x, x_inv_norm) + + def setup(self, axis=1): + self.np_y_true = np.asarray([[1, 9, 2], [-5, -2, 6]], dtype=np.float32) + self.np_y_pred = np.asarray([[4, 8, 12], [8, 1, 3]], dtype=np.float32) + + 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.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( - reduction=losses_impl.ReductionV2.SUM, name='cosine_loss') + axis=2, reduction=losses_impl.ReductionV2.SUM, name='cosine_loss') self.assertEqual(cosine_obj.name, 'cosine_loss') self.assertEqual(cosine_obj.reduction, losses_impl.ReductionV2.SUM) + self.assertEqual(cosine_obj.axis, 2) def test_unweighted(self): + self.setup() cosine_obj = keras.losses.CosineProximity() - y_true = constant_op.constant([1, 9, 2, -5, -2, 6], shape=(2, 3)) - y_pred = constant_op.constant([4, 8, 12, 8, 1, 3], - shape=(2, 3), - dtype=dtypes.float32) - loss = cosine_obj(y_true, y_pred) - self.assertAlmostEqual(self.evaluate(loss), -0.18722, 3) + 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() - y_true = constant_op.constant([1, 9, 2, -5, -2, 6], shape=(2, 3)) - y_pred = constant_op.constant([4, 8, 12, 8, 1, 3], - shape=(2, 3), - dtype=dtypes.float32) - loss = cosine_obj(y_true, y_pred, sample_weight=2.3) - self.assertAlmostEqual(self.evaluate(loss), -0.43060, 3) + 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) + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) def test_sample_weighted(self): + self.setup() cosine_obj = keras.losses.CosineProximity() - y_true = constant_op.constant([1, 9, 2, -5, -2, 6], shape=(2, 3)) - y_pred = constant_op.constant([4, 8, 12, 8, 1, 3], - shape=(2, 3), - dtype=dtypes.float32) - sample_weight = constant_op.constant([1.2, 3.4], shape=(2, 1)) - loss = cosine_obj(y_true, y_pred, sample_weight=sample_weight) - self.assertAlmostEqual(self.evaluate(loss), 0.15599, 3) + sample_weight = np.asarray([1.2, 3.4]) + loss = cosine_obj( + self.y_true, + self.y_pred, + sample_weight=constant_op.constant(sample_weight)) + expected_loss = np.mean(self.expected_loss * sample_weight) + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) def test_timestep_weighted(self): + self.setup() cosine_obj = keras.losses.CosineProximity() - y_true = constant_op.constant([1, 9, 2, -5, -2, 6], shape=(2, 3, 1)) - y_pred = constant_op.constant([4, 8, 12, 8, 1, 3], - shape=(2, 3, 1), - dtype=dtypes.float32) - sample_weight = constant_op.constant([3, 6, 5, 0, 4, 2], shape=(2, 3)) - loss = cosine_obj(y_true, y_pred, sample_weight=sample_weight) - self.assertAlmostEqual(self.evaluate(loss), -2.0000, 3) + 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,)) + + y_true = constant_op.constant(np_y_true) + y_pred = constant_op.constant(np_y_pred) + loss = cosine_obj( + y_true, y_pred, sample_weight=constant_op.constant(sample_weight)) + + expected_loss = np.mean(expected_loss * sample_weight) + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) def test_zero_weighted(self): + self.setup() cosine_obj = keras.losses.CosineProximity() - y_true = constant_op.constant([1, 9, 2, -5, -2, 6], shape=(2, 3)) - y_pred = constant_op.constant([4, 8, 12, 8, 1, 3], - shape=(2, 3), - dtype=dtypes.float32) - loss = cosine_obj(y_true, y_pred, sample_weight=0) + 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) + loss = cosine_obj(self.y_true, self.y_pred) + expected_loss = np.mean(self.expected_loss) + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + @test_util.run_all_in_graph_and_eager_modes class BinaryCrossentropyTest(test.TestCase): diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index 2c2700ffa3..a13d8747d6 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -1443,8 +1443,16 @@ class CosineProximity(MeanMetricWrapper): ``` """ - def __init__(self, name='cosine_proximity', dtype=None): - super(CosineProximity, self).__init__(cosine, name, dtype=dtype) + def __init__(self, name='cosine_proximity', dtype=None, axis=-1): + """Creates a `CosineProximity` 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. + """ + super(CosineProximity, self).__init__(cosine, name, dtype=dtype, axis=axis) @classmethod def from_config(cls, config): diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index 4f8406f0e3..42da1dfb99 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -1049,8 +1049,26 @@ class SpecificityAtSensitivityTest(test.TestCase, parameterized.TestCase): @test_util.run_all_in_graph_and_eager_modes class CosineProximityTest(test.TestCase): + def l2_norm(self, x, axis): + epsilon = 1e-12 + square_sum = np.sum(np.square(x), axis=axis, keepdims=True) + x_inv_norm = 1 / np.sqrt(np.maximum(square_sum, epsilon)) + return np.multiply(x, x_inv_norm) + + def setup(self, axis=1): + self.np_y_true = np.asarray([[1, 9, 2], [-5, -2, 6]], dtype=np.float32) + self.np_y_pred = np.asarray([[4, 8, 12], [8, 1, 3]], dtype=np.float32) + + 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.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(name='my_cos', dtype=dtypes.int32) + cosine_obj = metrics.CosineProximity( + axis=2, name='my_cos', dtype=dtypes.int32) self.assertEqual(cosine_obj.name, 'my_cos') self.assertEqual(cosine_obj._dtype, dtypes.int32) @@ -1060,29 +1078,33 @@ class CosineProximityTest(test.TestCase): self.assertEqual(cosine_obj2._dtype, dtypes.int32) def test_unweighted(self): + self.setup() cosine_obj = metrics.CosineProximity() self.evaluate(variables.variables_initializer(cosine_obj.variables)) - - y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1), - (1, 1, 1, 1, 0), (0, 0, 0, 0, 1))) - y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1), - (0, 1, 0, 1, 0), (1, 1, 1, 1, 1))) - - update_op = cosine_obj.update_state(y_true, y_pred) - self.evaluate(update_op) - result = cosine_obj.result() - self.assertAllClose(-0.60723, result, atol=1e-5) + 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_weighted(self): + self.setup() cosine_obj = metrics.CosineProximity() self.evaluate(variables.variables_initializer(cosine_obj.variables)) - y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1), - (1, 1, 1, 1, 0), (0, 0, 0, 0, 1))) - y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1), - (0, 1, 0, 1, 0), (1, 1, 1, 1, 1))) - sample_weight = constant_op.constant((1., 1.5, 2., 2.5)) - result = cosine_obj(y_true, y_pred, sample_weight=sample_weight) - self.assertAllClose(-0.59916, self.evaluate(result), atol=1e-5) + sample_weight = np.asarray([1.2, 3.4]) + loss = cosine_obj( + self.y_true, + self.y_pred, + sample_weight=constant_op.constant(sample_weight)) + expected_loss = np.sum( + self.expected_loss * sample_weight) / np.sum(sample_weight) + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) + + def test_axis(self): + self.setup(axis=1) + cosine_obj = metrics.CosineProximity(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) + self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) @test_util.run_all_in_graph_and_eager_modes diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-proximity.pbtxt index de35966f48..4952a76291 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-proximity.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'sum_over_batch_size\', \'None\'], " + argspec: "args=[\'self\', \'axis\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'-1\', \'sum_over_batch_size\', \'None\'], " } 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 532d2b2f88..b71e89883e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt @@ -74,11 +74,11 @@ tf_module { } member_method { name: "cosine" - argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { name: "cosine_proximity" - argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { name: "deserialize" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt index d095ed42ef..c50638740f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt @@ -89,7 +89,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'name\', \'dtype\'], varargs=None, keywords=None, defaults=[\'cosine_proximity\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'dtype\', \'axis\'], varargs=None, keywords=None, defaults=[\'cosine_proximity\', \'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 743a8ae34a..54f5b21f55 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt @@ -122,11 +122,11 @@ tf_module { } member_method { name: "cosine" - argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { name: "cosine_proximity" - argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { name: "deserialize" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-proximity.pbtxt index de35966f48..4952a76291 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-proximity.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'sum_over_batch_size\', \'None\'], " + argspec: "args=[\'self\', \'axis\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'-1\', \'sum_over_batch_size\', \'None\'], " } 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 ef4fd66a7a..673593ed6c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt @@ -78,11 +78,11 @@ tf_module { } member_method { name: "cosine" - argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { name: "cosine_proximity" - argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { name: "deserialize" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt index d095ed42ef..c50638740f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt @@ -89,7 +89,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'name\', \'dtype\'], varargs=None, keywords=None, defaults=[\'cosine_proximity\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'dtype\', \'axis\'], varargs=None, keywords=None, defaults=[\'cosine_proximity\', \'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 743a8ae34a..54f5b21f55 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt @@ -122,11 +122,11 @@ tf_module { } member_method { name: "cosine" - argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { name: "cosine_proximity" - argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { name: "deserialize" -- GitLab From 0d15dbe9c5df49c6a9757fedbe47f893837363db Mon Sep 17 00:00:00 2001 From: Mark Heffernan Date: Wed, 9 Jan 2019 12:31:46 -0800 Subject: [PATCH 0436/2345] Remove deprecated methods in ShapeUtil. No functional change. Replace with calls to Shape methods. PiperOrigin-RevId: 228566953 --- tensorflow/c/eager/c_api_debug.cc | 4 +- .../compiler/xla/service/scatter_expander.cc | 2 +- .../compiler/xla/service/shape_inference.cc | 4 +- tensorflow/compiler/xla/shape.cc | 4 +- tensorflow/compiler/xla/shape_util.cc | 107 ++++++++---------- tensorflow/compiler/xla/shape_util.h | 44 +------ tensorflow/compiler/xla/shape_util_test.cc | 4 - tensorflow/compiler/xla/tests/tuple_test.cc | 5 +- 8 files changed, 61 insertions(+), 113 deletions(-) diff --git a/tensorflow/c/eager/c_api_debug.cc b/tensorflow/c/eager/c_api_debug.cc index 52b0824552..ffcd5ace0b 100644 --- a/tensorflow/c/eager/c_api_debug.cc +++ b/tensorflow/c/eager/c_api_debug.cc @@ -83,7 +83,7 @@ TF_CAPI_EXPORT extern TFE_TensorDebugInfo* TFE_TensorHandleTensorDebugInfo( } } - if (xla::ShapeUtil::IsTuple(padded_shape)) { + if (padded_shape.IsTuple()) { if (xla::ShapeUtil::TupleElementCount(padded_shape) != 2) { // Currently, the only case of XlaTensor containing a tuple shape is to // represent 64 bit ints, doubles, and complex numbers (we don't support @@ -99,7 +99,7 @@ TF_CAPI_EXPORT extern TFE_TensorDebugInfo* TFE_TensorHandleTensorDebugInfo( xla::Shape shape0 = xla::ShapeUtil::GetTupleElementShape(padded_shape, 0); const xla::Shape& shape1 = xla::ShapeUtil::GetTupleElementShape(padded_shape, 1); - if (xla::ShapeUtil::IsTuple(shape0) || xla::ShapeUtil::IsTuple(shape1)) { + if (shape0.IsTuple() || shape1.IsTuple()) { status->status = tensorflow::errors::InvalidArgument( "XlaTensors should not contain nested tuples. Shape: ", padded_shape.DebugString()); diff --git a/tensorflow/compiler/xla/service/scatter_expander.cc b/tensorflow/compiler/xla/service/scatter_expander.cc index 8b9955faf8..036c3c36f6 100644 --- a/tensorflow/compiler/xla/service/scatter_expander.cc +++ b/tensorflow/compiler/xla/service/scatter_expander.cc @@ -59,7 +59,7 @@ static StatusOr CanonicalizeScatterIndices( TF_ASSIGN_OR_RETURN( HloInstruction * transposed_scatter_indices, TransposeIndexVectorDimToLast(scatter_indices, index_vector_dim)); - if (ShapeUtil::Rank(scatter_indices->shape()) == index_vector_dim + 1 && + if (scatter_indices->shape().rank() == index_vector_dim + 1 && scatter_indices->shape().dimensions(index_vector_dim) == 1) { auto new_shape = ShapeUtil::DeleteDimension(index_vector_dim, scatter_indices->shape()); diff --git a/tensorflow/compiler/xla/service/shape_inference.cc b/tensorflow/compiler/xla/service/shape_inference.cc index b729dd7660..1d3f84af95 100644 --- a/tensorflow/compiler/xla/service/shape_inference.cc +++ b/tensorflow/compiler/xla/service/shape_inference.cc @@ -2358,7 +2358,7 @@ ShapeInference::InferDegenerateDimensionBroadcastShape(HloOpcode operation, }; // Check the shapes of computation parameters and return types. - if (!ShapeUtil::ShapeIs(condition.result(), PRED, {})) { + if (!ShapeUtil::Equal(condition.result(), ShapeUtil::MakeShape(PRED, {}))) { return InvalidArgument("Condition must return a boolean; got %s.", shape_string()); } @@ -2378,7 +2378,7 @@ ShapeInference::InferDegenerateDimensionBroadcastShape(HloOpcode operation, const Shape& predicate, const Shape& true_operand, const Shape& false_operand, const ProgramShape& true_computation, const ProgramShape& false_computation) { - if (!ShapeUtil::ShapeIs(predicate, PRED, {})) { + if (!ShapeUtil::Equal(predicate, ShapeUtil::MakeShape(PRED, {}))) { return InvalidArgument("Predicate must be a boolean; got %s.", ShapeUtil::HumanString(predicate)); } diff --git a/tensorflow/compiler/xla/shape.cc b/tensorflow/compiler/xla/shape.cc index ea4f722ef4..1a029efe85 100644 --- a/tensorflow/compiler/xla/shape.cc +++ b/tensorflow/compiler/xla/shape.cc @@ -80,7 +80,7 @@ string Shape::ToString(bool print_layout) const { } bool Shape::is_static() const { - if (ShapeUtil::IsTuple(*this)) { + if (IsTuple()) { for (const Shape& subshape : tuple_shapes_) { if (!subshape.is_static()) { return false; @@ -91,7 +91,7 @@ bool Shape::is_static() const { } void Shape::DeleteDimension(int64 dim_to_delete) { - CHECK(ShapeUtil::IsArray(*this)); + CHECK(IsArray()); CHECK_GE(dim_to_delete, 0); CHECK_LT(dim_to_delete, dimensions_.size()); dimensions_.erase(dimensions_.begin() + dim_to_delete); diff --git a/tensorflow/compiler/xla/shape_util.cc b/tensorflow/compiler/xla/shape_util.cc index 7f359311c3..235b065585 100644 --- a/tensorflow/compiler/xla/shape_util.cc +++ b/tensorflow/compiler/xla/shape_util.cc @@ -146,7 +146,7 @@ bool CompareShapes(const Shape& lhs, const Shape& rhs, bool compare_layouts, return false; } - for (int i = 0; i < ShapeUtil::Rank(lhs); ++i) { + for (int i = 0; i < lhs.rank(); ++i) { if (lhs.is_dynamic_dimension(i) != rhs.is_dynamic_dimension(i)) { VLOG(3) << "CompareShapes: lhs and rhs have different dynamic dimensions."; @@ -208,11 +208,6 @@ StatusOr MakeShapeWithLayoutInternal( return equal; } -/* static */ int64 ShapeUtil::Rank(const Shape& shape) { - CHECK(shape.IsArray()) << "Non-arrays do not have a rank, shape: " << shape; - return shape.dimensions_size(); -} - /* static */ int64 ShapeUtil::TrueRank(const Shape& shape) { int64 accum = 0; for (int64 dimension : shape.dimensions()) { @@ -344,7 +339,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( /* static */ void ShapeUtil::AppendMajorDimension(int bound, Shape* shape) { CHECK(LayoutUtil::IsDenseArray(*shape)); - shape->mutable_layout()->add_minor_to_major(Rank(*shape)); + shape->mutable_layout()->add_minor_to_major(shape->rank()); shape->add_dimensions(bound); TF_DCHECK_OK(ValidateShape(*shape)); } @@ -359,7 +354,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( } /* static */ bool ShapeUtil::ElementHasBitWidth(const Shape& shape, int bits) { - if (!IsArray(shape)) { + if (!shape.IsArray()) { return false; } return primitive_util::BitWidth(shape.element_type()) == bits; @@ -401,26 +396,24 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( return primitive_util::IsFloatingPointType(shape.element_type()); } -/* static */ bool ShapeUtil::IsArray(const Shape& shape) { - return IsArrayPrimitiveType(shape.element_type()); -} - /* static */ bool ShapeUtil::IsNestedTuple(const Shape& shape) { - return IsTuple(shape) && absl::c_any_of(shape.tuple_shapes(), IsTuple); + return shape.IsTuple() && + absl::c_any_of(shape.tuple_shapes(), + [](const Shape& s) { return s.IsTuple(); }); } /* static */ bool ShapeUtil::IsEmptyTuple(const Shape& shape) { - return IsTuple(shape) && TupleElementCount(shape) == 0; + return shape.IsTuple() && TupleElementCount(shape) == 0; } /* static */ int64 ShapeUtil::TupleElementCount(const Shape& shape) { - CHECK(IsTuple(shape)) << HumanString(shape); + CHECK(shape.IsTuple()) << HumanString(shape); return shape.tuple_shapes_size(); } /* static */ const Shape& ShapeUtil::GetTupleElementShape(const Shape& shape, int64 index) { - CHECK(IsTuple(shape)); + CHECK(shape.IsTuple()); CHECK_GT(TupleElementCount(shape), index); TF_DCHECK_OK(ValidateShapeWithOptionalLayout(shape.tuple_shapes(index))); return shape.tuple_shapes(index); @@ -436,7 +429,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( /* static */ Shape ShapeUtil::SliceTuple(const Shape& tuple, int64 start, int64 limit) { TF_DCHECK_OK(ValidateShapeWithOptionalLayout(tuple)); - CHECK(IsTuple(tuple)); + CHECK(tuple.IsTuple()); CHECK_LE(start, TupleElementCount(tuple)); CHECK_LE(limit, TupleElementCount(tuple)); @@ -453,15 +446,9 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( complex_shape.element_type())); } -/* static */ bool ShapeUtil::ShapeIs(const Shape& shape, - PrimitiveType element_type, - std::initializer_list dimensions) { - return Equal(shape, MakeShape(element_type, dimensions)); -} - /* static */ int64 ShapeUtil::ElementsIn(const Shape& shape) { - DCHECK(IsArray(shape)) << ShapeUtil::HumanString(shape); - DCHECK_EQ(shape.dimensions_size(), Rank(shape)); + DCHECK(shape.IsArray()) << ShapeUtil::HumanString(shape); + DCHECK_EQ(shape.dimensions_size(), shape.rank()); if (shape.dimensions().size() == 1) { return shape.dimensions()[0]; } @@ -471,8 +458,8 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( } /* static */ int64 ShapeUtil::ElementsInRecursive(const Shape& shape) { - CHECK(IsArray(shape) || IsTuple(shape)); - if (IsArray(shape)) { + CHECK(shape.IsArray() || shape.IsTuple()); + if (shape.IsArray()) { return ElementsIn(shape); } int64 count = 0; @@ -505,7 +492,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( } /* static */ string ShapeUtil::HumanString(const Shape& shape) { - if (IsTuple(shape)) { + if (shape.IsTuple()) { string text = "("; const char* prefix = ""; for (const Shape& elem_shape : shape.tuple_shapes()) { @@ -529,7 +516,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( } /* static */ string ShapeUtil::HumanStringWithLayout(const Shape& shape) { - if (IsTuple(shape)) { + if (shape.IsTuple()) { string text = "("; const char* prefix = ""; for (const Shape& elem_shape : shape.tuple_shapes()) { @@ -545,7 +532,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( StrAppend(&result, (i > 0) ? "," : "", shape.dimensions(i)); } result += "]"; - if (!IsScalar(shape) && IsArray(shape)) { + if (!IsScalar(shape) && shape.IsArray()) { if (LayoutUtil::HasLayout(shape)) { StrAppend(&result, LayoutUtil::HumanString(shape.layout())); } @@ -580,8 +567,8 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( /* static */ bool ShapeUtil::CompatibleIgnoringElementType(const Shape& lhs, const Shape& rhs) { - if (IsArray(lhs)) { - return IsArray(rhs) && SameDimensions(lhs, rhs); + if (lhs.IsArray()) { + return rhs.IsArray() && SameDimensions(lhs, rhs); } else if (lhs.element_type() == TUPLE) { return rhs.element_type() == TUPLE && absl::c_equal(lhs.tuple_shapes(), rhs.tuple_shapes(), @@ -594,8 +581,8 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( /* static */ bool ShapeUtil::CompatibleIgnoringFpPrecision(const Shape& lhs, const Shape& rhs) { - if (IsArray(lhs)) { - return IsArray(rhs) && SameElementTypeIgnoringFpPrecision(lhs, rhs) && + if (lhs.IsArray()) { + return rhs.IsArray() && SameElementTypeIgnoringFpPrecision(lhs, rhs) && CompatibleIgnoringElementType(lhs, rhs); } else if (lhs.element_type() == TUPLE) { return rhs.element_type() == TUPLE && @@ -615,7 +602,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( /* static */ int64 ShapeUtil::GetDimensionNumber(const Shape& shape, int64 dimension_number) { if (dimension_number < 0) { - dimension_number += Rank(shape); + dimension_number += shape.rank(); } CHECK_GE(dimension_number, 0); return dimension_number; @@ -669,7 +656,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( TF_DCHECK_OK(ValidateShape(shape)); if (shape.element_type() == TUPLE) { return ByteSizeOfTupleIndexTable(shape, pointer_size); - } else if (IsArray(shape)) { + } else if (shape.IsArray()) { int64 byte_size = ByteSizeOfElements(shape); if (LayoutUtil::IsSparseArray(shape)) { byte_size += ByteSizeOfSparseIndices(shape); @@ -755,10 +742,10 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( return Status::OK(); } - if (LayoutUtil::IsSparseArray(shape) && Rank(shape) == 0) { + if (LayoutUtil::IsSparseArray(shape) && shape.rank() == 0) { return InvalidArgument("sparse arrays must have rank > 0"); } - for (int64 i = 0; i < Rank(shape); ++i) { + for (int64 i = 0; i < shape.rank(); ++i) { int64 dimension = shape.dimensions(i); if (dimension < 0) { return InvalidArgument( @@ -774,7 +761,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( /* static */ Status ShapeUtil::ValidateShapeSize(const Shape& shape) { VLOG(3) << "Validating shape size: " << ShapeUtil::HumanString(shape); - if (!IsArray(shape)) { + if (!shape.IsArray()) { return Status::OK(); } @@ -867,7 +854,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( ShapeIndexView index) { const Shape* subshape = &shape; for (auto i : index) { - if (!IsTuple(*subshape) || i >= subshape->tuple_shapes_size() || i < 0) { + if (!subshape->IsTuple() || i >= subshape->tuple_shapes_size() || i < 0) { return false; } subshape = &subshape->tuple_shapes(i); @@ -879,7 +866,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( ShapeIndexView index) { const Shape* return_shape = &shape; for (auto i : index) { - CHECK(IsTuple(*return_shape)) + CHECK(return_shape->IsTuple()) << "Invalid index " << index << " for shape " << shape; return_shape = &return_shape->tuple_shapes(i); } @@ -890,7 +877,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( const Shape& shape, ShapeIndexView index) { const Shape* return_shape = &shape; for (auto i : index) { - if (!IsTuple(*return_shape) || i < 0 || + if (!return_shape->IsTuple() || i < 0 || i >= return_shape->tuple_shapes_size()) { return InvalidArgument( "Shape index %s not a valid subshape index for tuple with shape %s", @@ -905,7 +892,7 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( ShapeIndexView index) { Shape* return_shape = shape; for (auto i : index) { - CHECK(IsTuple(*return_shape)); + CHECK(return_shape->IsTuple()); return_shape = return_shape->mutable_tuple_shapes(i); } return return_shape; @@ -913,11 +900,11 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( /* static */ bool ShapeUtil::IsLeafIndex(const Shape& shape, const ShapeIndex& index) { - return !IsTuple(GetSubshape(shape, index)); + return !GetSubshape(shape, index).IsTuple(); } /* static */ int64 ShapeUtil::GetLeafCount(const Shape& shape) { - if (!IsTuple(shape)) { + if (!shape.IsTuple()) { return 1; } int64 count = 0; @@ -1081,8 +1068,8 @@ Status ForEachMutableSubshapeHelper( /* static */ std::tuple, std::vector> ShapeUtil::InsertedOrDeleted1SizedDimensions(const Shape& shape_pre, const Shape& shape_post) { - CHECK(IsArray(shape_pre)); - CHECK(IsArray(shape_post)); + CHECK(shape_pre.IsArray()); + CHECK(shape_post.IsArray()); auto nil = std::make_tuple(false, std::vector(), std::vector()); @@ -1129,7 +1116,7 @@ ShapeUtil::InsertedOrDeleted1SizedDimensions(const Shape& shape_pre, auto unmodified_dim_pair = i < unmodified_dims.size() ? unmodified_dims[i] - : std::make_pair(Rank(shape_pre), Rank(shape_post)); + : std::make_pair(shape_pre.rank(), shape_post.rank()); if (!check_modified_dims(prior_unmodified_dim_pair, unmodified_dim_pair)) { return nil; } @@ -1141,8 +1128,8 @@ ShapeUtil::InsertedOrDeleted1SizedDimensions(const Shape& shape_pre, /* static */ std::vector> ShapeUtil::DimensionsUnmodifiedByReshape(const Shape& input_shape, const Shape& output_shape) { - CHECK(IsArray(input_shape)); - CHECK(IsArray(output_shape)); + CHECK(input_shape.IsArray()); + CHECK(output_shape.IsArray()); // Unmodified dimensions are merely common factors of rank 1. auto common_factors = CommonFactors(AsInt64Slice(input_shape.dimensions()), @@ -1192,8 +1179,8 @@ ShapeUtil::DimensionsUnmodifiedByReshape(const Shape& input_shape, /* static */ bool ShapeUtil::ReshapeIsBitcast(const Shape& input_shape, const Shape& output_shape) { - CHECK(IsArray(input_shape)); - CHECK(IsArray(output_shape)); + CHECK(input_shape.IsArray()); + CHECK(output_shape.IsArray()); CHECK(LayoutUtil::HasLayout(input_shape)); CHECK(LayoutUtil::HasLayout(output_shape)); @@ -1321,12 +1308,12 @@ ShapeUtil::DimensionsUnmodifiedByReshape(const Shape& input_shape, Shape output_shape_dim0_major = MakeShapeWithDescendingLayout( output_shape.element_type(), AsInt64Slice(output_shape.dimensions())); - for (int64 input_dim = 0; input_dim < Rank(input_shape); ++input_dim) { + for (int64 input_dim = 0; input_dim < input_shape.rank(); ++input_dim) { if (input_shape.dimensions(input_dim) <= 1) { continue; } - std::vector input_unit_index(Rank(input_shape), 0); + std::vector input_unit_index(input_shape.rank(), 0); input_unit_index[input_dim] = 1; int64 logical_linear_index = IndexUtil::MultidimensionalIndexToLinearIndex(input_shape_dim0_major, @@ -1352,11 +1339,11 @@ ShapeUtil::DimensionsUnmodifiedByReshape(const Shape& input_shape, /* static */ absl::optional ShapeUtil::AlignLayouts( const Shape& input_shape, const Shape& output_shape) { - CHECK(IsArray(input_shape)); - CHECK(IsArray(output_shape)); + CHECK(input_shape.IsArray()); + CHECK(output_shape.IsArray()); - int64 input_rank = Rank(input_shape); - int64 output_rank = Rank(output_shape); + int64 input_rank = input_shape.rank(); + int64 output_rank = output_shape.rank(); // First, calculate an alignment of the dimensions. A consecutive sequence of // input dimensions and output dimensions belong to the same alignment part if @@ -1493,14 +1480,14 @@ ShapeUtil::DimensionsUnmodifiedByReshape(const Shape& input_shape, /* static */ Shape ShapeUtil::DeleteDimension(int64 dim_to_delete, Shape shape) { - CHECK(IsArray(shape)); + CHECK(shape.IsArray()); shape.DeleteDimension(dim_to_delete); return shape; } /* static */ Shape ShapeUtil::FilterDimensions( const std::function& p, Shape shape) { - CHECK(IsArray(shape)); + CHECK(shape.IsArray()); std::vector dims_to_delete; for (int64 i = shape.dimensions().size() - 1; i >= 0; --i) { if (!p(i)) { diff --git a/tensorflow/compiler/xla/shape_util.h b/tensorflow/compiler/xla/shape_util.h index c8295e85ce..e98c6e024b 100644 --- a/tensorflow/compiler/xla/shape_util.h +++ b/tensorflow/compiler/xla/shape_util.h @@ -185,7 +185,7 @@ class ShapeUtil { // may not actually be able to store this number of elements. See // LayoutUtil::MaxSparseElements(shape) to obtain the maximum number of // elements that can be stored in a sparse shape. - // Precondition: IsArray(shape) + // Precondition: shape.IsArray() static int64 ElementsIn(const Shape& shape); // As ElementsIn(), but recurses through tuples. @@ -296,11 +296,6 @@ class ShapeUtil { // As Equal, but allow one of lhs and rhs to be F16 while the other is F32. static bool EqualIgnoringFpPrecision(const Shape& lhs, const Shape& rhs); - // Returns the rank (number of dimensions) of the given shape. - // Precondition: !IsTuple(shape) - ABSL_DEPRECATED("Use `Shape::rank` instead.") - static int64 Rank(const Shape& shape); - // Returns the number of dimensions for which the dimension is not (trivially) // 1. e.g., f32[2x1x1] has a true rank of 1D, the other dimensions are just // fluff. Note that zero dimensions are included in the true rank, e.g., @@ -314,10 +309,10 @@ class ShapeUtil { // Scalar-specific static bool IsScalar(const Shape& shape) { - return IsArray(shape) && Rank(shape) == 0; + return shape.IsArray() && shape.rank() == 0; } static bool IsEffectiveScalar(const Shape& shape) { - return IsArray(shape) && TrueRank(shape) == 0; + return shape.IsArray() && TrueRank(shape) == 0; } // Returns whether "shape" is a scalar (array) with the given element_type. @@ -457,31 +452,6 @@ class ShapeUtil { // that floating point numbers are signed. static bool ElementIsSigned(const Shape& shape); - // Returns whether the shape is a tuple. - ABSL_DEPRECATED("Use Shape::IsTuple instead.") - static bool IsTuple(const Shape& shape) { - return shape.element_type() == TUPLE; - } - - // Returns whether the shape is an opaque value (i.e. an 'existential' typed - // value that is passed to CustomCall operations). - ABSL_DEPRECATED("Use Shape::IsOpaque instead.") - static bool IsOpaque(const Shape& shape) { - return shape.element_type() == OPAQUE; - } - - // Returns whether the shape is an token value used for ordering - // side-effecting operations. - ABSL_DEPRECATED("Use Shape::IsToken instead.") - static bool IsToken(const Shape& shape) { - return shape.element_type() == TOKEN; - } - - // Returns whether the shape is an array. Note that scalars are considered - // arrays. - ABSL_DEPRECATED("Use Shape::IsArray instead.") - static bool IsArray(const Shape& shape); - // Returns whether the given primitive type corresponds to an array shape. static bool IsArrayPrimitiveType(PrimitiveType primitive_type); @@ -511,12 +481,6 @@ class ShapeUtil { // shape. static Shape ComplexComponentShape(const Shape& complex_shape); - // Shorthand for testing whether a shape is of a given element type and - // sequence of dimensions. - ABSL_DEPRECATED("Use Equal() instead.") - static bool ShapeIs(const Shape& shape, PrimitiveType element_type, - std::initializer_list dimensions); - // Returns true if the given shape has a subshape at the given index. static bool IndexIsValid(const Shape& shape, ShapeIndexView index); @@ -764,7 +728,7 @@ class ShapeUtil { if (ShapeUtil::IsZeroElementArray(shape)) { return Status::OK(); } - CHECK_EQ(Rank(shape), base.size()); + CHECK_EQ(shape.rank(), base.size()); CHECK_EQ(incr.size(), base.size()); CHECK_EQ(count.size(), base.size()); const int64 rank = LayoutUtil::MinorToMajor(shape).size(); diff --git a/tensorflow/compiler/xla/shape_util_test.cc b/tensorflow/compiler/xla/shape_util_test.cc index 8e7c208193..61b4e73e06 100644 --- a/tensorflow/compiler/xla/shape_util_test.cc +++ b/tensorflow/compiler/xla/shape_util_test.cc @@ -538,10 +538,6 @@ TEST(ShapeUtilTest, InsertedOrDeleted1SizedDimensions) { ShapeUtil::InsertedOrDeleted1SizedDimensions(shape0, shape2))); } -TEST(ShapeUtilTest, ShapeIs) { - EXPECT_FALSE(ShapeUtil::ShapeIs(ShapeUtil::MakeShape(PRED, {2}), PRED, {})); -} - TEST(ShapeUtilTest, ForEachIndex) { struct ShapeDimensionAndNumberInvocations { std::vector dimensions; diff --git a/tensorflow/compiler/xla/tests/tuple_test.cc b/tensorflow/compiler/xla/tests/tuple_test.cc index 9c586bdeb0..426d6c84ee 100644 --- a/tensorflow/compiler/xla/tests/tuple_test.cc +++ b/tensorflow/compiler/xla/tests/tuple_test.cc @@ -176,8 +176,9 @@ XLA_TEST_F(TupleTest, AddTupleElements) { {2.f, 4.f, 6.f}, // row 0 {5.f, 7.f, 9.f}, // row 1 }); - ASSERT_TRUE(ShapeUtil::ShapeIs(vector_shape, F32, {3})); - ASSERT_TRUE(ShapeUtil::ShapeIs(matrix_shape, F32, {/*y=*/2, /*x=*/3})); + ASSERT_TRUE(ShapeUtil::Equal(vector_shape, ShapeUtil::MakeShape(F32, {3}))); + ASSERT_TRUE(ShapeUtil::Equal(matrix_shape, + ShapeUtil::MakeShape(F32, {/*y=*/2, /*x=*/3}))); ComputeAndCompareR2(&builder, expected, {}, error_spec_); } -- GitLab From 4053e17dbb7a1be85553e5173c2e08fb9653d33f Mon Sep 17 00:00:00 2001 From: Pete Warden Date: Wed, 9 Jan 2019 12:33:36 -0800 Subject: [PATCH 0437/2345] Generate IDE projects automatically for various platforms PiperOrigin-RevId: 228567285 --- tensorflow/lite/experimental/micro/BUILD | 4 + tensorflow/lite/experimental/micro/README.md | 259 ++++++++++++++---- .../experimental/micro/bluepill/debug_log.cc | 27 ++ .../lite/experimental/micro/debug_log.cc | 41 +++ .../lite/experimental/micro/debug_log.h | 23 ++ .../experimental/micro/debug_log_numbers.cc | 185 +++++++++++++ .../experimental/micro/debug_log_numbers.h | 28 ++ .../examples/micro_speech/CMSIS/Makefile.inc | 43 +++ .../micro/examples/micro_speech/Makefile.inc | 183 +++++-------- .../micro_speech/disco_f746ng/Makefile.inc | 7 + .../disco_f746ng/audio_provider.cc | 182 ++++++++++++ .../micro_speech/disco_f746ng/timer.cc | 24 ++ .../examples/micro_speech/osx/Makefile.inc | 6 +- .../examples/micro_speech/preprocessor.cc | 9 +- .../lite/experimental/micro/mbed/debug_log.cc | 24 ++ .../experimental/micro/micro_error_reporter.h | 22 +- .../micro/riscv32_mcu/debug_log.cc | 172 ------------ .../experimental/micro/tools/make/Makefile | 101 ++++--- .../micro/tools/make/helper_functions.inc | 117 ++++++++ .../tools/make/targets/bluepill_makefile.inc | 5 +- .../tools/make/targets/mbed_makefile.inc | 4 + .../micro/tools/make/targets/osx_makefile.inc | 2 +- .../make/templates/AUDIO_DISCO_F746NG.lib.tpl | 1 + .../make/templates/BSP_DISCO_F746NG.lib.tpl | 1 + .../micro/tools/make/templates/Makefile.tpl | 26 ++ .../tools/make/templates/README_MAKE.md.tpl | 29 ++ .../tools/make/templates/README_MBED.md.tpl | 48 ++++ .../make/templates/SDRAM_DISCO_F746NG.lib.tpl | 1 + .../tools/make/templates/mbed-os.lib.tpl | 1 + .../tools/make/templates/mbed_app.json.tpl | 7 + 30 files changed, 1170 insertions(+), 412 deletions(-) create mode 100644 tensorflow/lite/experimental/micro/bluepill/debug_log.cc create mode 100644 tensorflow/lite/experimental/micro/debug_log.cc create mode 100644 tensorflow/lite/experimental/micro/debug_log.h create mode 100644 tensorflow/lite/experimental/micro/debug_log_numbers.cc create mode 100644 tensorflow/lite/experimental/micro/debug_log_numbers.h create mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc create mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/Makefile.inc create mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/audio_provider.cc create mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/timer.cc create mode 100644 tensorflow/lite/experimental/micro/mbed/debug_log.cc create mode 100644 tensorflow/lite/experimental/micro/tools/make/helper_functions.inc create mode 100644 tensorflow/lite/experimental/micro/tools/make/targets/mbed_makefile.inc create mode 100644 tensorflow/lite/experimental/micro/tools/make/templates/AUDIO_DISCO_F746NG.lib.tpl create mode 100644 tensorflow/lite/experimental/micro/tools/make/templates/BSP_DISCO_F746NG.lib.tpl create mode 100644 tensorflow/lite/experimental/micro/tools/make/templates/Makefile.tpl create mode 100644 tensorflow/lite/experimental/micro/tools/make/templates/README_MAKE.md.tpl create mode 100644 tensorflow/lite/experimental/micro/tools/make/templates/README_MBED.md.tpl create mode 100644 tensorflow/lite/experimental/micro/tools/make/templates/SDRAM_DISCO_F746NG.lib.tpl create mode 100644 tensorflow/lite/experimental/micro/tools/make/templates/mbed-os.lib.tpl create mode 100644 tensorflow/lite/experimental/micro/tools/make/templates/mbed_app.json.tpl diff --git a/tensorflow/lite/experimental/micro/BUILD b/tensorflow/lite/experimental/micro/BUILD index e11159868e..2d00ef76f4 100644 --- a/tensorflow/lite/experimental/micro/BUILD +++ b/tensorflow/lite/experimental/micro/BUILD @@ -12,6 +12,8 @@ load( cc_library( name = "micro_framework", srcs = [ + "debug_log.cc", + "debug_log_numbers.cc", "micro_error_reporter.cc", "micro_interpreter.cc", "micro_mutable_op_resolver.cc", @@ -19,6 +21,8 @@ cc_library( ], hdrs = [ "compatibility.h", + "debug_log.h", + "debug_log_numbers.h", "micro_error_reporter.h", "micro_interpreter.h", "micro_mutable_op_resolver.h", diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 464c7b6ad7..173f91fe90 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -1,46 +1,142 @@ # TensorFlow Lite for Microcontrollers -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. +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. 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. - -- **Easy to modify**: We know that there are a lot of different platforms and requirements in the embedded world, and we don't expect to cover all of them in one framework. Instead, we're hoping that it can be a good starting point for developers to build on top of to meet their own needs. For example, we tried to make it easy to replace the implementations of key computational operators that are often crucial for performance, without having to touch the data flow and other runtime code. We want it to make more sense to use our workflow to handle things like model import and less-important operations, and customize the parts that matter, rather than having to reimplement everything in your own engine. - -- **Well-tested**: If you're modifying code, you need to know if your changes are correct. Having an easy way to test lets you develop much faster. To help there, we've written tests for all the components, and we've made sure that the tests can be run on almost any platform, with no dependencies apart from the ability to log text to a debug console somewhere. We also provide an easy way to run all the tests on-device as part of an automated test framework, and we use qemu/Renode emulation so that tests can be run even without physical devices present. - -- **Easy to integrate**: We want to be as open a system as possible, and use the best code available for each platform. To do that, we're going to rely on projects like [CMSIS-NN](https://www.keil.com/pack/doc/CMSIS/NN/html/index.html), [uTensor](https://github.com/uTensor/uTensor), and other vendor libraries to handle as much performance-critical code as possible. We know that there are an increasing number of options to accelerate neural networks on microcontrollers, so we're aiming to be a good host for deploying those hardware technologies too. - -- **Compatible**: We're using the same file schema, interpreter API, and kernel interface as regular TensorFlow Lite, so we leverage the large existing set of tools, documentation, and examples for the project. The biggest barrier to deploying ML models is getting them from a training environment into a form that's easy to run inference on, so we see reusing this rich ecosystem as being crucial to being easily usable. We also hope to integrate this experimental work back into the main codebase in the future. +- **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. + +- **Easy to modify**: We know that there are a lot of different platforms and + requirements in the embedded world, and we don't expect to cover all of them + in one framework. Instead, we're hoping that it can be a good starting point + for developers to build on top of to meet their own needs. For example, we + tried to make it easy to replace the implementations of key computational + operators that are often crucial for performance, without having to touch + the data flow and other runtime code. We want it to make more sense to use + our workflow to handle things like model import and less-important + operations, and customize the parts that matter, rather than having to + reimplement everything in your own engine. + +- **Well-tested**: If you're modifying code, you need to know if your changes + are correct. Having an easy way to test lets you develop much faster. To + help there, we've written tests for all the components, and we've made sure + that the tests can be run on almost any platform, with no dependencies apart + from the ability to log text to a debug console somewhere. We also provide + an easy way to run all the tests on-device as part of an automated test + framework, and we use qemu/Renode emulation so that tests can be run even + without physical devices present. + +- **Easy to integrate**: We want to be as open a system as possible, and use + the best code available for each platform. To do that, we're going to rely + on projects like + [CMSIS-NN](https://www.keil.com/pack/doc/CMSIS/NN/html/index.html), + [uTensor](https://github.com/uTensor/uTensor), and other vendor libraries to + handle as much performance-critical code as possible. We know that there are + an increasing number of options to accelerate neural networks on + microcontrollers, so we're aiming to be a good host for deploying those + hardware technologies too. + +- **Compatible**: We're using the same file schema, interpreter API, and + kernel interface as regular TensorFlow Lite, so we leverage the large + existing set of tools, documentation, and examples for the project. The + biggest barrier to deploying ML models is getting them from a training + environment into a form that's easy to run inference on, so we see reusing + this rich ecosystem as being crucial to being easily usable. We also hope to + integrate this experimental work back into the main codebase in the future. To meet those goals, we've made some tradeoffs: -- **Simple C++**: To help with readability, our code is written in a modern version of C++, but we generally treat it as a "better C", rather relying on more complex features such as template meta-programming. As mentioned earlier, we avoid any use of dynamic memory allocation (new/delete) or the standard C/C++ libraries, so we believe this should still be fairly portable. It does mean that some older devices with C-only toolchains won't be supported, but we're hoping that the reference operator implementations (which are simple C-like functions) can still be useful in those cases. The interfaces are also designed to be C-only, so it should be possible to integrate the resulting library with pure C projects. - -- **Interpreted**: Code generation is a popular pattern for embedded code, because it gives standalone code that's easy to modify and step through, but we've chosen to go with an interpreted approach. In our internal microcontroller work we've found that using an extremely stripped-down interpreter with almost no dependencies gives us a lot of the same advantages, but is easier to maintain. For example, when new updates come out for the underlying library, you can just merge your local modifications in a single step, rather than having to regenerate new code and then patch in any changes you subsequently made. The coarse granularity of the interpreted primitives means that each operation call typically takes hundreds of thousands of instruction cycles at least, so we don't see noticeable performance gains from avoiding what's essentially a single switch statement at the interpreter level to call each operation. We're still working on improving the packaging though, for example we're considering having the ability to snapshot all the source files and headers used for a particular model, being able to compile the code and data together as a library, and then access it through a minimal set of C interface calls which hide the underlying complexity. - -- **Flatbuffers**: We represent our models using [the standard flatbuffer schema used by the rest of TensorFlow Lite](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/schema/schema.fbs), with the difference that we always keep it in read-only program memory (typically flash) rather than relying on having a file system to read it from. This is a good fit because flatbuffer's serialized format is designed to be mapped into memory without requiring any extra memory allocations or modifications to access it. All of the functions to read model values work directly on the serialized bytes, and large sections of data like weights are directly accessible as sequential C-style arrays of their data type, with no strides or unpacking needed. We do get a lot of value from using flatbuffers, but there is a cost in complexity. The flat buffer library code is all inline [inside the main headers](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/schema/schema_generated.h), but it isn't straightforward to inspect their implementations, and the model data structures aren't easy to comprehend from the debugger. The header for the schema itself also has to be periodically updated when new information is added to the file format, though we try to handle that transparently for most developers by checking in a pre-generated version. - -- **Code Duplication**: Some of the code in this prototype largely duplicates the logic in other parts of the TensorFlow Lite code base, for example the operator wrappers. We've tried to keep share as much as we can between the two interpreters, but there are some assumptions built into the original runtime that make this difficult. We'll be working on modularizing the main interpreter so that we can move to an entirely shared system. - -This initial preview release is designed to get early feedback, and is not intended to be a final product. It only includes enough operations to run a simple keyword recognition model, and the implementations are not optimized. We're hoping this will be a good way to get feedback and collaborate to improve the framework. - -## Getting Started +- **Simple C++**: To help with readability, our code is written in a modern + version of C++, but we generally treat it as a "better C", rather relying on + more complex features such as template meta-programming. As mentioned + earlier, we avoid any use of dynamic memory allocation (new/delete) or the + standard C/C++ libraries, so we believe this should still be fairly + portable. It does mean that some older devices with C-only toolchains won't + be supported, but we're hoping that the reference operator implementations + (which are simple C-like functions) can still be useful in those cases. The + interfaces are also designed to be C-only, so it should be possible to + integrate the resulting library with pure C projects. + +- **Interpreted**: Code generation is a popular pattern for embedded code, + because it gives standalone code that's easy to modify and step through, but + we've chosen to go with an interpreted approach. In our internal + microcontroller work we've found that using an extremely stripped-down + interpreter with almost no dependencies gives us a lot of the same + advantages, but is easier to maintain. For example, when new updates come + out for the underlying library, you can just merge your local modifications + in a single step, rather than having to regenerate new code and then patch + in any changes you subsequently made. The coarse granularity of the + interpreted primitives means that each operation call typically takes + hundreds of thousands of instruction cycles at least, so we don't see + noticeable performance gains from avoiding what's essentially a single + switch statement at the interpreter level to call each operation. We're + still working on improving the packaging though, for example we're + considering having the ability to snapshot all the source files and headers + used for a particular model, being able to compile the code and data + together as a library, and then access it through a minimal set of C + interface calls which hide the underlying complexity. + +- **Flatbuffers**: We represent our models using + [the standard flatbuffer schema used by the rest of TensorFlow Lite](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/schema/schema.fbs), + with the difference that we always keep it in read-only program memory + (typically flash) rather than relying on having a file system to read it + from. This is a good fit because flatbuffer's serialized format is designed + to be mapped into memory without requiring any extra memory allocations or + modifications to access it. All of the functions to read model values work + directly on the serialized bytes, and large sections of data like weights + are directly accessible as sequential C-style arrays of their data type, + with no strides or unpacking needed. We do get a lot of value from using + flatbuffers, but there is a cost in complexity. The flat buffer library code + is all inline + [inside the main headers](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/schema/schema_generated.h), + but it isn't straightforward to inspect their implementations, and the model + data structures aren't easy to comprehend from the debugger. The header for + the schema itself also has to be periodically updated when new information + is added to the file format, though we try to handle that transparently for + most developers by checking in a pre-generated version. + +- **Code Duplication**: Some of the code in this prototype largely duplicates + the logic in other parts of the TensorFlow Lite code base, for example the + operator wrappers. We've tried to keep share as much as we can between the + two interpreters, but there are some assumptions built into the original + runtime that make this difficult. We'll be working on modularizing the main + interpreter so that we can move to an entirely shared system. + +This initial preview release is designed to get early feedback, and is not +intended to be a final product. It only includes enough operations to run a +simple keyword recognition model, and the implementations are not optimized. +We're hoping this will be a good way to get feedback and collaborate to improve +the framework. + +## Getting Started with Make Building requires a Linux or OS X machine. - - Open a terminal - - Download the TensorFlow source with `git clone https://github.com/tensorflow/tensorflow.git` - - Enter the source root directory by running `cd tensorflow` - - Download the dependencies by running `tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh`. This may take a few minutes - - Build and test the library with `make -f tensorflow/lite/experimental/micro/tools/make/Makefile test` +- Open a terminal +- Download the TensorFlow source with `git clone + https://github.com/tensorflow/tensorflow.git` +- Enter the source root directory by running `cd tensorflow` +- Download the dependencies by running + `tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh`. + This may take a few minutes +- Build and test the library with `make -f + tensorflow/lite/experimental/micro/tools/make/Makefile test` You should see a series of compilation steps, followed by `~~~ALL TESTS PASSED~~~` for the various tests of the code that it will run. If there's an error, you should get an informative message from make about what went wrong. -These tests are all built as simple binaries with few dependencies, so you can run them manually. For example, here's how to run the depthwise convolution test, and its output: +These tests are all built as simple binaries with few dependencies, so you can +run them manually. For example, here's how to run the depthwise convolution +test, and its output: ``` tensorflow/lite/experimental/micro/tools/make/gen/linux_x86_64/bin/tensorflow/lite/experimental/micro/kernels/depthwise_conv_test @@ -53,7 +149,9 @@ Testing SimpleTestReluQuantized ~ALL TESTS PASSED~~~ ``` -Looking at the [depthwise_conv_test.cc](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/kernels/depthwise_conv_test.cc) code, you'll see a sequence that looks like this: +Looking at the +[depthwise_conv_test.cc](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/kernels/depthwise_conv_test.cc) +code, you'll see a sequence that looks like this: ``` ... @@ -74,19 +172,41 @@ output, and the test harness that runs the binary during the make process knows that everything ran correctly. If there's an error, the lack of the expected string lets the harness know that the test failed. -So, why are we running tests in this complicated way? So far, we've been building binaries that run locally on the Mac OS or Linux machine you're building on, but this approach becomes important when we're targeting simple micro controller devices. +So, why are we running tests in this complicated way? So far, we've been +building binaries that run locally on the Mac OS or Linux machine you're +building on, but this approach becomes important when we're targeting simple +micro controller devices. ## Building for the "Blue Pill" STM32F103 -The goal of this library is to enable machine learning on resource-constrained micro controllers and DSPs, and as part of that we've targeted the ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) as a cheap and popular platform. It only has 20KB of RAM and 64KB of flash, so it's a good device to ensure we can run efficiently on small chips. - -It's fairly easy to [buy and wire up a physical board](https://github.com/google/stm32_bare_lib#wiring-up-your-blue-pill), but even if you don't have an actual device, the [Renode project](https://renode.io/) makes it easy to run a faithful emulation on your desktop machine. You'll need [Docker](https://www.docker.com/) installed, but once you have that set up, try running the following command: - -`make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=bluepill test` - -You should see a similar set of outputs as you did in the previous section, with the addition of some extra Docker logging messages. These are because we're using Docker to run the Renode micro controller emulation tool, and the tests themselves are being run on a simulated STM32F103 device. The communication channels between an embedded device and the host are quite limited, so the test harness looks at the output of the debug log to see if tests have passed, just as it did in the previous section. This makes it a very flexible way to run cross-platform tests, even when a platform has no operating system facilities, as long as it can output debugging text logs. - -To understand what's happening here, try running the same depthwise convolution test, but through the emulated device test harness, with the following command: +The goal of this library is to enable machine learning on resource-constrained +micro controllers and DSPs, and as part of that we've targeted the +["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) +as a cheap and popular platform. It only has 20KB of RAM and 64KB of flash, so +it's a good device to ensure we can run efficiently on small chips. + +It's fairly easy to +[buy and wire up a physical board](https://github.com/google/stm32_bare_lib#wiring-up-your-blue-pill), +but even if you don't have an actual device, the +[Renode project](https://renode.io/) makes it easy to run a faithful emulation +on your desktop machine. You'll need [Docker](https://www.docker.com/) +installed, but once you have that set up, try running the following command: + +`make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=bluepill +test` + +You should see a similar set of outputs as you did in the previous section, with +the addition of some extra Docker logging messages. These are because we're +using Docker to run the Renode micro controller emulation tool, and the tests +themselves are being run on a simulated STM32F103 device. The communication +channels between an embedded device and the host are quite limited, so the test +harness looks at the output of the debug log to see if tests have passed, just +as it did in the previous section. This makes it a very flexible way to run +cross-platform tests, even when a platform has no operating system facilities, +as long as it can output debugging text logs. + +To understand what's happening here, try running the same depthwise convolution +test, but through the emulated device test harness, with the following command: ``` tensorflow/lite/experimental/micro/testing/test_bluepill_binary.sh \ @@ -115,7 +235,7 @@ LOGS: 03:27:32.4834 [DEBUG] cpu.uartSemihosting: [+0.18ms host +0s virt 0s virt from start] Testing SimpleTestReluQuantized 03:27:32.4838 [DEBUG] cpu.uartSemihosting: [+0.4ms host +0s virt 0s virt from start] 4/4 tests passed 03:27:32.4839 [DEBUG] cpu.uartSemihosting: [+41µs host +0s virt 0s virt from start] ~~~ALL TESTS PASSED~~~ -03:27:32.4839 [DEBUG] cpu.uartSemihosting: [+5µs host +0s virt 0s virt from start] +03:27:32.4839 [DEBUG] cpu.uartSemihosting: [+5µs host +0s virt 0s virt from start] ... tensorflow/lite/experimental/micro/tools/make/gen/bluepill_cortex-m3/bin/tensorflow/lite/experimental/micro/kernels/depthwise_conv_test: PASS ``` @@ -128,16 +248,19 @@ than your desktop. We hope that the simplicity of this testing approach will help make adding support for new platforms as easy as possible. ## Building for "Hifive1" SiFive FE310 development board -We've targeted the ["HiFive1" Arduino-compatible development board](https://www.sifive.com/boards/hifive1) as a test platform for RISC-V MCU. -Similar to Blue Pill setup, you will need Docker installed. The binary can be executed on either HiFive1 board or emulated using [Renode project](https://renode.io/) on your desktop machine. +We've targeted the +["HiFive1" Arduino-compatible development board](https://www.sifive.com/boards/hifive1) +as a test platform for RISC-V MCU. + +Similar to Blue Pill setup, you will need Docker installed. The binary can be +executed on either HiFive1 board or emulated using +[Renode project](https://renode.io/) on your desktop machine. The following instructions builds and transfers the source files to the Docker -``` -docker build -t riscv_build \ --f {PATH_TO_TENSORFLOW_ROOT_DIR}/tensorflow/lite/experimental/micro/testing/Dockerfile.riscv \ -{PATH_TO_TENSORFLOW_ROOT_DIR}/tensorflow/lite/experimental/micro/testing/ -``` +`docker build -t riscv_build \ -f +{PATH_TO_TENSORFLOW_ROOT_DIR}/tensorflow/lite/experimental/micro/testing/Dockerfile.riscv +\ {PATH_TO_TENSORFLOW_ROOT_DIR}/tensorflow/lite/experimental/micro/testing/` You should see output that looks something like this: @@ -160,18 +283,25 @@ Successfully tagged riscv_build:latest Building micro_speech_test binary - - Lauch 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 `./tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh`. This may take a few minutes. - - Set the path to RISC-V tools: `export PATH=${PATH}:/workspace/tensorflow/lite/experimental/micro/tools/make/downloads/riscv_toolchain/bin/` - - Build the binary: `make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=riscv32_mcu` +- Lauch 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 + `./tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh`. + This may take a few minutes. +- Set the path to RISC-V tools: `export + PATH=${PATH}:/workspace/tensorflow/lite/experimental/micro/tools/make/downloads/riscv_toolchain/bin/` +- 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. - - Until https://github.com/renode/renode/pull/30 is in the Docker image, patch the change manully: `sed -E -i 's/"rv32g"/"rv32imac"/g' /opt/renode/platforms/cpus/sifive-fe310.repl` - +- Until https://github.com/renode/renode/pull/30 is in the Docker image, patch + the change manully: `sed -E -i 's/"rv32g"/"rv32imac"/g' + /opt/renode/platforms/cpus/sifive-fe310.repl` - - Execute the binary on Renode: `renode -P 5000 --disable-xwt -e 's @/workspace/tensorflow/lite/experimental/micro/testing/sifive_fe310.resc'` +- Execute the binary on Renode: `renode -P 5000 --disable-xwt -e 's + @/workspace/tensorflow/lite/experimental/micro/testing/sifive_fe310.resc'` You should see the following log with the magic string `~~~ALL TEST PASSED~~~`: @@ -238,3 +368,24 @@ JFlashLiteExe 2. Device = AMA3B1KK-KBR 3. Interface = SWD at 1000 kHz 4. Data file = tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin 5. Prog Addr = 0x0000C000 + +## Generating Project Files + +It's not always easy or convenient to use a makefile-based build process, +especially if you're working on a product that uses a different IDE for the rest +of its code. To address that, it's possible to generate standalone project +folders for various popular build systems. These projects are self-contained, +with only the headers and source files needed by a particular binary, and +include project files to make loading them into an IDE easy. These can be +auto-generated for any target you can compile using the main Make system, using +a command like this (making sure you've run `download_dependencies.sh` first): + +``` +make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=mbed TAGS="CMSIS disco_f746ng" generate_micro_speech_main_test_mbed_project +``` + +This will create a folder in +`tensorflow/lite/experimental/micro/tools/make/gen/mbed_cortex-m4/prj/micro_speech_main_test/mbed` +that contains the source and header files, some Mbed configuration files, and a +README. You should then be able to copy this directory to another machine, and +use it just like any other Mbed project. diff --git a/tensorflow/lite/experimental/micro/bluepill/debug_log.cc b/tensorflow/lite/experimental/micro/bluepill/debug_log.cc new file mode 100644 index 0000000000..4812a91849 --- /dev/null +++ b/tensorflow/lite/experimental/micro/bluepill/debug_log.cc @@ -0,0 +1,27 @@ +/* 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/experimental/micro/debug_log.h" + +// For Arm Cortex-M devices, calling SYS_WRITE0 will output the zero-terminated +// string pointed to by R1 to any debug console that's attached to the system. +extern "C" void DebugLog(const char* s) { + asm("mov r0, #0x04\n" // SYS_WRITE0 + "mov r1, %[str]\n" + "bkpt #0xAB\n" + : + : [ str ] "r"(s) + : "r0", "r1"); +} diff --git a/tensorflow/lite/experimental/micro/debug_log.cc b/tensorflow/lite/experimental/micro/debug_log.cc new file mode 100644 index 0000000000..3d4ca44d76 --- /dev/null +++ b/tensorflow/lite/experimental/micro/debug_log.cc @@ -0,0 +1,41 @@ +/* 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. +==============================================================================*/ + +// Reference implementation of the DebugLog() function that's required for a +// platform to support the TensorFlow Lite for Microcontrollers library. This is +// the only function that's absolutely required to be available on a target +// device, since it's used for communicating test results back to the host so +// that we can verify the implementation is working correctly. +// It's designed to be as easy as possible to supply an implementation though. +// On platforms that have a POSIX stack or C library, it can be written as a +// single call to `fprintf(stderr, "%s", s)` to output a string to the error +// stream of the console, but if there's no OS or C library available, there's +// almost always an equivalent way to write out a string to some serial +// interface that can be used instead. For example on Arm M-series MCUs, calling +// the `bkpt #0xAB` assembler instruction will output the string in r1 to +// whatever debug serial connection is available. If you're running mbed, you +// can do the same by creating `Serial pc(USBTX, USBRX)` and then calling +// `pc.printf("%s", s)`. +// To add an equivalent function for your own platform, create your own +// implementation file, and place it in a subfolder with named after the OS +// you're targeting. For example, see the Cortex M bare metal version in +// tensorflow/lite/experimental/micro/bluepill/debug_log.cc or the mbed one on +// tensorflow/lite/experimental/micro/mbed/debug_log.cc. + +#include "tensorflow/lite/experimental/micro/debug_log.h" + +#include + +extern "C" void DebugLog(const char* s) { fprintf(stderr, "%s", s); } diff --git a/tensorflow/lite/experimental/micro/debug_log.h b/tensorflow/lite/experimental/micro/debug_log.h new file mode 100644 index 0000000000..c0e395c376 --- /dev/null +++ b/tensorflow/lite/experimental/micro/debug_log.h @@ -0,0 +1,23 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_H_ + +// This function should be implemented by each target platform, and provide a +// way for strings to be output to some text stream. For more information, see +// tensorflow/lite/experimental/micro/debug_log.cc. +extern "C" void DebugLog(const char* s); + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_H_ diff --git a/tensorflow/lite/experimental/micro/debug_log_numbers.cc b/tensorflow/lite/experimental/micro/debug_log_numbers.cc new file mode 100644 index 0000000000..8e86730674 --- /dev/null +++ b/tensorflow/lite/experimental/micro/debug_log_numbers.cc @@ -0,0 +1,185 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +// Implements debug logging for numbers by converting them into strings and then +// calling the main DebugLog(char*) function. These are separated into a +// different file so that platforms can just implement the string output version +// of DebugLog() and then get the numerical variations without requiring any +// more code. + +#include "tensorflow/lite/experimental/micro/debug_log_numbers.h" + +#include "tensorflow/lite/experimental/micro/debug_log.h" + +namespace { + +// All input buffers to the number conversion functions must be this long. +static const int kFastToBufferSize = 48; + +// Reverses a zero-terminated string in-place. +char* ReverseStringInPlace(char* start, char* end) { + char* p1 = start; + char* p2 = end - 1; + while (p1 < p2) { + char tmp = *p1; + *p1++ = *p2; + *p2-- = tmp; + } + return start; +} + +// Appends a string to a string, in-place. You need to pass in the maximum +// string length as the second argument. +char* StrCatStr(char* main, int main_max_length, const char* to_append) { + char* current = main; + while (*current != 0) { + ++current; + } + char* current_end = main + (main_max_length - 1); + while ((*to_append != 0) && (current < current_end)) { + *current = *to_append; + ++current; + ++to_append; + } + *current = 0; + return current; +} + +// Populates the provided buffer with an ASCII representation of the number. +char* FastUInt32ToBufferLeft(uint32_t i, char* buffer, int base) { + char* start = buffer; + do { + int32_t digit = i % base; + char character; + if (digit < 10) { + character = '0' + digit; + } else { + character = 'a' + (digit - 10); + } + *buffer++ = character; + i /= base; + } while (i > 0); + *buffer = 0; + ReverseStringInPlace(start, buffer); + return buffer; +} + +// Populates the provided buffer with an ASCII representation of the number. +char* FastInt32ToBufferLeft(int32_t i, char* buffer) { + uint32_t u = i; + if (i < 0) { + *buffer++ = '-'; + u = -u; + } + return FastUInt32ToBufferLeft(u, buffer, 10); +} + +// Converts a number to a string and appends it to another. +char* StrCatInt32(char* main, int main_max_length, int32_t number) { + char number_string[kFastToBufferSize]; + FastInt32ToBufferLeft(number, number_string); + return StrCatStr(main, main_max_length, number_string); +} + +// Converts a number to a string and appends it to another. +char* StrCatUInt32(char* main, int main_max_length, uint32_t number, int base) { + char number_string[kFastToBufferSize]; + FastUInt32ToBufferLeft(number, number_string, base); + return StrCatStr(main, main_max_length, number_string); +} + +// Populates the provided buffer with ASCII representation of the float number. +// Avoids the use of any floating point instructions (since these aren't +// supported on many microcontrollers) and as a consequence prints values with +// power-of-two exponents. +char* FastFloatToBufferLeft(float f, char* buffer) { + char* current = buffer; + char* current_end = buffer + (kFastToBufferSize - 1); + // Access the bit fields of the floating point value to avoid requiring any + // float instructions. These constants are derived from IEEE 754. + const uint32_t sign_mask = 0x80000000; + const uint32_t exponent_mask = 0x7f800000; + const int32_t exponent_shift = 23; + const int32_t exponent_bias = 127; + const uint32_t fraction_mask = 0x007fffff; + const uint32_t u = *reinterpret_cast(&f); + const int32_t exponent = + ((u & exponent_mask) >> exponent_shift) - exponent_bias; + const uint32_t fraction = (u & fraction_mask); + // Expect ~0x2B1B9D3 for fraction. + if (u & sign_mask) { + *current = '-'; + current += 1; + } + *current = 0; + // These are special cases for infinities and not-a-numbers. + if (exponent == 128) { + if (fraction == 0) { + current = StrCatStr(current, (current_end - current), "Inf"); + return current; + } else { + current = StrCatStr(current, (current_end - current), "NaN"); + return current; + } + } + // 0x007fffff (8388607) represents 0.99... for the fraction, so to print the + // correct decimal digits we need to scale our value before passing it to the + // conversion function. This scale should be 10000000/8388608 = 1.1920928955. + // We can approximate this using multiply-adds and right-shifts using the + // values in this array. The 1. portion of the number string is printed out + // in a fixed way before the fraction, below. + const int32_t scale_shifts_size = 13; + const int8_t scale_shifts[13] = {3, 4, 8, 11, 13, 14, 17, + 18, 19, 20, 21, 22, 23}; + uint32_t scaled_fraction = fraction; + for (int i = 0; i < scale_shifts_size; ++i) { + scaled_fraction += (fraction >> scale_shifts[i]); + } + *current = '1'; + current += 1; + *current = '.'; + current += 1; + *current = 0; + current = StrCatUInt32(current, (current_end - current), scaled_fraction, 10); + current = StrCatStr(current, (current_end - current), "*2^"); + current = StrCatInt32(current, (current_end - current), exponent); + return current; +} + +} // namespace + +extern "C" void DebugLogInt32(int32_t i) { + char number_string[kFastToBufferSize]; + FastInt32ToBufferLeft(i, number_string); + DebugLog(number_string); +} + +extern "C" void DebugLogUInt32(uint32_t i) { + char number_string[kFastToBufferSize]; + FastUInt32ToBufferLeft(i, number_string, 10); + DebugLog(number_string); +} + +extern "C" void DebugLogHex(uint32_t i) { + char number_string[kFastToBufferSize]; + FastUInt32ToBufferLeft(i, number_string, 16); + DebugLog(number_string); +} + +extern "C" void DebugLogFloat(float i) { + char number_string[kFastToBufferSize]; + FastFloatToBufferLeft(i, number_string); + DebugLog(number_string); +} diff --git a/tensorflow/lite/experimental/micro/debug_log_numbers.h b/tensorflow/lite/experimental/micro/debug_log_numbers.h new file mode 100644 index 0000000000..d889e75173 --- /dev/null +++ b/tensorflow/lite/experimental/micro/debug_log_numbers.h @@ -0,0 +1,28 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_NUMBERS_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_NUMBERS_H_ + +#include + +// Output numbers to the debug logging stream. +extern "C" { +void DebugLogInt32(int32_t i); +void DebugLogUInt32(uint32_t i); +void DebugLogHex(uint32_t i); +void DebugLogFloat(float i); +} + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_NUMBERS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc b/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc new file mode 100644 index 0000000000..3d560510ad --- /dev/null +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc @@ -0,0 +1,43 @@ +# Settings for targets that use the CMSIS library. +ifneq ($(filter CMSIS,$(ALL_TAGS)),) + INCLUDES += \ + -isystem$(MAKEFILE_DIR)/downloads/cmsis/CMSIS/Core/Include/ \ + -isystem$(MAKEFILE_DIR)/downloads/cmsis/CMSIS/DSP/Include/ \ + -I$(MAKEFILE_DIR)/downloads/CMSIS_ext/ + + CMSIS_PREPROCESSOR_SRCS := \ + tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.cc \ + tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.cc \ + + CMSIS_PREPROCESSOR_HDRS := \ + tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.h \ + tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.h \ + third_party/CMSIS_ext/arm_cmplx_mag_squared_q10p6.h + + PREPROCESSOR_TEST_SRCS += $(CMSIS_PREPROCESSOR_SRCS) + PREPROCESSOR_TEST_HDRS += $(CMSIS_PREPROCESSOR_HDRS) + + FEATURE_PROVIDER_TEST_SRCS += $(CMSIS_PREPROCESSOR_SRCS) + FEATURE_PROVIDER_TEST_HDRS += $(CMSIS_PREPROCESSOR_HDRS) + + MICRO_SPEECH_SRCS += $(CMSIS_PREPROCESSOR_SRCS) + MICRO_SPEECH_HDRS += $(CMSIS_PREPROCESSOR_HDRS) + + THIRD_PARTY_CC_SRCS += \ + third_party/CMSIS_ext/arm_cmplx_mag_squared_q10p6.c \ + third_party/cmsis/CMSIS/DSP/Source/BasicMathFunctions/arm_mult_q15.c \ + third_party/cmsis/CMSIS/DSP/Source/TransformFunctions/arm_rfft_init_q15.c \ + third_party/cmsis/CMSIS/DSP/Source/TransformFunctions/arm_rfft_q15.c \ + third_party/cmsis/CMSIS/DSP/Source/TransformFunctions/arm_cfft_q15.c \ + third_party/cmsis/CMSIS/DSP/Source/TransformFunctions/arm_cfft_radix4_q15.c \ + third_party/cmsis/CMSIS/DSP/Source/TransformFunctions/arm_bitreversal2.S \ + third_party/cmsis/CMSIS/DSP/Source/CommonTables/arm_const_structs.c \ + third_party/cmsis/CMSIS/DSP/Source/CommonTables/arm_common_tables.c \ + third_party/cmsis/CMSIS/DSP/Source/StatisticsFunctions/arm_mean_q15.c \ + third_party/cmsis/CMSIS/DSP/Source/StatisticsFunctions/arm_max_q7.c + + THIRD_PARTY_CC_HDRS += \ + third_party/cmsis/CMSIS/DSP/Include/arm_common_tables.h \ + third_party/cmsis/CMSIS/DSP/Include/arm_const_structs.h + +endif diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc b/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc index 6f7563c02f..49aace3d7d 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc @@ -1,140 +1,62 @@ -# Tests loading and running a speech model. MICRO_SPEECH_TEST_SRCS := \ tensorflow/lite/experimental/micro/examples/micro_speech/micro_speech_test.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/tiny_conv_model_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/no_features_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/yes_features_data.cc -MICRO_SPEECH_TEST_SRCS := $(call specialize,$(MICRO_SPEECH_TEST_SRCS)) -ALL_SRCS += $(MICRO_SPEECH_TEST_SRCS) -MICRO_SPEECH_TEST_OBJS := $(addprefix $(OBJDIR), \ -$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(MICRO_SPEECH_TEST_SRCS)))) -MICRO_SPEECH_TEST_BINARY := $(BINDIR)micro_speech_test -ALL_BINARIES += $(MICRO_SPEECH_TEST_BINARY) -$(MICRO_SPEECH_TEST_BINARY): $(MICRO_SPEECH_TEST_OBJS) $(MICROLITE_LIB_PATH) - @mkdir -p $(dir $@) - $(CXX) $(CXXFLAGS) $(INCLUDES) \ - -o $(MICRO_SPEECH_TEST_BINARY) $(MICRO_SPEECH_TEST_OBJS) \ - $(LIBFLAGS) $(MICROLITE_LIB_PATH) $(LDFLAGS) $(MICROLITE_LIBS) -micro_speech_test: $(MICRO_SPEECH_TEST_BINARY) -micro_speech_test_bin: $(MICRO_SPEECH_TEST_BINARY).bin -test_micro_speech: $(MICRO_SPEECH_TEST_BINARY) - $(TEST_SCRIPT) $(MICRO_SPEECH_TEST_BINARY) '~~~ALL TESTS PASSED~~~' - -# Source files that are used by multiple preprocessor tests. -PREPROCESSOR_TEST_SHARED_SRCS := \ + +MICRO_SPEECH_TEST_HDRS := \ +tensorflow/lite/experimental/micro/examples/micro_speech/tiny_conv_model_data.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/no_features_data.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/yes_features_data.h \ + +PREPROCESSOR_TEST_SRCS := \ +tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor_test.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/no_power_spectrum_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/yes_power_spectrum_data.cc -# Test the float reference code for feature generation. -PREPROCESSOR_REFERENCE_TEST_SRCS = \ -$(PREPROCESSOR_TEST_SHARED_SRCS) \ -tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc -PREPROCESSOR_REFERENCE_TEST_SRCS := $(call specialize,$(PREPROCESSOR_REFERENCE_TEST_SRCS)) -ALL_SRCS += $(PREPROCESSOR_REFERENCE_TEST_SRCS) -PREPROCESSOR_REFERENCE_TEST_OBJS := $(addprefix $(OBJDIR), \ -$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(PREPROCESSOR_REFERENCE_TEST_SRCS)))) -PREPROCESSOR_REFERENCE_TEST_BINARY := $(BINDIR)preprocessor_reference_test -ALL_BINARIES += $(PREPROCESSOR_REFERENCE_TEST_BINARY) -$(PREPROCESSOR_REFERENCE_TEST_BINARY): $(PREPROCESSOR_REFERENCE_TEST_OBJS) $(MICROLITE_LIB_PATH) - @mkdir -p $(dir $@) - $(CXX) $(CXXFLAGS) $(INCLUDES) \ - -o $(PREPROCESSOR_REFERENCE_TEST_BINARY) $(PREPROCESSOR_REFERENCE_TEST_OBJS) \ - $(LIBFLAGS) $(MICROLITE_LIB_PATH) $(LDFLAGS) $(MICROLITE_LIBS) -preprocessor_reference_test: $(PREPROCESSOR_REFERENCE_TEST_BINARY) -preprocessor_reference_test_bin: $(PREPROCESSOR_REFERENCE_TEST_BINARY).bin -test_preprocessor_reference: $(PREPROCESSOR_REFERENCE_TEST_BINARY) - $(TEST_SCRIPT) $(PREPROCESSOR_REFERENCE_TEST_BINARY) '~~~ALL TESTS PASSED~~~' - -# Test the fixed point reference code for feature generation. -PREPROCESSOR_FIXED_TEST_SRCS = \ -$(PREPROCESSOR_TEST_SHARED_SRCS) \ -tensorflow/lite/experimental/micro/examples/micro_speech/fixed_point/preprocessor.cc -PREPROCESSOR_FIXED_TEST_SRCS := $(call specialize,$(PREPROCESSOR_FIXED_TEST_SRCS)) -ALL_SRCS += $(PREPROCESSOR_FIXED_TEST_SRCS) -PREPROCESSOR_FIXED_TEST_OBJS := $(addprefix $(OBJDIR), \ -$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(PREPROCESSOR_FIXED_TEST_SRCS)))) -PREPROCESSOR_FIXED_TEST_BINARY := $(BINDIR)preprocessor_fixed_test -ALL_BINARIES += $(PREPROCESSOR_FIXED_TEST_BINARY) -$(PREPROCESSOR_FIXED_TEST_BINARY): $(PREPROCESSOR_FIXED_TEST_OBJS) $(MICROLITE_LIB_PATH) - @mkdir -p $(dir $@) - $(CXX) $(CXXFLAGS) $(INCLUDES) \ - -o $(PREPROCESSOR_FIXED_TEST_BINARY) $(PREPROCESSOR_FIXED_TEST_OBJS) \ - $(LIBFLAGS) $(MICROLITE_LIB_PATH) $(LDFLAGS) $(MICROLITE_LIBS) -preprocessor_fixed_test: $(PREPROCESSOR_FIXED_TEST_BINARY) -preprocessor_fixed_test_bin: $(PREPROCESSOR_FIXED_TEST_BINARY).bin -test_preprocessor_fixed: $(PREPROCESSOR_FIXED_TEST_BINARY) - $(TEST_SCRIPT) $(PREPROCESSOR_FIXED_TEST_BINARY) '~~~ALL TESTS PASSED~~~' +PREPROCESSOR_TEST_HDRS := \ +tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.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/no_power_spectrum_data.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/yes_power_spectrum_data.h -# Tests the audio provider module. AUDIO_PROVIDER_TEST_SRCS := \ tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc -AUDIO_PROVIDER_TEST_SRCS := $(call specialize,$(AUDIO_PROVIDER_TEST_SRCS)) -ALL_SRCS += $(AUDIO_PROVIDER_TEST_SRCS) -AUDIO_PROVIDER_TEST_OBJS := $(addprefix $(OBJDIR), \ -$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(AUDIO_PROVIDER_TEST_SRCS)))) -AUDIO_PROVIDER_TEST_BINARY := $(BINDIR)audio_provider_test -ALL_BINARIES += $(AUDIO_PROVIDER_TEST_BINARY) -$(AUDIO_PROVIDER_TEST_BINARY): $(AUDIO_PROVIDER_TEST_OBJS) $(MICROLITE_LIB_PATH) - @mkdir -p $(dir $@) - $(CXX) $(CXXFLAGS) $(INCLUDES) \ - -o $(AUDIO_PROVIDER_TEST_BINARY) $(AUDIO_PROVIDER_TEST_OBJS) \ - $(LIBFLAGS) $(MICROLITE_LIB_PATH) $(LDFLAGS) $(MICROLITE_LIBS) -audio_provider_test: $(AUDIO_PROVIDER_TEST_BINARY) -audio_provider_test_bin: $(AUDIO_PROVIDER_TEST_BINARY).bin -test_audio_provider: $(AUDIO_PROVIDER_TEST_BINARY) - $(TEST_SCRIPT) $(AUDIO_PROVIDER_TEST_BINARY) '~~~ALL TESTS PASSED~~~' -# Tests the feature provider module. +AUDIO_PROVIDER_TEST_HDRS := \ +tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h \ + FEATURE_PROVIDER_TEST_SRCS := \ tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider_test.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc -FEATURE_PROVIDER_TEST_SRCS := $(call specialize,$(FEATURE_PROVIDER_TEST_SRCS)) -ALL_SRCS += $(FEATURE_PROVIDER_TEST_SRCS) -FEATURE_PROVIDER_TEST_OBJS := $(addprefix $(OBJDIR), \ -$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(FEATURE_PROVIDER_TEST_SRCS)))) -FEATURE_PROVIDER_TEST_BINARY := $(BINDIR)feature_provider_test -ALL_BINARIES += $(FEATURE_PROVIDER_TEST_BINARY) -$(FEATURE_PROVIDER_TEST_BINARY): $(FEATURE_PROVIDER_TEST_OBJS) $(MICROLITE_LIB_PATH) - @mkdir -p $(dir $@) - $(CXX) $(CXXFLAGS) $(INCLUDES) \ - -o $(FEATURE_PROVIDER_TEST_BINARY) $(FEATURE_PROVIDER_TEST_OBJS) \ - $(LIBFLAGS) $(MICROLITE_LIB_PATH) $(LDFLAGS) $(MICROLITE_LIBS) -feature_provider_test: $(FEATURE_PROVIDER_TEST_BINARY) -feature_provider_test_bin: $(FEATURE_PROVIDER_TEST_BINARY).bin -test_feature_provider: $(FEATURE_PROVIDER_TEST_BINARY) - $(TEST_SCRIPT) $(FEATURE_PROVIDER_TEST_BINARY) '~~~ALL TESTS PASSED~~~' -# Tests the feature provider module. +FEATURE_PROVIDER_TEST_HDRS := \ +tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h + RECOGNIZE_COMMANDS_TEST_SRCS := \ tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands_test.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc -RECOGNIZE_COMMANDS_TEST_SRCS := $(call specialize,$(RECOGNIZE_COMMANDS_TEST_SRCS)) -ALL_SRCS += $(RECOGNIZE_COMMANDS_TEST_SRCS) -RECOGNIZE_COMMANDS_TEST_OBJS := $(addprefix $(OBJDIR), \ -$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(RECOGNIZE_COMMANDS_TEST_SRCS)))) -RECOGNIZE_COMMANDS_TEST_BINARY := $(BINDIR)recognize_commands_test -ALL_BINARIES += $(RECOGNIZE_COMMANDS_TEST_BINARY) -$(RECOGNIZE_COMMANDS_TEST_BINARY): $(RECOGNIZE_COMMANDS_TEST_OBJS) $(MICROLITE_LIB_PATH) - @mkdir -p $(dir $@) - $(CXX) $(CXXFLAGS) $(INCLUDES) \ - -o $(RECOGNIZE_COMMANDS_TEST_BINARY) $(RECOGNIZE_COMMANDS_TEST_OBJS) \ - $(LIBFLAGS) $(MICROLITE_LIB_PATH) $(LDFLAGS) $(MICROLITE_LIBS) -recognize_commands_test: $(RECOGNIZE_COMMANDS_TEST_BINARY) -recognize_commands_test_bin: $(RECOGNIZE_COMMANDS_TEST_BINARY).bin -test_recognize_commands: $(RECOGNIZE_COMMANDS_TEST_BINARY) - $(TEST_SCRIPT) $(RECOGNIZE_COMMANDS_TEST_BINARY) '~~~ALL TESTS PASSED~~~' -# Builds a standalone speech command recognizer binary. +RECOGNIZE_COMMANDS_TEST_HDRS := \ +tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h + MICRO_SPEECH_SRCS := \ tensorflow/lite/experimental/micro/examples/micro_speech/main.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.cc \ @@ -145,19 +67,40 @@ tensorflow/lite/experimental/micro/examples/micro_speech/no_features_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/yes_features_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/tiny_conv_model_data.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc -MICRO_SPEECH_SRCS := $(call specialize,$(MICRO_SPEECH_SRCS)) -ALL_SRCS += $(MICRO_SPEECH_SRCS) -MICRO_SPEECH_OBJS := $(addprefix $(OBJDIR), \ -$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(MICRO_SPEECH_SRCS)))) -MICRO_SPEECH_BINARY := $(BINDIR)micro_speech -ALL_BINARIES += $(MICRO_SPEECH_BINARY) -$(MICRO_SPEECH_BINARY): $(MICRO_SPEECH_OBJS) $(MICROLITE_LIB_PATH) - @mkdir -p $(dir $@) - $(CXX) $(CXXFLAGS) $(INCLUDES) \ - -o $(MICRO_SPEECH_BINARY) $(MICRO_SPEECH_OBJS) \ - $(LIBFLAGS) $(MICROLITE_LIB_PATH) $(LDFLAGS) $(MICROLITE_LIBS) -micro_speech: $(MICRO_SPEECH_BINARY) -micro_speech_bin: $(MICRO_SPEECH_BINARY).bin + +MICRO_SPEECH_HDRS := \ +tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/no_features_data.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/yes_features_data.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/tiny_conv_model_data.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h # Find any platform-specific rules for this example. include $(wildcard tensorflow/lite/experimental/micro/examples/micro_speech/*/Makefile.inc) + +# Tests loading and running a speech model. +$(eval $(call microlite_test,micro_speech_test,\ +$(MICRO_SPEECH_TEST_SRCS),$(MICRO_SPEECH_TEST_HDRS))) + +# Test the code for feature generation. +$(eval $(call microlite_test,preprocessor_test,\ +$(PREPROCESSOR_TEST_SRCS), $(PREPROCESSOR_TEST_HDRS))) + +# Tests the audio provider module. +$(eval $(call microlite_test,audio_provider_test,\ +$(AUDIO_PROVIDER_TEST_SRCS),$(AUDIO_PROVIDER_TEST_HDRS))) + +# Tests the feature provider module. +$(eval $(call microlite_test,feature_provider_test,\ +$(FEATURE_PROVIDER_TEST_SRCS),$(FEATURE_PROVIDER_TEST_HDRS))) + +# Tests the feature provider module. +$(eval $(call microlite_test,recognize_commands_test,\ +$(RECOGNIZE_COMMANDS_TEST_SRCS),$(RECOGNIZE_COMMANDS_TEST_HDRS))) + +# Builds a standalone speech command recognizer binary. +$(eval $(call microlite_test,micro_speech,\ +$(MICRO_SPEECH_SRCS),$(MICRO_SPEECH_HDRS))) diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/Makefile.inc b/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/Makefile.inc new file mode 100644 index 0000000000..5585ed7269 --- /dev/null +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/Makefile.inc @@ -0,0 +1,7 @@ +# Settings for the Discovery STM32F746NG board. +ifneq ($(filter disco_f746ng,$(ALL_TAGS)),) + MBED_PROJECT_FILES += \ + AUDIO_DISCO_F746NG.lib \ + BSP_DISCO_F746NG.lib \ + SDRAM_DISCO_F746NG.lib +endif diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/audio_provider.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/audio_provider.cc new file mode 100644 index 0000000000..06647d0c53 --- /dev/null +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/audio_provider.cc @@ -0,0 +1,182 @@ +/* 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/experimental/micro/examples/micro_speech/audio_provider.h" + +#include "tensorflow/lite/experimental/micro/examples/micro_speech/model_settings.h" + +#include "AUDIO_DISCO_F746NG.h" +#include "SDRAM_DISCO_F746NG.h" +#include "mbed.h" // NOLINT + +namespace { + +bool g_is_audio_initialized = false; +constexpr int kAudioCaptureBufferSize = kAudioSampleFrequency * 0.5; +int16_t g_audio_capture_buffer[kAudioCaptureBufferSize]; +int16_t g_audio_output_buffer[kMaxAudioSampleSize]; +int32_t g_latest_audio_timestamp = 0; + +// For a full example of how to access audio on the STM32F746NG board, see +// https://os.mbed.com/teams/ST/code/DISCO-F746NG_AUDIO_demo/ +AUDIO_DISCO_F746NG g_audio_device; +SDRAM_DISCO_F746NG g_sdram_device; + +typedef enum { + BUFFER_OFFSET_NONE = 0, + BUFFER_OFFSET_HALF = 1, + BUFFER_OFFSET_FULL = 2, +} BUFFER_StateTypeDef; + +#define AUDIO_BLOCK_SIZE ((uint32_t)2048) +#define AUDIO_BUFFER_IN SDRAM_DEVICE_ADDR /* In SDRAM */ +#define AUDIO_BUFFER_OUT \ + (SDRAM_DEVICE_ADDR + (AUDIO_BLOCK_SIZE * 2)) /* In SDRAM */ +__IO uint32_t g_audio_rec_buffer_state = BUFFER_OFFSET_NONE; + +uint8_t SetSysClock_PLL_HSE_200MHz() { + RCC_ClkInitTypeDef RCC_ClkInitStruct; + RCC_OscInitTypeDef RCC_OscInitStruct; + + // Enable power clock + __PWR_CLK_ENABLE(); + + // Enable HSE oscillator and activate PLL with HSE as source + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; + RCC_OscInitStruct.HSEState = RCC_HSE_ON; /* External xtal on OSC_IN/OSC_OUT */ + + // Warning: this configuration is for a 25 MHz xtal clock only + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; + RCC_OscInitStruct.PLL.PLLM = 25; // VCO input clock = 1 MHz (25 MHz / 25) + RCC_OscInitStruct.PLL.PLLN = 400; // VCO output clock = 400 MHz (1 MHz * 400) + RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // PLLCLK = 200 MHz (400 MHz / 2) + RCC_OscInitStruct.PLL.PLLQ = 8; // USB clock = 50 MHz (400 MHz / 8) + + if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { + return 0; // FAIL + } + + // Activate the OverDrive to reach the 216 MHz Frequency + if (HAL_PWREx_EnableOverDrive() != HAL_OK) { + return 0; // FAIL + } + + // Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 + // clocks dividers + RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | + RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 200 MHz + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // 200 MHz + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; // 50 MHz + RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // 100 MHz + + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7) != HAL_OK) { + return 0; // FAIL + } + HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_4); + return 1; // OK +} + +TfLiteStatus InitAudioRecording(tflite::ErrorReporter* error_reporter) { + SetSysClock_PLL_HSE_200MHz(); + + // Initialize SDRAM buffers. + memset((uint16_t*)AUDIO_BUFFER_IN, 0, AUDIO_BLOCK_SIZE * 2); + memset((uint16_t*)AUDIO_BUFFER_OUT, 0, AUDIO_BLOCK_SIZE * 2); + g_audio_rec_buffer_state = BUFFER_OFFSET_NONE; + + // Start Recording. + g_audio_device.IN_Record((uint16_t*)AUDIO_BUFFER_IN, AUDIO_BLOCK_SIZE); + + // Also play results out to headphone jack. + g_audio_device.OUT_SetAudioFrameSlot(CODEC_AUDIOFRAME_SLOT_02); + g_audio_device.OUT_Play((uint16_t*)AUDIO_BUFFER_OUT, AUDIO_BLOCK_SIZE * 2); + + return kTfLiteOk; +} + +void CaptureSamples(const int16_t* sample_data) { + const int sample_size = AUDIO_BLOCK_SIZE / (sizeof(int16_t) * 2); + const int32_t time_in_ms = + g_latest_audio_timestamp + (sample_size / (kAudioSampleFrequency / 1000)); + + const int32_t start_sample_offset = + g_latest_audio_timestamp * (kAudioSampleFrequency / 1000); + for (int i = 0; i < sample_size; ++i) { + const int capture_index = + (start_sample_offset + i) % kAudioCaptureBufferSize; + g_audio_capture_buffer[capture_index] = + (sample_data[(i * 2) + 0] / 2) + (sample_data[(i * 2) + 1] / 2); + } + // This is how we let the outside world know that new audio data has arrived. + g_latest_audio_timestamp = time_in_ms; +} + +} // namespace + +// These callbacks need to be linkable symbols, because they override weak +// default versions. +void BSP_AUDIO_IN_TransferComplete_CallBack(void) { + g_audio_rec_buffer_state = BUFFER_OFFSET_FULL; + /* Copy recorded 1st half block */ + memcpy((uint16_t*)(AUDIO_BUFFER_OUT), (uint16_t*)(AUDIO_BUFFER_IN), + AUDIO_BLOCK_SIZE); + CaptureSamples(reinterpret_cast(AUDIO_BUFFER_IN)); + return; +} + +// Another weak symbol override. +void BSP_AUDIO_IN_HalfTransfer_CallBack(void) { + g_audio_rec_buffer_state = BUFFER_OFFSET_HALF; + /* Copy recorded 2nd half block */ + memcpy((uint16_t*)(AUDIO_BUFFER_OUT + (AUDIO_BLOCK_SIZE)), + (uint16_t*)(AUDIO_BUFFER_IN + (AUDIO_BLOCK_SIZE)), AUDIO_BLOCK_SIZE); + CaptureSamples( + reinterpret_cast(AUDIO_BUFFER_IN + AUDIO_BLOCK_SIZE)); + return; +} + +// Main entry point for getting audio data. +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 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; + g_audio_output_buffer[i] = g_audio_capture_buffer[capture_index]; + } + + *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/examples/micro_speech/disco_f746ng/timer.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/timer.cc new file mode 100644 index 0000000000..a8f0fe4bd5 --- /dev/null +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/timer.cc @@ -0,0 +1,24 @@ +/* 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/experimental/micro/examples/micro_speech/timer.h" + +namespace { +int32_t g_current_time = 0; +} + +void SetTimeInMilliseconds(int32_t time) { g_current_time = time; } + +int32_t TimeInMilliseconds() { return g_current_time; } diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc b/tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc index 89d107cfe0..8f8b33a9fa 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc @@ -1,6 +1,8 @@ -#Settings for Mac OS platforms. +# Settings for Mac OS platforms. ifeq ($(TARGET), osx) - MICROLITE_LIBS += \ + LINKER_FLAGS := \ -framework Foundation \ -framework AudioToolbox + + MICROLITE_LIBS += $(LINKER_FLAGS) endif diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc index 7b7db173ab..f8858aad72 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/preprocessor.cc @@ -32,6 +32,9 @@ limitations under the License. namespace { +// Needed because some platforms don't have M_PI defined. +constexpr float kPi = 3.14159265358979323846f; + // Performs a discrete Fourier transform on the real inputs. This corresponds to // rdft() in the FFT package at http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html, // and to kiss_fftr() in KISSFFT at https://github.com/mborgerding/kissfft. @@ -48,11 +51,11 @@ void CalculateDiscreteFourierTransform(float* time_series, int time_series_size, for (int i = 0; i < time_series_size / 2; ++i) { float real = 0; for (int j = 0; j < time_series_size; ++j) { - real += time_series[j] * cos(j * i * M_PI * 2 / time_series_size); + real += time_series[j] * cos(j * i * kPi * 2 / time_series_size); } float imaginary = 0; for (int j = 0; j < time_series_size; ++j) { - imaginary -= time_series[j] * sin(j * i * M_PI * 2 / time_series_size); + imaginary -= time_series[j] * sin(j * i * kPi * 2 / time_series_size); } fourier_output[(i * 2) + 0] = real; fourier_output[(i * 2) + 1] = imaginary; @@ -63,7 +66,7 @@ void CalculateDiscreteFourierTransform(float* time_series, int time_series_size, // of the current sample window are weighted more heavily than those at the end. void CalculatePeriodicHann(int window_length, float* window_function) { for (int i = 0; i < window_length; ++i) { - window_function[i] = 0.5 - 0.5 * cos((2 * M_PI * i) / window_length); + window_function[i] = 0.5 - 0.5 * cos((2 * kPi * i) / window_length); } } diff --git a/tensorflow/lite/experimental/micro/mbed/debug_log.cc b/tensorflow/lite/experimental/micro/mbed/debug_log.cc new file mode 100644 index 0000000000..d4a4a5a842 --- /dev/null +++ b/tensorflow/lite/experimental/micro/mbed/debug_log.cc @@ -0,0 +1,24 @@ +/* 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/experimental/micro/debug_log.h" + +#include + +// On mbed platforms, we set up a serial port and write to it for debug logging. +extern "C" void DebugLog(const char* s) { + static Serial pc(USBTX, USBRX); + pc.printf("%s", s); +} diff --git a/tensorflow/lite/experimental/micro/micro_error_reporter.h b/tensorflow/lite/experimental/micro/micro_error_reporter.h index 0ab853ec2a..6c18367c95 100644 --- a/tensorflow/lite/experimental/micro/micro_error_reporter.h +++ b/tensorflow/lite/experimental/micro/micro_error_reporter.h @@ -17,26 +17,8 @@ limitations under the License. #include "tensorflow/lite/core/api/error_reporter.h" #include "tensorflow/lite/experimental/micro/compatibility.h" - -#ifdef TF_LITE_MCU_DEBUG_LOG -// These functions should be supplied by the micro target library -extern "C" { -#include -void DebugLog(const char* s); -void DebugLogInt32(int32_t i); -void DebugLogUInt32(uint32_t i); -void DebugLogHex(uint32_t i); -void DebugLogFloat(float i); -} -#else // TF_LITE_MCU_DEBUG_LOG -#include -#include -static void inline DebugLog(const char* s) { fprintf(stderr, "%s", s); } -static void inline DebugLogInt32(int32_t i) { fprintf(stderr, "%d", i); } -static void inline DebugLogUInt32(uint32_t i) { fprintf(stderr, "%d", i); } -static void inline DebugLogHex(uint32_t i) { fprintf(stderr, "0x%8x", i); } -static void inline DebugLogFloat(float i) { fprintf(stderr, "%f", i); } -#endif // TF_LITE_MCU_DEBUG_LOG +#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/experimental/micro/debug_log_numbers.h" namespace tflite { diff --git a/tensorflow/lite/experimental/micro/riscv32_mcu/debug_log.cc b/tensorflow/lite/experimental/micro/riscv32_mcu/debug_log.cc index f0065267bf..d1c2df866e 100644 --- a/tensorflow/lite/experimental/micro/riscv32_mcu/debug_log.cc +++ b/tensorflow/lite/experimental/micro/riscv32_mcu/debug_log.cc @@ -15,176 +15,4 @@ limitations under the License. #include -namespace { - -// All input buffers to the number conversion functions must be this long. -static const int kFastToBufferSize = 48; - -// Reverses a zero-terminated string in-place. -char* ReverseStringInPlace(char* start, char* end) { - char* p1 = start; - char* p2 = end - 1; - while (p1 < p2) { - char tmp = *p1; - *p1++ = *p2; - *p2-- = tmp; - } - return start; -} - -// Appends a string to a string, in-place. You need to pass in the maximum -// string length as the second argument. -char* StrCatStr(char* main, int main_max_length, char* to_append) { - char* current = main; - while (*current != 0) { - ++current; - } - char* current_end = main + (main_max_length - 1); - while ((*to_append != 0) && (current < current_end)) { - *current = *to_append; - ++current; - ++to_append; - } - *current = 0; - return current; -} - -char* StrCpy(char* main, int main_max_length, const char* source) { - char* current = main; - char* current_end = main + (main_max_length - 1); - while ((*source != 0) && (current < current_end)) { - *current = *source; - ++current; - ++source; - } - *current = 0; - return current; -} - -// Populates the provided buffer with an ASCII representation of the number. -char* FastUInt32ToBufferLeft(uint32_t i, char* buffer, int base) { - char* start = buffer; - do { - int32_t digit = i % base; - char character; - if (digit < 10) { - character = '0' + digit; - } else { - character = 'a' + (digit - 10); - } - *buffer++ = character; - i /= base; - } while (i > 0); - *buffer = 0; - ReverseStringInPlace(start, buffer); - return buffer; -} - -// Populates the provided buffer with an ASCII representation of the number. -char* FastInt32ToBufferLeft(int32_t i, char* buffer) { - uint32_t u = i; - if (i < 0) { - *buffer++ = '-'; - u = -u; - } - return FastUInt32ToBufferLeft(u, buffer, 10); -} - -// Converts a number to a string and appends it to another. -char* StrCatInt32(char* main, int main_max_length, int32_t number) { - char number_string[kFastToBufferSize]; - FastInt32ToBufferLeft(number, number_string); - return StrCatStr(main, main_max_length, number_string); -} - -// Converts a number to a string and appends it to another. -char* StrCatUInt32(char* main, int main_max_length, uint32_t number, int base) { - char number_string[kFastToBufferSize]; - FastUInt32ToBufferLeft(number, number_string, base); - return StrCatStr(main, main_max_length, number_string); -} - -// Populates the provided buffer with ASCII representation of the float number. -// Avoids the use of any floating point instructions (since these aren't -// supported on many microcontrollers) and as a consequence prints values with -// power-of-two exponents. -char* FastFloatToBufferLeft(float i, char* buffer) { - char* current = buffer; - char* current_end = buffer + (kFastToBufferSize - 1); - // Access the bit fields of the floating point value to avoid requiring any - // float instructions. These constants are derived from IEEE 754. - const uint32_t sign_mask = 0x80000000; - const uint32_t exponent_mask = 0x7f800000; - const int32_t exponent_shift = 23; - const int32_t exponent_bias = 127; - const uint32_t fraction_mask = 0x007fffff; - const uint32_t u = *(uint32_t*)(&i); - const int32_t exponent = - ((u & exponent_mask) >> exponent_shift) - exponent_bias; - const uint32_t fraction = (u & fraction_mask); - // Expect ~0x2B1B9D3 for fraction. - if (u & sign_mask) { - *current = '-'; - current += 1; - } - *current = 0; - // These are special cases for infinities and not-a-numbers. - if (exponent == 128) { - if (fraction == 0) { - current = StrCatStr(current, (current_end - current), "Inf"); - return current; - } else { - current = StrCatStr(current, (current_end - current), "NaN"); - return current; - } - } - // 0x007fffff represents 0.99... for the fraction, so to print the correct - // decimal digits we need to scale our value before passing it to the - // conversion function. This scale should be 10000000/8388608 = 1.1920928955. - // We can approximate this using multipy-adds and right-shifts using the - // values in this array. - const int32_t scale_shifts_size = 13; - const int8_t scale_shifts[13] = {3, 4, 8, 11, 13, 14, 17, - 18, 19, 20, 21, 22, 23}; - uint32_t scaled_fraction = fraction; - for (int i = 0; i < scale_shifts_size; ++i) { - scaled_fraction += (fraction >> scale_shifts[i]); - } - *current = '1'; - current += 1; - *current = '.'; - current += 1; - *current = 0; - current = StrCatUInt32(current, (current_end - current), scaled_fraction, 10); - current = StrCatStr(current, (current_end - current), "*2^"); - current = StrCatInt32(current, (current_end - current), exponent); - return current; -} - -} // namespace - extern "C" void DebugLog(const char* s) { puts(s); } - -extern "C" void DebugLogInt32(int32_t i) { - char number_string[kFastToBufferSize]; - FastInt32ToBufferLeft(i, number_string); - DebugLog(number_string); -} - -extern "C" void DebugLogUInt32(uint32_t i) { - char number_string[kFastToBufferSize]; - FastUInt32ToBufferLeft(i, number_string, 10); - DebugLog(number_string); -} - -extern "C" void DebugLogHex(uint32_t i) { - char number_string[kFastToBufferSize]; - FastUInt32ToBufferLeft(i, number_string, 16); - DebugLog(number_string); -} - -extern "C" void DebugLogFloat(float i) { - char number_string[kFastToBufferSize]; - FastFloatToBufferLeft(i, number_string); - DebugLog(number_string); -} diff --git a/tensorflow/lite/experimental/micro/tools/make/Makefile b/tensorflow/lite/experimental/micro/tools/make/Makefile index f9a2dffae2..fde195118b 100644 --- a/tensorflow/lite/experimental/micro/tools/make/Makefile +++ b/tensorflow/lite/experimental/micro/tools/make/Makefile @@ -1,5 +1,9 @@ + MAKEFILE_DIR := tensorflow/lite/experimental/micro/tools/make +# Pull in some convenience functions. +include $(MAKEFILE_DIR)/helper_functions.inc + # Try to figure out the host system HOST_OS := ifeq ($(OS),Windows_NT) @@ -21,37 +25,10 @@ HOST_ARCH := $(shell if [[ $(shell uname -m) =~ i[345678]86 ]]; then echo x86_32 TARGET := $(HOST_OS) TARGET_ARCH := $(HOST_ARCH) -# Look for platform or target-specific implementation files to replace reference -# implementations with, given a tag. These are expected to occur in subfolders -# of a directory where a reference implementation exists, and have the same -# interface and header file. For example, -# tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc -# defines a module for supplying audio data, but since no platform or OS can be -# presumed, it just always returns zeroes for its samples. The MacOS-specific -# tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc -# has an implementation that relies on CoreAudio, and there are equivalent -# versions for other operating systems. -# All lists of source files are put through this substitution process with the -# tags of their target OS and architecture, so that implementations can be added -# by simply placing them in the file tree, with no changes to the build files -# needed. -# One confusing thing about this implementation is that we're using wildcard to -# act as a 'does file exist?' function, rather than expanding an expression. -# Wildcard will return an empty string if given a plain file path with no actual -# wildcards, if the file doesn't exist, so taking the first word of the list -# between that and the reference path will pick the specialized one if it's -# available. -substitute_specialized_implementation = \ - $(firstword $(wildcard $(dir $(1))$(2)/$(notdir $(1))) $(wildcard $(1))) -substitute_specialized_implementations = \ - $(foreach source,$(1),$(call substitute_specialized_implementation,$(source),$(2))) -# Here we're first looking for specialized implementations in ref_dir/$(TARGET) -# and then ref_dir/$(TARGET_ARCH), before falling back to ref_dir's -# implementation. -# The argument to this function should be a list of space-separated file paths, -# with any wildcards already expanded. -specialize = \ - $(call substitute_specialized_implementations,$(call substitute_specialized_implementations,$(1),$(TARGET)),$(TARGET_ARCH)) +# Specify TAGS on the command line to add a particular set of specialized +# implementations, for example TAGS="CMSIS disco_f746ng" to target a Discovery +# STM32F746NG board, using the CMSIS library's implementations where possible. +ALL_TAGS := $(TAGS) $(TARGET) INCLUDES := \ -I. \ @@ -89,6 +66,9 @@ MICROLITE_TEST_SRCS := \ $(wildcard tensorflow/lite/experimental/micro/*test.cc) \ $(wildcard tensorflow/lite/experimental/micro/kernels/*test.cc) +MICROLITE_TEST_HDRS := \ +$(wildcard tensorflow/lite/experimental/micro/testing/*.h) + MICROLITE_CC_BASE_SRCS := \ $(wildcard tensorflow/lite/experimental/micro/*.cc) \ $(wildcard tensorflow/lite/experimental/micro/kernels/*.cc) \ @@ -101,12 +81,58 @@ tensorflow/lite/kernels/internal/quantization_util.cc MICROLITE_CC_SRCS := $(filter-out $(MICROLITE_TEST_SRCS), $(MICROLITE_CC_BASE_SRCS)) MICROLITE_CC_SRCS := $(call specialize,$(MICROLITE_CC_SRCS)) +MICROLITE_CC_HDRS := \ +$(wildcard tensorflow/lite/experimental/micro/*.h) \ +$(wildcard tensorflow/lite/experimental/micro/kernels/*.h) \ +LICENSE \ +tensorflow/lite/c/c_api_internal.h \ +tensorflow/lite/c/builtin_op_data.h \ +tensorflow/lite/core/api/error_reporter.h \ +tensorflow/lite/core/api/flatbuffer_conversions.h \ +tensorflow/lite/core/api/op_resolver.h \ +tensorflow/lite/kernels/kernel_util.h \ +tensorflow/lite/kernels/op_macros.h \ +tensorflow/lite/kernels/padding.h \ +tensorflow/lite/kernels/internal/common.h \ +tensorflow/lite/kernels/internal/compatibility.h \ +tensorflow/lite/kernels/internal/reference/depthwiseconv_float.h \ +tensorflow/lite/kernels/internal/reference/depthwiseconv_uint8.h \ +tensorflow/lite/kernels/internal/reference/fully_connected.h \ +tensorflow/lite/kernels/internal/reference/softmax.h \ +tensorflow/lite/kernels/internal/round.h \ +tensorflow/lite/kernels/internal/tensor_ctypes.h \ +tensorflow/lite/kernels/internal/types.h \ +tensorflow/lite/kernels/internal/quantization_util.h \ +tensorflow/lite/schema/schema_generated.h \ +tensorflow/lite/version.h + +THIRD_PARTY_CC_HDRS := \ +third_party/gemmlowp/fixedpoint/fixedpoint.h \ +third_party/gemmlowp/fixedpoint/fixedpoint_sse.h \ +third_party/gemmlowp/internal/detect_platform.h \ +third_party/gemmlowp/LICENSE \ +third_party/flatbuffers/include/flatbuffers/base.h \ +third_party/flatbuffers/include/flatbuffers/stl_emulation.h \ +third_party/flatbuffers/include/flatbuffers/flatbuffers.h \ +third_party/flatbuffers/LICENSE.txt + +MAKE_PROJECT_FILES := \ + README_MAKE.md \ + Makefile + +MBED_PROJECT_FILES := \ + README_MBED.md \ + mbed-os.lib \ + mbed_app.json + # These target-specific makefiles should modify or replace options like # CXXFLAGS or LIBS to work for a specific targetted architecture. All logic # based on platforms or architectures should happen within these files, to # keep this main makefile focused on the sources and dependencies. include $(wildcard $(MAKEFILE_DIR)/targets/*_makefile.inc) +ALL_TAGS += $(TARGET_ARCH) + ALL_SRCS := \ $(MICROLITE_CC_SRCS) \ $(MICROLITE_TEST_SRCS) @@ -116,6 +142,7 @@ GENDIR := $(MAKEFILE_DIR)/gen/$(TARGET)_$(TARGET_ARCH)/ OBJDIR := $(GENDIR)obj/ BINDIR := $(GENDIR)bin/ LIBDIR := $(GENDIR)lib/ +PRJDIR := $(GENDIR)prj/ MICROLITE_LIB_PATH := $(LIBDIR)$(MICROLITE_LIB_NAME) @@ -142,7 +169,7 @@ $(OBJDIR)%.o: %.c @mkdir -p $(dir $@) $(CC) $(CCFLAGS) $(INCLUDES) -c $< -o $@ - # For normal manually-created TensorFlow ASM source files. +# For normal manually-created TensorFlow ASM source files. $(OBJDIR)%.o: %.S @mkdir -p $(dir $@) $(CC) $(CCFLAGS) $(INCLUDES) -c $< -o $@ @@ -170,13 +197,9 @@ $(BINDIR)%_test : $(OBJDIR)%_test.o $(MICROLITE_LIB_PATH) $(BINDIR)%.test_target: $(BINDIR)%_test $(TEST_SCRIPT) $< '~~~ALL TESTS PASSED~~~' -# snease: Add %.bin rule here since BINDIR is now defined -# These are microcontroller-specific rules for converting the ELF output -# of the linker into a binary image that can be loaded directly. -OBJCOPY := $(TARGET_TOOLCHAIN_PREFIX)objcopy -$(BINDIR)%.bin: $(BINDIR)% - @mkdir -p $(dir $@) - $(OBJCOPY) $< $@ -O binary +# Generate standalone makefile projects for all of the test targets. +$(foreach TEST_TARGET,$(MICROLITE_TEST_SRCS),\ +$(eval $(call microlite_test,$(notdir $(basename $(TEST_TARGET))),$(TEST_TARGET)))) test: test_micro_speech $(MICROLITE_TEST_TARGETS) diff --git a/tensorflow/lite/experimental/micro/tools/make/helper_functions.inc b/tensorflow/lite/experimental/micro/tools/make/helper_functions.inc new file mode 100644 index 0000000000..87c002635f --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/helper_functions.inc @@ -0,0 +1,117 @@ + +# Reverses a space-separated list of words. +reverse = $(if $(1),$(call reverse,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1)) + +# Look for platform or target-specific implementation files to replace reference +# implementations with, given a tag. These are expected to occur in subfolders +# of a directory where a reference implementation exists, and have the same +# interface and header file. For example, +# tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc +# defines a module for supplying audio data, but since no platform or OS can be +# presumed, it just always returns zeroes for its samples. The MacOS-specific +# tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc +# has an implementation that relies on CoreAudio, and there are equivalent +# versions for other operating systems. +# The specific implementation yielded by the first tag in the list that produces +# a match is returned, else the reference version if none of the tags produce a +# match. +# All lists of source files are put through this substitution process with the +# tags of their target OS and architecture, so that implementations can be added +# by simply placing them in the file tree, with no changes to the build files +# needed. +# One confusing thing about this implementation is that we're using wildcard to +# act as a 'does file exist?' function, rather than expanding an expression. +# Wildcard will return an empty string if given a plain file path with no actual +# wildcards, if the file doesn't exist, so taking the first word of the list +# between that and the reference path will pick the specialized one if it's +# available. +substitute_specialized_implementation = \ + $(firstword $(wildcard $(dir $(1))$(2)/$(notdir $(1))) $(wildcard $(1))) +substitute_specialized_implementations = \ + $(foreach source,$(1),$(call substitute_specialized_implementation,$(source),$(2))) +# Here we're first looking for specialized implementations in ref_dir/$(TAG1) +# and then ref_dir/$(TAG2), etc, before falling back to ref_dir's +# implementation. +# The argument to this function should be a list of space-separated file paths, +# with any wildcards already expanded. +define specialize_on_tags +$(if $(2),$(call substitute_specialized_implementations,$(call specialize_on_tags,$(1),$(wordlist 2,$(words $(2)),$(2))),$(firstword $(2))),$(1)) +endef +# The entry point that most targets should use to find implementation-specific +# versions of their source files. The only argument is a list of file paths. +specialize = $(call specialize_on_tags,$(1),$(strip $(call reverse,$(ALL_TAGS)))) + +# Creates a set of rules to build a standalone makefile project for an +# executable, including all of the source and header files required in a +# separate folder and a simple makefile. +# Arguments are: +# 1 - Project type (make, mbed, etc). +# 2 - Project file template name. +# 3 - Name of executable. +# 4 - List of C/C++ source files needed to build the target. +# 5 - List of C/C++ header files needed to build the target. +# 6 - Linker flags required. +# 7 - C++ compilation flags needed. +# Calling eval on the output will create a _makefile target that you +# can invoke to create the standalone project. +define generate_project +$(PRJDIR)$(3)/$(1)/%: % + @mkdir -p $$(dir $$@) + cp $$< $$@ + +$(PRJDIR)$(3)/$(1)/third_party/%: tensorflow/lite/experimental/micro/tools/make/downloads/% + @mkdir -p $$(dir $$@) + cp $$< $$@ + +$(PRJDIR)$(3)/$(1)/%: tensorflow/lite/experimental/micro/tools/make/templates/%.tpl + @mkdir -p $$(dir $$@) + sed -E 's#\%\{SRCS\}\%#$(4)#g' $$< | \ + sed -E 's#\%\{EXECUTABLE\}\%#$(3)#g' | \ + sed -E 's#\%\{LINKER_FLAGS\}\%#$(6)#g' | \ + sed -E 's#\%\{CXX_FLAGS\}\%#$(7)#g' > $$@ + +generate_$(3)_$(1)_project: $(addprefix $(PRJDIR)$(3)/$(1)/, $(4) $(5) $(2)) +endef + +# Specialized version of generate_project for TF Lite Micro test targets that +# automatically includes standard library files, so you just need to pass the +# test name and any extra source files required. +# Arguments are: +# 1 - Name of test. +# 2 - C/C++ source files implementing the test. +# 3 - C/C++ header files needed for the test. +# Calling eval on the output will create targets that you can invoke to +# generate the standalone project. +define generate_microlite_projects +$(call generate_project,make,$(MAKE_PROJECT_FILES),$(1),$(MICROLITE_CC_SRCS) $(THIRD_PARTY_CC_SRCS) $(2),$(MICROLITE_CC_HDRS) $(THIRD_PARTY_CC_HDRS) $(MICROLITE_TEST_HDRS) $(3),$(MICROLITE_LIBS),$(CXXFLAGS)) +$(call generate_project,mbed,$(MBED_PROJECT_FILES),$(1),$(MICROLITE_CC_SRCS) $(THIRD_PARTY_CC_SRCS) $(2),$(MICROLITE_CC_HDRS) $(THIRD_PARTY_CC_HDRS) $(MICROLITE_TEST_HDRS) $(3),$(MICROLITE_LIBS),$(CXXFLAGS)) +endef + + +# Handles the details of generating a binary target, including specializing +# for the current platform, and generating project file targets. +# Arguments are: +# 1 - Name of test. +# 2 - C/C++ source files implementing the test. +# 3 - C/C++ header files needed for the test. +# Calling eval on the output will create the targets that you need. +define microlite_test +$(1)_LOCAL_SRCS := $(2) +$(1)_LOCAL_SRCS := $$(call specialize,$$($(1)_LOCAL_SRCS)) +ALL_SRCS += $$($(1)_LOCAL_SRCS) +$(1)_LOCAL_HDRS := $(3) +$(1)_LOCAL_OBJS := $$(addprefix $$(OBJDIR), \ +$$(patsubst %.cc,%.o,$$(patsubst %.c,%.o,$$($(1)_LOCAL_SRCS)))) +$(1)_BINARY := $$(BINDIR)$(1) +ALL_BINARIES += $$($(1)_BINARY) +$$($(1)_BINARY): $$($(1)_LOCAL_OBJS) $$(MICROLITE_LIB_PATH) + @mkdir -p $$(dir $$@) + $$(CXX) $$(CXXFLAGS) $$(INCLUDES) \ + -o $$($(1)_BINARY) $$($(1)_LOCAL_OBJS) \ + $$(LIBFLAGS) $$(MICROLITE_LIB_PATH) $$(LDFLAGS) $$(MICROLITE_LIBS) +$(1): $$($(1)_BINARY) +$(1)_bin: $$($(1)_BINARY).bin +test_$(1): $$($(1)_BINARY) + $$(TEST_SCRIPT) $$($(1)_BINARY) '~~~ALL TESTS PASSED~~~' +$(eval $(call generate_microlite_projects,$(1),$(call specialize,$(2)),$(3))) +endef diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/bluepill_makefile.inc b/tensorflow/lite/experimental/micro/tools/make/targets/bluepill_makefile.inc index 5e3105a109..b344f844bc 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/bluepill_makefile.inc +++ b/tensorflow/lite/experimental/micro/tools/make/targets/bluepill_makefile.inc @@ -47,7 +47,10 @@ ifeq ($(TARGET), bluepill) MICROLITE_CC_SRCS += \ $(wildcard $(MAKEFILE_DIR)/downloads/stm32_bare_lib/source/*.c) \ $(wildcard $(MAKEFILE_DIR)/downloads/stm32_bare_lib/source/*.cc) - TEST_SCRIPT := tensorflow/lite/experimental/micro/testing/test_bluepill_binary.sh + EXCLUDED_SRCS := \ + $(MAKEFILE_DIR)/downloads/stm32_bare_lib/source/debug_log.c + MICROLITE_CC_SRCS := $(filter-out $(EXCLUDED_SRCS), $(MICROLITE_CC_SRCS)) + TEST_SCRIPT := tensorflow/lite/experimental/micro/testing/test_bluepill_binary.sh # These are tests that don't currently work on the blue pill. EXCLUDED_TESTS := \ tensorflow/lite/experimental/micro/micro_interpreter_test.cc \ diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/mbed_makefile.inc b/tensorflow/lite/experimental/micro/tools/make/targets/mbed_makefile.inc new file mode 100644 index 0000000000..161ff34cdb --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/targets/mbed_makefile.inc @@ -0,0 +1,4 @@ +# Settings for mbed platforms. +ifeq ($(TARGET), mbed) + TARGET_ARCH := cortex-m4 +endif \ No newline at end of file diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc b/tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc index 0e8aad1993..3b91eeff9f 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc +++ b/tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc @@ -1,4 +1,4 @@ -#Settings for Mac OS platforms. +# Settings for Mac OS platforms. ifeq ($(TARGET), osx) PLATFORM_FLAGS = \ diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/AUDIO_DISCO_F746NG.lib.tpl b/tensorflow/lite/experimental/micro/tools/make/templates/AUDIO_DISCO_F746NG.lib.tpl new file mode 100644 index 0000000000..11dae1ea16 --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/templates/AUDIO_DISCO_F746NG.lib.tpl @@ -0,0 +1 @@ +https://os.mbed.com/teams/ST/code/AUDIO_DISCO_F746NG/#7046ce26b7ed diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/BSP_DISCO_F746NG.lib.tpl b/tensorflow/lite/experimental/micro/tools/make/templates/BSP_DISCO_F746NG.lib.tpl new file mode 100644 index 0000000000..48dc131707 --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/templates/BSP_DISCO_F746NG.lib.tpl @@ -0,0 +1 @@ +https://os.mbed.com/teams/ST/code/BSP_DISCO_F746NG/#df2ea349c37a diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/Makefile.tpl b/tensorflow/lite/experimental/micro/tools/make/templates/Makefile.tpl new file mode 100644 index 0000000000..74d54f1ebe --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/templates/Makefile.tpl @@ -0,0 +1,26 @@ +SRCS := \ +%{SRCS}% + +OBJS := \ +$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(SRCS))) + +INCLUDES := \ +-I. \ +-I./third_party/gemmlowp \ +-I./third_party/flatbuffers/include + +CXXFLAGS += %{CXX_FLAGS}% + +LDFLAGS += %{LINKER_FLAGS}% + +%.o: %.cc + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + +%.o: %.c + $(CC) $(CCFLAGS) $(INCLUDES) -c $< -o $@ + +%{EXECUTABLE}% : $(OBJS) + $(CXX) $(LDFLAGS) $(OBJS) \ + -o $@ + +all: %{EXECUTABLE}% diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/README_MAKE.md.tpl b/tensorflow/lite/experimental/micro/tools/make/templates/README_MAKE.md.tpl new file mode 100644 index 0000000000..7906a3226a --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/templates/README_MAKE.md.tpl @@ -0,0 +1,29 @@ +# TensorFlow Lite Micro Make Project + +This folder has been autogenerated by TensorFlow, and contains source, header, +and project files needed to build a single TensorFlow Lite Micro target using +the make tool. + +## Usage + +To build this, run: + +``` +make +``` + +This should attempt to build the target locally on your platform, using the +standard Makefile variables like CFLAGS, CC, CXX, and so on. + +## Project Generation + +See +[tensorflow/lite/experimental/micro](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro) +for details on how projects like this can be generated from the main source +tree. + +## License + +TensorFlow's code is covered by the Apache2 License included in the repository, +and third party dependencies are covered by their respective licenses, in the +third_party folder of this package. diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/README_MBED.md.tpl b/tensorflow/lite/experimental/micro/tools/make/templates/README_MBED.md.tpl new file mode 100644 index 0000000000..2682236edf --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/templates/README_MBED.md.tpl @@ -0,0 +1,48 @@ +# TensorFlow Lite Micro Mbed Project + +This folder has been autogenerated by TensorFlow, and contains source, header, +and project files needed to build a single TensorFlow Lite Micro target using +the Mbed command line interface. + +## Usage + +To load the dependencies this code requires, run: + +``` +mbed config root . +mbed deploy +``` + +TensorFlow requires C++ 11, so you'll need to update your profiles to reflect +this. Here's a short Python command that does that: + +``` +python -c 'import fileinput, glob; +for filename in glob.glob("mbed-os/tools/profiles/*.json"): + for line in fileinput.input(filename, inplace=True): + print line.replace("\"-std=gnu++98\"","\"-std=c++11\", \"-fpermissive\"")' +``` + +With that setting updated, you should now be able to compile: + +``` +mbed compile -m auto -t GCC_ARM +``` + +If this works, it will give you a .bin file that you can flash onto the device +you're targeting. For example, using a Discovery STM3246G board, you can deploy +it by copying the bin to the volume mounted as a USB drive, just by dragging +over the file. + +## Project Generation + +See +[tensorflow/lite/experimental/micro](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro) +for details on how projects like this can be generated from the main source +tree. + +## License + +TensorFlow's code is covered by the Apache2 License included in the repository, +and third party dependencies are covered by their respective licenses, in the +third_party folder of this package. diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/SDRAM_DISCO_F746NG.lib.tpl b/tensorflow/lite/experimental/micro/tools/make/templates/SDRAM_DISCO_F746NG.lib.tpl new file mode 100644 index 0000000000..e2ccd7b81b --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/templates/SDRAM_DISCO_F746NG.lib.tpl @@ -0,0 +1 @@ +https://os.mbed.com/teams/ST/code/SDRAM_DISCO_F746NG/#370f402a2219 diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/mbed-os.lib.tpl b/tensorflow/lite/experimental/micro/tools/make/templates/mbed-os.lib.tpl new file mode 100644 index 0000000000..69fff22f33 --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/templates/mbed-os.lib.tpl @@ -0,0 +1 @@ +https://github.com/ARMmbed/mbed-os/#6a0a86538c0b9b2bfcc4583b1e2b7fea8f4e71e9 diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/mbed_app.json.tpl b/tensorflow/lite/experimental/micro/tools/make/templates/mbed_app.json.tpl new file mode 100644 index 0000000000..1c547369fb --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/templates/mbed_app.json.tpl @@ -0,0 +1,7 @@ +{ + "config": { + "main-stack-size": { + "value": 65536 + } + } +} -- GitLab From 8fefcf7b8b236baf94674ca8fcb0777fd48c2ccd Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Wed, 9 Jan 2019 12:42:37 -0800 Subject: [PATCH 0438/2345] Roll-forward: PR #24081: Split convolution invocation into preparation and actual invocation Please approve this CL. It will be submitted automatically, and its GitHub pull request will be marked as merged. Imported from GitHub PR #24081 - split DoConvolve into: PrepareForConvolution DoConvolve - split DoConvolveBackwardData into: PrepareForConvolutionBackwardData DoConvolveBackwardData - split DoConvolveBackwardFilter into: PrepareForConvolutionBackwardFilter DoConvolveBackwardFilter PrepareForConvolutionXXX would allocate scratch memory. DoConolveXXX would invoke actual convolution algorithms. Implement forward convoution, backward input convolution, backward filter convolution on CUDA path. Copybara import of the project: - fe9a9dbb8d0ef118b15beb4724f256190fd04d13 Split convolution invocation into preparation and actual ... by Wen-Heng (Jack) Chung - baeda0d8b5da58a97d28ddce264c80c58d937699 Merge fe9a9dbb8d0ef118b15beb4724f256190fd04d13 into bbe33... by Wen-Heng (Jack) Chung PiperOrigin-RevId: 228568812 --- tensorflow/stream_executor/cuda/cuda_dnn.cc | 471 +++++++++++++++----- tensorflow/stream_executor/cuda/cuda_dnn.h | 194 +++++++- tensorflow/stream_executor/dnn.h | 207 +++++++-- tensorflow/stream_executor/stream.cc | 172 +++++-- 4 files changed, 859 insertions(+), 185 deletions(-) diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index a34aa9354d..0bd953fe3b 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -2799,7 +2799,7 @@ void LogCudaProto(const dnn::ConvolutionProto& conv, float profile_time_ms, } // namespace template -port::Status CudnnSupport::DoConvolveImpl( +port::Status CudnnSupport::PrepareForConvolutionImpl( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, const dnn::FilterDescriptor& filter_descriptor, @@ -2808,6 +2808,34 @@ port::Status CudnnSupport::DoConvolveImpl( const dnn::BatchDescriptor& output_descriptor, DeviceMemory* output_data, dnn::DataType accumulator_type, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + cudnnDataType_t cudnn_type = GetCudnnDataType(); + CudnnTensorDescriptor input_nd(input_descriptor, cudnn_type); + CudnnTensorDescriptor output_nd(output_descriptor, cudnn_type); + CudnnFilterDescriptor filter(filter_descriptor, cudnn_type); + CudnnConvolutionDescriptor conv(convolution_descriptor, + ToCudnnDataType(accumulator_type)); + + auto cudnn = cudnn_->GetHandle(parent_, stream); + + SE_ASSIGN_OR_RETURN(*algorithm_desc, + GetCudnnConvolutionForwardAlgorithm( + stream, cudnn, algorithm_config, input_nd, filter, + conv, output_nd, scratch_allocator, scratch_memory)); + + return port::Status::OK(); +} + +template +port::Status CudnnSupport::DoConvolveImpl( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, DeviceMemory* output_data, + dnn::DataType accumulator_type, const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) { cudnnDataType_t cudnn_type = GetCudnnDataType(); CudnnTensorDescriptor input_nd(input_descriptor, cudnn_type); @@ -2830,12 +2858,6 @@ port::Status CudnnSupport::DoConvolveImpl( const bool is_profiling = output_profile_result != nullptr; - DeviceMemory scratch; - SE_ASSIGN_OR_RETURN(dnn::AlgorithmDesc algo_desc, - GetCudnnConvolutionForwardAlgorithm( - stream, cudnn, algorithm_config, input_nd, filter, - conv, output_nd, scratch_allocator, &scratch)); - std::unique_ptr timer; if (is_profiling) { timer.reset(new CUDATimer(parent_)); // NOLINT @@ -2851,7 +2873,7 @@ port::Status CudnnSupport::DoConvolveImpl( // memory. See nvbugs/2138754, b/80018418. if (CUDNN_VERSION < 7300) { SE_RETURN_IF_ERROR([&] { - if (algo_desc.algo_id() != CUDNN_CONVOLUTION_FWD_ALGO_FFT_TILING) { + if (algorithm_desc.algo_id() != CUDNN_CONVOLUTION_FWD_ALGO_FFT_TILING) { return port::Status::OK(); } if (input_descriptor.ndims() < 3) { @@ -2876,7 +2898,8 @@ port::Status CudnnSupport::DoConvolveImpl( }()); } - if (algo_desc.algo_id() == CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD_NONFUSED && + if (algorithm_desc.algo_id() == + CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD_NONFUSED && !ShouldIncludeWinogradNonfusedAlgo(input_descriptor, output_descriptor)) { return port::Status(port::error::FAILED_PRECONDITION, "This configuration has potential integer overflow in " @@ -2888,24 +2911,25 @@ port::Status CudnnSupport::DoConvolveImpl( /*alpha=*/alpha, /*srcDesc=*/input_nd.handle(), /*srcData=*/input_data.opaque(), /*filterDesc=*/filter.handle(), /*filterData=*/filter_data.opaque(), /*convDesc=*/conv.handle(), - /*algo=*/ToConvForwardAlgo(algo_desc), /*workSpace=*/scratch.opaque(), - /*workSpaceSizeInBytes=*/scratch.size(), /*beta=*/beta, + /*algo=*/ToConvForwardAlgo(algorithm_desc), + /*workSpace=*/scratch_memory->opaque(), + /*workSpaceSizeInBytes=*/scratch_memory->size(), /*beta=*/beta, /*yDesc=*/output_nd.handle(), /*y=*/output_data->opaque())); if (is_profiling) { if (!timer->Stop(AsCUDAStream(stream))) { return port::Status(port::error::INTERNAL, "Failed to stop timer"); } - output_profile_result->set_algorithm(algo_desc); + output_profile_result->set_algorithm(algorithm_desc); output_profile_result->set_elapsed_time_in_ms( timer->GetElapsedMilliseconds()); - output_profile_result->set_scratch_size(scratch.size()); + output_profile_result->set_scratch_size(scratch_memory->size()); LogCudaProto( - GenerateConvProto(dnn::ConvolutionKind::FORWARD, input_descriptor, - filter_descriptor, output_descriptor, algo_desc, - convolution_descriptor, dalpha, dbeta, - accumulator_type, dnn::ActivationMode::kNone), + GenerateConvProto( + dnn::ConvolutionKind::FORWARD, input_descriptor, filter_descriptor, + output_descriptor, algorithm_desc, convolution_descriptor, dalpha, + dbeta, accumulator_type, dnn::ActivationMode::kNone), output_profile_result->elapsed_time_in_ms(), stream->parent()); } @@ -3310,7 +3334,7 @@ port::Status CudnnSupport::DoBatchNormalizationBackwardImpl( return port::Status::OK(); } -bool CudnnSupport::DoConvolve( +bool CudnnSupport::PrepareForConvolution( Stream* stream, const dnn::BatchDescriptor& batch_descriptor, const DeviceMemory& input_data, const dnn::FilterDescriptor& filter_descriptor, @@ -3319,12 +3343,70 @@ bool CudnnSupport::DoConvolve( const dnn::BatchDescriptor& output_descriptor, DeviceMemory* output_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + return IsStatusOk(PrepareForConvolutionImpl( + stream, batch_descriptor, input_data, filter_descriptor, + filter_data, convolution_descriptor, output_descriptor, + output_data, dnn::DataType::kFloat, scratch_allocator, + algorithm_config, algorithm_desc, scratch_memory), + /*report_error=*/true); +} + +bool CudnnSupport::PrepareForConvolution( + Stream* stream, const dnn::BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + return IsStatusOk(PrepareForConvolutionImpl( + stream, batch_descriptor, input_data, filter_descriptor, + filter_data, convolution_descriptor, output_descriptor, + output_data, dnn::DataType::kDouble, scratch_allocator, + algorithm_config, algorithm_desc, scratch_memory), + /*report_error=*/true); +} + +bool CudnnSupport::PrepareForConvolution( + Stream* stream, const dnn::BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + dnn::DataType acc_type = + CudnnEnvVar::IsEnabled() + ? dnn::DataType::kFloat + : dnn::DataType::kHalf; + return IsStatusOk( + PrepareForConvolutionImpl( + stream, batch_descriptor, input_data, filter_descriptor, filter_data, + convolution_descriptor, output_descriptor, output_data, acc_type, + scratch_allocator, algorithm_config, algorithm_desc, scratch_memory), + /*report_error=*/true); +} + +bool CudnnSupport::DoConvolve( + Stream* stream, const dnn::BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) { return IsStatusOk( DoConvolveImpl(stream, batch_descriptor, input_data, filter_descriptor, filter_data, convolution_descriptor, output_descriptor, - output_data, dnn::DataType::kFloat, scratch_allocator, - algorithm_config, output_profile_result), + output_data, dnn::DataType::kFloat, algorithm_desc, + scratch_memory, output_profile_result), /*report_error=*/!output_profile_result); } @@ -3335,14 +3417,14 @@ bool CudnnSupport::DoConvolve( const DeviceMemory& filter_data, const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& output_descriptor, - DeviceMemory* output_data, ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + DeviceMemory* output_data, const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) { return IsStatusOk( DoConvolveImpl(stream, batch_descriptor, input_data, filter_descriptor, filter_data, convolution_descriptor, output_descriptor, - output_data, dnn::DataType::kDouble, scratch_allocator, - algorithm_config, output_profile_result), + output_data, dnn::DataType::kDouble, algorithm_desc, + scratch_memory, output_profile_result), /*report_error=*/!output_profile_result); } @@ -3353,8 +3435,9 @@ bool CudnnSupport::DoConvolve( const DeviceMemory& filter_data, const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& output_descriptor, - DeviceMemory* output_data, ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + DeviceMemory* output_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) { dnn::DataType acc_type = CudnnEnvVar::IsEnabled() @@ -3363,7 +3446,7 @@ bool CudnnSupport::DoConvolve( return IsStatusOk( DoConvolveImpl(stream, batch_descriptor, input_data, filter_descriptor, filter_data, convolution_descriptor, output_descriptor, - output_data, acc_type, scratch_allocator, algorithm_config, + output_data, acc_type, algorithm_desc, scratch_memory, output_profile_result), /*report_error=*/!output_profile_result); } @@ -3499,7 +3582,7 @@ bool CudnnSupport::DoTransformTensor(Stream* stream, } template -port::Status CudnnSupport::DoConvolveBackwardDataImpl( +port::Status CudnnSupport::PrepareForConvolutionBackwardDataImpl( Stream* stream, const dnn::FilterDescriptor& filter_descriptor, const DeviceMemory& filter_data, const dnn::BatchDescriptor& output_descriptor, @@ -3509,6 +3592,36 @@ port::Status CudnnSupport::DoConvolveBackwardDataImpl( DeviceMemory* backward_input_data, dnn::DataType accumulator_type, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + cudnnDataType_t cudnn_type = GetCudnnDataType(); + auto cudnn = cudnn_->GetHandle(parent_, stream); + + CudnnTensorDescriptor out_back_nd(output_descriptor, cudnn_type); + CudnnTensorDescriptor in_back_nd(input_descriptor, cudnn_type); + CudnnFilterDescriptor filter(filter_descriptor, cudnn_type); + CudnnConvolutionDescriptor conv(convolution_descriptor, + ToCudnnDataType(accumulator_type)); + + SE_ASSIGN_OR_RETURN( + *algorithm_desc, + GetCudnnConvolutionBackwardDataAlgorithm( + stream, cudnn, algorithm_config, in_back_nd, filter, conv, + out_back_nd, scratch_allocator, scratch_memory)); + + return port::Status::OK(); +} + +template +port::Status CudnnSupport::DoConvolveBackwardDataImpl( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, dnn::DataType accumulator_type, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) { cudnnDataType_t cudnn_type = GetCudnnDataType(); // Alpha is the scaling factor for input. @@ -3532,12 +3645,6 @@ port::Status CudnnSupport::DoConvolveBackwardDataImpl( const bool is_profiling = output_profile_result != nullptr; - DeviceMemory scratch; - SE_ASSIGN_OR_RETURN(dnn::AlgorithmDesc algo_desc, - GetCudnnConvolutionBackwardDataAlgorithm( - stream, cudnn, algorithm_config, in_back_nd, filter, - conv, out_back_nd, scratch_allocator, &scratch)); - std::unique_ptr timer; if (is_profiling) { timer.reset(new CUDATimer(parent_)); // NOLINT @@ -3549,7 +3656,8 @@ port::Status CudnnSupport::DoConvolveBackwardDataImpl( } } - if (algo_desc.algo_id() == CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD_NONFUSED && + if (algorithm_desc.algo_id() == + CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD_NONFUSED && !ShouldIncludeWinogradNonfusedAlgo(input_descriptor, output_descriptor)) { return port::Status(port::error::FAILED_PRECONDITION, "This configuration has potential integer overflow in " @@ -3559,42 +3667,42 @@ port::Status CudnnSupport::DoConvolveBackwardDataImpl( // Cudnn 7.1.4 has a bug if the workspace of the following convolution is not // zero-initialized, nvbugs/2254619. if (CUDNN_VERSION >= 7000 && CUDNN_VERSION < 7300 && - algo_desc.algo_id() == CUDNN_CONVOLUTION_BWD_DATA_ALGO_1 && - cudnn_type == CUDNN_DATA_HALF && algo_desc.tensor_ops_enabled() && + algorithm_desc.algo_id() == CUDNN_CONVOLUTION_BWD_DATA_ALGO_1 && + cudnn_type == CUDNN_DATA_HALF && algorithm_desc.tensor_ops_enabled() && input_descriptor.layout() == dnn::DataLayout::kBatchYXDepth && filter_descriptor.layout() == dnn::FilterLayout::kOutputInputYX && output_descriptor.layout() == dnn::DataLayout::kBatchDepthYX && (convolution_descriptor.vertical_filter_stride() > 1 || convolution_descriptor.horizontal_filter_stride() > 1)) { - stream->ThenMemZero(&scratch, scratch.size()); + stream->ThenMemZero(scratch_memory, scratch_memory->size()); } - RETURN_IF_CUDNN_ERROR( - cudnnConvolutionBackwardData(cudnn.handle(), - /*alpha=*/alpha, - /*wDesc=*/filter.handle(), - /*w=*/filter_data.opaque(), - /*dyDesc=*/out_back_nd.handle(), - /*dy=*/backward_output_data.opaque(), - /*convDesc=*/conv.handle(), - /*algo=*/ToConvBackwardDataAlgo(algo_desc), - /*workSpace=*/scratch.opaque(), - /*workSpaceSizeInBytes=*/scratch.size(), - /*beta=*/beta, - /*dxDesc=*/in_back_nd.handle(), - /*dx=*/backward_input_data->opaque())); + RETURN_IF_CUDNN_ERROR(cudnnConvolutionBackwardData( + cudnn.handle(), + /*alpha=*/alpha, + /*wDesc=*/filter.handle(), + /*w=*/filter_data.opaque(), + /*dyDesc=*/out_back_nd.handle(), + /*dy=*/backward_output_data.opaque(), + /*convDesc=*/conv.handle(), + /*algo=*/ToConvBackwardDataAlgo(algorithm_desc), + /*workSpace=*/scratch_memory->opaque(), + /*workSpaceSizeInBytes=*/scratch_memory->size(), + /*beta=*/beta, + /*dxDesc=*/in_back_nd.handle(), + /*dx=*/backward_input_data->opaque())); if (is_profiling) { if (!timer->Stop(AsCUDAStream(stream))) { return port::Status(port::error::INTERNAL, "Failed to stop timer"); } - output_profile_result->set_algorithm(algo_desc); + output_profile_result->set_algorithm(algorithm_desc); output_profile_result->set_elapsed_time_in_ms( timer->GetElapsedMilliseconds()); - output_profile_result->set_scratch_size(scratch.size()); + output_profile_result->set_scratch_size(scratch_memory->size()); LogCudaProto(GenerateConvProto( dnn::ConvolutionKind::BACKWARD_DATA, input_descriptor, - filter_descriptor, output_descriptor, algo_desc, + filter_descriptor, output_descriptor, algorithm_desc, convolution_descriptor, dalpha, dbeta, accumulator_type, dnn::ActivationMode::kNone), output_profile_result->elapsed_time_in_ms(), stream->parent()); @@ -3603,7 +3711,7 @@ port::Status CudnnSupport::DoConvolveBackwardDataImpl( return port::Status::OK(); } -bool CudnnSupport::DoConvolveBackwardData( +bool CudnnSupport::PrepareForConvolutionBackwardData( Stream* stream, const dnn::FilterDescriptor& filter_descriptor, const DeviceMemory& filter_data, const dnn::BatchDescriptor& output_descriptor, @@ -3613,17 +3721,17 @@ bool CudnnSupport::DoConvolveBackwardData( DeviceMemory* backward_input_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, - dnn::ProfileResult* output_profile_result) { + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { return IsStatusOk( - DoConvolveBackwardDataImpl( + PrepareForConvolutionBackwardDataImpl( stream, filter_descriptor, filter_data, output_descriptor, backward_output_data, convolution_descriptor, input_descriptor, backward_input_data, dnn::DataType::kDouble, scratch_allocator, - algorithm_config, output_profile_result), - /*report_error=*/!output_profile_result); + algorithm_config, algorithm_desc, scratch_memory), + /*report_error=*/true); } -bool CudnnSupport::DoConvolveBackwardData( +bool CudnnSupport::PrepareForConvolutionBackwardData( Stream* stream, const dnn::FilterDescriptor& filter_descriptor, const DeviceMemory& filter_data, const dnn::BatchDescriptor& output_descriptor, @@ -3633,17 +3741,17 @@ bool CudnnSupport::DoConvolveBackwardData( DeviceMemory* backward_input_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, - dnn::ProfileResult* output_profile_result) { + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { return IsStatusOk( - DoConvolveBackwardDataImpl( + PrepareForConvolutionBackwardDataImpl( stream, filter_descriptor, filter_data, output_descriptor, backward_output_data, convolution_descriptor, input_descriptor, backward_input_data, dnn::DataType::kFloat, scratch_allocator, - algorithm_config, output_profile_result), - /*report_error=*/!output_profile_result); + algorithm_config, algorithm_desc, scratch_memory), + /*report_error=*/true); } -bool CudnnSupport::DoConvolveBackwardData( +bool CudnnSupport::PrepareForConvolutionBackwardData( Stream* stream, const dnn::FilterDescriptor& filter_descriptor, const DeviceMemory& filter_data, const dnn::BatchDescriptor& output_descriptor, @@ -3653,22 +3761,86 @@ bool CudnnSupport::DoConvolveBackwardData( DeviceMemory* backward_input_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, - dnn::ProfileResult* output_profile_result) { + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { dnn::DataType acc_type = CudnnEnvVar::IsEnabled() ? dnn::DataType::kFloat : dnn::DataType::kHalf; return IsStatusOk( - DoConvolveBackwardDataImpl( + PrepareForConvolutionBackwardDataImpl( stream, filter_descriptor, filter_data, output_descriptor, backward_output_data, convolution_descriptor, input_descriptor, backward_input_data, acc_type, scratch_allocator, algorithm_config, - output_profile_result), + algorithm_desc, scratch_memory), + /*report_error=*/true); +} + +bool CudnnSupport::DoConvolveBackwardData( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, + dnn::ProfileResult* output_profile_result) { + return IsStatusOk( + DoConvolveBackwardDataImpl( + stream, filter_descriptor, filter_data, output_descriptor, + backward_output_data, convolution_descriptor, input_descriptor, + backward_input_data, dnn::DataType::kDouble, algorithm_desc, + scratch_memory, output_profile_result), + /*report_error=*/!output_profile_result); +} + +bool CudnnSupport::DoConvolveBackwardData( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, + dnn::ProfileResult* output_profile_result) { + return IsStatusOk( + DoConvolveBackwardDataImpl( + stream, filter_descriptor, filter_data, output_descriptor, + backward_output_data, convolution_descriptor, input_descriptor, + backward_input_data, dnn::DataType::kFloat, algorithm_desc, + scratch_memory, output_profile_result), + /*report_error=*/!output_profile_result); +} + +bool CudnnSupport::DoConvolveBackwardData( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, + dnn::ProfileResult* output_profile_result) { + dnn::DataType acc_type = + CudnnEnvVar::IsEnabled() + ? dnn::DataType::kFloat + : dnn::DataType::kHalf; + return IsStatusOk( + DoConvolveBackwardDataImpl(stream, filter_descriptor, filter_data, + output_descriptor, backward_output_data, + convolution_descriptor, input_descriptor, + backward_input_data, acc_type, algorithm_desc, + scratch_memory, output_profile_result), /*report_error=*/!output_profile_result); } template -port::Status CudnnSupport::DoConvolveBackwardFilterImpl( +port::Status CudnnSupport::PrepareForConvolutionBackwardFilterImpl( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, const dnn::BatchDescriptor& output_descriptor, @@ -3678,6 +3850,36 @@ port::Status CudnnSupport::DoConvolveBackwardFilterImpl( DeviceMemory* backward_filter_data, dnn::DataType accumulator_type, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + cudnnDataType_t cudnn_type = GetCudnnDataType(); + auto cudnn = cudnn_->GetHandle(parent_, stream); + + CudnnTensorDescriptor out_back_nd(output_descriptor, cudnn_type); + CudnnTensorDescriptor input_nd(input_descriptor, cudnn_type); + CudnnFilterDescriptor filter(filter_descriptor, cudnn_type); + CudnnConvolutionDescriptor conv(convolution_descriptor, + ToCudnnDataType(accumulator_type)); + + SE_ASSIGN_OR_RETURN( + *algorithm_desc, + GetCudnnConvolutionBackwardFilterAlgorithm( + stream, cudnn, algorithm_config, input_nd, filter, conv, out_back_nd, + scratch_allocator, scratch_memory)); + + return port::Status::OK(); +} + +template +port::Status CudnnSupport::DoConvolveBackwardFilterImpl( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, dnn::DataType accumulator_type, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) { cudnnDataType_t cudnn_type = GetCudnnDataType(); // Alpha is the scaling factor for input. @@ -3701,12 +3903,6 @@ port::Status CudnnSupport::DoConvolveBackwardFilterImpl( const bool is_profiling = output_profile_result != nullptr; - DeviceMemory scratch; - SE_ASSIGN_OR_RETURN(dnn::AlgorithmDesc algo_desc, - GetCudnnConvolutionBackwardFilterAlgorithm( - stream, cudnn, algorithm_config, input_nd, filter, - conv, out_back_nd, scratch_allocator, &scratch)); - std::unique_ptr timer; if (is_profiling) { timer.reset(new CUDATimer(parent_)); // NOLINT @@ -3722,7 +3918,8 @@ port::Status CudnnSupport::DoConvolveBackwardFilterImpl( // results. See nvbugs/2072856 if (CUDNN_VERSION < 7300) { SE_RETURN_IF_ERROR([&] { - if (algo_desc.algo_id() != CUDNN_CONVOLUTION_BWD_FILTER_ALGO_FFT_TILING) { + if (algorithm_desc.algo_id() != + CUDNN_CONVOLUTION_BWD_FILTER_ALGO_FFT_TILING) { return port::Status::OK(); } if (output_descriptor.height() > 1 && output_descriptor.width() > 1) { @@ -3748,7 +3945,8 @@ port::Status CudnnSupport::DoConvolveBackwardFilterImpl( }()); } - if (algo_desc.algo_id() == CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD_NONFUSED && + if (algorithm_desc.algo_id() == + CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD_NONFUSED && !ShouldIncludeWinogradNonfusedAlgo(input_descriptor, output_descriptor)) { return port::Status(port::error::FAILED_PRECONDITION, "This configuration has potential integer overflow in " @@ -3764,7 +3962,7 @@ port::Status CudnnSupport::DoConvolveBackwardFilterImpl( // // See nvbugs/2379553. if (CUDNN_VERSION >= 7100 && CUDNN_VERSION < 7300 && - algo_desc.algo_id() == CUDNN_CONVOLUTION_BWD_FILTER_ALGO_1 && + algorithm_desc.algo_id() == CUDNN_CONVOLUTION_BWD_FILTER_ALGO_1 && cudnn_type == CUDNN_DATA_HALF && input_descriptor.layout() == dnn::DataLayout::kBatchYXDepth && filter_descriptor.layout() == dnn::FilterLayout::kOutputYXInput && @@ -3782,9 +3980,9 @@ port::Status CudnnSupport::DoConvolveBackwardFilterImpl( /*diffDesc=*/out_back_nd.handle(), /*diffData=*/backward_output_data.opaque(), /*convDesc=*/conv.handle(), - /*algo=*/ToConvBackwardFilterAlgo(algo_desc), - /*workSpace=*/scratch.opaque(), - /*workSpaceSizeInBytes=*/scratch.size(), + /*algo=*/ToConvBackwardFilterAlgo(algorithm_desc), + /*workSpace=*/scratch_memory->opaque(), + /*workSpaceSizeInBytes=*/scratch_memory->size(), /*beta=*/beta, /*gradDesc=*/filter.handle(), /*dw=*/backward_filter_data->opaque())); @@ -3792,14 +3990,14 @@ port::Status CudnnSupport::DoConvolveBackwardFilterImpl( if (!timer->Stop(AsCUDAStream(stream))) { return port::Status(port::error::INTERNAL, "Failed to stop timer"); } - output_profile_result->set_algorithm(algo_desc); + output_profile_result->set_algorithm(algorithm_desc); output_profile_result->set_elapsed_time_in_ms( timer->GetElapsedMilliseconds()); - output_profile_result->set_scratch_size(scratch.size()); + output_profile_result->set_scratch_size(scratch_memory->size()); LogCudaProto(GenerateConvProto( dnn::ConvolutionKind::BACKWARD_FILTER, input_descriptor, - filter_descriptor, output_descriptor, algo_desc, + filter_descriptor, output_descriptor, algorithm_desc, convolution_descriptor, dalpha, dbeta, accumulator_type, dnn::ActivationMode::kNone), output_profile_result->elapsed_time_in_ms(), stream->parent()); @@ -3808,7 +4006,7 @@ port::Status CudnnSupport::DoConvolveBackwardFilterImpl( return port::Status::OK(); } -bool CudnnSupport::DoConvolveBackwardFilter( +bool CudnnSupport::PrepareForConvolutionBackwardFilter( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, const dnn::BatchDescriptor& output_descriptor, @@ -3818,18 +4016,17 @@ bool CudnnSupport::DoConvolveBackwardFilter( DeviceMemory* backward_filter_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, - dnn::ProfileResult* output_profile_result) { + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { return IsStatusOk( - DoConvolveBackwardFilterImpl( + PrepareForConvolutionBackwardFilterImpl( stream, input_descriptor, input_data, output_descriptor, backward_output_data, convolution_descriptor, filter_descriptor, - backward_filter_data, dnn::DataType::kDouble, - - scratch_allocator, algorithm_config, output_profile_result), - /*report_error=*/!output_profile_result); + backward_filter_data, dnn::DataType::kDouble, scratch_allocator, + algorithm_config, algorithm_desc, scratch_memory), + /*report_error=*/true); } -bool CudnnSupport::DoConvolveBackwardFilter( +bool CudnnSupport::PrepareForConvolutionBackwardFilter( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, const dnn::BatchDescriptor& output_descriptor, @@ -3839,18 +4036,17 @@ bool CudnnSupport::DoConvolveBackwardFilter( DeviceMemory* backward_filter_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, - dnn::ProfileResult* output_profile_result) { - return IsStatusOk(DoConvolveBackwardFilterImpl( - stream, input_descriptor, input_data, output_descriptor, - backward_output_data, convolution_descriptor, - filter_descriptor, backward_filter_data, - - dnn::DataType::kFloat, scratch_allocator, - algorithm_config, output_profile_result), - /*report_error=*/!output_profile_result); + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + return IsStatusOk( + PrepareForConvolutionBackwardFilterImpl( + stream, input_descriptor, input_data, output_descriptor, + backward_output_data, convolution_descriptor, filter_descriptor, + backward_filter_data, dnn::DataType::kFloat, scratch_allocator, + algorithm_config, algorithm_desc, scratch_memory), + /*report_error=*/true); } -bool CudnnSupport::DoConvolveBackwardFilter( +bool CudnnSupport::PrepareForConvolutionBackwardFilter( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, const dnn::BatchDescriptor& output_descriptor, @@ -3860,20 +4056,83 @@ bool CudnnSupport::DoConvolveBackwardFilter( DeviceMemory* backward_filter_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, - dnn::ProfileResult* output_profile_result) { + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { dnn::DataType acc_type = CudnnEnvVar::IsEnabled() ? dnn::DataType::kFloat : dnn::DataType::kHalf; return IsStatusOk( - DoConvolveBackwardFilterImpl( + PrepareForConvolutionBackwardFilterImpl( stream, input_descriptor, input_data, output_descriptor, backward_output_data, convolution_descriptor, filter_descriptor, backward_filter_data, acc_type, scratch_allocator, algorithm_config, - output_profile_result), + algorithm_desc, scratch_memory), + /*report_error=*/true); +} + +bool CudnnSupport::DoConvolveBackwardFilter( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, + dnn::ProfileResult* output_profile_result) { + return IsStatusOk( + DoConvolveBackwardFilterImpl( + stream, input_descriptor, input_data, output_descriptor, + backward_output_data, convolution_descriptor, filter_descriptor, + backward_filter_data, dnn::DataType::kDouble, algorithm_desc, + scratch_memory, output_profile_result), + /*report_error=*/!output_profile_result); +} + +bool CudnnSupport::DoConvolveBackwardFilter( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, + dnn::ProfileResult* output_profile_result) { + return IsStatusOk( + DoConvolveBackwardFilterImpl( + stream, input_descriptor, input_data, output_descriptor, + backward_output_data, convolution_descriptor, filter_descriptor, + backward_filter_data, dnn::DataType::kFloat, algorithm_desc, + scratch_memory, output_profile_result), /*report_error=*/!output_profile_result); } +bool CudnnSupport::DoConvolveBackwardFilter( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, + dnn::ProfileResult* output_profile_result) { + dnn::DataType acc_type = + CudnnEnvVar::IsEnabled() + ? dnn::DataType::kFloat + : dnn::DataType::kHalf; + return IsStatusOk(DoConvolveBackwardFilterImpl( + stream, input_descriptor, input_data, output_descriptor, + backward_output_data, convolution_descriptor, + filter_descriptor, backward_filter_data, acc_type, + algorithm_desc, scratch_memory, output_profile_result), + /*report_error=*/!output_profile_result); +} + template port::Status CudnnSupport::DoConvolveBackwardBiasImpl( Stream* stream, const dnn::BatchDescriptor& input_descriptor, diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.h b/tensorflow/stream_executor/cuda/cuda_dnn.h index 4cce3c5626..d7514981d5 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.h +++ b/tensorflow/stream_executor/cuda/cuda_dnn.h @@ -258,6 +258,43 @@ class CudnnSupport : public dnn::DnnSupport { DeviceMemory* scale_backprop, DeviceMemory* offset_backprop) override; + bool PrepareForConvolution( + Stream* stream, const dnn::BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; + + bool PrepareForConvolution( + Stream* stream, const dnn::BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; + + bool PrepareForConvolution( + Stream* stream, const dnn::BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; + bool DoConvolve(Stream* stream, const dnn::BatchDescriptor& batch_descriptor, const DeviceMemory& input_data, const dnn::FilterDescriptor& filter_descriptor, @@ -265,8 +302,8 @@ class CudnnSupport : public dnn::DnnSupport { const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& output_descriptor, DeviceMemory* output_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) override; bool DoConvolve(Stream* stream, const dnn::BatchDescriptor& batch_descriptor, @@ -276,8 +313,8 @@ class CudnnSupport : public dnn::DnnSupport { const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& output_descriptor, DeviceMemory* output_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) override; bool DoConvolve(Stream* stream, const dnn::BatchDescriptor& batch_descriptor, @@ -287,8 +324,8 @@ class CudnnSupport : public dnn::DnnSupport { const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& output_descriptor, DeviceMemory* output_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) override; bool DoFusedConvolve( @@ -390,7 +427,20 @@ class CudnnSupport : public dnn::DnnSupport { return false; } - bool DoConvolveBackwardData( + bool PrepareForConvolutionBackwardData( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; + + bool PrepareForConvolutionBackwardData( Stream* stream, const dnn::FilterDescriptor& filter_descriptor, const DeviceMemory& filter_data, const dnn::BatchDescriptor& output_descriptor, @@ -400,6 +450,32 @@ class CudnnSupport : public dnn::DnnSupport { DeviceMemory* backward_input_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; + + bool PrepareForConvolutionBackwardData( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; + + bool DoConvolveBackwardData( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) override; bool DoConvolveBackwardData( @@ -410,8 +486,8 @@ class CudnnSupport : public dnn::DnnSupport { const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& input_descriptor, DeviceMemory* backward_input_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) override; bool DoConvolveBackwardData( @@ -422,11 +498,11 @@ class CudnnSupport : public dnn::DnnSupport { const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& input_descriptor, DeviceMemory* backward_input_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) override; - bool DoConvolveBackwardFilter( + bool PrepareForConvolutionBackwardFilter( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, const dnn::BatchDescriptor& output_descriptor, @@ -436,9 +512,10 @@ class CudnnSupport : public dnn::DnnSupport { DeviceMemory* backward_filter_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, - dnn::ProfileResult* output_profile_result) override; + dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; - bool DoConvolveBackwardFilter( + bool PrepareForConvolutionBackwardFilter( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, const dnn::BatchDescriptor& output_descriptor, @@ -448,9 +525,10 @@ class CudnnSupport : public dnn::DnnSupport { DeviceMemory* backward_filter_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, - dnn::ProfileResult* output_profile_result) override; + dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; - bool DoConvolveBackwardFilter( + bool PrepareForConvolutionBackwardFilter( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, const dnn::BatchDescriptor& output_descriptor, @@ -460,6 +538,43 @@ class CudnnSupport : public dnn::DnnSupport { DeviceMemory* backward_filter_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; + + bool DoConvolveBackwardFilter( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, + dnn::ProfileResult* output_profile_result) override; + + bool DoConvolveBackwardFilter( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, + dnn::ProfileResult* output_profile_result) override; + + bool DoConvolveBackwardFilter( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) override; bool DoConvolveBackwardBias( @@ -677,7 +792,7 @@ class CudnnSupport : public dnn::DnnSupport { DeviceMemory* offset_backprop); template - port::Status DoConvolveImpl( + port::Status PrepareForConvolutionImpl( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, const dnn::FilterDescriptor& filter_descriptor, @@ -687,6 +802,19 @@ class CudnnSupport : public dnn::DnnSupport { DeviceMemory* output_data, dnn::DataType accumulator_type, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory); + + template + port::Status DoConvolveImpl( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, dnn::DataType accumulator_type, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result); template @@ -707,7 +835,7 @@ class CudnnSupport : public dnn::DnnSupport { dnn::ProfileResult* output_profile_result); template - port::Status DoConvolveBackwardDataImpl( + port::Status PrepareForConvolutionBackwardDataImpl( Stream* stream, const dnn::FilterDescriptor& filter_descriptor, const DeviceMemory& filter_data, const dnn::BatchDescriptor& output_descriptor, @@ -717,19 +845,45 @@ class CudnnSupport : public dnn::DnnSupport { DeviceMemory* backward_input_data, dnn::DataType accumulator_type, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory); + + template + port::Status DoConvolveBackwardDataImpl( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, dnn::DataType accumulator_type, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result); template - port::Status DoConvolveBackwardFilterImpl( + port::Status PrepareForConvolutionBackwardFilterImpl( Stream* stream, const dnn::BatchDescriptor& input_descriptor, const DeviceMemory& input_data, - const dnn::BatchDescriptor& output_descriptor, + const dnn::BatchDescriptor& output_descriptor_in, DeviceMemory backward_output_data, const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::FilterDescriptor& filter_descriptor, DeviceMemory* backward_filter_data, dnn::DataType accumulator_type, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory); + + template + port::Status DoConvolveBackwardFilterImpl( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, dnn::DataType accumulator_type, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result); template diff --git a/tensorflow/stream_executor/dnn.h b/tensorflow/stream_executor/dnn.h index ff7b02cf6b..f5a77d6525 100644 --- a/tensorflow/stream_executor/dnn.h +++ b/tensorflow/stream_executor/dnn.h @@ -738,6 +738,7 @@ class PoolingDescriptor { class AlgorithmDesc { public: typedef int64 Index; + AlgorithmDesc() : AlgorithmDesc(0, false) {} AlgorithmDesc(Index a, bool use_tensor_ops) { proto_.set_algo_id(a); proto_.set_math_type(use_tensor_ops ? AlgorithmProto::TENSOR_OP_MATH @@ -1186,6 +1187,52 @@ class DnnSupport { return false; } + virtual bool PrepareForConvolution( + Stream* stream, const BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const ConvolutionDescriptor& convolution_descriptor, + const BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + *algorithm_desc = {}; + *scratch_memory = {}; + return true; + } + + virtual bool PrepareForConvolution( + Stream* stream, const BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const ConvolutionDescriptor& convolution_descriptor, + const BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + *algorithm_desc = {}; + *scratch_memory = {}; + return true; + } + + virtual bool PrepareForConvolution( + Stream* stream, const BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const ConvolutionDescriptor& convolution_descriptor, + const BatchDescriptor& output_descriptor, + DeviceMemory* output_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + *algorithm_desc = {}; + *scratch_memory = {}; + return true; + } + // Enqueues a single-precision convolution operation onto the stream. // // Arguments (all borrowed): @@ -1199,10 +1246,10 @@ class DnnSupport { // output_descriptor: dimensions of the output layer. // output_data: un-owned device memory region in which to place the // convolution result. - // scratch_allocator: un-owned, may-be-null object that may allocate scratch - // space in order to speed up the convolution operation. - // algorithm_config: specifies which algorithm should be used for the + // algorithm_desc: specifies which algorithm should be used for the // operation. + // scratch: un-owned device memory for scratch space in order to speed up + // the convolution operation. // output_profile_result: the output profile result for this call. The // profiling is only enabled when this is not nullptr. // @@ -1227,8 +1274,9 @@ class DnnSupport { const DeviceMemory& filter_data, const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& output_descriptor, - DeviceMemory* output_data, ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + DeviceMemory* output_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, ProfileResult* output_profile_result) = 0; // Enqueues a double-precision convolution operation onto the stream. @@ -1240,8 +1288,9 @@ class DnnSupport { const DeviceMemory& filter_data, const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& output_descriptor, - DeviceMemory* output_data, ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + DeviceMemory* output_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, dnn::ProfileResult* output_profile_result) = 0; // Enqueues a half-precision convolution operation onto the stream. @@ -1254,8 +1303,8 @@ class DnnSupport { const dnn::ConvolutionDescriptor& convolution_descriptor, const dnn::BatchDescriptor& output_descriptor, DeviceMemory* output_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, ProfileResult* output_profile_result) = 0; // Return a list of algorithms supported by the forward convolution pass. @@ -1311,6 +1360,54 @@ class DnnSupport { const BatchDescriptor& output_descriptor, DeviceMemory* output_data) = 0; + virtual bool PrepareForConvolutionBackwardData( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + *algorithm_desc = {}; + *scratch_memory = {}; + return true; + } + + virtual bool PrepareForConvolutionBackwardData( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + *algorithm_desc = {}; + *scratch_memory = {}; + return true; + } + + virtual bool PrepareForConvolutionBackwardData( + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + *algorithm_desc = {}; + *scratch_memory = {}; + return true; + } + // Enqueues a single-precision backward convolution (for data) operation onto // the stream. // @@ -1330,15 +1427,15 @@ class DnnSupport { // scratch_allocator: un-owned, may-be-null object that may allocate scratch // space in order to speed up the convolution operation. virtual bool DoConvolveBackwardData( - Stream* stream, const FilterDescriptor& filter_descriptor, + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, const DeviceMemory& filter_data, - const BatchDescriptor& output_descriptor, + const dnn::BatchDescriptor& output_descriptor, DeviceMemory backward_output_data, - const ConvolutionDescriptor& convolution_descriptor, - const BatchDescriptor& input_descriptor, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, DeviceMemory* backward_input_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, ProfileResult* output_profile_result) = 0; // Return a list of algorithms supported by the backward convolution pass for @@ -1348,28 +1445,76 @@ class DnnSupport { std::vector* out_algorithms); virtual bool DoConvolveBackwardData( - Stream* stream, const FilterDescriptor& filter_descriptor, + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, const DeviceMemory& filter_data, - const BatchDescriptor& output_descriptor, + const dnn::BatchDescriptor& output_descriptor, DeviceMemory backward_output_data, - const ConvolutionDescriptor& convolution_descriptor, - const BatchDescriptor& input_descriptor, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, DeviceMemory* backward_input_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, ProfileResult* output_profile_result) = 0; virtual bool DoConvolveBackwardData( - Stream* stream, const FilterDescriptor& filter_descriptor, + Stream* stream, const dnn::FilterDescriptor& filter_descriptor, const DeviceMemory& filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& input_descriptor, + DeviceMemory* backward_input_data, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, + ProfileResult* output_profile_result) = 0; + + virtual bool PrepareForConvolutionBackwardFilter( + Stream* stream, const BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const ConvolutionDescriptor& convolution_descriptor, + const FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + *algorithm_desc = {}; + *scratch_memory = {}; + return true; + } + + virtual bool PrepareForConvolutionBackwardFilter( + Stream* stream, const BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const BatchDescriptor& output_descriptor, + DeviceMemory backward_output_data, + const ConvolutionDescriptor& convolution_descriptor, + const FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + *algorithm_desc = {}; + *scratch_memory = {}; + return true; + } + + virtual bool PrepareForConvolutionBackwardFilter( + Stream* stream, const BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, const BatchDescriptor& output_descriptor, DeviceMemory backward_output_data, const ConvolutionDescriptor& convolution_descriptor, - const BatchDescriptor& input_descriptor, - DeviceMemory* backward_input_data, + const FilterDescriptor& filter_descriptor, + DeviceMemory* backward_filter_data, ScratchAllocator* scratch_allocator, const dnn::AlgorithmConfig& algorithm_config, - ProfileResult* output_profile_result) = 0; + dnn::AlgorithmDesc* algorithm_desc, DeviceMemory* scratch_memory) { + *algorithm_desc = {}; + *scratch_memory = {}; + return true; + } // Enqueues a single-precision backward convolution (for filter) operation // onto the stream. @@ -1398,8 +1543,8 @@ class DnnSupport { const ConvolutionDescriptor& convolution_descriptor, const FilterDescriptor& filter_descriptor, DeviceMemory* backward_filter_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, ProfileResult* output_profile_result) = 0; // Return a list of algorithms supported by the backward convolution pass for @@ -1416,8 +1561,8 @@ class DnnSupport { const ConvolutionDescriptor& convolution_descriptor, const FilterDescriptor& filter_descriptor, DeviceMemory* backward_filter_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, ProfileResult* output_profile_result) = 0; virtual bool DoConvolveBackwardFilter( @@ -1428,8 +1573,8 @@ class DnnSupport { const ConvolutionDescriptor& convolution_descriptor, const FilterDescriptor& filter_descriptor, DeviceMemory* backward_filter_data, - ScratchAllocator* scratch_allocator, - const dnn::AlgorithmConfig& algorithm_config, + const dnn::AlgorithmDesc& algorithm_desc, + DeviceMemory* scratch_memory, ProfileResult* output_profile_result) = 0; // Enqueues a single-precision backward convolution (for bias) operation onto diff --git a/tensorflow/stream_executor/stream.cc b/tensorflow/stream_executor/stream.cc index 1b14e4c339..1befc18e19 100644 --- a/tensorflow/stream_executor/stream.cc +++ b/tensorflow/stream_executor/stream.cc @@ -549,11 +549,16 @@ Stream &Stream::ThenConvolveWithScratch( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoConvolve( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + CheckError(dnn->PrepareForConvolution( this, input_descriptor, input_data, filter_descriptor, filter_data, convolution_descriptor, output_descriptor, output, scratch_allocator, - dnn::AlgorithmConfig(), - /*output_profile_result=*/nullptr)); + dnn::AlgorithmConfig(), &algorithm_desc, &scratch_memory)); + CheckError(dnn->DoConvolve( + this, input_descriptor, input_data, filter_descriptor, filter_data, + convolution_descriptor, output_descriptor, output, algorithm_desc, + &scratch_memory, nullptr)); } else { SetErrorAndLogNoDnnSupport(); } @@ -576,11 +581,16 @@ Stream &Stream::ThenConvolveWithScratch( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoConvolve( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + CheckError(dnn->PrepareForConvolution( this, input_descriptor, input_data, filter_descriptor, filter_data, convolution_descriptor, output_descriptor, output, scratch_allocator, - dnn::AlgorithmConfig(), - /*output_profile_result=*/nullptr)); + dnn::AlgorithmConfig(), &algorithm_desc, &scratch_memory)); + CheckError(dnn->DoConvolve( + this, input_descriptor, input_data, filter_descriptor, filter_data, + convolution_descriptor, output_descriptor, output, algorithm_desc, + &scratch_memory, nullptr)); } else { SetErrorAndLogNoDnnSupport(); } @@ -758,10 +768,18 @@ Stream &Stream::ThenConvolveWithAlgorithm( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - auto status = dnn->DoConvolve( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + auto status = dnn->PrepareForConvolution( this, input_descriptor, input_data, filter_descriptor, filter_data, convolution_descriptor, output_descriptor, output, scratch_allocator, - algorithm_config, output_profile_result); + algorithm_config, &algorithm_desc, &scratch_memory); + if (status) { + status = dnn->DoConvolve( + this, input_descriptor, input_data, filter_descriptor, filter_data, + convolution_descriptor, output_descriptor, output, algorithm_desc, + &scratch_memory, output_profile_result); + } if (!status && !output_profile_result) { SetError(); } @@ -789,10 +807,18 @@ Stream &Stream::ThenConvolveWithAlgorithm( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - auto status = dnn->DoConvolve( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + auto status = dnn->PrepareForConvolution( this, input_descriptor, input_data, filter_descriptor, filter_data, convolution_descriptor, output_descriptor, output, scratch_allocator, - algorithm_config, output_profile_result); + algorithm_config, &algorithm_desc, &scratch_memory); + if (status) { + status = dnn->DoConvolve( + this, input_descriptor, input_data, filter_descriptor, filter_data, + convolution_descriptor, output_descriptor, output, algorithm_desc, + &scratch_memory, output_profile_result); + } if (!status && !output_profile_result) { SetError(); } @@ -820,10 +846,18 @@ Stream &Stream::ThenConvolveWithAlgorithm( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - auto status = dnn->DoConvolve( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + auto status = dnn->PrepareForConvolution( this, input_descriptor, input_data, filter_descriptor, filter_data, convolution_descriptor, output_descriptor, output, scratch_allocator, - algorithm_config, output_profile_result); + algorithm_config, &algorithm_desc, &scratch_memory); + if (status) { + status = dnn->DoConvolve( + this, input_descriptor, input_data, filter_descriptor, filter_data, + convolution_descriptor, output_descriptor, output, algorithm_desc, + &scratch_memory, output_profile_result); + } if (!status && !output_profile_result) { SetError(); } @@ -969,10 +1003,17 @@ Stream &Stream::ThenConvolveBackwardDataWithScratch( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoConvolveBackwardData( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + CheckError(dnn->PrepareForConvolutionBackwardData( this, filter_descriptor, filter_data, output_descriptor, backward_output_data, convolution_descriptor, input_descriptor, backward_input_data, scratch_allocator, dnn::AlgorithmConfig(), + &algorithm_desc, &scratch_memory)); + CheckError(dnn->DoConvolveBackwardData( + this, filter_descriptor, filter_data, output_descriptor, + backward_output_data, convolution_descriptor, input_descriptor, + backward_input_data, algorithm_desc, &scratch_memory, /*output_profile_result=*/nullptr)); } else { SetErrorAndLogNoDnnSupport(); @@ -999,11 +1040,20 @@ Stream &Stream::ThenConvolveBackwardDataWithAlgorithm( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - auto status = dnn->DoConvolveBackwardData( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + auto status = dnn->PrepareForConvolutionBackwardData( this, filter_descriptor, filter_data, output_descriptor, backward_output_data, convolution_descriptor, input_descriptor, backward_input_data, scratch_allocator, algorithm_config, - output_profile_result); + &algorithm_desc, &scratch_memory); + if (status) { + status = dnn->DoConvolveBackwardData( + this, filter_descriptor, filter_data, output_descriptor, + backward_output_data, convolution_descriptor, input_descriptor, + backward_input_data, algorithm_desc, &scratch_memory, + output_profile_result); + } if (!status && !output_profile_result) { SetError(); } @@ -1032,11 +1082,20 @@ Stream &Stream::ThenConvolveBackwardDataWithAlgorithm( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - auto status = dnn->DoConvolveBackwardData( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + auto status = dnn->PrepareForConvolutionBackwardData( this, filter_descriptor, filter_data, output_descriptor, backward_output_data, convolution_descriptor, input_descriptor, backward_input_data, scratch_allocator, algorithm_config, - output_profile_result); + &algorithm_desc, &scratch_memory); + if (status) { + status = dnn->DoConvolveBackwardData( + this, filter_descriptor, filter_data, output_descriptor, + backward_output_data, convolution_descriptor, input_descriptor, + backward_input_data, algorithm_desc, &scratch_memory, + output_profile_result); + } if (!status && !output_profile_result) { SetError(); } @@ -1065,11 +1124,20 @@ Stream &Stream::ThenConvolveBackwardDataWithAlgorithm( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - auto status = dnn->DoConvolveBackwardData( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + auto status = dnn->PrepareForConvolutionBackwardData( this, filter_descriptor, filter_data, output_descriptor, backward_output_data, convolution_descriptor, input_descriptor, backward_input_data, scratch_allocator, algorithm_config, - output_profile_result); + &algorithm_desc, &scratch_memory); + if (status) { + status = dnn->DoConvolveBackwardData( + this, filter_descriptor, filter_data, output_descriptor, + backward_output_data, convolution_descriptor, input_descriptor, + backward_input_data, algorithm_desc, &scratch_memory, + output_profile_result); + } if (!status && !output_profile_result) { SetError(); } @@ -1096,10 +1164,17 @@ Stream &Stream::ThenConvolveBackwardDataWithScratch( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoConvolveBackwardData( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + CheckError(dnn->PrepareForConvolutionBackwardData( this, filter_descriptor, filter_data, output_descriptor, backward_output_data, convolution_descriptor, input_descriptor, backward_input_data, scratch_allocator, dnn::AlgorithmConfig(), + &algorithm_desc, &scratch_memory)); + CheckError(dnn->DoConvolveBackwardData( + this, filter_descriptor, filter_data, output_descriptor, + backward_output_data, convolution_descriptor, input_descriptor, + backward_input_data, algorithm_desc, &scratch_memory, /*output_profile_result=*/nullptr)); } else { SetErrorAndLogNoDnnSupport(); @@ -1138,10 +1213,17 @@ Stream &Stream::ThenConvolveBackwardFilterWithScratch( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoConvolveBackwardFilter( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + CheckError(dnn->PrepareForConvolutionBackwardFilter( this, input_descriptor, input_data, output_descriptor, backward_output_data, convolution_descriptor, filter_descriptor, backward_filter_data, scratch_allocator, dnn::AlgorithmConfig(), + &algorithm_desc, &scratch_memory)); + CheckError(dnn->DoConvolveBackwardFilter( + this, input_descriptor, input_data, output_descriptor, + backward_output_data, convolution_descriptor, filter_descriptor, + backward_filter_data, algorithm_desc, &scratch_memory, /*output_profile_result=*/nullptr)); } else { SetErrorAndLogNoDnnSupport(); @@ -1168,11 +1250,20 @@ Stream &Stream::ThenConvolveBackwardFilterWithAlgorithm( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - auto status = dnn->DoConvolveBackwardFilter( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + auto status = dnn->PrepareForConvolutionBackwardFilter( this, input_descriptor, input_data, output_descriptor, backward_output_data, convolution_descriptor, filter_descriptor, backward_filter_data, scratch_allocator, algorithm_config, - output_profile_result); + &algorithm_desc, &scratch_memory); + if (status) { + status = dnn->DoConvolveBackwardFilter( + this, input_descriptor, input_data, output_descriptor, + backward_output_data, convolution_descriptor, filter_descriptor, + backward_filter_data, algorithm_desc, &scratch_memory, + output_profile_result); + } if (!status && !output_profile_result) { SetError(); } @@ -1201,11 +1292,20 @@ Stream &Stream::ThenConvolveBackwardFilterWithAlgorithm( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - auto status = dnn->DoConvolveBackwardFilter( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + auto status = dnn->PrepareForConvolutionBackwardFilter( this, input_descriptor, input_data, output_descriptor, backward_output_data, convolution_descriptor, filter_descriptor, backward_filter_data, scratch_allocator, algorithm_config, - output_profile_result); + &algorithm_desc, &scratch_memory); + if (status) { + status = dnn->DoConvolveBackwardFilter( + this, input_descriptor, input_data, output_descriptor, + backward_output_data, convolution_descriptor, filter_descriptor, + backward_filter_data, algorithm_desc, &scratch_memory, + output_profile_result); + } if (!status && !output_profile_result) { SetError(); } @@ -1232,10 +1332,17 @@ Stream &Stream::ThenConvolveBackwardFilterWithScratch( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoConvolveBackwardFilter( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + CheckError(dnn->PrepareForConvolutionBackwardFilter( this, input_descriptor, input_data, output_descriptor, backward_output_data, convolution_descriptor, filter_descriptor, backward_filter_data, scratch_allocator, dnn::AlgorithmConfig(), + &algorithm_desc, &scratch_memory)); + CheckError(dnn->DoConvolveBackwardFilter( + this, input_descriptor, input_data, output_descriptor, + backward_output_data, convolution_descriptor, filter_descriptor, + backward_filter_data, algorithm_desc, &scratch_memory, /*output_profile_result=*/nullptr)); } else { SetErrorAndLogNoDnnSupport(); @@ -1262,11 +1369,20 @@ Stream &Stream::ThenConvolveBackwardFilterWithAlgorithm( if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - auto status = dnn->DoConvolveBackwardFilter( + DeviceMemory scratch_memory; + dnn::AlgorithmDesc algorithm_desc; + auto status = dnn->PrepareForConvolutionBackwardFilter( this, input_descriptor, input_data, output_descriptor, backward_output_data, convolution_descriptor, filter_descriptor, backward_filter_data, scratch_allocator, algorithm_config, - output_profile_result); + &algorithm_desc, &scratch_memory); + if (status) { + status = dnn->DoConvolveBackwardFilter( + this, input_descriptor, input_data, output_descriptor, + backward_output_data, convolution_descriptor, filter_descriptor, + backward_filter_data, algorithm_desc, &scratch_memory, + output_profile_result); + } if (!status && !output_profile_result) { SetError(); } -- GitLab From 04266f6c791113ec9e0098e52579a4c53e9acc0b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 12:55:40 -0800 Subject: [PATCH 0439/2345] Fix off-by-one error in time calculation for eta ecm3531 platform. PiperOrigin-RevId: 228571040 --- .../lite/experimental/micro/tools/make/targets/ecm3531/_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/_main.c b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/_main.c index 93941e119f..2764f3ba50 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/_main.c +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/_main.c @@ -85,7 +85,7 @@ void EtaPrintExecutionTime(uint64_t time_ms) { time_ms = time_ms / 10; time_string[k1] = (char)(0x30 + c); } - for (k1 = 4; k1 > 0; k1--) { // print out 1 char at a time + for (k1 = 4; k1 >= 0; k1--) { // print out 1 char at a time EtaCspUartPutc(&g_sUart1, time_string[k1]); } } else { -- GitLab From 998a22ab58d8e5faa1f041afea1e322797d70329 Mon Sep 17 00:00:00 2001 From: Shivani Agrawal Date: Wed, 9 Jan 2019 13:05:00 -0800 Subject: [PATCH 0440/2345] [tf.data] document update to correct the non-working example. PiperOrigin-RevId: 228572720 --- tensorflow/python/data/experimental/ops/stats_aggregator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/data/experimental/ops/stats_aggregator.py b/tensorflow/python/data/experimental/ops/stats_aggregator.py index d5fcc033ab..3e4c66be27 100644 --- a/tensorflow/python/data/experimental/ops/stats_aggregator.py +++ b/tensorflow/python/data/experimental/ops/stats_aggregator.py @@ -45,7 +45,7 @@ class StatsAggregator(object): # Apply `StatsOptions` to associate `dataset` with `aggregator`. options = dataset_ops.Options() - options.experimental_stats = tf.data.experimental.StatsOptions(aggregator) + options.experimental_stats.aggregator = aggregator dataset = dataset.with_options(options) ``` -- GitLab From 02747c96978bc76427cf45d32ecb24a866520043 Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Wed, 9 Jan 2019 13:06:08 -0800 Subject: [PATCH 0441/2345] Fix tensor_utils_test Update another test target relying on lite/kernels:test_util, which pulls in TF and requires the tf_cc_test build rule. A follow-up CL will remove this requirement for this and other kernel tests. PiperOrigin-RevId: 228572896 --- tensorflow/lite/kernels/internal/BUILD | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/kernels/internal/BUILD b/tensorflow/lite/kernels/internal/BUILD index 74a11a72ef..b734b2d6cc 100644 --- a/tensorflow/lite/kernels/internal/BUILD +++ b/tensorflow/lite/kernels/internal/BUILD @@ -1,3 +1,4 @@ +load("//tensorflow:tensorflow.bzl", "tf_cc_test") load("//tensorflow/lite:build_def.bzl", "tflite_copts") load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite") @@ -555,10 +556,11 @@ cc_library( ], ) -cc_test( +# TODO(b/122597976): Eliminate TF dependency from lite/kernels:test_util, +# in turn eliminating the need to use tf_cc_test for any dependent tests. +tf_cc_test( name = "tensor_utils_test", srcs = ["tensor_utils_test.cc"], - copts = NEON_FLAGS_IF_APPLICABLE, linkopts = select({ "//tensorflow:android": [ "-fPIE -pie", -- GitLab From 4495df6cb19802fabaae9999a3b3fdcfe88b268d Mon Sep 17 00:00:00 2001 From: Shashi Shekhar Date: Wed, 9 Jan 2019 13:32:55 -0800 Subject: [PATCH 0442/2345] Update types in accuracy tool. PiperOrigin-RevId: 228577610 --- tensorflow/lite/tools/accuracy/utils.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tensorflow/lite/tools/accuracy/utils.cc b/tensorflow/lite/tools/accuracy/utils.cc index c19dc1ff7c..953892b8dd 100644 --- a/tensorflow/lite/tools/accuracy/utils.cc +++ b/tensorflow/lite/tools/accuracy/utils.cc @@ -38,6 +38,12 @@ DataType GetTFDataType(TfLiteType tflite_type) { return DT_FLOAT; case kTfLiteUInt8: return DT_UINT8; + case kTfLiteInt8: + return DT_INT8; + case kTfLiteInt32: + return DT_INT32; + case kTfLiteInt64: + return DT_INT64; default: return DT_INVALID; } -- GitLab From 043e72fa2cab247290e0015682e8bc818bee5a15 Mon Sep 17 00:00:00 2001 From: Grzegorz Pawelczak Date: Wed, 9 Jan 2019 21:41:09 +0000 Subject: [PATCH 0443/2345] Fix copyright year and run clang format --- tensorflow/compiler/xla/service/sort_simplifier.cc | 6 ++---- tensorflow/compiler/xla/service/sort_simplifier_test.cc | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/xla/service/sort_simplifier.cc b/tensorflow/compiler/xla/service/sort_simplifier.cc index ee91762253..6136dc278c 100644 --- a/tensorflow/compiler/xla/service/sort_simplifier.cc +++ b/tensorflow/compiler/xla/service/sort_simplifier.cc @@ -92,8 +92,7 @@ StatusOr RemoveUnusedOperandFromSort(HloInstruction* sort) { } return true; } - -} // namespace +} // namespace StatusOr SortSimplifier::Run(HloModule* module) { VLOG(2) << "HLO module before SortSimplifier:"; @@ -109,8 +108,7 @@ StatusOr SortSimplifier::Run(HloModule* module) { } for (HloInstruction* sort_instr : sort_instrs) { - TF_ASSIGN_OR_RETURN(bool result, - RemoveUnusedOperandFromSort(sort_instr)); + TF_ASSIGN_OR_RETURN(bool result, RemoveUnusedOperandFromSort(sort_instr)); changed |= result; } diff --git a/tensorflow/compiler/xla/service/sort_simplifier_test.cc b/tensorflow/compiler/xla/service/sort_simplifier_test.cc index 5a81e537de..4df833bbeb 100644 --- a/tensorflow/compiler/xla/service/sort_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/sort_simplifier_test.cc @@ -1,4 +1,4 @@ -/* 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. -- GitLab From 0dc03e940edcab6e6680990557634b335d3be4e9 Mon Sep 17 00:00:00 2001 From: Pete Warden Date: Wed, 9 Jan 2019 13:45:56 -0800 Subject: [PATCH 0444/2345] Fixed target name in documentation PiperOrigin-RevId: 228580269 --- 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 173f91fe90..931c8d9d31 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -381,7 +381,7 @@ auto-generated for any target you can compile using the main Make system, using a command like this (making sure you've run `download_dependencies.sh` first): ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=mbed TAGS="CMSIS disco_f746ng" generate_micro_speech_main_test_mbed_project +make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=mbed TAGS="CMSIS disco_f746ng" generate_micro_speech_mbed_project ``` This will create a folder in -- GitLab From 9545dccbd749a626c5451421b090ed1c70125d6a Mon Sep 17 00:00:00 2001 From: Russell Power Date: Wed, 9 Jan 2019 13:48:27 -0800 Subject: [PATCH 0445/2345] graph_partition: Fix absl::Hash related issue in Windows build. PiperOrigin-RevId: 228580775 --- tensorflow/core/graph/graph_partition.cc | 35 ++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/tensorflow/core/graph/graph_partition.cc b/tensorflow/core/graph/graph_partition.cc index be0cac50a1..00c7a5b091 100644 --- a/tensorflow/core/graph/graph_partition.cc +++ b/tensorflow/core/graph/graph_partition.cc @@ -67,16 +67,14 @@ struct DupRecvKey { c.recv_output_on_host); } - friend bool operator==(const DupRecvKey& x, const DupRecvKey& y); + friend bool operator==(const DupRecvKey& x, const DupRecvKey& y) { + return (x.src_node_id == y.src_node_id) && + (x.src_output_slot == y.src_output_slot) && + (x.dst_graph == y.dst_graph) && + (x.recv_output_on_host == y.recv_output_on_host); + } }; -bool operator==(const DupRecvKey& x, const DupRecvKey& y) { - return (x.src_node_id == y.src_node_id) && - (x.src_output_slot == y.src_output_slot) && - (x.dst_graph == y.dst_graph) && - (x.recv_output_on_host == y.recv_output_on_host); -} - // struct used to store the recvs, so that start times can be properly updated struct RecvInfo { NodeDef* recv; @@ -88,7 +86,22 @@ typedef absl::flat_hash_map DupRecvTable; // A map used to store memory types for the inputs/outputs of every node. // The key is a pair of ints consisting of a node id and input/output index. -typedef absl::flat_hash_map, MemoryType> MemoryTypeMap; +// TODO(power): migrate back to std::pair when absl::Hash is fixed for MSVC. +struct NodePort { + int node_id; + int index; + + friend bool operator==(const NodePort& x, const NodePort& y) { + return x.node_id == y.node_id && x.index == y.index; + } + + template + friend H AbslHashValue(H h, const NodePort& c) { + return H::combine(std::move(h), c.node_id, c.index); + } +}; + +typedef absl::flat_hash_map MemoryTypeMap; // We collect the following information about the graph before performing // graph partitioning. @@ -552,10 +565,10 @@ Status BuildMemoryDeviceInfo(const Graph& g, GraphInfo* info) { int node_id = node->id(); info->device_types[node_id] = DeviceType(parsed.type); - for (size_t i = 0; i < input_memory_types.size(); ++i) { + for (int i = 0; i < input_memory_types.size(); ++i) { info->input_types[{node_id, i}] = input_memory_types[i]; } - for (size_t i = 0; i < output_memory_types.size(); ++i) { + for (int i = 0; i < output_memory_types.size(); ++i) { info->output_types[{node_id, i}] = output_memory_types[i]; } } -- GitLab From 2b2c042367675fdac67a12bc9bae9689babae07d Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Wed, 9 Jan 2019 13:51:30 -0800 Subject: [PATCH 0446/2345] [Grappler] Add identity nodes to read function outputs in GrapplerFunctionItem instantiations PiperOrigin-RevId: 228581341 --- .../core/grappler/costs/graph_properties.cc | 4 +- .../optimizers/constant_folding_test.cc | 2 +- .../grappler/optimizers/function_optimizer.cc | 129 +++++++++--- .../optimizers/function_optimizer_test.cc | 2 +- .../optimizers/meta_optimizer_test.cc | 111 ++++++++-- tensorflow/core/grappler/utils/BUILD | 1 + tensorflow/core/grappler/utils/functions.cc | 196 ++++++++++++------ tensorflow/core/grappler/utils/functions.h | 83 ++++---- .../core/grappler/utils/functions_test.cc | 105 +++++++--- .../core/grappler/utils/grappler_test.cc | 74 ++++--- .../core/grappler/utils/grappler_test.h | 7 + 11 files changed, 513 insertions(+), 201 deletions(-) diff --git a/tensorflow/core/grappler/costs/graph_properties.cc b/tensorflow/core/grappler/costs/graph_properties.cc index 1ed96ca12a..863a5087ae 100644 --- a/tensorflow/core/grappler/costs/graph_properties.cc +++ b/tensorflow/core/grappler/costs/graph_properties.cc @@ -669,7 +669,7 @@ class SymbolicShapeRefiner { ctx->output_tensor_protos.resize(grappler_function_item.output_size(), nullptr); for (auto const& out_arg : grappler_function_item.outputs()) { - if (out_arg.output_tensors.size() > 1) { + if (out_arg.output_nodes.size() > 1) { // TODO(jmdecker): Handle case of multiple output tensors return errors::Unimplemented( "Output arguments with multiple output tensors are not yet " @@ -678,7 +678,7 @@ class SymbolicShapeRefiner { // It is guaranteed that output_tensors does not contain any control // inputs, so port_id >= 0. - TensorId out_tensor = ParseTensorName(out_arg.output_tensors[0]); + TensorId out_tensor = ParseTensorName(out_arg.output_nodes[0]); const NodeDef* retnode = gv.GetNode(out_tensor.node()); if (retnode == nullptr) { diff --git a/tensorflow/core/grappler/optimizers/constant_folding_test.cc b/tensorflow/core/grappler/optimizers/constant_folding_test.cc index 192f48272f..d7cabf5a8b 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding_test.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding_test.cc @@ -1601,7 +1601,7 @@ TEST_F(ConstantFoldingTest, SplitRemoval) { AddNode("split_dim", "Const", {}, {}, &want); AddNode("s1", "Identity", {"in1", AsControlDependency("split_dim")}, {}, &want); - AddNode("s2", "Split", {"in2", "split_dim"}, {}, &want); + AddNode("s2", "Split", {"split_dim", "in2"}, {}, &want); AddNode("out", "Add", {"s1", "s2"}, {}, &want); CompareGraphs(want, got); diff --git a/tensorflow/core/grappler/optimizers/function_optimizer.cc b/tensorflow/core/grappler/optimizers/function_optimizer.cc index d074676c3d..e9c30fee25 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer.cc @@ -806,8 +806,17 @@ Status SpecializeFunction(const NodeDef& func_node, const FunctionDef& func, // update outputs for the fetch nodes, so we just skip them. std::vector> output_mapping; if (!signature.is_in_fetch_set) { - TF_RETURN_IF_ERROR( - RemoveUnusedOutputs(signature.active_outputs, &item, &output_mapping)); + int num_func_outputs = 0; + for (const auto& out_arg : item.outputs()) { + num_func_outputs += out_arg.output_nodes.size(); + } + + absl::flat_hash_set remove; + for (int i = 0; i < num_func_outputs; ++i) { + if (!signature.active_outputs.count(i)) remove.insert(i); + } + + TF_RETURN_IF_ERROR(RemoveFunctionOutputs(remove, &item, &output_mapping)); } // TODO(ezhulenev): Push down known input shapes. @@ -962,8 +971,10 @@ NodeDef InlinedFunctionInputsNode(const NodeDef& func_node, // Create an IdentityN node to hook the function outputs to: this ensures that // the function body is fully evaluated before its fanout gets scheduled. -NodeDef InlinedFunctionOutputsNode(const NodeDef& func_node, - const GrapplerFunctionItem& item) { +NodeDef InlinedFunctionOutputsNode( + const NodeDef& func_node, const GrapplerFunctionItem& item, + const absl::flat_hash_map + output_tensors) { NodeDef outputs; outputs.set_name(func_node.name()); outputs.set_op("IdentityN"); @@ -972,7 +983,8 @@ NodeDef InlinedFunctionOutputsNode(const NodeDef& func_node, (*outputs.mutable_attr())["T"].mutable_list(); for (const OutputArgExpansion& output_arg : item.outputs()) { - for (const string& output_tensor : output_arg.output_tensors) { + for (const string& output_node : output_arg.output_nodes) { + const absl::string_view output_tensor = output_tensors.at(output_node); type_list->add_type(output_arg.data_type); outputs.add_input(strings::StrCat(func_node.name(), "/", output_tensor)); } @@ -1004,29 +1016,51 @@ Status InlineDirectFunctionCall(const NodeDef& func_node, } // Mapping from input placeholder name to function input position. - int idx = 0; - absl::flat_hash_map input_placeholders_idx; + absl::flat_hash_map input_placeholders_idx; for (const InputArgExpansion& input_arg : item.inputs()) { for (const string& placeholder : input_arg.placeholders) { - input_placeholders_idx[placeholder] = idx++; + const int idx = input_placeholders_idx.size(); + input_placeholders_idx[placeholder] = idx; + } + } + + // Bypass identity nodes added to the graph in place of function outputs. + absl::flat_hash_set output_nodes; + for (const OutputArgExpansion& output_arg : item.outputs()) { + for (const string& output_node : output_arg.output_nodes) { + output_nodes.insert(output_node); } } + // For each function output value we added an identity node that reads the + // tensor from one of the function body nodes. When we inline function into + // the main graph we want to bypass these nodes, so we keep a mapping from + // 'output node name' -> 'output tensor name'. + absl::flat_hash_map output_tensors; + // Hook inlined function inputs to IdentityN node. NodeDef* func_inputs = optimized_graph->add_node(); *func_inputs = InlinedFunctionInputsNode(func_node, item); for (NodeDef& func_body_node : *item.mutable_function_body().mutable_node()) { - if (item.IsInputPlaceholder(func_body_node.name())) { - // Turn input placeholders into identity nodes. + const string& node_name = func_body_node.name(); + + // Skip output identity node, and update a mapping to the output tensor. + if (IsIdentity(func_body_node) && output_nodes.count(node_name)) { + output_tensors.emplace(node_name, func_body_node.input(0)); + continue; + } + + // Turn placeholders added in place of input arguments into identity nodes. + const auto input_placeholder_idx = input_placeholders_idx.find(node_name); + if (input_placeholder_idx != input_placeholders_idx.end()) { CHECK_EQ(0, func_body_node.input_size()); func_body_node.set_op("Identity"); (*func_body_node.mutable_attr())["T"] = func_body_node.attr().at("dtype"); func_body_node.mutable_attr()->erase("dtype"); func_body_node.mutable_attr()->erase("shape"); - int input_idx = input_placeholders_idx[func_body_node.name()]; - func_body_node.add_input( - strings::StrCat(func_inputs->name(), ":", input_idx)); + func_body_node.add_input(strings::StrCat(func_inputs->name(), ":", + input_placeholder_idx->second)); } else { // Update the input names if any. for (string& input : *func_body_node.mutable_input()) { @@ -1082,9 +1116,12 @@ Status InlineDirectFunctionCall(const NodeDef& func_node, } } + DCHECK(output_tensors.size() == item.output_size()) + << "Each function output must be mapped to an output tensor"; + // Hook inlined function outputs to IdentityN node. NodeDef* func_outputs = optimized_graph->add_node(); - *func_outputs = InlinedFunctionOutputsNode(func_node, item); + *func_outputs = InlinedFunctionOutputsNode(func_node, item, output_tensors); return Status::OK(); } @@ -1363,11 +1400,11 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, } // Mapping from input placeholder name to function input position. - int idx = 0; absl::flat_hash_map input_placeholders_idx; for (const InputArgExpansion& input_arg : item.inputs()) { for (const string& placeholder : input_arg.placeholders) { - input_placeholders_idx[placeholder] = idx++; + const int idx = input_placeholders_idx.size(); + input_placeholders_idx[placeholder] = idx; } } @@ -1378,8 +1415,11 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // same device as their corresponding input nodes. for (NodeDef& func_body_node : *item.graph.mutable_node()) { - if (item.IsInputPlaceholder(func_body_node.name())) { - const int input_idx = input_placeholders_idx[func_body_node.name()]; + const auto input_placeholder_idx = + input_placeholders_idx.find(func_body_node.name()); + + if (input_placeholder_idx != input_placeholders_idx.end()) { + const int input_idx = input_placeholder_idx->second; const GraphView::OutputPort output_port = ctx->graph_view().GetRegularFanin({&func_node, input_idx}); @@ -1441,16 +1481,23 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // optimized graph: turn placeholders into identities, update nodes // connectivity, etc... + const auto inlined_node_name = [&func_node](const string& name) -> string { + return AddPrefixToNodeName(name, /*prefix=*/func_node.name()); + }; + for (NodeDef& func_body_node : *placed_graph_def.mutable_node()) { - if (item.IsInputPlaceholder(func_body_node.name())) { - // Turn input placeholders into identity node. + const string& node_name = func_body_node.name(); + + // Turn placeholders added in place of input arguments into identity nodes. + const auto input_placeholder_idx = input_placeholders_idx.find(node_name); + if (input_placeholder_idx != input_placeholders_idx.end()) { DCHECK_EQ(0, func_body_node.input_size()); func_body_node.set_op("Identity"); (*func_body_node.mutable_attr())["T"] = func_body_node.attr().at("dtype"); func_body_node.mutable_attr()->erase("dtype"); func_body_node.mutable_attr()->erase("shape"); - const int input_idx = input_placeholders_idx[func_body_node.name()]; - func_body_node.add_input(strings::StrCat(inputs[input_idx].ToString())); + const int input_idx = input_placeholder_idx->second; + func_body_node.add_input(inputs[input_idx].ToString()); // All side effects must happen before inputs can start executing. for (const string& hb_node : happens_before) { @@ -1460,7 +1507,7 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, } else { // Update inputs of the regular function body nodes. for (string& input : *func_body_node.mutable_input()) { - input = AddPrefixToNodeName(input, /*prefix=*/func_node.name()); + input = inlined_node_name(input); } if (func_body_node.input_size() == 0 && !empty_inputs_hook.empty()) { *func_body_node.add_input() = empty_inputs_hook[0]; @@ -1494,7 +1541,7 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, for (NodeDef& func_body_node : *placed_graph_def.mutable_node()) { if (!IsFreeOfSideEffect(func_body_node, &ctx->function_library())) { int num_fanouts = placed_graph_view.NumFanouts( - func_body_node, /*include_controlling_nodes=*/true); + func_body_node, /*include_controlled_nodes=*/true); // If the node doesn't have any outgoing edges and we do not have any // nodes in the `happens_after` set, we can't inline a function and @@ -1514,11 +1561,36 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, } } + // Identity nodes added to the function body in place of function outputs. + absl::flat_hash_set output_nodes; + for (const OutputArgExpansion& output_arg : item.outputs()) { + for (const string& output_node : output_arg.output_nodes) { + output_nodes.insert(inlined_node_name(output_node)); + } + } + + // For each function output value we added an identity node that reads the + // tensor from one of the function body nodes. When we inline function into + // the main graph we want to bypass these nodes, so we keep a mapping from + // 'output node name' -> 'output tensor name'. + absl::flat_hash_map output_tensors; + // Move all the nodes to the optimized graph after successful preprocessing. for (NodeDef& func_body_node : *placed_graph_def.mutable_node()) { + const string& node_name = func_body_node.name(); + + // Skip output identity node, and add a mapping to the output tensor. + if (IsIdentity(func_body_node) && output_nodes.count(node_name)) { + output_tensors.emplace(node_name, func_body_node.input(0)); + continue; + } + optimized_graph->add_node()->Swap(&func_body_node); } + DCHECK(output_tensors.size() == item.output_size()) + << "Each function output must be mapped to an output tensor"; + // TODO(ezhulenev): Inline nested indirect function calls. // Indirect function call is fully inlined into the optimized graph, and we do @@ -1526,10 +1598,13 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // mapping from old output tensors, to the outputs of inlined nodes. int output_idx = 0; for (const OutputArgExpansion& output : item.outputs()) { - for (const string& output_tensor : output.output_tensors) { + for (const string& output_node : output.output_nodes) { + const string inlined_output = inlined_node_name(output_node); + const string& output_tensor = output_tensors.at(inlined_output); + const SafeTensorId from_tensor(func_node.name(), output_idx++); - const SafeTensorId to_tensor = ParseTensorName( - AddPrefixToNodeName(output_tensor, /*prefix=*/func_node.name())); + const SafeTensorId to_tensor = ParseTensorName(output_tensor); + ctx->AddTensorMapping(from_tensor, to_tensor); } } diff --git a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc index 79da7dfa2d..cebd002bed 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc @@ -660,7 +660,7 @@ TEST_F(FunctionOptimizerTest, InlineSymbolicGradient_IdentityFunc) { test::ExpectTensorEqual(expected[0], optimized[0]); } -TEST_F(FunctionOptimizerTest, InlineSymbolicGradient_NoInlineFunc) { +TEST_F(FunctionOptimizerTest, InlineSymbolicGradientNoInlineFunc) { FunctionOptimizer optimizer(RewriterConfig::ON); FunctionDef func = FunctionDefHelper::Define( diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc b/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc index 12db5d6ca9..a061f6194a 100644 --- a/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc @@ -231,7 +231,7 @@ TEST_F(MetaOptimizerTest, RunToggleOptimizersAndCustomGraphOptimizerTwice) { TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { using test::function::NDef; - // Enable ony function optimization. + // Enable only function optimization. ConfigProto config_proto; auto& rewriter_config = *config_proto.mutable_graph_options()->mutable_rewrite_options(); @@ -300,7 +300,7 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { output.library()); // Specialized and optimized functions should be added to the graph. - EXPECT_EQ(6, optimized_flib.num_functions()); + EXPECT_EQ(5, optimized_flib.num_functions()); // Get a specialized function name. const auto specialized_name = [](const string& fn, const string& node, @@ -314,25 +314,22 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { specialized_name("MyQuadratic", "quadratic", "tf_graph"); // MySquare should be specialized and optimized for 3 instantiations: - // 1. 'square' node in the main graph - // 2. 'square' node in the MyQuadratic specialization (not in a fetch set) - // 3. 'quadratic' node in the MyQuadratic specialization (is in a fetch set) + // 1. 'square' node in the main graph + // 2. 'square' node in the MyQuadratic specialization + // 3*. 'quadratic' node in the MyQuadratic specialization + // has identical instantiation context to #2 const string optimized_1 = specialized_name("MySquare", "square", "tf_graph"); const string optimized_2 = specialized_name("MySquare", "square", optimized_0); - const string optimized_3 = - specialized_name("MySquare", "quadratic", optimized_0); const FunctionDef* optimized_func_0 = optimized_flib.Find(optimized_0); const FunctionDef* optimized_func_1 = optimized_flib.Find(optimized_1); const FunctionDef* optimized_func_2 = optimized_flib.Find(optimized_2); - const FunctionDef* optimized_func_3 = optimized_flib.Find(optimized_3); ASSERT_NE(optimized_func_0, nullptr); ASSERT_NE(optimized_func_1, nullptr); ASSERT_NE(optimized_func_2, nullptr); - ASSERT_NE(optimized_func_3, nullptr); // Graph should call optimized function. int count = 0; @@ -351,13 +348,13 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { if (node.name() == "square" && ++count) { EXPECT_EQ(optimized_2, node.op()); } else if (node.name() == "quadratic" && ++count) { - EXPECT_EQ(optimized_3, node.op()); + EXPECT_EQ(optimized_2, node.op()); } } EXPECT_EQ(2, count); - const std::vector optimized_funcs = { - optimized_func_1, optimized_func_2, optimized_func_3}; + const std::vector optimized_funcs = {optimized_func_1, + optimized_func_2}; // MyMul should be inlined into all optimized versions of MySquare. for (const FunctionDef* optimized_func : optimized_funcs) { @@ -403,6 +400,96 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { test::ExpectTensorEqual(tensors_expected[1], tensors[1]); } +TEST_F(MetaOptimizerTest, OptimizeFunctionLibraryPruneUnusedOutputs) { + using test::function::NDef; + + ConfigProto config_proto; + MetaOptimizer optimizer(nullptr, config_proto); + + // MyMul computes x*y three times and has three output values. + FunctionDef my_mul = FunctionDefHelper::Create( + "MyMul", {"x:T", "y:T"}, {"z0:T", "z1:T", "z2:T"}, {"T: {float, int32}"}, + {{{"output0"}, "Mul", {"x", "y"}, {{"T", "$T"}}}, + {{"output1"}, "Mul", {"x", "y"}, {{"T", "$T"}}}, + {{"output2"}, "Mul", {"x", "y"}, {{"T", "$T"}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z0", "output0:z:0"}, {"z1", "output1:z:0"}, {"z2", "output2:z:0"}}); + + // Call MyMyl and forward all three outputs. + FunctionDef my_fwd = FunctionDefHelper::Create( + "Fwd", {"x:T", "y:T"}, {"z0:T", "z1:T", "z2:T"}, {"T: {float, int32}"}, + {{{"output"}, "MyMul", {"x", "y"}, {{"T", "$T"}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z0", "output:z0:0"}, {"z1", "output:z1:0"}, {"z2", "output:z2:0"}}); + + // Mark both functions as `_noinline` to trigger specialization. + (*my_mul.mutable_attr())["_noinline"].set_b(true); + (*my_fwd.mutable_attr())["_noinline"].set_b(true); + std::vector function_library = {my_mul, my_fwd}; + + // Tensorflow graph: + // a = Placeholder[T=float] + // b = Placeholder[T=float] + // fwd = Fwd(a, b) + // + // Fetch fwd:2 via Identity node. + GrapplerItem item; + item.id = "tf_graph"; + item.fetch = {"ret"}; + item.graph = test::function::GDef( + {NDef("a", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + NDef("b", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + NDef("fwd", "Fwd", {"a", "b"}, {{"T", DT_FLOAT}}, kDevice), + NDef("ret", "Identity", {"fwd:2"}, {{"T", DT_FLOAT}}, kDevice)}, + function_library); + + GraphDef output; + TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &output)); + + FunctionLibraryDefinition optimized_flib(OpRegistry::Global(), + output.library()); + + // Specialized functions should be added to the graph. + EXPECT_EQ(3, optimized_flib.num_functions()); + + // Expected names of the specialized functions. + const string specialized_my_fwd = "Fwd_specialized_for_fwd_at_tf_graph"; + const string specialized_my_mul = + absl::StrCat("MyMul_specialized_for_output_at_", specialized_my_fwd); + + // Specialized MyMul should have just one output argument. + FunctionDef expected_my_mul = FunctionDefHelper::Create( + specialized_my_mul, {"x:float", "y:float"}, {"z2:float"}, {}, + {{{"output2"}, "Mul", {"x", "y"}, {{"T", DT_FLOAT}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z2", "output2:z:0"}}); + + // Specialized Fwd should also have just one output argument. + FunctionDef expected_my_fwd = FunctionDefHelper::Create( + specialized_my_fwd, {"x:float", "y:float"}, {"z2:float"}, {}, + {{{"output"}, specialized_my_mul, {"x", "y"}, {{"T", DT_FLOAT}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z2", "output:z2:0"}}); + + const FunctionDef* my_mul_spec = optimized_flib.Find(specialized_my_mul); + const FunctionDef* my_fwd_spec = optimized_flib.Find(specialized_my_fwd); + + ASSERT_NE(my_mul_spec, nullptr); + ASSERT_NE(my_fwd_spec, nullptr); + + CompareFunctions(expected_my_mul, *my_mul_spec); + CompareFunctions(expected_my_fwd, *my_fwd_spec); + + item.feed.emplace_back("a", test::AsScalar(2.0f)); + item.feed.emplace_back("b", test::AsScalar(4.0f)); + auto tensors_expected = EvaluateFetchNodes(item); + + GrapplerItem optimized = item.WithGraph(std::move(output)); + auto tensors = EvaluateFetchNodes(optimized); + + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); +} + TEST_F(MetaOptimizerTest, OptimizeFunctionLibraryPruneFunctionBody) { using test::function::NDef; diff --git a/tensorflow/core/grappler/utils/BUILD b/tensorflow/core/grappler/utils/BUILD index ff15541cdc..1fd0a02b65 100644 --- a/tensorflow/core/grappler/utils/BUILD +++ b/tensorflow/core/grappler/utils/BUILD @@ -182,6 +182,7 @@ cc_library( "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/container:inlined_vector", "@com_google_absl//absl/strings", ], ) diff --git a/tensorflow/core/grappler/utils/functions.cc b/tensorflow/core/grappler/utils/functions.cc index 7c2180ae40..150728d030 100644 --- a/tensorflow/core/grappler/utils/functions.cc +++ b/tensorflow/core/grappler/utils/functions.cc @@ -328,13 +328,12 @@ GrapplerFunctionItem::GrapplerFunctionItem( for (const InputArgExpansion& input_arg : input_arg_expansions_) { for (const string& placeholder : input_arg.placeholders) { feed.push_back({placeholder, Tensor()}); - input_arg_placeholders_.insert(placeholder); } } // Fill the fetch nodes with outputs. for (const OutputArgExpansion& output_arg : output_arg_expansions_) { - for (const string& output_tensor : output_arg.output_tensors) { - fetch.push_back(output_tensor); + for (const string& output_node : output_arg.output_nodes) { + fetch.push_back(output_node); } } @@ -357,11 +356,6 @@ const std::size_t GrapplerFunctionItem::input_size() const { return input_arg_expansions_.size(); } -bool GrapplerFunctionItem::IsInputPlaceholder(const string& node_name) const { - return input_arg_placeholders_.find(node_name) != - input_arg_placeholders_.end(); -} - const std::vector& GrapplerFunctionItem::outputs() const { return output_arg_expansions_; } @@ -514,12 +508,18 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, // TODO(ezhulenev): support functions with tensor sequence inputs/outputs - // Make sure that there is no tensor sequences in outputs + // Make sure that there are no tensor lists in inputs or outputs. + for (const OpDef::ArgDef& input : signature.input_arg()) { + if (!input.type_list_attr().empty() || !input.number_attr().empty()) { + return errors::InvalidArgument( + "Inputs with lists of tensors are not supported. Input: ", + input.name()); + } + } for (const OpDef::ArgDef& output : signature.output_arg()) { if (!output.type_list_attr().empty() || !output.number_attr().empty()) { return errors::InvalidArgument( - "Outputs with sequence of tensors are not supported. Unsupported " - "output: ", + "Outputs with lists of tensors are not supported. Output: ", output.name()); } } @@ -529,13 +529,6 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, // For each input argument create a placeholder in function body. for (const OpDef::ArgDef& input : signature.input_arg()) { - if (!input.type_list_attr().empty() || !input.number_attr().empty()) { - return errors::InvalidArgument( - "Inputs with sequence of tensors are not supported. Unsupported " - "input: ", - input.name()); - } - DataType input_data_type; TF_RETURN_IF_ERROR(instantiation.GetArgType(input, &input_data_type)); @@ -554,8 +547,25 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, inputs.push_back(std::move(input_expansion)); } - // Add all function nodes to the function body + // Keep names of all nodes in the function body to guarantee that we do not + // add an identity with a duplicate name. + absl::flat_hash_set func_body_nodes; + + // Generate unique output node name: "${out_arg_name}_output_node_${index}". + const auto output_node_name = [&func_body_nodes](const OpDef::ArgDef& out, + int index) -> string { + string name = absl::StrCat(out.name(), "_output_node_", index); + int i = 1; + while (func_body_nodes.find(name) != func_body_nodes.end()) { + name = absl::StrCat(out.name(), "_output_node_", index, "_", i++); + } + return name; + }; + + // Add all function nodes to the function body. for (const NodeDef& func_def_node : func.node_def()) { + func_body_nodes.insert(func_def_node.name()); + NodeDef* new_node = function_body.add_node(); *new_node = func_def_node; @@ -578,8 +588,13 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, std::vector outputs; outputs.reserve(signature.output_arg_size()); - // Add function outputs + + // For each function output argument we create an Identity node in the + // function body, that reads output tensor from the function body node. for (const OpDef::ArgDef& out : signature.output_arg()) { + DataType output_data_type; + TF_RETURN_IF_ERROR(instantiation.GetArgType(out, &output_data_type)); + std::vector output_tensors; auto ret = func.ret().find(out.name()); TF_RETURN_IF_ERROR( @@ -589,13 +604,23 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, // Otherwise output must be one of the function inputs : connectivity.ExpandFunctionDefInput(out.name(), &output_tensors)); - DataType output_data_type; - TF_RETURN_IF_ERROR(instantiation.GetArgType(out, &output_data_type)); + absl::InlinedVector output_nodes; + for (int i = 0; i < output_tensors.size(); ++i) { + const string& output_tensor = output_tensors[i]; + + NodeDef* identity = function_body.add_node(); + identity->set_name(output_node_name(out, i)); + identity->set_op("Identity"); + (*identity->mutable_attr())["T"].set_type(output_data_type); + identity->add_input(output_tensor); + + output_nodes.push_back(identity->name()); + } OutputArgExpansion output{/*output_name=*/out.name(), /*data_type=*/output_data_type, /*is_ref=*/out.is_ref(), - /*output_tensors=*/std::move(output_tensors)}; + /*output_nodes=*/std::move(output_nodes)}; outputs.push_back(std::move(output)); } @@ -663,7 +688,6 @@ Status ReplaceInputWithConst(const NodeDef& input_const, int input_index, // Delete placeholder from input expansion. string placeholder_name = input_arg_expansion->placeholders[placeholder_idx]; - item->input_arg_placeholders_.erase(placeholder_name); input_arg_expansion->placeholders.erase( input_arg_expansion->placeholders.begin() + placeholder_idx); @@ -687,43 +711,46 @@ Status ReplaceInputWithConst(const NodeDef& input_const, int input_index, return Status::OK(); } -Status RemoveUnusedOutputs(const absl::flat_hash_set& active_outputs, - GrapplerFunctionItem* item, - std::vector>* output_mapping) { +Status RemoveFunctionOutputs(const absl::flat_hash_set& remove_outputs, + GrapplerFunctionItem* item, + std::vector>* output_mapping) { DCHECK(output_mapping->empty()); - // Do some sanity checking of the active outputs positions. - for (int active_output : active_outputs) { - if (active_output < 0 || active_output >= item->output_size()) { + // Code below assumes that we do not support tensor list outputs and there is + // a 1-to-1 mapping between output tensor and output argument expansion. + for (const OutputArgExpansion& out_arg : item->outputs()) { + DCHECK(out_arg.output_nodes.size() == 1) + << "Output arg expansion must have single output"; + } + + // Do some sanity checking of the removed outputs positions. + for (int remove_output : remove_outputs) { + if (remove_output < 0 || remove_output >= item->output_size()) { return errors::InvalidArgument( - "Active output position is out of bound: active_output=", - active_output, " num_output_args=", item->output_size()); + "Function output index is out of bound: index=", remove_output, + " max_output_index=", item->output_size()); } } - absl::flat_hash_set unused_output_args; - - const auto is_unused_output_arg = [&](const OutputArgExpansion& output) { - return unused_output_args.find(&output) != unused_output_args.end(); + absl::flat_hash_set remove_output_args; + const auto is_remove_output_arg = [&](const OutputArgExpansion& output) { + return remove_output_args.find(&output) != remove_output_args.end(); }; for (int i = 0; i < item->output_size(); ++i) { const OutputArgExpansion& output = item->output(i); - DCHECK(output.output_tensors.size() == 1) - << "Output arg expansion must have single tensor"; - - if (active_outputs.find(i) == active_outputs.end()) { - VLOG(3) << "Remove unused output: output_name=" << output.output_name - << " output_position=" << i; - unused_output_args.insert(&output); - } else if (!unused_output_args.empty()) { + if (remove_outputs.find(i) != remove_outputs.end()) { + VLOG(3) << "Remove functions output: output_name=" << output.output_name + << "(index = " << i << ")"; + remove_output_args.insert(&output); + } else if (!remove_output_args.empty()) { // Add output mapping only if output position changed. - output_mapping->push_back({i, i - unused_output_args.size()}); + output_mapping->push_back({i, i - remove_output_args.size()}); } } auto& o = item->output_arg_expansions_; - o.erase(std::remove_if(o.begin(), o.end(), is_unused_output_arg), o.end()); + o.erase(std::remove_if(o.begin(), o.end(), is_remove_output_arg), o.end()); return Status::OK(); } @@ -735,6 +762,55 @@ Status MakeFunctionDef(const GrapplerFunctionItem& item, func->mutable_signature()->set_description(item.description()); func->mutable_signature()->set_is_stateful(item.is_stateful()); + // Keep track of placeholders that were added to the graph in place of + // expanded function input arguments. + absl::flat_hash_set input_placeholders; + for (const InputArgExpansion& input_arg : item.inputs()) { + for (const string& placeholder : input_arg.placeholders) { + input_placeholders.insert(placeholder); + } + } + + // Keep track of identity nodes that were added to the graph in place of + // expanded function output arguments. + absl::flat_hash_set output_nodes; + for (const OutputArgExpansion& output_arg : item.outputs()) { + for (const string& output_node : output_arg.output_nodes) { + output_nodes.insert(output_node); + } + } + + // If the output identity node was not modified by any optimizer, we can + // bypass it and returns the function value from its input. + absl::flat_hash_map output_tensors; + for (const NodeDef& func_body_node : item.function_body().node()) { + if (!IsIdentity(func_body_node)) continue; + + const string& node_name = func_body_node.name(); + if (output_nodes.find(node_name) != output_nodes.end()) { + // Grappler optimizers might optimize nodes in the fanin of the output + // node, and forward their control dependencies. We can't express control + // dependencies in a function signature, so we have to keep the node. + if (func_body_node.input_size() == 1) { + VLOG(3) << "Bypass function output node: " << node_name << " -> " + << func_body_node.input(0); + output_tensors.emplace(node_name, func_body_node.input(0)); + } else { + VLOG(3) << "Keep function output node: " << node_name; + } + } + } + + // Return output tensor name (input of the output node) if it's safe to bypass + // output node, otherwise returns the output node name. + const auto output_tensor = + [&output_tensors](const OutputArgExpansion& output_arg) -> const string& { + const string& output_node = output_arg.output_nodes[0]; + const auto is_output_tensor = output_tensors.find(output_node); + return is_output_tensor == output_tensors.end() ? output_node + : is_output_tensor->second; + }; + // Build a GrapplerFunctionConnectivity from inputs and new function body. GrapplerFunctionConnectivity connectivity; TF_RETURN_IF_ERROR( @@ -742,8 +818,8 @@ Status MakeFunctionDef(const GrapplerFunctionItem& item, // Add function input arguments. for (const InputArgExpansion& input_arg : item.inputs()) { - CHECK(input_arg.placeholders.size() == 1) // do some sanity checking - << "Inputs of tensor sequences are not supported"; + DCHECK(input_arg.placeholders.size() == 1) // do some sanity checking + << "Inputs of tensor lists are not supported"; OpDef::ArgDef arg_def; arg_def.set_name(input_arg.input_name); @@ -754,8 +830,8 @@ Status MakeFunctionDef(const GrapplerFunctionItem& item, // Add function output arguments. for (const OutputArgExpansion& output_arg : item.outputs()) { - CHECK(output_arg.output_tensors.size() == 1) // do some sanity checking - << "Outputs of tensor sequences are not supported"; + DCHECK(output_arg.output_nodes.size() == 1) // do some sanity checking + << "Outputs of tensor lists are not supported"; OpDef::ArgDef arg_def; arg_def.set_name(output_arg.output_name); @@ -763,11 +839,9 @@ Status MakeFunctionDef(const GrapplerFunctionItem& item, arg_def.set_is_ref(output_arg.is_ref); *func->mutable_signature()->add_output_arg() = arg_def; - string ret; - for (const string& output_tensor : output_arg.output_tensors) { - TF_RETURN_IF_ERROR(connectivity.AsFunctionDefInput(output_tensor, &ret)); - (*func->mutable_ret())[output_arg.output_name] = ret; - } + TF_RETURN_IF_ERROR(connectivity.AsFunctionDefInput( + output_tensor(output_arg), + &(*func->mutable_ret())[output_arg.output_name])); } // Copy function definition specific attributes. @@ -778,12 +852,16 @@ Status MakeFunctionDef(const GrapplerFunctionItem& item, } // Copy function body nodes to the FunctionDef and update input format - for (const NodeDef& func_body_node : item.function_body().node()) { - // Do not copy input placeholders - if (item.IsInputPlaceholder(func_body_node.name())) continue; + for (const NodeDef& func_node : item.function_body().node()) { + const string& name = func_node.name(); + + // Do not copy input placeholders. + if (IsPlaceholder(func_node) && input_placeholders.count(name)) continue; + // Do not copy output nodes that we bypassed. + if (IsIdentity(func_node) && output_tensors.count(name)) continue; NodeDef* func_def_node = func->add_node_def(); - *func_def_node = func_body_node; + *func_def_node = func_node; TF_RETURN_IF_ERROR(connectivity.AsFunctionDefNode(func_def_node)); } diff --git a/tensorflow/core/grappler/utils/functions.h b/tensorflow/core/grappler/utils/functions.h index ce8a3e5ac7..d5a41e7473 100644 --- a/tensorflow/core/grappler/utils/functions.h +++ b/tensorflow/core/grappler/utils/functions.h @@ -21,6 +21,7 @@ limitations under the License. #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" +#include "absl/container/inlined_vector.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/function.pb.h" @@ -32,6 +33,21 @@ limitations under the License. namespace tensorflow { namespace grappler { +// WARNING(ezhulenev): Currently we do not support functions with inputs or +// outputs instantiated into multiple tensors. This can happen if the +// input/output type is 'T*N' or 'list(type)'. This is enforced by multiple +// checks across this file and also function_optimizer.cc. InputArgExpansion and +// OutputArgExpansion already support lists of tensors, but that's pretty much +// it, all other code is written with assumption that expansions are always of +// size 1. MakeGrapplerFunctionItem will gracefully fail with Status error. +// +// This is a low priority feature, because in practice we don't see a lot (any +// at all?) functions with such arguments. Tensorflow-Eager always produces +// functions with plain input/output arguments. + +// TODO(ezhulenev): Support inputs and outputs of type 'T*N'. +// TODO(ezhulenev): Support inputs and outputs of type 'list(type)'. + // Depending on the function instantiation attributes, input argument to the // function might be a single tensor, list of tensors of the same type, or a // list of tensors of different types. @@ -39,30 +55,23 @@ namespace grappler { // InputArgExpansion keeps track of the placeholders that were added to the // function body in place of function inputs and a resolved input data type. struct InputArgExpansion { - // TODO(ezhulenev): Add support for functions with tensor sequence inputs of - // different data types. - // TODO(ezhulenev): Support type parametrized inputs? - string input_name; // name of the function input argument - DataType data_type; // input data type - bool is_ref; // if true, inputs are required to be refs - std::vector placeholders; // names of placeholder nodes in the - // function body + string input_name; + DataType data_type; + bool is_ref; + absl::InlinedVector placeholders; }; // Depending on the function instantiation attributes, output argument is mapped // to one or more outputs of one of the function body nodes. // -// OutputArgExpansion keeps mapping from a function output arg to the output -// tensors of a function body nodes and a resolved output data type +// OutputArgExpansion keeps track of the Identity nodes that were added to the +// function body to forward output tensors. Adding these output nodes allows +// nested function inlining and specialization (see function optimizer). struct OutputArgExpansion { - // TODO(ezhulenev): Add support for functions with tensor sequence outputs of - // different data types. - // TODO(ezhulenev): Support type parametrized outputs? - string output_name; // name of the function output argument - DataType data_type; // output data type - bool is_ref; // if true, outputs are refs - std::vector output_tensors; // names of output tensor from the - // function body nodes + string output_name; + DataType data_type; + bool is_ref; + absl::InlinedVector output_nodes; }; // FunctionDef uses different connectivity encoding for the function body nodes, @@ -87,7 +96,7 @@ class GrapplerFunctionConnectivity { // When expanding inputs in function def format, single input might be // expanded into multiple tensors. When converting back to the function def // format from graph def format, it's always a 1-to-1 relationship. - // FunctionDef built from GrapplerFunctionItem is always specialized to it's + // FunctionDef built from GrapplerFunctionItem is always specialized to its // instantiation attributes and length of input args (and node def outputs) is // known. @@ -144,8 +153,6 @@ class GrapplerFunctionItem : public GrapplerItem { const string& description() const; - bool IsInputPlaceholder(const string& node_name) const; - const std::vector& inputs() const; const InputArgExpansion& input(int i) const; const std::size_t input_size() const; @@ -168,10 +175,9 @@ class GrapplerFunctionItem : public GrapplerItem { GrapplerFunctionItem*); friend Status ReplaceInputWithConst(const NodeDef&, int, GrapplerFunctionItem*); - friend Status RemoveUnusedOutputs( - const absl::flat_hash_set& active_outputs, - GrapplerFunctionItem* item, - std::vector>* output_mapping); + friend Status RemoveFunctionOutputs(const absl::flat_hash_set&, + GrapplerFunctionItem*, + std::vector>*); GrapplerFunctionItem(string func_name, string description, AttrSlice func_attr, @@ -187,16 +193,14 @@ class GrapplerFunctionItem : public GrapplerItem { std::vector input_arg_expansions_; std::vector output_arg_expansions_; - std::set input_arg_placeholders_; - bool is_stateful_ = false; }; // Check if function input/output types are fully defined only at instantiation -// time (parametrized by it's instantiation node). +// time (parametrized by its instantiation node). bool HasParametrizedType(const FunctionDef& func); -// Check if a function body is parametrized by it's instantiation node. Function +// Check if a function body is parametrized by its instantiation node. Function // body is parametrized, if it has at least one node with a 'placeholder' // attribute. bool HasParametrizedBody(const FunctionDef& func); @@ -228,15 +232,16 @@ Status RegisterGrapplerFunctionConnectivity( Status ReplaceInputWithConst(const NodeDef& input_const, int input_index, GrapplerFunctionItem* item); -// Remove function output arguments that do not have any active outputs (output -// tensor connected to other node inputs or in a fetch set). Active outputs uses -// GraphDef output position encoding, and multiple active outputs could -// potentially be connected to the same output argument (in case of tensor list -// outputs). Add output mapping for all active outputs that changed it's output -// position (std::pair). -Status RemoveUnusedOutputs(const absl::flat_hash_set& active_outputs, - GrapplerFunctionItem* item, - std::vector>* output_mapping); +// Removes outputs from instantiated grappler function item. Function node +// outputs use GraphDef output index encoding, and multiple outputs might belong +// to the same output argument expansion (in case of tensor list outputs). For +// all active function outputs that changed its output index, this function adds +// an output mapping (std::pair). +Status RemoveFunctionOutputs(const absl::flat_hash_set& remove_outputs, + GrapplerFunctionItem* item, + std::vector>* output_mapping); + +// TODO(ezhulennev, b/120103818): Add RemoveFunctionInputs. // Make a GrapplerFunctionItem from the function definition and function // instantiation attributes (caller node attributes). Returns error if the given @@ -251,7 +256,7 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, // fully defined (no type or body parametrization). // TODO(ezhulenev): Support parametrized functions without fully defined // instantiation attributes? Do we ever want to optimize parametrized function -// without specializing it to it's instantiation attributes (at least types)? +// without specializing it to its instantiation attributes (at least types)? Status MakeGrapplerFunctionItem(const FunctionDef& func, const FunctionLibraryDefinition& flib, int graph_def_version, diff --git a/tensorflow/core/grappler/utils/functions_test.cc b/tensorflow/core/grappler/utils/functions_test.cc index 29d6100d23..c49920c79c 100644 --- a/tensorflow/core/grappler/utils/functions_test.cc +++ b/tensorflow/core/grappler/utils/functions_test.cc @@ -249,15 +249,16 @@ TEST_F(FunctionsTest, FromSimpleFunctionDef) { flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ("XTimesTwo", item.id); - EXPECT_EQ(4, item.function_body().node_size()); + EXPECT_EQ(5, item.function_body().node_size()); EXPECT_EQ(1, item.input_size()); EXPECT_EQ("x", item.input(0).input_name); - EXPECT_EQ(std::vector{"x"}, item.input(0).placeholders); + ASSERT_EQ(1, item.input(0).placeholders.size()); + EXPECT_EQ("x", item.input(0).placeholders[0]); EXPECT_EQ(1, item.output_size()); EXPECT_EQ("y", item.output(0).output_name); - EXPECT_EQ("y", item.output(0).output_tensors[0]); + EXPECT_EQ("y_output_node_0", item.output(0).output_nodes[0]); int count = 0; for (const NodeDef &node : item.function_body().node()) { @@ -279,9 +280,13 @@ TEST_F(FunctionsTest, FromSimpleFunctionDef) { EXPECT_EQ(2, node.input_size()); EXPECT_EQ("x", node.input(0)); EXPECT_EQ("scale", node.input(1)); + } else if (node.name() == "y_output_node_0" && ++count) { + EXPECT_EQ("Identity", node.op()); + ASSERT_EQ(1, node.input_size()); + EXPECT_EQ("y", node.input(0)); } } - EXPECT_EQ(4, count); + EXPECT_EQ(5, count); } TEST_F(FunctionsTest, FromFunctionDefWithMultiOutputNodes) { @@ -326,7 +331,7 @@ TEST_F(FunctionsTest, FromFunctionDefWithMultiOutputNodes) { flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ("SubGrad", item.id); - EXPECT_EQ(12, item.function_body().node_size()); + EXPECT_EQ(14, item.function_body().node_size()); ASSERT_EQ(3, item.input_size()); EXPECT_EQ("x", item.input(0).input_name); @@ -334,8 +339,8 @@ TEST_F(FunctionsTest, FromFunctionDefWithMultiOutputNodes) { EXPECT_EQ("dz", item.input(2).input_name); ASSERT_EQ(2, item.output_size()); - EXPECT_EQ("dx", item.output(0).output_tensors[0]); - EXPECT_EQ("dy", item.output(1).output_tensors[0]); + EXPECT_EQ("dx_output_node_0", item.output(0).output_nodes[0]); + EXPECT_EQ("dy_output_node_0", item.output(1).output_nodes[0]); int count = 0; for (const NodeDef &node : item.function_body().node()) { @@ -359,9 +364,17 @@ TEST_F(FunctionsTest, FromFunctionDefWithMultiOutputNodes) { EXPECT_EQ(2, node.input_size()); EXPECT_EQ("gy", node.input(0)); EXPECT_EQ("rx:1", node.input(1)); + } else if (node.name() == "dx_output_node_0" && ++count) { + EXPECT_EQ("Identity", node.op()); + ASSERT_EQ(1, node.input_size()); + EXPECT_EQ("dx", node.input(0)); + } else if (node.name() == "dy_output_node_0" && ++count) { + EXPECT_EQ("Identity", node.op()); + ASSERT_EQ(1, node.input_size()); + EXPECT_EQ("dy", node.input(0)); } } - EXPECT_EQ(6, count); + EXPECT_EQ(8, count); } TEST_F(FunctionsTest, FromFunctionDefWithNestedFuncs) { @@ -472,7 +485,7 @@ TEST_F(FunctionsTest, FromFunctionDefWithOutputMappings) { flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ(1, item.output_size()); - EXPECT_EQ("Exp", item.output(0).output_tensors[0]); + EXPECT_EQ("out_output_node_0", item.output(0).output_nodes[0]); int count = 0; for (const NodeDef &node : item.function_body().node()) { @@ -488,9 +501,13 @@ TEST_F(FunctionsTest, FromFunctionDefWithOutputMappings) { EXPECT_EQ("Exp", node.op()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("Linear_func", node.input(0)); + } else if (node.name() == "out_output_node_0" && ++count) { + EXPECT_EQ("Identity", node.op()); + ASSERT_EQ(1, node.input_size()); + EXPECT_EQ("Exp", node.input(0)); } } - EXPECT_EQ(3, count); + EXPECT_EQ(4, count); } TEST_F(FunctionsTest, FromFunctionDefWithInputForwarding) { @@ -517,27 +534,44 @@ TEST_F(FunctionsTest, FromFunctionDefWithInputForwarding) { flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ("ForwardInputs", item.id); - EXPECT_EQ(5, item.function_body().node_size()); + EXPECT_EQ(8, item.function_body().node_size()); EXPECT_EQ(3, item.output_size()); - EXPECT_EQ("in0", item.output(0).output_tensors[0]); - EXPECT_EQ("arg2", item.output(1).output_tensors[0]); - EXPECT_EQ("arg3", item.output(2).output_tensors[0]); + EXPECT_EQ("out0_output_node_0", item.output(0).output_nodes[0]); + EXPECT_EQ("arg2_output_node_0", item.output(1).output_nodes[0]); + EXPECT_EQ("arg3_output_node_0", item.output(2).output_nodes[0]); int count = 0; + + const auto is_arg_placeholder = [](const string &name) { + return name == "in0" || name == "in1" || name == "arg2" || name == "arg3" || + name == "arg4"; + }; + for (const NodeDef &node : item.function_body().node()) { - EXPECT_TRUE(node.name() == "in0" || node.name() == "in1" || - node.name() == "arg2" || node.name() == "arg3" || - node.name() == "arg4"); - count++; - EXPECT_EQ("Placeholder", node.op()); - if (node.name() == "arg3") { - EXPECT_EQ(DT_INT32, node.attr().at("dtype").type()); - } else { - EXPECT_EQ(DT_FLOAT, node.attr().at("dtype").type()); + if (is_arg_placeholder(node.name()) && node.op() == "Placeholder") { + count++; + if (node.name() == "arg3") { + EXPECT_EQ(DT_INT32, node.attr().at("dtype").type()); + } else { + EXPECT_EQ(DT_FLOAT, node.attr().at("dtype").type()); + } + continue; + } + + EXPECT_EQ("Identity", node.op()); + ASSERT_EQ(1, node.input_size()); + EXPECT_TRUE(is_arg_placeholder(node.input(0))); + + if (node.name() == "out0_output_node_0" && ++count) { + EXPECT_EQ("in0", node.input(0)); + } else if (node.name() == "arg2_output_node_0" && ++count) { + EXPECT_EQ("arg2", node.input(0)); + } else if (node.name() == "arg3_output_node_0" && ++count) { + EXPECT_EQ("arg3", node.input(0)); } } - EXPECT_EQ(5, count); + EXPECT_EQ(8, count); } TEST_F(FunctionsTest, FromFunctionDefWithoutInput) { @@ -566,16 +600,22 @@ TEST_F(FunctionsTest, FromFunctionDefWithoutInput) { EXPECT_EQ(0, item.input_size()); EXPECT_EQ(1, item.output_size()); - EXPECT_EQ("o", item.output(0).output_tensors[0]); + EXPECT_EQ("o_output_node_0", item.output(0).output_nodes[0]); + EXPECT_EQ(3, item.function_body().node_size()); - EXPECT_EQ(2, item.function_body().node_size()); const NodeDef &two = item.function_body().node(0); EXPECT_EQ("two", two.name()); EXPECT_EQ(0, two.input_size()); + const NodeDef &cast = item.function_body().node(1); EXPECT_EQ("o", cast.name()); EXPECT_EQ(1, cast.input_size()); EXPECT_EQ("two", cast.input(0)); + + const NodeDef &retval = item.function_body().node(2); + EXPECT_EQ("o_output_node_0", retval.name()); + EXPECT_EQ(1, retval.input_size()); + EXPECT_EQ("o", retval.input(0)); } TEST_F(FunctionsTest, FromFunctionDefWithSideEffectfulOps) { @@ -674,7 +714,7 @@ TEST_F(FunctionsTest, ReplaceInputWithConst) { EXPECT_EQ(2, item.input_size()); EXPECT_EQ(1, item.output_size()); - ASSERT_EQ(3, item.function_body().node_size()); + ASSERT_EQ(4, item.function_body().node_size()); const NodeDef &input_x = item.function_body().node(0); const NodeDef &input_y = item.function_body().node(1); @@ -748,8 +788,9 @@ TEST_F(FunctionsTest, SwapFunctionBodyAndMakeFunctionDef) { {{"z", "output:z:0"}}); GraphDef id_func_body = test::function::GDef( - {/* pass input to output through identity */ - NDef("output", "Identity", {"x"}, {{"T", "float"}})}); + {/* Read and return input argument through Identity node. */ + NDef("read_x", "Identity", {"x"}, {{"T", "float"}}), + NDef("z_output_node_0", "Identity", {"read_x"}, {{"T", "float"}})}); protobuf::Map func_instantiation_attr; func_instantiation_attr["T"].set_type(DT_FLOAT); @@ -772,15 +813,15 @@ TEST_F(FunctionsTest, SwapFunctionBodyAndMakeFunctionDef) { // Check that graph body was updated. int count = 0; for (const NodeDef &node : specialized.node_def()) { - if (node.name() == "output" && ++count) { + if (node.name() == "read_x" && ++count) { EXPECT_EQ("Identity", node.op()); EXPECT_EQ("x:0", node.input(0)); } } EXPECT_EQ(1, count); - // And return tensor mapping was updated with a new output name (z->output). - EXPECT_EQ("output:output:0", (*specialized.mutable_ret())["z"]); + // And return tensor mapping was updated with a new output name (z->read_x). + EXPECT_EQ("read_x:output:0", (*specialized.mutable_ret())["z"]); } TEST_F(FunctionsTest, FunctionDefGrapplerFunctionItemRoundTrip) { diff --git a/tensorflow/core/grappler/utils/grappler_test.cc b/tensorflow/core/grappler/utils/grappler_test.cc index 5ef1cf444b..1b4b9f9a51 100644 --- a/tensorflow/core/grappler/utils/grappler_test.cc +++ b/tensorflow/core/grappler/utils/grappler_test.cc @@ -14,7 +14,9 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/grappler/utils/grappler_test.h" + #include + #include "absl/algorithm/container.h" #include "tensorflow/core/framework/attr_value_util.h" #include "tensorflow/core/grappler/utils.h" @@ -25,6 +27,46 @@ limitations under the License. namespace tensorflow { namespace grappler { +namespace { +void CompareGraphNodes(protobuf::RepeatedPtrField* want, + protobuf::RepeatedPtrField* got) { + auto comparator = [](const NodeDef& n1, const NodeDef& n2) -> bool { + return n1.name() < n2.name(); + }; + + std::sort(want->begin(), want->end(), comparator); + std::sort(got->begin(), got->end(), comparator); + + ASSERT_EQ(want->size(), got->size()); + + for (int i = 0; i < want->size(); ++i) { + NodeDef& want_node = (*want)[i]; + NodeDef& got_node = (*got)[i]; + + EXPECT_EQ(want_node.op(), got_node.op()); + EXPECT_EQ(want_node.name(), got_node.name()); + EXPECT_EQ(want_node.device(), got_node.device()); + ASSERT_EQ(want_node.input_size(), got_node.input_size()); + + // Order of control dependencies doesn't matter, so we sort them first. + const auto is_control = [](const string& input) -> bool { + return ParseTensorName(input).index() < 0; + }; + + auto want_inputs = want_node.mutable_input(); + auto got_inputs = got_node.mutable_input(); + std::sort(absl::c_find_if(*want_inputs, is_control), want_inputs->end()); + std::sort(absl::c_find_if(*got_inputs, is_control), got_inputs->end()); + + for (int j = 0; j < want_node.input_size(); ++j) { + const TensorId want_tensor = ParseTensorName(want_node.input(j)); + const TensorId got_tensor = ParseTensorName(got_node.input(j)); + EXPECT_EQ(want_tensor.ToString(), got_tensor.ToString()); + } + } +} +} // namespace + GrapplerTest::GrapplerTest() { // Turn off all the automatic optimizations to ensure that we run the graph // exactly as it is given to us. This ensures that we can compare the results @@ -96,35 +138,11 @@ NodeDef* GrapplerTest::AddNode( } void GrapplerTest::CompareGraphs(GraphDef want, GraphDef got) const { - auto comparator = [](const NodeDef& n1, const NodeDef& n2) -> bool { - return n1.name() < n2.name(); - }; - std::sort(want.mutable_node()->begin(), want.mutable_node()->end(), - comparator); - std::sort(got.mutable_node()->begin(), got.mutable_node()->end(), comparator); - - for (int i = 0; i < want.node_size(); ++i) { - std::sort(want.mutable_node(i)->mutable_input()->begin(), - want.mutable_node(i)->mutable_input()->end()); - } - for (int i = 0; i < got.node_size(); ++i) { - std::sort(got.mutable_node(i)->mutable_input()->begin(), - got.mutable_node(i)->mutable_input()->end()); - } - - ASSERT_EQ(want.node_size(), got.node_size()); - for (int i = 0; i < want.node_size(); ++i) { - EXPECT_EQ(want.node(i).op(), got.node(i).op()); - EXPECT_EQ(want.node(i).name(), got.node(i).name()); - EXPECT_EQ(want.node(i).device(), got.node(i).device()); + CompareGraphNodes(want.mutable_node(), got.mutable_node()); +} - ASSERT_EQ(want.node(i).input_size(), got.node(i).input_size()); - for (int j = 0; j < want.node(i).input_size(); ++j) { - const TensorId want_tensor = ParseTensorName(want.node(i).input(j)); - const TensorId got_tensor = ParseTensorName(got.node(i).input(j)); - EXPECT_EQ(want_tensor.ToString(), got_tensor.ToString()); - } - } +void GrapplerTest::CompareFunctions(FunctionDef want, FunctionDef got) const { + CompareGraphNodes(want.mutable_node_def(), got.mutable_node_def()); } void GrapplerTest::CompareNodes(const NodeDef& want, const NodeDef& got) const { diff --git a/tensorflow/core/grappler/utils/grappler_test.h b/tensorflow/core/grappler/utils/grappler_test.h index 20fd04c1c6..26c1db3740 100644 --- a/tensorflow/core/grappler/utils/grappler_test.h +++ b/tensorflow/core/grappler/utils/grappler_test.h @@ -62,6 +62,13 @@ class GrapplerTest : public ::testing::Test { // equality, and adds all failuires to the current test. void CompareNodes(const NodeDef& want, const NodeDef& got) const; + // Checks if two functions are equal. Both functions must have the same set of + // nodes with the same inputs and attributes. Nodes can be in different order. + // + // NOTE: This function uses EXPECT/ASSERT macros to check node properties + // equality, and adds all failures to the current test. + void CompareFunctions(FunctionDef want, FunctionDef got) const; + // Checks if node 'src' is directly connected to the input($position) of // 'dst'. bool IsNodesDirectlyConnected(const NodeMap& node_map, const string& src, -- GitLab From 0a88318ac73a3d30dfee4ab1541070cbf82fe05c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 14:04:07 -0800 Subject: [PATCH 0447/2345] Add `tf.distribute.Strategy.experimental_make_numpy_iterator()` function. PiperOrigin-RevId: 228584021 --- tensorflow/contrib/distribute/python/BUILD | 4 + .../python/collective_all_reduce_strategy.py | 8 +- .../collective_all_reduce_strategy_test.py | 7 ++ .../contrib/distribute/python/keras_test.py | 58 +++------ .../distribute/python/mirrored_strategy.py | 30 +++++ .../python/mirrored_strategy_multigpu_test.py | 3 + .../distribute/python/one_device_strategy.py | 9 +- .../python/one_device_strategy_test.py | 4 + .../python/parameter_server_strategy.py | 31 +++++ .../python/parameter_server_strategy_test.py | 12 ++ .../distribute/python/strategy_test_lib.py | 29 +++++ .../contrib/distribute/python/tpu_strategy.py | 9 ++ tensorflow/python/distribute/BUILD | 32 +++++ .../python/distribute/distribute_lib.py | 72 +++++++++++- .../python/distribute/mirrored_strategy.py | 10 ++ tensorflow/python/distribute/numpy_dataset.py | 97 +++++++++++++++ .../python/distribute/numpy_dataset_test.py | 44 +++++++ .../distribute/parameter_server_strategy.py | 13 +- .../engine/distributed_training_utils.py | 111 +----------------- tensorflow/python/keras/engine/training.py | 62 +++++----- .../keras/engine/training_distributed.py | 6 - tensorflow/python/training/optimizer.py | 13 +- ...orflow.distribute.-mirrored-strategy.pbtxt | 4 + ...orflow.distribute.-strategy-extended.pbtxt | 4 + .../v1/tensorflow.distribute.-strategy.pbtxt | 4 + ...orflow.distribute.-mirrored-strategy.pbtxt | 4 + ...orflow.distribute.-strategy-extended.pbtxt | 4 + .../v2/tensorflow.distribute.-strategy.pbtxt | 4 + 28 files changed, 490 insertions(+), 198 deletions(-) create mode 100644 tensorflow/python/distribute/numpy_dataset.py create mode 100644 tensorflow/python/distribute/numpy_dataset_test.py diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 7ee12812af..d4758d7518 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -131,6 +131,7 @@ py_library( "//tensorflow/python:math_ops", "//tensorflow/python/distribute:distribute_lib", "//tensorflow/python/distribute:input_lib", + "//tensorflow/python/distribute:numpy_dataset", "//tensorflow/python/distribute:reduce_util", "//tensorflow/python/distribute:values", "//tensorflow/python/eager:context", @@ -153,6 +154,7 @@ py_library( "//tensorflow/python/distribute:cross_device_utils", "//tensorflow/python/distribute:input_lib", "//tensorflow/python/distribute:multi_worker_util", + "//tensorflow/python/distribute:numpy_dataset", "//tensorflow/python/distribute:values", "//tensorflow/python/eager:context", ], @@ -178,6 +180,7 @@ py_library( "//tensorflow/python/eager:backprop", "//tensorflow/python/eager:context", "//tensorflow/python/eager:test", + "//third_party/py/numpy", ], ) @@ -303,6 +306,7 @@ py_library( "//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", ], diff --git a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py index f6361cb6e8..39756b32c5 100644 --- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py +++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py @@ -28,6 +28,7 @@ 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 multi_worker_util +from tensorflow.python.distribute import numpy_dataset from tensorflow.python.distribute import values from tensorflow.python.eager import context from tensorflow.python.framework import ops @@ -86,6 +87,7 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): else: local_devices = ("/device:CPU:0",) self._worker_device = device_util.canonicalize("/device:CPU:0") + self._host_input_device = numpy_dataset.SingleDevice(self._worker_device) self._collective_keys = cross_device_utils.CollectiveKeys() self._initialize_local(local_devices) @@ -121,6 +123,7 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): task_id) self._worker_device = "/job:%s/task:%d" % (task_type, task_id) + self._host_input_device = numpy_dataset.SingleDevice(self._worker_device) if num_gpus_per_worker: local_devices = tuple( "%s/device:GPU:%d" % (self._worker_device, i) @@ -157,6 +160,9 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): 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 @@ -347,4 +353,4 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): # TODO(priyag): Delete this once all strategies use global batch size. @property def _global_batch_size(self): - return False + return True 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 4c8c01a216..c3e9c55e96 100644 --- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py +++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py @@ -466,6 +466,13 @@ class LocalCollectiveAllReduceStrategy( with self.cached_session(config=config, target=target): self._test_all_reduce_mean_gradient_tape(distribution) + def testNumpyIterator(self): + num_gpus = 2 + if context.num_gpus() < num_gpus: + self.skipTest('Not enough GPUs') + strategy, _, _ = self._get_test_object(None, None, num_gpus) + self._test_numpy_iterator(strategy) + if __name__ == '__main__': test.main() diff --git a/tensorflow/contrib/distribute/python/keras_test.py b/tensorflow/contrib/distribute/python/keras_test.py index 1cb5fa30a3..40916afcfa 100644 --- a/tensorflow/contrib/distribute/python/keras_test.py +++ b/tensorflow/contrib/distribute/python/keras_test.py @@ -429,15 +429,6 @@ class TestEstimatorDistributionStrategy(test_util.TensorFlowTestCase, class TestDistributionStrategyWithNumpyArrays(test.TestCase, parameterized.TestCase): - @combinations.generate(strategy_for_numpy_input_combinations()) - def test_creating_var_with_numpy_arrays(self, distribution): - with self.cached_session(): - x = np.asarray(np.random.random((64, 3)), dtype=np.float32) - var_x = distributed_training_utils.get_var_for_numpy(distribution, x) - val = self.evaluate(var_x.value()) - # Verify that the numpy value is copied to the variable. - self.assertAllEqual(x, val) - @combinations.generate(strategy_for_numpy_input_combinations()) def test_calculating_input_params_no_steps_no_batch_size(self, distribution): # Calculate the per_replica_batch_size scaling factor for strategies @@ -576,26 +567,26 @@ class TestDistributionStrategyWithNumpyArrays(test.TestCase, metrics = ['mae'] model.compile(optimizer, loss, metrics=metrics) - inputs = np.zeros((64, 3), dtype=np.float32) - targets = np.zeros((64, 4), dtype=np.float32) + inputs = np.zeros((64, 3), dtype=np.float32) + targets = np.zeros((64, 4), dtype=np.float32) - # Call fit with validation data - model.fit(inputs, targets, epochs=1, batch_size=2, verbose=0, - validation_data=(inputs, targets)) + # Call fit with validation data + model.fit(inputs, targets, epochs=1, batch_size=2, verbose=0, + validation_data=(inputs, targets)) - # TODO(anjalisridhar): We need tests for when the batch size and steps are - # smaller and results in a 0 batch_size and steps value. - model.evaluate(inputs, targets) - # with steps - model.evaluate(inputs, targets, steps=2) - # with batch_size - model.evaluate(inputs, targets, batch_size=8) + # TODO(anjalisridhar): We need tests for when the batch size and steps + # are smaller and results in a 0 batch_size and steps value. + model.evaluate(inputs, targets) + # with steps + model.evaluate(inputs, targets, steps=2) + # with batch_size + model.evaluate(inputs, targets, batch_size=8) - model.predict(inputs) - # with steps - model.predict(inputs, steps=2) - # with batch_size - model.predict(inputs, batch_size=8) + model.predict(inputs) + # with steps + model.predict(inputs, steps=2) + # with batch_size + model.predict(inputs, batch_size=8) @combinations.generate(strategy_for_numpy_input_combinations()) def test_calling_model_with_nested_numpy_arrays(self, distribution): @@ -1192,21 +1183,6 @@ class TestDistributionStrategyValidation(test.TestCase, metrics = ['mae', keras.metrics.CategoricalAccuracy()] model.compile(optimizer, loss, metrics=metrics) - @combinations.generate(all_strategy_combinations_minus_default()) - def test_loop_in_scope(self, distribution): - with self.cached_session(): - with self.assertRaisesRegexp( - RuntimeError, 'should not be run inside the tf.distribute.Strategy'): - with distribution.scope(): - x = keras.layers.Input(shape=(3,), name='input') - y = keras.layers.Dense(4, name='dense')(x) - model = keras.Model(x, y) - optimizer = gradient_descent.GradientDescentOptimizer(0.001) - loss = 'mse' - model.compile(optimizer, loss) - input_array = np.zeros((3, 3), dtype=np.float32) - model.predict(input_array) - if __name__ == '__main__': test.main() diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy.py b/tensorflow/contrib/distribute/python/mirrored_strategy.py index e3ab2bf19e..5fa36fb402 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy.py @@ -104,6 +104,36 @@ class MirroredStrategy(distribute_lib.DistributionStrategy): auto_shard_dataset) super(MirroredStrategy, self).__init__(extended) + # Override to change the documentation to reflect the different handling of + # global vs. local batch size between core and contrib. + def experimental_make_numpy_iterator( # pylint: disable=useless-super-delegation + self, numpy_input, batch_size, num_epochs=1, shuffle=1024, session=None): + """Makes an iterator for input provided via a nest of numpy arrays. + + NOTE: The `batch_size` argument here has different behavior for this + contrib version of `MirroredStrategy`. + + Args: + numpy_input: A nest of NumPy input arrays that will be distributed evenly + across all replicas. + batch_size: The number of entries from the array we should consume in one + step of the computation, across all replicas. This is the per-replica + batch size. The global batch size will be this times + `num_replicas_in_sync`. + num_epochs: The number of times to iterate through the examples. A value + of `None` means repeat forever. + shuffle: Size of buffer to use for shuffling the input examples. + Use `None` to disable shuffling. + session: (TensorFlow v1.x graph execution only) A session used for + initialization. + + Returns: + An `tf.distribute.InputIterator` which returns inputs for each step of the + computation. User should call `initialize` on the returned iterator. + """ + return super(MirroredStrategy, self).experimental_make_numpy_iterator( + numpy_input, batch_size, num_epochs, shuffle, session) + class MirroredExtended(CoreMirroredExtended): """Implementation of (contrib) MirroredStrategy.""" diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py index 9821828b2d..59d711ae01 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py @@ -116,6 +116,9 @@ class MirroredTwoDeviceDistributionTest( self._test_input_fn_iterator(iterator, distribution.extended.worker_devices, expected_values) + def testNumpyIterator(self, distribution): + self._test_numpy_iterator(distribution) + def testGlobalStepUpdate(self, distribution): self._test_global_step_update(distribution) diff --git a/tensorflow/contrib/distribute/python/one_device_strategy.py b/tensorflow/contrib/distribute/python/one_device_strategy.py index fb470f8546..34b0c31087 100644 --- a/tensorflow/contrib/distribute/python/one_device_strategy.py +++ b/tensorflow/contrib/distribute/python/one_device_strategy.py @@ -21,6 +21,7 @@ from __future__ import print_function 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 values from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes @@ -50,8 +51,8 @@ class OneDeviceExtended(distribute_lib.DistributionStrategyExtended): super(OneDeviceExtended, self).__init__(container_strategy) self._device = device self._default_device = device - worker = device_util.canonicalize("/device:CPU:0") - worker_device_pairs = [(worker, [self._device])] + self._input_device = device_util.canonicalize("/device:CPU:0") + worker_device_pairs = [(self._input_device, [self._device])] device_map = values.SingleDeviceMap(device) self._input_workers = input_lib.InputWorkers( device_map, worker_device_pairs) @@ -82,6 +83,10 @@ class OneDeviceExtended(distribute_lib.DistributionStrategyExtended): return input_lib.InputFunctionIterator( input_fn, self._input_workers, [distribute_lib.InputContext()]) + def _experimental_make_numpy_dataset(self, numpy_input, session): + return numpy_dataset.one_host_numpy_dataset( + numpy_input, self._input_device, session) + def _broadcast_to(self, tensor, destinations): del destinations return tensor diff --git a/tensorflow/contrib/distribute/python/one_device_strategy_test.py b/tensorflow/contrib/distribute/python/one_device_strategy_test.py index 2403dc8f12..f81466a6c7 100644 --- a/tensorflow/contrib/distribute/python/one_device_strategy_test.py +++ b/tensorflow/contrib/distribute/python/one_device_strategy_test.py @@ -59,6 +59,10 @@ class OneDeviceStrategyTest( self._test_input_fn_iterator( iterator, d.extended.worker_devices, expected_values) + @test_util.run_in_graph_and_eager_modes + def testNumpyIterator(self): + self._test_numpy_iterator(self._get_distribution_strategy()) + def testAllReduceSum(self): self._test_all_reduce_sum(self._get_distribution_strategy()) diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy.py b/tensorflow/contrib/distribute/python/parameter_server_strategy.py index bb0b8eb992..0785427c2c 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy.py @@ -89,6 +89,37 @@ class ParameterServerStrategy(distribute_lib.DistributionStrategy): super(ParameterServerStrategy, self).__init__( ParameterServerExtended(self, num_gpus_per_worker)) + # Override to change the documentation to reflect the different handling of + # global vs. local batch size between core and contrib. + def experimental_make_numpy_iterator( # pylint: disable=useless-super-delegation + self, numpy_input, batch_size, num_epochs=1, shuffle=1024, session=None): + """Makes an iterator for input provided via a nest of numpy arrays. + + NOTE: The `batch_size` argument here has different behavior for this + contrib version of `ParameterServerStrategy`. + + Args: + numpy_input: A nest of NumPy input arrays that will be distributed evenly + across all replicas. + batch_size: The number of entries from the array we should consume in one + step of the computation, across all replicas. This is the per-replica + batch size. The global batch size will be this times + `num_replicas_in_sync`. + num_epochs: The number of times to iterate through the examples. A value + of `None` means repeat forever. + shuffle: Size of buffer to use for shuffling the input examples. + Use `None` to disable shuffling. + session: (TensorFlow v1.x graph execution only) A session used for + initialization. + + Returns: + An `tf.distribute.InputIterator` which returns inputs for each step of the + computation. User should call `initialize` on the returned iterator. + """ + return super(ParameterServerStrategy, + self).experimental_make_numpy_iterator( + numpy_input, batch_size, num_epochs, shuffle, session) + class ParameterServerExtended(CoreParameterServerExtended): """Implementation of ParameterServerStrategy.""" diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py index 7836687e7d..802809e7c7 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py @@ -893,5 +893,17 @@ class ParameterServerStrategyWithChiefTest(ParameterServerStrategyTestBase, strategy.extended.call_for_each_replica(f) +class LocalParameterServerStrategyTest(strategy_test_lib.DistributionTestBase, + parameterized.TestCase): + + @combinations.generate(combinations.combine(mode=['graph', 'eager'], + use_core_strategy=[True, False], + required_gpus=2)) + def testNumpyIterator(self, use_core_strategy): + strategy, _, _ = create_test_objects( + num_gpus=2, use_core_strategy=use_core_strategy) + self._test_numpy_iterator(strategy) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/contrib/distribute/python/strategy_test_lib.py b/tensorflow/contrib/distribute/python/strategy_test_lib.py index 4fbd630cf7..7455cbd02a 100644 --- a/tensorflow/contrib/distribute/python/strategy_test_lib.py +++ b/tensorflow/contrib/distribute/python/strategy_test_lib.py @@ -18,6 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import numpy as np + from tensorflow.core.protobuf import config_pb2 from tensorflow.python.data.ops import dataset_ops from tensorflow.python.distribute import distribution_strategy_context as ds_context @@ -295,6 +297,33 @@ class DistributionTestBase(test.TestCase): global_step_values = self.evaluate(global_step_tensors) self.assertEqual((1,) * len(global_step_tensors), global_step_values) + def _test_numpy_iterator(self, strategy): + with strategy.scope(), self.cached_session() as sess: + x = np.asarray([[1, 2], [6, 12], [2, 4], + [5, 10], [3, 6], [4, 8]]) + y = np.asarray([5, 4, 3, 2, 1, 0]) + batch_size = 6 + if not strategy.extended._global_batch_size: # pylint: disable=protected-access + batch_size = batch_size // strategy.num_replicas_in_sync + i = strategy.experimental_make_numpy_iterator( + (x, y), batch_size=batch_size, num_epochs=2, shuffle=None, + session=sess) + self.evaluate(i.initialize()) + + def run_and_concatenate(strategy, i): + x, y = strategy.experimental_run(lambda z: z, i) + x, y = self.evaluate((strategy.unwrap(x), strategy.unwrap(y))) + return np.concatenate(x), np.concatenate(y) + + x_1, y_1 = run_and_concatenate(strategy, i) + self.assertAllEqual(x, x_1) + self.assertAllEqual(y, y_1) + x_2, y_2 = run_and_concatenate(strategy, i) + self.assertAllEqual(x, x_2) + self.assertAllEqual(y, y_2) + with self.assertRaises(errors.OutOfRangeError): + run_and_concatenate(strategy, i) + class OneDeviceDistributionTestBase(test.TestCase): """Some tests that should work with any one-device DistributionStrategy.""" diff --git a/tensorflow/contrib/distribute/python/tpu_strategy.py b/tensorflow/contrib/distribute/python/tpu_strategy.py index 00360bf996..3f89c5869e 100644 --- a/tensorflow/contrib/distribute/python/tpu_strategy.py +++ b/tensorflow/contrib/distribute/python/tpu_strategy.py @@ -34,6 +34,7 @@ from tensorflow.python.distribute import cross_device_ops as cross_device_ops_li 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 tpu_cluster_resolver as resolver_lib @@ -303,6 +304,11 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended): functools.partial(self._call_dataset_fn, dataset_fn), self._input_workers) + 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. @@ -466,6 +472,9 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended): 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 diff --git a/tensorflow/python/distribute/BUILD b/tensorflow/python/distribute/BUILD index faaa61934a..a6a1c470b4 100644 --- a/tensorflow/python/distribute/BUILD +++ b/tensorflow/python/distribute/BUILD @@ -124,6 +124,7 @@ py_library( srcs_version = "PY2AND3", deps = [ ":device_util", + ":numpy_dataset", ":reduce_util", "//tensorflow/python:array_ops", "//tensorflow/python:constant_op", @@ -221,6 +222,7 @@ py_library( ":distribute_lib", ":input_lib", ":multi_worker_util", + ":numpy_dataset", ":reduce_util", ":shared_variable_creator", ":values", @@ -249,6 +251,7 @@ py_library( deps = [ ":input_lib", ":mirrored_strategy", + ":numpy_dataset", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:framework_ops", @@ -276,6 +279,35 @@ py_library( ], ) +py_library( + name = "numpy_dataset", + srcs = ["numpy_dataset.py"], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:dtypes", + "//tensorflow/python:framework_ops", + "//tensorflow/python:util", + "//tensorflow/python:variable_scope", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/eager:context", + "//third_party/py/numpy", + ], +) + +py_test( + name = "numpy_dataset_test", + size = "small", + srcs = ["numpy_dataset_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":numpy_dataset", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:variable_scope", + "//tensorflow/python/eager:test", + "//third_party/py/numpy", + ], +) + py_library( name = "input_lib", srcs = ["input_lib.py"], diff --git a/tensorflow/python/distribute/distribute_lib.py b/tensorflow/python/distribute/distribute_lib.py index 4ad8cc00b8..31213ab647 100644 --- a/tensorflow/python/distribute/distribute_lib.py +++ b/tensorflow/python/distribute/distribute_lib.py @@ -26,6 +26,7 @@ import enum from tensorflow.python.data.ops import dataset_ops from tensorflow.python.distribute import device_util from tensorflow.python.distribute import distribution_strategy_context +from tensorflow.python.distribute import numpy_dataset from tensorflow.python.distribute import reduce_util from tensorflow.python.eager import context as eager_context from tensorflow.python.framework import constant_op @@ -360,7 +361,7 @@ class DistributionStrategy(object): return self._extended._distribute_dataset(dataset_fn) # pylint: disable=protected-access def make_dataset_iterator(self, dataset): - """Makes an iterator for input provided via input_dataset. + """Makes an iterator for input provided via `dataset`. Data from the given dataset will be distributed evenly across all the compute replicas. We will assume that the input dataset is batched by the @@ -418,6 +419,40 @@ class DistributionStrategy(object): return self.extended._make_input_fn_iterator( # pylint: disable=protected-access input_fn, replication_mode=replication_mode) + def experimental_make_numpy_iterator( + self, numpy_input, batch_size, num_epochs=1, shuffle=1024, session=None): + """Makes an iterator for input provided via a nest of numpy arrays. + + Args: + numpy_input: A nest of NumPy input arrays that will be distributed evenly + across all replicas. Note that lists of Numpy arrays are stacked, + as that is normal `tf.data.Dataset` behavior. + batch_size: The number of entries from the array we should consume in one + step of the computation, across all replicas. This is the global batch + size. It should be divisible by `num_replicas_in_sync`. + num_epochs: The number of times to iterate through the examples. A value + of `None` means repeat forever. + shuffle: Size of buffer to use for shuffling the input examples. + Use `None` to disable shuffling. + session: (TensorFlow v1.x graph execution only) A session used for + initialization. + + Returns: + An `tf.distribute.InputIterator` which returns inputs for each step of the + computation. User should call `initialize` on the returned iterator. + """ + ds = self.extended.experimental_make_numpy_dataset( + numpy_input, session=session) + if shuffle: + ds = ds.shuffle(shuffle) + if num_epochs != 1: + ds = ds.repeat(num_epochs) + # We need to use the drop_remainder argument to get a known static + # input shape which is required for TPUs. + drop_remainder = self.extended.experimental_require_static_shapes + ds = ds.batch(batch_size, drop_remainder=drop_remainder) + return self.make_dataset_iterator(ds) + def experimental_run(self, fn, input_iterator=None): """Runs ops in `fn` on each replica, with inputs from `input_iterator`. @@ -1083,6 +1118,29 @@ class DistributionStrategyExtended(object): def _make_input_fn_iterator(self, input_fn, replication_mode): raise NotImplementedError("must be implemented in descendants") + def experimental_make_numpy_dataset(self, numpy_input, session=None): + """Makes a dataset for input provided via a numpy array. + + This avoids adding `numpy_input` as a large constant in the graph, + and copies the data to the machine or machines that will be processing + the input. + + Args: + numpy_input: A nest of NumPy input arrays that will be distributed evenly + across all replicas. Note that lists of Numpy arrays are stacked, + as that is normal `tf.data.Dataset` behavior. + session: (TensorFlow v1.x graph execution only) A session used for + initialization. + + Returns: + A `tf.data.Dataset` representing `numpy_input`. + """ + _require_cross_replica_context_extended(self) + return self._experimental_make_numpy_dataset(numpy_input, session=session) + + def _experimental_make_numpy_dataset(self, numpy_input, session): + raise NotImplementedError("must be implemented in descendants") + def broadcast_to(self, tensor, destinations): """Mirror a tensor on one device to all worker devices. @@ -1660,6 +1718,18 @@ class _DefaultDistributionExtended(DistributionStrategyExtended): replication_mode=InputReplicationMode.PER_WORKER): return input_fn(InputContext()).make_initializable_iterator() + def _experimental_make_numpy_dataset(self, numpy_input, session): + numpy_flat = nest.flatten(numpy_input) + vars_flat = tuple( + variable_scope.variable(array_ops.zeros(i.shape, i.dtype), + trainable=False, use_resource=True) + for i in numpy_flat + ) + for v, i in zip(vars_flat, numpy_flat): + numpy_dataset.init_var_from_numpy(v, i, session) + vars_nested = nest.pack_sequence_as(numpy_input, vars_flat) + return dataset_ops.Dataset.from_tensor_slices(vars_nested) + def _broadcast_to(self, tensor, destinations): if destinations is None: return tensor diff --git a/tensorflow/python/distribute/mirrored_strategy.py b/tensorflow/python/distribute/mirrored_strategy.py index 37b493d0f7..c0a39d4b55 100644 --- a/tensorflow/python/distribute/mirrored_strategy.py +++ b/tensorflow/python/distribute/mirrored_strategy.py @@ -29,6 +29,7 @@ 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 multi_worker_util +from tensorflow.python.distribute import numpy_dataset from tensorflow.python.distribute import reduce_util from tensorflow.python.distribute import shared_variable_creator from tensorflow.python.distribute import values @@ -460,6 +461,7 @@ class MirroredExtended(distribute_lib.DistributionStrategyExtended): self._input_workers = input_lib.InputWorkers(self._device_map) self._inferred_cross_device_ops = cross_device_ops_lib.choose_the_best( devices) + self._host_input_device = numpy_dataset.SingleDevice("/cpu:0") def _initialize_multi_worker(self, devices): """Initializes the object for multi-worker training.""" @@ -488,6 +490,7 @@ class MirroredExtended(distribute_lib.DistributionStrategyExtended): # their ops will end up on the cpu device of its first worker, e.g. # "/job:worker/task:0/device:CPU:0". Note this is not used in replica mode. self._default_device = workers[0] + self._host_input_device = numpy_dataset.SingleDevice(workers[0]) self._device_map = values.ReplicaDeviceMap(devices) self._input_workers = input_lib.InputWorkers( @@ -501,6 +504,9 @@ class MirroredExtended(distribute_lib.DistributionStrategyExtended): 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 @@ -571,6 +577,10 @@ class MirroredExtended(distribute_lib.DistributionStrategyExtended): 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, self._host_input_device, session) + # TODO(priyag): Deal with OutOfRange errors once b/111349762 is fixed. def _experimental_run_steps_on_iterator(self, fn, iterator, iterations, initial_loop_values=None): diff --git a/tensorflow/python/distribute/numpy_dataset.py b/tensorflow/python/distribute/numpy_dataset.py new file mode 100644 index 0000000000..5881e4cd59 --- /dev/null +++ b/tensorflow/python/distribute/numpy_dataset.py @@ -0,0 +1,97 @@ +# 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. +# ============================================================================== +"""Code for creating a dataset out of a NumPy array.""" + +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.eager import context +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.util import nest + + +def init_var_from_numpy(input_var, numpy_input, session): + """Initialize `input_var` to `numpy_input` using `session` in graph mode.""" + with ops.init_scope(): + if context.executing_eagerly(): + input_var.assign(numpy_input) + return + + assert session is not None + session.run(input_var.initializer) + + start_placeholder = array_ops.placeholder(dtypes.int64, ()) + end_placeholder = array_ops.placeholder(dtypes.int64, ()) + slice_placeholder = array_ops.placeholder(input_var.dtype) + assign_slice_op = input_var[start_placeholder:end_placeholder].assign( + slice_placeholder) + + # If each batch element is > 64 MB, then we copy each batch element + # individually. Otherwise, the slices will be < 128 MB. There might be + # padding which might mean that the slices are 128 MB even if the size of + # the tensor allocated is less than 128 MB. This formula gives slices with + # size: ceil(64 MB / byte size per batch element) bytes. Using ceil() + # guarantees we get a number >= 1. + + # Calculate the size of each batch element. + byte_size_per_batch_element = ( + np.prod(numpy_input.shape[1:]) * input_var.dtype.size) + + # Calculate number of elements we want to copy per slice. + batch_size_per_slice = int( + np.ceil((64 << 20) / byte_size_per_batch_element)) + + # Copy slices of the above size starting at 0, except the last slice will be + # smaller. + start = 0 + limit = numpy_input.shape[0] + while start < limit: + end = min(start + batch_size_per_slice, limit) + session.run(assign_slice_op, feed_dict={ + start_placeholder: start, + end_placeholder: end, + slice_placeholder: numpy_input[start:end]}) + start = end + + +def one_host_numpy_dataset(numpy_input, colocate_with, session): + """Create a dataset on `colocate_with` from `numpy_input`.""" + def create_colocated_variable(next_creator, *args, **kwargs): + kwargs["colocate_with"] = colocate_with + return next_creator(*args, **kwargs) + + numpy_flat = nest.flatten(numpy_input) + with variable_scope.variable_creator_scope(create_colocated_variable): + vars_flat = tuple(variable_scope.variable(array_ops.zeros(i.shape, i.dtype), + trainable=False) + for i in numpy_flat) + for v, i in zip(vars_flat, numpy_flat): + init_var_from_numpy(v, i, session) + vars_nested = nest.pack_sequence_as(numpy_input, vars_flat) + return dataset_ops.Dataset.from_tensor_slices(vars_nested) + + +class SingleDevice(object): + """Used with `colocate_with` to create a non-mirrored variable.""" + + def __init__(self, device): + self.device = device diff --git a/tensorflow/python/distribute/numpy_dataset_test.py b/tensorflow/python/distribute/numpy_dataset_test.py new file mode 100644 index 0000000000..04eae1daa2 --- /dev/null +++ b/tensorflow/python/distribute/numpy_dataset_test.py @@ -0,0 +1,44 @@ +# 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 numpy_dataset.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.python.distribute import numpy_dataset +from tensorflow.python.eager import test +from tensorflow.python.framework import test_util +from tensorflow.python.ops import variable_scope + + +class InitVarFromNumpyTest(test.TestCase): + + @test_util.run_in_graph_and_eager_modes + def test_creating_var_with_numpy_arrays(self): + with self.cached_session() as session: + x = np.asarray(np.random.random((64, 3)), dtype=np.float32) + initial = np.zeros_like(x) + var_x = variable_scope.variable(initial) + numpy_dataset.init_var_from_numpy(var_x, x, session) + val = self.evaluate(var_x.value()) + # Verify that the numpy value is copied to the variable. + self.assertAllEqual(x, val) + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/python/distribute/parameter_server_strategy.py b/tensorflow/python/distribute/parameter_server_strategy.py index 71fbffdc0d..ac5ee6f589 100644 --- a/tensorflow/python/distribute/parameter_server_strategy.py +++ b/tensorflow/python/distribute/parameter_server_strategy.py @@ -26,6 +26,7 @@ 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 multi_worker_util +from tensorflow.python.distribute import numpy_dataset from tensorflow.python.distribute import values from tensorflow.python.distribute.cluster_resolver import SimpleClusterResolver from tensorflow.python.distribute.cluster_resolver import TFConfigClusterResolver @@ -137,6 +138,7 @@ class ParameterServerStrategyExtended( assert cluster_spec.as_dict() worker_device = "/job:%s/task:%d" % (task_type, task_id) + self._input_host_device = numpy_dataset.SingleDevice(worker_device) # Define compute devices which is a list of device strings and one for each # replica. When there are GPUs, replicate operations on these GPUs. @@ -195,6 +197,7 @@ class ParameterServerStrategyExtended( def _initialize_local(self, cluster_resolver): """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() # Define compute devices which is a list of device strings and one for each # replica. When there are GPUs, replicate operations on these GPUs. @@ -262,6 +265,10 @@ class ParameterServerStrategyExtended( return input_lib.InputFunctionIterator(input_fn, self._input_workers, [input_context]) + def _experimental_make_numpy_dataset(self, numpy_input, session): + return numpy_dataset.one_host_numpy_dataset( + numpy_input, self._input_host_device, session) + def _broadcast_to(self, tensor, destinations): # This is both a fast path for Python constants, and a way to delay # converting Python values to a tensor until we know what type it @@ -329,8 +336,12 @@ class ParameterServerStrategyExtended( var_creator = next_creator if "colocate_with" in kwargs: + colocate_with = kwargs["colocate_with"] + if isinstance(colocate_with, numpy_dataset.SingleDevice): + with ops.device(colocate_with.device): + return var_creator(*args, **kwargs) with ops.device(None): - with ops.colocate_with(kwargs["colocate_with"]): + with ops.colocate_with(colocate_with): return var_creator(*args, **kwargs) with ops.colocate_with(None, ignore_existing=True): diff --git a/tensorflow/python/keras/engine/distributed_training_utils.py b/tensorflow/python/keras/engine/distributed_training_utils.py index 1678d84307..b6ef33f700 100644 --- a/tensorflow/python/keras/engine/distributed_training_utils.py +++ b/tensorflow/python/keras/engine/distributed_training_utils.py @@ -34,25 +34,13 @@ from tensorflow.python.keras import callbacks from tensorflow.python.keras import metrics as metrics_module from tensorflow.python.keras import optimizers from tensorflow.python.keras.optimizer_v2 import optimizer_v2 -from tensorflow.python.ops import array_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import variables from tensorflow.python.platform import tf_logging as logging -from tensorflow.python.training import distribution_strategy_context from tensorflow.python.training.mode_keys import ModeKeys from tensorflow.python.util import nest -def validate_not_in_strategy_scope(): - """Validate fit/eval/predict are not running in DS scope.""" - if distribution_strategy_context.has_distribution_strategy(): - if distribution_strategy_context.in_cross_replica_context(): - raise RuntimeError( - 'Fit/Eval/Predict should not be run inside the tf.distribute.Strategy' - ' scope. Only model creation and compilation should be in ' - 'tf.distribute.Strategy scope.') - - def set_weights(distribution_strategy, dist_model, weights): """Sets the weights of the replicated models. @@ -530,100 +518,11 @@ def get_batch_dimension(iterator): return dims[0] if dims else None -def get_cpu_device(distribution_strategy): - """Returns the CPU device of the TPU host or the default CPU device string. - - Args: - distribution_strategy: The DistributionStrategy used to compile the model. - - Returns: - A device string which is the TPU host's CPU device in case of - TPUDistributionStrategy or the default CPU device string in all other - cases. - - Raises: - NotImplementedError: We currently don't support copying numpy data to - multiple hosts in the case of Cloud TPU pods. - """ - if is_tpu_strategy(distribution_strategy): - if distribution_strategy.extended.num_hosts > 1: - raise NotImplementedError('TPUDistributionStrategy does not ' - 'support numpy inputs when running on Cloud' - 'TPU pods.') - return distribution_strategy.extended.get_host_cpu_device(0) - else: - # For all strategies except TPUDistributionStrategy - # TODO(anjalisridhar): We may need to modify this when we add support for - # multi-worker strategy. - return '/CPU:0' - - -def get_var_for_numpy(distribution_strategy, x): - if isinstance(x, list): - var_x = tuple([_get_var_for_numpy(distribution_strategy, single_input) - for single_input in x]) - else: - var_x = _get_var_for_numpy(distribution_strategy, x) - return var_x - - -def _get_var_for_numpy(distribution_strategy, input_array): - """Creates a variable and assigns the value of the numpy array to it. - - Args: - distribution_strategy: The DistributionStrategy used to compile the model. - input_array: The input numpy array whose value will be assigned to the - variable we create. - - Returns: - The variable to which we will copy the value of the input numpy array. - - """ - with ops.device(get_cpu_device(distribution_strategy)): - # Create and initialize a variable on the CPU device. This is the CPU - # device of the host in the case of TPUDistributionStrategy. - input_var = variables.VariableV1(array_ops.zeros(input_array.shape, - input_array.dtype), - trainable=False, use_resource=True) - K.get_session().run(input_var.initializer) - - # Create a placeholder for the numpy array input slices. We copy the value - # of the input numpy array to the variable in slices of size 64 MB to avoid - # running into memory issues or RPC message limits. - start_placeholder = array_ops.placeholder(dtypes.int64, ()) - end_placeholder = array_ops.placeholder(dtypes.int64, ()) - slice_placeholder = array_ops.placeholder(input_var.dtype) - assign_slice_op = input_var[start_placeholder:end_placeholder].assign( - slice_placeholder) - - # If each batch element is > 64 MB, then we copy each batch element - # individually. Otherwise, the slices will be < 128 MB. There might be padding - # which might mean that the slices are 128 MB even if the size of the - # tensor allocated is less than 128 MB. - # This formula gives slices with size: - # ceil(64 MB / byte size per batch element) bytes. - # Using ceil() guarantees we get a number >= 1. - - # Calculate the size of each batch element. - byte_size_per_batch_element = np.prod(input_array.shape[1:]) * \ - input_var.dtype.size - - # Calculate number of elements we want to copy per slice. - batch_size_per_slice = int(np.ceil((64 << 20) / byte_size_per_batch_element)) - - # Copy slices of the above size starting at 0, except the last slice will be - # smaller. - start = 0 - limit = input_array.shape[0] - while start < limit: - end = min(start + batch_size_per_slice, limit) - K.get_session().run(assign_slice_op, feed_dict={ - start_placeholder: start, - end_placeholder: end, - slice_placeholder: input_array[start:end]}) - start = end - - return input_var +def list_to_tuple(maybe_list): + """Datasets treat lists specially, so switch them to tuples.""" + if isinstance(maybe_list, list): + return tuple(maybe_list) + return maybe_list def _get_input_from_iterator(iterator, model): diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index 1eda8cf797..a65c2b6413 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -2161,53 +2161,47 @@ class Model(Network): 'you should specify the `{steps_name}` argument.' .format(steps_name=steps_name)) - first_x_value = nest.flatten(x)[0] - if isinstance(first_x_value, np.ndarray): - # We need to use the drop_remainder argument to allow for a static - # input shape which is required for TPUs. - drop_remainder = self._distribution_strategy.require_static_shapes - if y is not None: - var_x = distributed_training_utils.get_var_for_numpy( - self._distribution_strategy, x) - var_y = distributed_training_utils.get_var_for_numpy( - self._distribution_strategy, y) - if sample_weight is not None: - var_sample_weights = distributed_training_utils.get_var_for_numpy( - self._distribution_strategy, sample_weight) - - x = dataset_ops.Dataset.from_tensor_slices((var_x, var_y, - var_sample_weights)) + if ops.executing_eagerly_outside_functions(): + session = None + else: + session = K.get_session() + + with self._distribution_strategy.scope(): + first_x_value = nest.flatten(x)[0] + if isinstance(first_x_value, np.ndarray): + x = distributed_training_utils.list_to_tuple(x) + if y is not None: + y = distributed_training_utils.list_to_tuple(y) + if sample_weight is not None: + sample_weight = distributed_training_utils.list_to_tuple( + sample_weight) + in_tuple = (x, y, sample_weight) + else: + in_tuple = (x, y) else: - x = dataset_ops.Dataset.from_tensor_slices((var_x, var_y)) + in_tuple = x if shuffle: # 1024 is a good buffer size since it is much larger than the average # batch size provided by the user and provides sufficient randomness. # One thing to keep in mind is the memory usage based on the size of # each sample. - x = x.shuffle(1024) - x = x.repeat() - x = x.batch(batch_size, drop_remainder=drop_remainder) - y = None - sample_weight = None + shuffle_buffer = 1024 + else: + shuffle_buffer = None + iterator = self._distribution_strategy.experimental_make_numpy_iterator( + in_tuple, batch_size, num_epochs=None, shuffle=shuffle_buffer, + session=session) else: - # This case is for the predict call where the dataset only contains - # inputs and no targets, i.e. it does not return a tuple - var_x = distributed_training_utils.get_var_for_numpy( - self._distribution_strategy, x) - x = dataset_ops.Dataset.from_tensor_slices(var_x) - x = x.batch(batch_size, drop_remainder=drop_remainder) - - assert isinstance(x, dataset_ops.DatasetV2) + assert isinstance(x, dataset_ops.DatasetV2) + training_utils.validate_dataset_input(x, y, sample_weight, + validation_split) + iterator = self._distribution_strategy.make_dataset_iterator(x) - with self._distribution_strategy.scope(): - iterator = self._distribution_strategy.make_dataset_iterator(x) init_op = iterator.initialize() if not context.executing_eagerly(): K.get_session().run(init_op) - training_utils.validate_dataset_input(x, y, sample_weight, - validation_split) return iterator def _standardize_user_data(self, diff --git a/tensorflow/python/keras/engine/training_distributed.py b/tensorflow/python/keras/engine/training_distributed.py index 92a46e399a..0bd79f2b47 100644 --- a/tensorflow/python/keras/engine/training_distributed.py +++ b/tensorflow/python/keras/engine/training_distributed.py @@ -59,8 +59,6 @@ def fit_distributed(model, first_x_value = nest.flatten(x)[0] if isinstance(first_x_value, np.ndarray): - # TODO(b/122314600): Remove the scope validate. - distributed_training_utils.validate_not_in_strategy_scope() steps_per_epoch, batch_size = ( distributed_training_utils.get_input_params( model._distribution_strategy, first_x_value, steps_per_epoch, @@ -141,8 +139,6 @@ def evaluate_distributed(model, distributed_training_utils.validate_inputs(x, y, model._distribution_strategy) first_x_value = nest.flatten(x)[0] if isinstance(first_x_value, np.ndarray): - # TODO(b/122314600): Remove the scope validate. - distributed_training_utils.validate_not_in_strategy_scope() steps, batch_size = distributed_training_utils.get_input_params( model._distribution_strategy, first_x_value, steps, batch_size) batch_size = model._validate_or_infer_batch_size(batch_size, steps, x) @@ -179,8 +175,6 @@ def predict_distributed(model, x, None, model._distribution_strategy) first_x_value = nest.flatten(x)[0] if isinstance(first_x_value, np.ndarray): - # TODO(b/122314600): Remove the scope validate. - distributed_training_utils.validate_not_in_strategy_scope() steps, batch_size = distributed_training_utils.get_input_params( model._distribution_strategy, first_x_value, steps, batch_size) batch_size = model._validate_or_infer_batch_size(batch_size, steps, x) diff --git a/tensorflow/python/training/optimizer.py b/tensorflow/python/training/optimizer.py index eaa563e84a..c6cc0b6044 100644 --- a/tensorflow/python/training/optimizer.py +++ b/tensorflow/python/training/optimizer.py @@ -554,14 +554,15 @@ class Optimizer( # by most optimizers. It relies on the subclass implementing the following # methods: _create_slots(), _prepare(), _apply_dense(), and _apply_sparse(). - # Handle DistributionStrategy case. - if distribute_ctx.get_cross_replica_context(): - raise RuntimeError("Use `_distributed_apply()` instead of " - "`apply_gradients()` in a cross-replica context.") - # TODO(isaprykin): Get rid of `has_distribution_strategy()` check by + # TODO(isaprykin): Get rid of `has_strategy()` check by # always calling _distributed_apply(), using the default distribution # as needed. - if distribute_ctx.has_distribution_strategy(): + if distribute_ctx.has_strategy(): + # Handle DistributionStrategy case. + if distribute_ctx.in_cross_replica_context(): + raise RuntimeError("Use `_distributed_apply()` instead of " + "`apply_gradients()` in a cross-replica context.") + grads_and_vars = get_filtered_grad_fn(lambda: grads_and_vars)() return distribute_ctx.get_replica_context().merge_call( self._distributed_apply, args=(grads_and_vars, global_step, name)) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt index b06c73d126..9c29067b6d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt @@ -75,6 +75,10 @@ tf_class { name: "experimental_initialize" argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "experimental_make_numpy_iterator" + argspec: "args=[\'self\', \'numpy_input\', \'batch_size\', \'num_epochs\', \'shuffle\', \'session\'], varargs=None, keywords=None, defaults=[\'1\', \'1024\', \'None\'], " + } member_method { name: "experimental_run" argspec: "args=[\'self\', \'fn\', \'input_iterator\'], varargs=None, keywords=None, defaults=[\'None\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy-extended.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy-extended.pbtxt index 77706e5713..37b620891f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy-extended.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy-extended.pbtxt @@ -50,6 +50,10 @@ tf_class { name: "colocate_vars_with" argspec: "args=[\'self\', \'colocate_with_variable\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "experimental_make_numpy_dataset" + argspec: "args=[\'self\', \'numpy_input\', \'session\'], varargs=None, keywords=None, defaults=[\'None\'], " + } member_method { name: "experimental_run_steps_on_iterator" argspec: "args=[\'self\', \'fn\', \'iterator\', \'iterations\', \'initial_loop_values\'], varargs=None, keywords=None, defaults=[\'1\', \'None\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt index 9a1df55142..4aa6f1c4e1 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt @@ -74,6 +74,10 @@ tf_class { name: "experimental_initialize" argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "experimental_make_numpy_iterator" + argspec: "args=[\'self\', \'numpy_input\', \'batch_size\', \'num_epochs\', \'shuffle\', \'session\'], varargs=None, keywords=None, defaults=[\'1\', \'1024\', \'None\'], " + } member_method { name: "experimental_run" argspec: "args=[\'self\', \'fn\', \'input_iterator\'], varargs=None, keywords=None, defaults=[\'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt index b06c73d126..9c29067b6d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt @@ -75,6 +75,10 @@ tf_class { name: "experimental_initialize" argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "experimental_make_numpy_iterator" + argspec: "args=[\'self\', \'numpy_input\', \'batch_size\', \'num_epochs\', \'shuffle\', \'session\'], varargs=None, keywords=None, defaults=[\'1\', \'1024\', \'None\'], " + } member_method { name: "experimental_run" argspec: "args=[\'self\', \'fn\', \'input_iterator\'], varargs=None, keywords=None, defaults=[\'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy-extended.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy-extended.pbtxt index 77706e5713..37b620891f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy-extended.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy-extended.pbtxt @@ -50,6 +50,10 @@ tf_class { name: "colocate_vars_with" argspec: "args=[\'self\', \'colocate_with_variable\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "experimental_make_numpy_dataset" + argspec: "args=[\'self\', \'numpy_input\', \'session\'], varargs=None, keywords=None, defaults=[\'None\'], " + } member_method { name: "experimental_run_steps_on_iterator" argspec: "args=[\'self\', \'fn\', \'iterator\', \'iterations\', \'initial_loop_values\'], varargs=None, keywords=None, defaults=[\'1\', \'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt index 9a1df55142..4aa6f1c4e1 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt @@ -74,6 +74,10 @@ tf_class { name: "experimental_initialize" argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "experimental_make_numpy_iterator" + argspec: "args=[\'self\', \'numpy_input\', \'batch_size\', \'num_epochs\', \'shuffle\', \'session\'], varargs=None, keywords=None, defaults=[\'1\', \'1024\', \'None\'], " + } member_method { name: "experimental_run" argspec: "args=[\'self\', \'fn\', \'input_iterator\'], varargs=None, keywords=None, defaults=[\'None\'], " -- GitLab From c94be5c69b9e540c27cad8c1d3f8604606874d00 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 19 Dec 2018 16:08:48 -0800 Subject: [PATCH 0448/2345] initial commit --- .../contrib/tensorrt/ops/trt_engine_op.cc | 4 +- .../contrib/tensorrt/test/multiple_types.py | 94 +++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 tensorflow/contrib/tensorrt/test/multiple_types.py diff --git a/tensorflow/contrib/tensorrt/ops/trt_engine_op.cc b/tensorflow/contrib/tensorrt/ops/trt_engine_op.cc index 92405906eb..1dce862568 100644 --- a/tensorflow/contrib/tensorrt/ops/trt_engine_op.cc +++ b/tensorflow/contrib/tensorrt/ops/trt_engine_op.cc @@ -33,8 +33,8 @@ REGISTER_OP("TRTEngineOp") .Attr("input_shapes: list(shape)") .Attr("output_shapes: list(shape)") .Attr("segment_funcdef_name: string") - .Attr("InT: list({int8,float16,float32})") - .Attr("OutT: list({int8,float16,float32})") + .Attr("InT: list({int8,float16,float32,int32})") + .Attr("OutT: list({int8,float16,float32,int32})") .Attr("static_engine: bool = true") .Attr("fixed_input_size: bool = true") .Attr("cached_engine_batches: list(int) = []") diff --git a/tensorflow/contrib/tensorrt/test/multiple_types.py b/tensorflow/contrib/tensorrt/test/multiple_types.py new file mode 100644 index 0000000000..35b5a8519a --- /dev/null +++ b/tensorflow/contrib/tensorrt/test/multiple_types.py @@ -0,0 +1,94 @@ +# 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. +# ============================================================================== +"""Model script to test TF-TensorRT integration.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.tensorrt.test import tf_trt_integration_test_base as trt_test +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_array_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import nn +from tensorflow.python.platform import test + + +class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): + + def _ConstOp(self, shape, dtype): + return constant_op.constant(np.random.randn(*shape), dtype=dtype) + + def GetParams(self): + """Testing conversion of BiasAdd MatMul in TF-TRT conversion.""" + # Note that tf.nn.bias_add supports up to 5 dimensions. + input_dims = [100, 4] + output_name = "output" + g = ops.Graph() + with g.as_default(): + input1 = array_ops.placeholder( + dtype=dtypes.float32, shape=input_dims, name='input_float32') + + b1 = self._ConstOp((4, 10), dtypes.float32) + x1 = math_ops.matmul(input1, b) + b1 = self._ConstOp((1, 10), dtypes.float32) + x1 = x1 + b1 + + input2 = array_ops.placeholder( + dtype=dtypes.int32, shape=input_dims, name='input_int32') + + b2 = self._ConstOp((4, 10), dtypes.float32) + x2 = math_ops.matmul(input2, b) + b2 = self._ConstOp((1, 10), dtypes.float32) + x2 = x2 + b2 + out = array_ops.concat([x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11], + axis=-1) + out = array_ops.squeeze(out, name=output_name) + return trt_test.TfTrtIntegrationTestParams( + gdef=g.as_graph_def(), + input_names=[input_name], + input_dims=[input_dims], + output_names=[output_name], + expected_output_dims=[(4, 6680)]) + + def GetConversionParams(self, run_params): + """Return a ConversionParams for test.""" + conversion_params = super(BiasaddMatMulTest, + self).GetConversionParams(run_params) + return conversion_params._replace( + max_batch_size=4, + maximum_cached_engines=1, + # Disable layout optimizer, since it will convert BiasAdd with NHWC + # format to NCHW format under four dimentional input. + rewriter_config=trt_test.OptimizerDisabledRewriterConfig()) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["TRTEngineOp_0"] + + def ShouldRunTest(self, run_params): + """Whether to run the test.""" + # TODO(aaroey): Trt 4.0 forbids conversion for tensors with rank <3 in int8 + # mode, which is a bug. Re-enable this when trt library is fixed. + return not trt_test.IsQuantizationMode(run_params.precision_mode) + + +if __name__ == "__main__": + test.main() -- GitLab From 62d6bdef48601770d6fc0e335b5602a4a86c6d00 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 21 Dec 2018 11:45:00 -0800 Subject: [PATCH 0449/2345] rename test --- tensorflow/contrib/tensorrt/BUILD | 1 + .../test/{multiple_types.py => int_test.py} | 26 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) rename tensorflow/contrib/tensorrt/test/{multiple_types.py => int_test.py} (86%) diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 3d34b91a74..e58a259a18 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -493,6 +493,7 @@ cuda_py_tests( "test/const_broadcast_test.py", "test/conv2d_test.py", "test/identity_output_test.py", + "test/int_test.py", "test/manual_test.py", "test/memory_alignment_test.py", "test/multi_connection_neighbor_engine_test.py", diff --git a/tensorflow/contrib/tensorrt/test/multiple_types.py b/tensorflow/contrib/tensorrt/test/int_test.py similarity index 86% rename from tensorflow/contrib/tensorrt/test/multiple_types.py rename to tensorflow/contrib/tensorrt/test/int_test.py index 35b5a8519a..a4d625a213 100644 --- a/tensorflow/contrib/tensorrt/test/multiple_types.py +++ b/tensorflow/contrib/tensorrt/test/int_test.py @@ -40,33 +40,35 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): """Testing conversion of BiasAdd MatMul in TF-TRT conversion.""" # Note that tf.nn.bias_add supports up to 5 dimensions. input_dims = [100, 4] - output_name = "output" g = ops.Graph() with g.as_default(): + # float part input1 = array_ops.placeholder( dtype=dtypes.float32, shape=input_dims, name='input_float32') - b1 = self._ConstOp((4, 10), dtypes.float32) x1 = math_ops.matmul(input1, b) b1 = self._ConstOp((1, 10), dtypes.float32) x1 = x1 + b1 + # int part input2 = array_ops.placeholder( dtype=dtypes.int32, shape=input_dims, name='input_int32') - - b2 = self._ConstOp((4, 10), dtypes.float32) + b2 = self._ConstOp((4, 10), dtypes.int32) x2 = math_ops.matmul(input2, b) - b2 = self._ConstOp((1, 10), dtypes.float32) + b2 = self._ConstOp((1, 10), dtypes.int32) x2 = x2 + b2 - out = array_ops.concat([x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11], - axis=-1) - out = array_ops.squeeze(out, name=output_name) + + # combine + #y = x1 * x2 + + out1 = array_ops.identity(x1, name='output_float32') + out2 = array_ops.identity(x2, name='output_int32') return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), - input_names=[input_name], - input_dims=[input_dims], - output_names=[output_name], - expected_output_dims=[(4, 6680)]) + input_names=['input_float32', 'input_int32'], + input_dims=[[100, 4], [100, 4]], + output_names=['output_int32', 'output_float32'], + expected_output_dims=[(100, 10), (100, 10)]) def GetConversionParams(self, run_params): """Return a ConversionParams for test.""" -- GitLab From dd88f64811365d313423e9b28148921ae0f92003 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 21 Dec 2018 14:17:04 -0800 Subject: [PATCH 0450/2345] fix segfault in this case. should add a check to GetDeviceAndAllocator --- .../contrib/tensorrt/convert/convert_graph.cc | 13 ++++++++ .../contrib/tensorrt/convert/convert_nodes.cc | 30 ++++++++++++------ .../contrib/tensorrt/segment/segment.cc | 2 +- tensorflow/contrib/tensorrt/test/int_test.py | 31 ++++++++++--------- 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index bf2de94e04..ffe1840c68 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -356,10 +356,18 @@ tensorflow::Status GetEngineInfo( if (segment_nodes.count(node_name) == 0) continue; auto node = *it; auto node_device = node->requested_device(); + LOG(INFO) << "requested: " << node_device; + DeviceNameUtils::ParsedName parsed_name; + if (DeviceNameUtils::ParseFullName(node_device, &parsed_name) && parsed_name.type == "CPU") { + LOG(INFO) << "type: " << parsed_name.type; + LOG(INFO) << "clear!"; + node_device.clear(); + } if (!node_device.empty()) { segment_devices.insert(node_device); } else { if (node->has_assigned_device_name()) { + LOG(INFO) << "assigned: " << node->assigned_device_name(); segment_devices.insert(node->assigned_device_name()); } else { VLOG(2) << "Node " << node->name() @@ -897,6 +905,8 @@ std::pair GetDeviceAndAllocator( StrAppend(&msg, ". Will get the allocator from first one."); LOG(WARNING) << msg; } + LOG(INFO) << "here12"; + LOG(INFO) << devices[0]->name(); tensorflow::AllocatorAttributes alloc_attr; cuda_device_id = devices[0]->tensorflow_gpu_device_info()->gpu_id; dev_allocator = devices[0]->GetAllocator(alloc_attr); @@ -1008,8 +1018,11 @@ tensorflow::Status ConvertAfterShapes(ConversionParams& params) { VLOG(1) << "Current cuda device is " << old_cuda_device; std::vector engine_nodes; engine_nodes.resize(engine_segments.size()); + LOG(INFO) << "here " << engine_segments.size(); for (int i = 0; i < engine_segments.size(); ++i) { auto& engine = engine_segments.at(i); + + LOG(INFO) << "here4 "; // Partition the workspace size by the average of node ratio and segment // graphdef size engine.max_workspace_size_bytes = diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 179d631a3e..9d517b3be2 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -694,6 +694,14 @@ void ReorderCKtoKC(const TRT_ShapedWeights& iweights, ostrides); break; } + case tensorflow::DataType::DT_INT32: { + Reorder2( + {k, c}, static_cast(iweights.GetValues()), + istrides, + static_cast(const_cast(oweights->GetValues())), + ostrides); + break; + } default: LOG(FATAL) << "Unsupported type in reorder expected fp32 or fp16 but got " << DataTypeString(iweights.type_); @@ -1725,6 +1733,12 @@ Status BinaryTensorOpTensor(OpConverterParams* params, "Unsupported binary op broadcast scheme for op ", node_def.name(), ": ", status.error_message()); } + TFAttrs attrs(node_def); + nvinfer1::DataType dtype = attrs.get("T"); + if (dtype == nvinfer1::DataType::kINT32) { + return errors::Unimplemented( + "Binary op ", node_def.op(), " does not support INT32, at ", node_def.name()); + } if (params->validation_only) return Status::OK(); const nvinfer1::ITensor* tensor_l = nullptr; @@ -1741,8 +1755,6 @@ Status BinaryTensorOpTensor(OpConverterParams* params, } // Check type consistency. - TFAttrs attrs(node_def); - nvinfer1::DataType dtype = attrs.get("T"); TFTRT_CHECK_EQ_TYPE(tensor_l->getType(), dtype) << DebugString(tensor_l->getType()) << " vs " << DebugString(dtype); TFTRT_CHECK_EQ_TYPE(tensor_r->getType(), dtype) @@ -3450,12 +3462,12 @@ tensorflow::Status ConvertMatMul(OpConverterParams* params) { TFAttrs attrs(node_def); // TODO(jie): INT32 should be converted? - tensorflow::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)); - } + // tensorflow::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("transpose_a"); bool transpose_b = attrs.get("transpose_b"); @@ -3844,7 +3856,7 @@ tensorflow::Status ConvertSegmentToGraphDef( marker_nodes.insert(node_name); auto seg_node = segment_def->add_node(); tensorflow::NodeDefBuilder builder(node_name, "Identity"); - auto status = builder.Input(connection.inside_node_name, 0, dtype) + auto status = builder.Input(connection.inside_node_name, connection.inside_port, dtype) .Finalize(seg_node); VLOG(1) << "Constructing output " << node_name << " for the edge " << connection.inside_node_name << ":" << connection.inside_port diff --git a/tensorflow/contrib/tensorrt/segment/segment.cc b/tensorflow/contrib/tensorrt/segment/segment.cc index 084a96e0fa..171ae41575 100644 --- a/tensorflow/contrib/tensorrt/segment/segment.cc +++ b/tensorflow/contrib/tensorrt/segment/segment.cc @@ -441,7 +441,7 @@ tensorflow::Status SegmentGraph( } else { const Status status = candidate_fn(node->tf_node()); if (!status.ok()) { - VLOG(1) << "Not a TF-TRT candidate, " + LOG(INFO) << "Not a TF-TRT candidate, " << "(Op type: " << node->tf_node()->type_string() << "), " << "(Op name: " << node->name() << "), " << "(Reason: " << status << ")"; diff --git a/tensorflow/contrib/tensorrt/test/int_test.py b/tensorflow/contrib/tensorrt/test/int_test.py index a4d625a213..b5d69a799f 100644 --- a/tensorflow/contrib/tensorrt/test/int_test.py +++ b/tensorflow/contrib/tensorrt/test/int_test.py @@ -43,39 +43,40 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): g = ops.Graph() with g.as_default(): # float part - input1 = array_ops.placeholder( - dtype=dtypes.float32, shape=input_dims, name='input_float32') - b1 = self._ConstOp((4, 10), dtypes.float32) - x1 = math_ops.matmul(input1, b) - b1 = self._ConstOp((1, 10), dtypes.float32) - x1 = x1 + b1 + # input1 = array_ops.placeholder( + # dtype=dtypes.float32, shape=input_dims, name='input_float32') + # b1 = self._ConstOp((4, 10), dtypes.float32) + # x1 = math_ops.matmul(input1, b1) + # b1 = self._ConstOp((1, 10), dtypes.float32) + # x1 = x1 + b1 # int part input2 = array_ops.placeholder( dtype=dtypes.int32, shape=input_dims, name='input_int32') + #with g.device("/GPU:0"): b2 = self._ConstOp((4, 10), dtypes.int32) - x2 = math_ops.matmul(input2, b) - b2 = self._ConstOp((1, 10), dtypes.int32) - x2 = x2 + b2 + x2 = math_ops.matmul(input2, b2) + b2 = self._ConstOp((10,), dtypes.int32) + x2 = nn.bias_add(x2, b2) # combine #y = x1 * x2 - out1 = array_ops.identity(x1, name='output_float32') + #out1 = array_ops.identity(x1, name='output_float32') out2 = array_ops.identity(x2, name='output_int32') return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), - input_names=['input_float32', 'input_int32'], - input_dims=[[100, 4], [100, 4]], - output_names=['output_int32', 'output_float32'], - expected_output_dims=[(100, 10), (100, 10)]) + input_names=['input_int32'], + input_dims=[[100, 4]], + output_names=['output_int32'], + expected_output_dims=[(100, 10)]) def GetConversionParams(self, run_params): """Return a ConversionParams for test.""" conversion_params = super(BiasaddMatMulTest, self).GetConversionParams(run_params) return conversion_params._replace( - max_batch_size=4, + max_batch_size=100, maximum_cached_engines=1, # Disable layout optimizer, since it will convert BiasAdd with NHWC # format to NCHW format under four dimentional input. -- GitLab From d72dae711ec00e9b9b3e60b0c70fda616a21bbc1 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 21 Dec 2018 15:28:20 -0800 Subject: [PATCH 0451/2345] Exclude CPU devices via segmenter. Prevent BiasAdd in int32 --- .../contrib/tensorrt/convert/convert_graph.cc | 13 +++-------- .../contrib/tensorrt/convert/convert_nodes.cc | 22 +++++++++++-------- .../contrib/tensorrt/segment/segment.cc | 21 +++++++++++++++++- tensorflow/contrib/tensorrt/test/int_test.py | 5 ++--- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index ffe1840c68..646451d802 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -355,19 +355,14 @@ tensorflow::Status GetEngineInfo( const auto& node_name = (*it)->name(); if (segment_nodes.count(node_name) == 0) continue; auto node = *it; + // TODO: check for CPU device here + // If device is CPU, we should've caught that in the segmenter. Fall back here. + auto node_device = node->requested_device(); - LOG(INFO) << "requested: " << node_device; - DeviceNameUtils::ParsedName parsed_name; - if (DeviceNameUtils::ParseFullName(node_device, &parsed_name) && parsed_name.type == "CPU") { - LOG(INFO) << "type: " << parsed_name.type; - LOG(INFO) << "clear!"; - node_device.clear(); - } if (!node_device.empty()) { segment_devices.insert(node_device); } else { if (node->has_assigned_device_name()) { - LOG(INFO) << "assigned: " << node->assigned_device_name(); segment_devices.insert(node->assigned_device_name()); } else { VLOG(2) << "Node " << node->name() @@ -905,8 +900,6 @@ std::pair GetDeviceAndAllocator( StrAppend(&msg, ". Will get the allocator from first one."); LOG(WARNING) << msg; } - LOG(INFO) << "here12"; - LOG(INFO) << devices[0]->name(); tensorflow::AllocatorAttributes alloc_attr; cuda_device_id = devices[0]->tensorflow_gpu_device_info()->gpu_id; dev_allocator = devices[0]->GetAllocator(alloc_attr); diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 9d517b3be2..0d98da08b0 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -2609,12 +2609,18 @@ tensorflow::Status ConvertBiasAdd(OpConverterParams* params) { return errors::InvalidArgument("Input expects tensor and weights, at ", node_def.name()); } + TFAttrs attrs(node_def); + tensorflow::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)); + } if (params->validation_only) return Status::OK(); nvinfer1::ITensor* tensor = const_cast(inputs.at(0).tensor()); const nvinfer1::Dims original_dims = tensor->getDimensions(); - TFAttrs attrs(node_def); const string data_format = attrs.get("data_format"); const int channel_index = (data_format == "NHWC" ? original_dims.nbDims - 1 : 0); @@ -3461,13 +3467,12 @@ tensorflow::Status ConvertMatMul(OpConverterParams* params) { } TFAttrs attrs(node_def); - // TODO(jie): INT32 should be converted? - // tensorflow::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)); - // } + tensorflow::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("transpose_a"); bool transpose_b = attrs.get("transpose_b"); @@ -3487,7 +3492,6 @@ tensorflow::Status ConvertBatchMatMul(OpConverterParams* params) { const auto& node_def = params->node_def; TFAttrs attrs(node_def); - // TODO(jie): INT32 should be converted? tensorflow::DataType tf_dtype = attrs.get("T"); if (tf_dtype != tensorflow::DataType::DT_FLOAT && tf_dtype != tensorflow::DataType::DT_HALF) { diff --git a/tensorflow/contrib/tensorrt/segment/segment.cc b/tensorflow/contrib/tensorrt/segment/segment.cc index 171ae41575..9f72f1490c 100644 --- a/tensorflow/contrib/tensorrt/segment/segment.cc +++ b/tensorflow/contrib/tensorrt/segment/segment.cc @@ -28,6 +28,7 @@ limitations under the License. #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/platform/types.h" +#include "tensorflow/core/util/device_name_utils.h" namespace tensorflow { namespace tensorrt { @@ -419,6 +420,11 @@ tensorflow::Status SegmentGraph( // segment but are not eligible, using input/output_candidate_fn to // determine the eligibilities; // 3. convert the segment into expected return format and return the result. + auto get_device_type = [](const string& device) { + DeviceNameUtils::ParsedName parsed_name; + DeviceNameUtils::ParseFullName(device, &parsed_name); + return parsed_name.type; + }; // --------------------------------- Step 1 --------------------------------- auto graph = std::unique_ptr(new SimpleGraph(tf_graph)); @@ -430,6 +436,11 @@ tensorflow::Status SegmentGraph( std::vector> node_segments; for (int i = 0; i < graph->num_node_ids(); ++i) { SimpleNode* node = graph->FindNodeId(i); + const string requested_device = + get_device_type(node->tf_node()->requested_device()); + const string assigned_device = + get_device_type(node->tf_node()->assigned_device_name()); + if (options.exclude_node_list.count(node->name()) != 0) { VLOG(1) << "Not a TF-TRT candidate, " << "(Op type: " << node->tf_node()->type_string() << "), " @@ -438,10 +449,18 @@ tensorflow::Status SegmentGraph( unsupported_ops.emplace(node->tf_node()->type_string()); num_unsupported_ops++; node = nullptr; + } else if (requested_device == "CPU" || assigned_device == "CPU") { + VLOG(1) << "Not a TF-TRT candidate, " + << "(Op type: " << node->tf_node()->type_string() << "), " + << "(Op name: " << node->name() << "), " + << "(Reason: node is assigned to CPU)"; + unsupported_ops.emplace(node->tf_node()->type_string()); + num_unsupported_ops++; + node = nullptr; } else { const Status status = candidate_fn(node->tf_node()); if (!status.ok()) { - LOG(INFO) << "Not a TF-TRT candidate, " + VLOG(1) << "Not a TF-TRT candidate, " << "(Op type: " << node->tf_node()->type_string() << "), " << "(Op name: " << node->name() << "), " << "(Reason: " << status << ")"; diff --git a/tensorflow/contrib/tensorrt/test/int_test.py b/tensorflow/contrib/tensorrt/test/int_test.py index b5d69a799f..bb90c34168 100644 --- a/tensorflow/contrib/tensorrt/test/int_test.py +++ b/tensorflow/contrib/tensorrt/test/int_test.py @@ -53,7 +53,6 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): # int part input2 = array_ops.placeholder( dtype=dtypes.int32, shape=input_dims, name='input_int32') - #with g.device("/GPU:0"): b2 = self._ConstOp((4, 10), dtypes.int32) x2 = math_ops.matmul(input2, b2) b2 = self._ConstOp((10,), dtypes.int32) @@ -84,13 +83,13 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" - return ["TRTEngineOp_0"] + return [] def ShouldRunTest(self, run_params): """Whether to run the test.""" # TODO(aaroey): Trt 4.0 forbids conversion for tensors with rank <3 in int8 # mode, which is a bug. Re-enable this when trt library is fixed. - return not trt_test.IsQuantizationMode(run_params.precision_mode) + return not trt_test.IsQuantizationMode(run_params.precision_mode) #and not run_params.dynamic_engine if __name__ == "__main__": -- GitLab From 12a755183b32681281960050798bae02de4a58cd Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 21 Dec 2018 15:39:45 -0800 Subject: [PATCH 0452/2345] Clean up code --- .../contrib/tensorrt/convert/convert_graph.cc | 3 -- .../contrib/tensorrt/convert/convert_nodes.cc | 8 ---- tensorflow/contrib/tensorrt/test/int_test.py | 43 +++++++------------ 3 files changed, 15 insertions(+), 39 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index 646451d802..fd31c356c1 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -1011,11 +1011,8 @@ tensorflow::Status ConvertAfterShapes(ConversionParams& params) { VLOG(1) << "Current cuda device is " << old_cuda_device; std::vector engine_nodes; engine_nodes.resize(engine_segments.size()); - LOG(INFO) << "here " << engine_segments.size(); for (int i = 0; i < engine_segments.size(); ++i) { auto& engine = engine_segments.at(i); - - LOG(INFO) << "here4 "; // Partition the workspace size by the average of node ratio and segment // graphdef size engine.max_workspace_size_bytes = diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 0d98da08b0..39f9e220e4 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -694,14 +694,6 @@ void ReorderCKtoKC(const TRT_ShapedWeights& iweights, ostrides); break; } - case tensorflow::DataType::DT_INT32: { - Reorder2( - {k, c}, static_cast(iweights.GetValues()), - istrides, - static_cast(const_cast(oweights->GetValues())), - ostrides); - break; - } default: LOG(FATAL) << "Unsupported type in reorder expected fp32 or fp16 but got " << DataTypeString(iweights.type_); diff --git a/tensorflow/contrib/tensorrt/test/int_test.py b/tensorflow/contrib/tensorrt/test/int_test.py index bb90c34168..b9636fa098 100644 --- a/tensorflow/contrib/tensorrt/test/int_test.py +++ b/tensorflow/contrib/tensorrt/test/int_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Model script to test TF-TensorRT integration.""" +"""Test conversion of graphs involving INT32 tensors and operations.""" from __future__ import absolute_import from __future__ import division @@ -37,37 +37,24 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): return constant_op.constant(np.random.randn(*shape), dtype=dtype) def GetParams(self): - """Testing conversion of BiasAdd MatMul in TF-TRT conversion.""" - # Note that tf.nn.bias_add supports up to 5 dimensions. + """Test exclusion of ops which are not supported in INT32 mode by TF-TRT""" + input_name = 'input' + output_name = 'output' input_dims = [100, 4] g = ops.Graph() with g.as_default(): - # float part - # input1 = array_ops.placeholder( - # dtype=dtypes.float32, shape=input_dims, name='input_float32') - # b1 = self._ConstOp((4, 10), dtypes.float32) - # x1 = math_ops.matmul(input1, b1) - # b1 = self._ConstOp((1, 10), dtypes.float32) - # x1 = x1 + b1 - - # int part - input2 = array_ops.placeholder( - dtype=dtypes.int32, shape=input_dims, name='input_int32') - b2 = self._ConstOp((4, 10), dtypes.int32) - x2 = math_ops.matmul(input2, b2) - b2 = self._ConstOp((10,), dtypes.int32) - x2 = nn.bias_add(x2, b2) - - # combine - #y = x1 * x2 - - #out1 = array_ops.identity(x1, name='output_float32') - out2 = array_ops.identity(x2, name='output_int32') + x = array_ops.placeholder( + dtype=dtypes.int32, shape=input_dims, name=input_name) + b = self._ConstOp((4, 10), dtypes.int32) + x = math_ops.matmul(x, b) + b = self._ConstOp((10,), dtypes.int32) + x = nn.bias_add(x, b) + x = array_ops.identity(x, name=output_name) return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), - input_names=['input_int32'], - input_dims=[[100, 4]], - output_names=['output_int32'], + input_names=[input_name], + input_dims=[input_dims], + output_names=[output_name], expected_output_dims=[(100, 10)]) def GetConversionParams(self, run_params): @@ -89,7 +76,7 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): """Whether to run the test.""" # TODO(aaroey): Trt 4.0 forbids conversion for tensors with rank <3 in int8 # mode, which is a bug. Re-enable this when trt library is fixed. - return not trt_test.IsQuantizationMode(run_params.precision_mode) #and not run_params.dynamic_engine + return not trt_test.IsQuantizationMode(run_params.precision_mode) if __name__ == "__main__": -- GitLab From f84227cac2abe03818cecd80ac02b86b8090e376 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 21 Dec 2018 15:41:01 -0800 Subject: [PATCH 0453/2345] Rename test --- tensorflow/contrib/tensorrt/BUILD | 2 +- .../contrib/tensorrt/test/{int_test.py => int32_test.py} | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) rename tensorflow/contrib/tensorrt/test/{int_test.py => int32_test.py} (96%) diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index e58a259a18..418e1564bf 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -493,7 +493,7 @@ cuda_py_tests( "test/const_broadcast_test.py", "test/conv2d_test.py", "test/identity_output_test.py", - "test/int_test.py", + "test/int32_test.py", "test/manual_test.py", "test/memory_alignment_test.py", "test/multi_connection_neighbor_engine_test.py", diff --git a/tensorflow/contrib/tensorrt/test/int_test.py b/tensorflow/contrib/tensorrt/test/int32_test.py similarity index 96% rename from tensorflow/contrib/tensorrt/test/int_test.py rename to tensorflow/contrib/tensorrt/test/int32_test.py index b9636fa098..8f272efc91 100644 --- a/tensorflow/contrib/tensorrt/test/int_test.py +++ b/tensorflow/contrib/tensorrt/test/int32_test.py @@ -31,7 +31,7 @@ from tensorflow.python.ops import nn from tensorflow.python.platform import test -class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): +class ExcludeUnsupportedInt32Test(trt_test.TfTrtIntegrationTestBase): def _ConstOp(self, shape, dtype): return constant_op.constant(np.random.randn(*shape), dtype=dtype) @@ -59,7 +59,7 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): def GetConversionParams(self, run_params): """Return a ConversionParams for test.""" - conversion_params = super(BiasaddMatMulTest, + conversion_params = super(ExcludeUnsupportedInt32Test, self).GetConversionParams(run_params) return conversion_params._replace( max_batch_size=100, @@ -78,6 +78,5 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): # mode, which is a bug. Re-enable this when trt library is fixed. return not trt_test.IsQuantizationMode(run_params.precision_mode) - if __name__ == "__main__": test.main() -- GitLab From 1a73fdfa83bd50695a7d374d14a5cb3835d94d9e Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 21 Dec 2018 16:00:20 -0800 Subject: [PATCH 0454/2345] Add extra check incase segmenter does not exclude CPU in order to prevent segfault --- .../contrib/tensorrt/convert/convert_graph.cc | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index fd31c356c1..cfde0049fc 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -355,12 +355,18 @@ tensorflow::Status GetEngineInfo( const auto& node_name = (*it)->name(); if (segment_nodes.count(node_name) == 0) continue; auto node = *it; - // TODO: check for CPU device here - // If device is CPU, we should've caught that in the segmenter. Fall back here. - auto node_device = node->requested_device(); if (!node_device.empty()) { - segment_devices.insert(node_device); + // If device is CPU, we should've caught that in the segmenter. + DeviceNameUtils::ParsedName parsed_name; + DeviceNameUtils::ParseFullName(node_device, &parsed_name); + if (parsed_name.type == "CPU") { + LOG(WARNING) << "Node " << node->name() << " was assigned to the CPU " + << "but did not get excluded by the segmenter. " + << "Attempting to place on GPU."; + } else { + segment_devices.insert(node_device); + } } else { if (node->has_assigned_device_name()) { segment_devices.insert(node->assigned_device_name()); -- GitLab From 399ae14f9829957e0b6eda6cc48f2a99133255e5 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 21 Dec 2018 16:01:39 -0800 Subject: [PATCH 0455/2345] Improve comment --- tensorflow/contrib/tensorrt/convert/convert_graph.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index cfde0049fc..d8356b31a0 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -357,7 +357,9 @@ tensorflow::Status GetEngineInfo( auto node = *it; auto node_device = node->requested_device(); if (!node_device.empty()) { - // If device is CPU, we should've caught that in the segmenter. + // If device is CPU, we should've caught that in the segmenter. Don't add + // CPU to segment_device because that would cause a segfault in + // GetDeviceAndAllocator. DeviceNameUtils::ParsedName parsed_name; DeviceNameUtils::ParseFullName(node_device, &parsed_name); if (parsed_name.type == "CPU") { -- GitLab From 2434065e691a553a120aef6c16c76f29b55267a7 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 4 Jan 2019 10:40:57 -0800 Subject: [PATCH 0456/2345] Add TopK test. Specify type for topk indices output. Fix formatting --- tensorflow/contrib/tensorrt/BUILD | 1 + .../contrib/tensorrt/convert/convert_nodes.cc | 8 ++- tensorflow/contrib/tensorrt/test/topk_test.py | 59 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tensorflow/contrib/tensorrt/test/topk_test.py diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 418e1564bf..b0f55fed44 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -501,6 +501,7 @@ cuda_py_tests( "test/quantization_test.py", "test/rank_two_test.py", "test/reshape_transpose_test.py", + "test/topk_test.py", "test/vgg_block_nchw_test.py", "test/vgg_block_test.py", ], diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 39f9e220e4..ee51c287ab 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -1728,8 +1728,9 @@ Status BinaryTensorOpTensor(OpConverterParams* params, TFAttrs attrs(node_def); nvinfer1::DataType dtype = attrs.get("T"); if (dtype == nvinfer1::DataType::kINT32) { - return errors::Unimplemented( - "Binary op ", node_def.op(), " does not support INT32, at ", node_def.name()); + return errors::Unimplemented("Binary op ", node_def.op(), + " does not support INT32, at ", + node_def.name()); } if (params->validation_only) return Status::OK(); @@ -3605,6 +3606,9 @@ tensorflow::Status ConvertTopK(OpConverterParams* params) { nvinfer1::ITensor* output_value_tensor = layer->getOutput(0); nvinfer1::ITensor* output_indices_tensor = layer->getOutput(1); + // Tensor type for network output is not inferred. Indices should be INT32 + // (default is float). + output_indices_tensor->setType(nvinfer1::DataType::kINT32); params->outputs->push_back(TRT_TensorOrWeights(output_value_tensor)); params->outputs->push_back(TRT_TensorOrWeights(output_indices_tensor)); return tensorflow::Status::OK(); diff --git a/tensorflow/contrib/tensorrt/test/topk_test.py b/tensorflow/contrib/tensorrt/test/topk_test.py new file mode 100644 index 0000000000..cb539bbb93 --- /dev/null +++ b/tensorflow/contrib/tensorrt/test/topk_test.py @@ -0,0 +1,59 @@ +# 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. +# ============================================================================== +"""Model script to test TF-TensorRT integration.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.tensorrt.test import tf_trt_integration_test_base as trt_test +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import nn_ops +from tensorflow.python.ops import array_ops +from tensorflow.python.platform import test + + +class TopKTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Testing Top-K in TF-TRT conversion.""" + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 100] + k = 5 + g = ops.Graph() + with g.as_default(): + x = array_ops.placeholder(dtype=dtype, shape=input_dims, name=input_name) + values, indices = nn_ops.top_k(x, k) + values = array_ops.identity(values, name='output_values') + indices = array_ops.identity(indices, name='output_indices') + return trt_test.TfTrtIntegrationTestParams( + gdef=g.as_graph_def(), + input_names=[input_name], + input_dims=[input_dims], + output_names=['output_values', 'output_indices'], + expected_output_dims=[(100, k), (100, k)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["TRTEngineOp_0"] + + +if __name__ == "__main__": + test.main() -- GitLab From 4d0a420c4b4d1fbe3e666bd377de2a40401177d2 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 4 Jan 2019 10:48:57 -0800 Subject: [PATCH 0457/2345] Fix clang-format and pylint --- tensorflow/contrib/tensorrt/convert/convert_nodes.cc | 6 ++++-- tensorflow/contrib/tensorrt/test/topk_test.py | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index ee51c287ab..9c0d79b810 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -3856,8 +3856,10 @@ tensorflow::Status ConvertSegmentToGraphDef( marker_nodes.insert(node_name); auto seg_node = segment_def->add_node(); tensorflow::NodeDefBuilder builder(node_name, "Identity"); - auto status = builder.Input(connection.inside_node_name, connection.inside_port, dtype) - .Finalize(seg_node); + auto status = + builder + .Input(connection.inside_node_name, connection.inside_port, dtype) + .Finalize(seg_node); VLOG(1) << "Constructing output " << node_name << " for the edge " << connection.inside_node_name << ":" << connection.inside_port << " -> " << connection.outside_node_name << ":" diff --git a/tensorflow/contrib/tensorrt/test/topk_test.py b/tensorflow/contrib/tensorrt/test/topk_test.py index cb539bbb93..2edf7cb587 100644 --- a/tensorflow/contrib/tensorrt/test/topk_test.py +++ b/tensorflow/contrib/tensorrt/test/topk_test.py @@ -18,10 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import numpy as np - from tensorflow.contrib.tensorrt.test import tf_trt_integration_test_base as trt_test -from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.ops import nn_ops -- GitLab From 0107c9155260f9aeb32d7660f7a9e8a8a5126850 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 4 Jan 2019 14:18:37 -0800 Subject: [PATCH 0458/2345] Remove segmenter rule against CPU ops. --- tensorflow/contrib/tensorrt/convert/convert_graph.cc | 2 ++ tensorflow/contrib/tensorrt/segment/segment.cc | 12 ------------ tensorflow/contrib/tensorrt/test/int32_test.py | 7 ++++--- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index d8356b31a0..1ed6bbe5a3 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -371,6 +371,8 @@ tensorflow::Status GetEngineInfo( } } else { if (node->has_assigned_device_name()) { + // It appears that nodes will not have assigned devices at this point in + // execution. segment_devices.insert(node->assigned_device_name()); } else { VLOG(2) << "Node " << node->name() diff --git a/tensorflow/contrib/tensorrt/segment/segment.cc b/tensorflow/contrib/tensorrt/segment/segment.cc index 9f72f1490c..6f2a7846d7 100644 --- a/tensorflow/contrib/tensorrt/segment/segment.cc +++ b/tensorflow/contrib/tensorrt/segment/segment.cc @@ -436,10 +436,6 @@ tensorflow::Status SegmentGraph( std::vector> node_segments; for (int i = 0; i < graph->num_node_ids(); ++i) { SimpleNode* node = graph->FindNodeId(i); - const string requested_device = - get_device_type(node->tf_node()->requested_device()); - const string assigned_device = - get_device_type(node->tf_node()->assigned_device_name()); if (options.exclude_node_list.count(node->name()) != 0) { VLOG(1) << "Not a TF-TRT candidate, " @@ -449,14 +445,6 @@ tensorflow::Status SegmentGraph( unsupported_ops.emplace(node->tf_node()->type_string()); num_unsupported_ops++; node = nullptr; - } else if (requested_device == "CPU" || assigned_device == "CPU") { - VLOG(1) << "Not a TF-TRT candidate, " - << "(Op type: " << node->tf_node()->type_string() << "), " - << "(Op name: " << node->name() << "), " - << "(Reason: node is assigned to CPU)"; - unsupported_ops.emplace(node->tf_node()->type_string()); - num_unsupported_ops++; - node = nullptr; } else { const Status status = candidate_fn(node->tf_node()); if (!status.ok()) { diff --git a/tensorflow/contrib/tensorrt/test/int32_test.py b/tensorflow/contrib/tensorrt/test/int32_test.py index 8f272efc91..50af816d8e 100644 --- a/tensorflow/contrib/tensorrt/test/int32_test.py +++ b/tensorflow/contrib/tensorrt/test/int32_test.py @@ -41,13 +41,14 @@ class ExcludeUnsupportedInt32Test(trt_test.TfTrtIntegrationTestBase): input_name = 'input' output_name = 'output' input_dims = [100, 4] + dtype = dtypes.int32 g = ops.Graph() with g.as_default(): x = array_ops.placeholder( - dtype=dtypes.int32, shape=input_dims, name=input_name) - b = self._ConstOp((4, 10), dtypes.int32) + dtype=dtype, shape=input_dims, name=input_name) + b = self._ConstOp((4, 10), dtype) x = math_ops.matmul(x, b) - b = self._ConstOp((10,), dtypes.int32) + b = self._ConstOp((10,), dtype) x = nn.bias_add(x, b) x = array_ops.identity(x, name=output_name) return trt_test.TfTrtIntegrationTestParams( -- GitLab From a5037695df5cc9eee60ea31fc1136c92786b57b1 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 4 Jan 2019 14:25:17 -0800 Subject: [PATCH 0459/2345] Update comment on fix to prevent segfault --- tensorflow/contrib/tensorrt/convert/convert_graph.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index 1ed6bbe5a3..df4bc4777c 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -357,15 +357,15 @@ tensorflow::Status GetEngineInfo( auto node = *it; auto node_device = node->requested_device(); if (!node_device.empty()) { - // If device is CPU, we should've caught that in the segmenter. Don't add - // CPU to segment_device because that would cause a segfault in - // GetDeviceAndAllocator. + // If device is CPU, treat as if no device was assigned. Don't add CPU to + // segment_device because that would cause a segfault in + // GetDeviceAndAllocator. This is because GetDeviceAndAllocator assumes + // any already set device is a GPU. DeviceNameUtils::ParsedName parsed_name; DeviceNameUtils::ParseFullName(node_device, &parsed_name); if (parsed_name.type == "CPU") { - LOG(WARNING) << "Node " << node->name() << " was assigned to the CPU " - << "but did not get excluded by the segmenter. " - << "Attempting to place on GPU."; + VLOG(1) << "Node " << node->name() << " was assigned to the CPU. " + << "Attempting to place on GPU."; } else { segment_devices.insert(node_device); } -- GitLab From 55d34860cd43967a7a19ff9c6c9605168aa191cf Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 9 Jan 2019 22:50:01 +0000 Subject: [PATCH 0460/2345] Address additional review comments Signed-off-by: Yong Tang --- tensorflow/python/ops/ragged/ragged_getitem.py | 2 +- tensorflow/python/ops/ragged/ragged_tensor_test.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/ops/ragged/ragged_getitem.py b/tensorflow/python/ops/ragged/ragged_getitem.py index f5ac3e9a2a..a7124bf8e5 100644 --- a/tensorflow/python/ops/ragged/ragged_getitem.py +++ b/tensorflow/python/ops/ragged/ragged_getitem.py @@ -169,7 +169,7 @@ def _ragged_getitem(rt_input, key_list): # will simply be ignored as it will be processed later anyway. try: if row_key >= len(starts): - raise IndexError('row key {} out of bounds'.format(row_key)) + raise IndexError('Row key {} out of bounds'.format(row_key)) except TypeError: pass row = rt_input.values[starts[row_key]:limits[row_key]] diff --git a/tensorflow/python/ops/ragged/ragged_tensor_test.py b/tensorflow/python/ops/ragged/ragged_tensor_test.py index 0c9506c731..d92accb0a0 100644 --- a/tensorflow/python/ops/ragged/ragged_tensor_test.py +++ b/tensorflow/python/ops/ragged/ragged_tensor_test.py @@ -1226,9 +1226,8 @@ class RaggedTensorTest(ragged_test_util.RaggedTensorTestCase, r = ragged_factory_ops.constant(values) i = 0 for elem in r: - value = values[i] + self.assertAllEqual(elem, values[i]) i += 1 - self.assertAllEqual(elem, value) if __name__ == '__main__': googletest.main() -- GitLab From 0fb8e5f9c0149c4cfaf8ec41b1293a947bc91895 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 14:52:30 -0800 Subject: [PATCH 0461/2345] This change makes errors more informative in case of functions. We now report two additional things: 1. The line number/file name of the culprit node inside the function. 2. The function calling stack For eg, consider the following code: import tensorflow as tf tf.enable_eager_execution() @tf.contrib.eager.defun def fn3(x): return x + 2 @tf.contrib.eager.defun def fn2(x): tf.assert_equal(fn3(x), 3) return 2 @tf.contrib.eager.defun def fn(x): return fn2(x) @tf.contrib.eager.defun def main(argv): x = fn(2) print(x) if __name__ == '__main__': tf.app.run() In this, the error message would contain the following: InvalidArgumentError: assertion failed: [] [Condition x == y did not hold element-wise:] [x (PartitionedCall:0) = ] [4] [y (assert_equal/y:0) = ] [3] [[node assert_equal/Assert/Assert (defined at filename:line_num) ]] [[StatefulPartitionedCall]] [[StatefulPartitionedCall]] [Op:StatefulPartitionedCall] Function calling stack: main -> fn -> fn2 PiperOrigin-RevId: 228593653 --- .../core/kernels/partitioned_function_ops.cc | 9 ++- tensorflow/core/lib/core/errors.h | 4 ++ tensorflow/python/eager/function.py | 61 ++++++++++++++++--- tensorflow/python/eager/function_test.py | 24 ++++++++ .../python/framework/error_interpolation.py | 8 ++- 5 files changed, 92 insertions(+), 14 deletions(-) diff --git a/tensorflow/core/kernels/partitioned_function_ops.cc b/tensorflow/core/kernels/partitioned_function_ops.cc index e493dca188..cadb83d8cf 100644 --- a/tensorflow/core/kernels/partitioned_function_ops.cc +++ b/tensorflow/core/kernels/partitioned_function_ops.cc @@ -20,6 +20,7 @@ limitations under the License. #include "tensorflow/core/framework/types.h" #include "tensorflow/core/framework/types.pb.h" #include "tensorflow/core/grappler/optimizers/meta_optimizer.h" +#include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/protobuf/config.pb.h" #include "tensorflow/core/protobuf/rewriter_config.pb.h" #include "tensorflow/core/util/ptr_util.h" @@ -213,10 +214,14 @@ class PartitionedCallOp : public AsyncOpKernel { run_opts.rendezvous = rendez; std::vector* rets = new std::vector; + const string& func_name = func_.name(); lib->Run(run_opts, handle, inputs, rets, - [rets, rendez, done, ctx](const Status& status) { + [rets, rendez, done, ctx, func_name](const Status& status) { if (!status.ok()) { - ctx->SetStatus(status); + const string function_and_msg = + strings::StrCat(errors::FormatFunctionForError(func_name), + " ", status.error_message()); + ctx->SetStatus(Status(status.code(), function_and_msg)); } else { for (int i = 0; i < rets->size(); ++i) { ctx->set_output(i, (*rets)[i]); diff --git a/tensorflow/core/lib/core/errors.h b/tensorflow/core/lib/core/errors.h index d5cbe6c616..4815f7c2cc 100644 --- a/tensorflow/core/lib/core/errors.h +++ b/tensorflow/core/lib/core/errors.h @@ -150,6 +150,10 @@ string FormatColocationNodeForError(const T& names) { }); } +inline string FormatFunctionForError(const string& name) { + return strings::StrCat("{{function_node ", name, "}}"); +} + // The CanonicalCode() for non-errors. using ::tensorflow::error::OK; diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 67c633726f..83cd140158 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -41,6 +41,8 @@ from tensorflow.python.framework import c_api_util from tensorflow.python.framework import constant_op from tensorflow.python.framework import device as pydev from tensorflow.python.framework import dtypes as dtypes_module +from tensorflow.python.framework import error_interpolation +from tensorflow.python.framework import errors from tensorflow.python.framework import func_graph as func_graph_module from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_spec @@ -131,6 +133,46 @@ def _parse_func_attrs(attributes): return attrs +class _InterpolateFunctionError(object): + """Context Manager that interpolates the exception from 'top_level_func'.""" + + def __init__(self, top_level_func): + self._func = top_level_func + + def __enter__(self): + pass + + def __exit__(self, typ, exc, tb): + if not exc or not isinstance(exc, errors.OpError): + return False + message = compat.as_text(exc.message) + _, tags = error_interpolation.parse_message(message) + g = None + func_stack = [] + # pylint: disable=protected-access + for t in tags: + if t.type == "function_node": + if t.name == compat.as_str(self._func.name): + g = self._func._graph + elif g: + next_func = g._get_function(t.name) + if next_func is not None and isinstance(next_func, + _EagerDefinedFunction): + g = next_func._graph + if g: + func_stack.append(g.name) + else: + func_stack.append("") + # pylint: enable=protected-access + if g: + message = error_interpolation.interpolate(message, g) + message += "\n\nFunction call stack:\n" + message += " -> ".join(func_stack) + message += "\n" + exc._message = message # pylint: disable=protected-access + return False + + def _forward_name(n): """The name of a generated forward defun named n.""" return "__forward_%s_%s" % (n, ops.uid()) @@ -279,13 +321,14 @@ class _EagerDefinedFunction(object): "Arguments and signature arguments do not match: %s %s " % (len(args), len(list(self.signature.input_arg)))) function_call_options = ctx.get_function_call_options() - outputs = functional_ops.partitioned_call( - args=args, - f=self, - tout=self._output_types, - executing_eagerly=executing_eagerly, - config=function_call_options.config_proto_serialized, - executor_type=function_call_options.executor_type) + with _InterpolateFunctionError(self): + outputs = functional_ops.partitioned_call( + args=args, + f=self, + tout=self._output_types, + executing_eagerly=executing_eagerly, + config=function_call_options.config_proto_serialized, + executor_type=function_call_options.executor_type) if executing_eagerly: return outputs @@ -401,8 +444,8 @@ class Function(object): """ return self._call_flat( (t for t in nest.flatten((args, kwargs)) - if isinstance( - t, (ops.Tensor, resource_variable_ops.ResourceVariable)))) + if isinstance(t, (ops.Tensor, + resource_variable_ops.ResourceVariable)))) def _call_flat(self, args): """Executes the wrapped function. diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index 55a9cc4e92..1966b259bf 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -45,6 +45,7 @@ from tensorflow.python.framework import test_util from tensorflow.python.keras.engine import training as keras_training from tensorflow.python.layers import convolutional from tensorflow.python.ops import array_ops +from tensorflow.python.ops import check_ops from tensorflow.python.ops import clip_ops from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import init_ops @@ -2063,6 +2064,29 @@ class FunctionTest(test.TestCase, parameterized.TestCase): # function itself is not involved in a reference cycle. self.assertIs(None, weak_fn()) + def testFunctionStackInErrorMessage(self): + + @def_function.function() + def fn3(x): + return x + 2 + + @def_function.function() + def fn2(x): + check_ops.assert_equal(fn3(x), 3) + return 2 + + @def_function.function() + def fn(x): + return fn2(x) + + try: + fn(2) + self.assertFail() + except errors.InvalidArgumentError as e: + self.assertIn('fn -> fn2', e.message) + self.assertIn('node assert_equal/Assert/Assert (defined at', e.message) + self.assertNotIn('fn3', e.message) + if __name__ == '__main__': ops.enable_eager_execution( diff --git a/tensorflow/python/framework/error_interpolation.py b/tensorflow/python/framework/error_interpolation.py index 5e1bed8e0e..af83b70a46 100644 --- a/tensorflow/python/framework/error_interpolation.py +++ b/tensorflow/python/framework/error_interpolation.py @@ -31,7 +31,7 @@ import six from tensorflow.python.util import tf_stack -_NAME_REGEX = r"[A-Za-z0-9.][A-Za-z0-9_.\-/]*?" +_NAME_REGEX = r"[A-Za-z0-9_.][A-Za-z0-9_.\-/]*?" _TAG_REGEX = r"{{{{({name}) ({name})}}}}".format(name=_NAME_REGEX) _INTERPOLATION_REGEX = r"^(.*?)({tag})".format(tag=_TAG_REGEX) _INTERPOLATION_PATTERN = re.compile(_INTERPOLATION_REGEX, re.DOTALL) @@ -45,7 +45,7 @@ _BAD_FILE_SUBSTRINGS = [ ] -def _parse_message(message): +def parse_message(message): """Parses the message. Splits the message into separators and tags. Tags are named tuples @@ -376,7 +376,7 @@ def interpolate(error_message, graph): Returns: The string with tags of the form {{type name}} interpolated. """ - seps, tags = _parse_message(error_message) + seps, tags = parse_message(error_message) subs = [] end_msg = collections.defaultdict(list) tagged_ops = [] @@ -404,6 +404,8 @@ def interpolate(error_message, graph): msg = "node %s%s placed on device %s " % ( ops[0].name, field_dict["defined_at"], field_dict["devices"]) end_msg["colocations"].append(field_dict["devs_and_colocs"]) + if tag.type == "function_node": + msg = "" subs.append(msg) if "source_nodes" in end_msg: -- GitLab From 00a3be761b620761a71effb793e2440096f1eebd Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 14:55:31 -0800 Subject: [PATCH 0462/2345] Add LinearOperator.inverse method. PiperOrigin-RevId: 228594130 --- .../linalg/linear_operator_algebra_test.py | 48 ++++++ .../linalg/linear_operator_block_diag_test.py | 38 +++-- .../linalg/linear_operator_diag_test.py | 9 +- .../linalg/linear_operator_identity_test.py | 24 ++- .../linalg/linear_operator_kronecker_test.py | 37 +++-- .../linalg/linear_operator_zeros_test.py | 3 +- .../ops/linalg/inverse_registrations.py | 108 +++++++++++++ tensorflow/python/ops/linalg/linalg.py | 1 + .../python/ops/linalg/linear_operator.py | 25 +++ .../ops/linalg/linear_operator_algebra.py | 71 +++++++++ .../ops/linalg/linear_operator_test_util.py | 25 ++- ...w.linalg.-linear-operator-block-diag.pbtxt | 4 + ...ow.linalg.-linear-operator-circulant.pbtxt | 4 + ...linalg.-linear-operator-circulant2-d.pbtxt | 4 + ...linalg.-linear-operator-circulant3-d.pbtxt | 4 + ....linalg.-linear-operator-composition.pbtxt | 4 + ...sorflow.linalg.-linear-operator-diag.pbtxt | 4 + ....linalg.-linear-operator-full-matrix.pbtxt | 4 + ...low.linalg.-linear-operator-identity.pbtxt | 4 + ...ow.linalg.-linear-operator-inversion.pbtxt | 142 ++++++++++++++++++ ...ow.linalg.-linear-operator-kronecker.pbtxt | 4 + ...alg.-linear-operator-low-rank-update.pbtxt | 4 + ...lg.-linear-operator-lower-triangular.pbtxt | 4 + ...alg.-linear-operator-scaled-identity.pbtxt | 4 + ...orflow.linalg.-linear-operator-zeros.pbtxt | 4 + .../tensorflow.linalg.-linear-operator.pbtxt | 4 + .../api/golden/v1/tensorflow.linalg.pbtxt | 4 + ...w.linalg.-linear-operator-block-diag.pbtxt | 4 + ...ow.linalg.-linear-operator-circulant.pbtxt | 4 + ...linalg.-linear-operator-circulant2-d.pbtxt | 4 + ...linalg.-linear-operator-circulant3-d.pbtxt | 4 + ....linalg.-linear-operator-composition.pbtxt | 4 + ...sorflow.linalg.-linear-operator-diag.pbtxt | 4 + ....linalg.-linear-operator-full-matrix.pbtxt | 4 + ...low.linalg.-linear-operator-identity.pbtxt | 4 + ...ow.linalg.-linear-operator-inversion.pbtxt | 142 ++++++++++++++++++ ...ow.linalg.-linear-operator-kronecker.pbtxt | 4 + ...alg.-linear-operator-low-rank-update.pbtxt | 4 + ...lg.-linear-operator-lower-triangular.pbtxt | 4 + ...alg.-linear-operator-scaled-identity.pbtxt | 4 + ...orflow.linalg.-linear-operator-zeros.pbtxt | 4 + .../tensorflow.linalg.-linear-operator.pbtxt | 4 + .../api/golden/v2/tensorflow.linalg.pbtxt | 4 + 43 files changed, 761 insertions(+), 32 deletions(-) create mode 100644 tensorflow/python/ops/linalg/inverse_registrations.py create mode 100644 tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-inversion.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-inversion.pbtxt diff --git a/tensorflow/python/kernel_tests/linalg/linear_operator_algebra_test.py b/tensorflow/python/kernel_tests/linalg/linear_operator_algebra_test.py index 8e296c026c..ec78a3ffe0 100644 --- a/tensorflow/python/kernel_tests/linalg/linear_operator_algebra_test.py +++ b/tensorflow/python/kernel_tests/linalg/linear_operator_algebra_test.py @@ -30,6 +30,8 @@ _CHOLESKY_DECOMPS = linear_operator_algebra._CHOLESKY_DECOMPS _MATMUL = linear_operator_algebra._MATMUL _registered_cholesky = linear_operator_algebra._registered_cholesky _registered_matmul = linear_operator_algebra._registered_matmul +_INVERSES = linear_operator_algebra._INVERSES +_registered_inverse = linear_operator_algebra._registered_inverse # pylint: enable=protected-access @@ -129,5 +131,51 @@ class MatmulTest(test.TestCase): self.assertEqual(v, _registered_matmul(k[0], k[1])) +class InverseTest(test.TestCase): + + def testRegistration(self): + + class CustomLinOp(linear_operator.LinearOperator): + + def _matmul(self, a): + pass + + def _shape(self): + return tensor_shape.TensorShape([1, 1]) + + def _shape_tensor(self): + pass + + # Register Inverse to a lambda that spits out the name parameter + @linear_operator_algebra.RegisterInverse(CustomLinOp) + def _inverse(a): # pylint: disable=unused-argument,unused-variable + return "OK" + + with self.assertRaisesRegexp(ValueError, "singular"): + CustomLinOp(dtype=None, is_non_singular=False).inverse() + + self.assertEqual("OK", CustomLinOp( + dtype=None, is_non_singular=True).inverse()) + + def testRegistrationFailures(self): + + class CustomLinOp(linear_operator.LinearOperator): + pass + + with self.assertRaisesRegexp(TypeError, "must be callable"): + linear_operator_algebra.RegisterInverse(CustomLinOp)("blah") + + # First registration is OK + linear_operator_algebra.RegisterInverse(CustomLinOp)(lambda a: None) + + # Second registration fails + with self.assertRaisesRegexp(ValueError, "has already been registered"): + linear_operator_algebra.RegisterInverse(CustomLinOp)(lambda a: None) + + def testExactRegistrationsAllMatch(self): + for (k, v) in _INVERSES.items(): + self.assertEqual(v, _registered_inverse(k[0])) + + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/kernel_tests/linalg/linear_operator_block_diag_test.py b/tensorflow/python/kernel_tests/linalg/linear_operator_block_diag_test.py index f0cc5d709f..96e6e3c04c 100644 --- a/tensorflow/python/kernel_tests/linalg/linear_operator_block_diag_test.py +++ b/tensorflow/python/kernel_tests/linalg/linear_operator_block_diag_test.py @@ -155,20 +155,38 @@ class SquareLinearOperatorBlockDiagTest( is_self_adjoint=True, ) cholesky_factor = operator.cholesky() - self.assertTrue(isinstance( + self.assertIsInstance( cholesky_factor, - block_diag.LinearOperatorBlockDiag)) + block_diag.LinearOperatorBlockDiag) self.assertEqual(2, len(cholesky_factor.operators)) - self.assertTrue( - isinstance( - cholesky_factor.operators[0], - lower_triangular.LinearOperatorLowerTriangular) + self.assertIsInstance( + cholesky_factor.operators[0], + lower_triangular.LinearOperatorLowerTriangular) + self.assertIsInstance( + cholesky_factor.operators[1], + lower_triangular.LinearOperatorLowerTriangular ) - self.assertTrue( - isinstance( - cholesky_factor.operators[1], - lower_triangular.LinearOperatorLowerTriangular) + + def test_block_diag_inverse_type(self): + matrix = [[1., 0.], [0., 1.]] + operator = block_diag.LinearOperatorBlockDiag( + [ + linalg.LinearOperatorFullMatrix( + matrix, + is_non_singular=True, + ), + linalg.LinearOperatorFullMatrix( + matrix, + is_non_singular=True, + ), + ], + is_non_singular=True, ) + inverse = operator.inverse() + self.assertIsInstance( + inverse, + block_diag.LinearOperatorBlockDiag) + self.assertEqual(2, len(inverse.operators)) def test_is_non_singular_auto_set(self): # Matrix with two positive eigenvalues, 11 and 8. diff --git a/tensorflow/python/kernel_tests/linalg/linear_operator_diag_test.py b/tensorflow/python/kernel_tests/linalg/linear_operator_diag_test.py index dcbc0dd7c9..4d7a31be87 100644 --- a/tensorflow/python/kernel_tests/linalg/linear_operator_diag_test.py +++ b/tensorflow/python/kernel_tests/linalg/linear_operator_diag_test.py @@ -194,9 +194,12 @@ class LinearOperatorDiagTest( is_positive_definite=True, is_self_adjoint=True, ) - self.assertTrue(isinstance( - operator.cholesky(), - linalg.LinearOperatorDiag)) + self.assertIsInstance(operator.cholesky(), linalg.LinearOperatorDiag) + + def test_diag_inverse_type(self): + diag = [1., 3., 5., 8.] + operator = linalg.LinearOperatorDiag(diag, is_non_singular=True) + self.assertIsInstance(operator.inverse(), linalg.LinearOperatorDiag) if __name__ == "__main__": diff --git a/tensorflow/python/kernel_tests/linalg/linear_operator_identity_test.py b/tensorflow/python/kernel_tests/linalg/linear_operator_identity_test.py index 2da5e712d7..14b5228bca 100644 --- a/tensorflow/python/kernel_tests/linalg/linear_operator_identity_test.py +++ b/tensorflow/python/kernel_tests/linalg/linear_operator_identity_test.py @@ -265,9 +265,14 @@ class LinearOperatorIdentityTest( is_positive_definite=True, is_self_adjoint=True, ) - self.assertTrue(isinstance( - operator.cholesky(), - linalg_lib.LinearOperatorIdentity)) + self.assertIsInstance( + operator.cholesky(), linalg_lib.LinearOperatorIdentity) + + def test_identity_inverse_type(self): + operator = linalg_lib.LinearOperatorIdentity( + num_rows=2, is_non_singular=True) + self.assertIsInstance( + operator.inverse(), linalg_lib.LinearOperatorIdentity) class LinearOperatorScaledIdentityTest( @@ -491,10 +496,19 @@ class LinearOperatorScaledIdentityTest( is_positive_definite=True, is_self_adjoint=True, ) - self.assertTrue(isinstance( + self.assertIsInstance( operator.cholesky(), - linalg_lib.LinearOperatorScaledIdentity)) + linalg_lib.LinearOperatorScaledIdentity) + def test_scaled_identity_inverse_type(self): + operator = linalg_lib.LinearOperatorScaledIdentity( + num_rows=2, + multiplier=3., + is_non_singular=True, + ) + self.assertIsInstance( + operator.inverse(), + linalg_lib.LinearOperatorScaledIdentity) if __name__ == "__main__": test.main() diff --git a/tensorflow/python/kernel_tests/linalg/linear_operator_kronecker_test.py b/tensorflow/python/kernel_tests/linalg/linear_operator_kronecker_test.py index 513b246803..54ccc0c5f6 100644 --- a/tensorflow/python/kernel_tests/linalg/linear_operator_kronecker_test.py +++ b/tensorflow/python/kernel_tests/linalg/linear_operator_kronecker_test.py @@ -100,7 +100,7 @@ class SquareLinearOperatorKroneckerTest( @property def _tests_to_skip(self): - return ["det", "solve", "solve_with_broadcast"] + return ["det", "inverse", "solve", "solve_with_broadcast"] def _operator_and_matrix( self, build_info, dtype, use_placeholder, @@ -211,20 +211,33 @@ class SquareLinearOperatorKroneckerTest( is_self_adjoint=True, ) cholesky_factor = operator.cholesky() - self.assertTrue(isinstance( + self.assertIsInstance( cholesky_factor, - kronecker.LinearOperatorKronecker)) + kronecker.LinearOperatorKronecker) self.assertEqual(2, len(cholesky_factor.operators)) - self.assertTrue( - isinstance( - cholesky_factor.operators[0], - lower_triangular.LinearOperatorLowerTriangular) - ) - self.assertTrue( - isinstance( - cholesky_factor.operators[1], - lower_triangular.LinearOperatorLowerTriangular) + self.assertIsInstance( + cholesky_factor.operators[0], + lower_triangular.LinearOperatorLowerTriangular) + self.assertIsInstance( + cholesky_factor.operators[1], + lower_triangular.LinearOperatorLowerTriangular) + + def test_kronecker_inverse_type(self): + matrix = [[1., 0.], [0., 1.]] + operator = kronecker.LinearOperatorKronecker( + [ + linalg.LinearOperatorFullMatrix( + matrix, is_non_singular=True), + linalg.LinearOperatorFullMatrix( + matrix, is_non_singular=True), + ], + is_non_singular=True, ) + inverse = operator.inverse() + self.assertIsInstance( + inverse, + kronecker.LinearOperatorKronecker) + self.assertEqual(2, len(inverse.operators)) if __name__ == "__main__": diff --git a/tensorflow/python/kernel_tests/linalg/linear_operator_zeros_test.py b/tensorflow/python/kernel_tests/linalg/linear_operator_zeros_test.py index eb0b8ef127..10651d3c8a 100644 --- a/tensorflow/python/kernel_tests/linalg/linear_operator_zeros_test.py +++ b/tensorflow/python/kernel_tests/linalg/linear_operator_zeros_test.py @@ -36,7 +36,8 @@ class LinearOperatorZerosTest( @property def _tests_to_skip(self): - return ["cholesky", "log_abs_det", "solve", "solve_with_broadcast"] + return [ + "cholesky", "log_abs_det", "inverse", "solve", "solve_with_broadcast"] @property def _operator_build_infos(self): diff --git a/tensorflow/python/ops/linalg/inverse_registrations.py b/tensorflow/python/ops/linalg/inverse_registrations.py new file mode 100644 index 0000000000..b89bdd240e --- /dev/null +++ b/tensorflow/python/ops/linalg/inverse_registrations.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. +# ============================================================================== +"""Registrations for LinearOperator.inverse.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.ops.linalg import linear_operator +from tensorflow.python.ops.linalg import linear_operator_algebra +from tensorflow.python.ops.linalg import linear_operator_block_diag +from tensorflow.python.ops.linalg import linear_operator_circulant +from tensorflow.python.ops.linalg import linear_operator_diag +from tensorflow.python.ops.linalg import linear_operator_identity +from tensorflow.python.ops.linalg import linear_operator_inversion +from tensorflow.python.ops.linalg import linear_operator_kronecker + + +# By default, return LinearOperatorInversion which switched the .matmul +# and .solve methods. +@linear_operator_algebra.RegisterInverse(linear_operator.LinearOperator) +def _inverse_linear_operator(linop): + return linear_operator_inversion.LinearOperatorInversion( + linop, + is_non_singular=linop.is_non_singular, + is_self_adjoint=linop.is_self_adjoint, + is_positive_definite=linop.is_positive_definite, + is_square=linop.is_square) + + +@linear_operator_algebra.RegisterInverse( + linear_operator_diag.LinearOperatorDiag) +def _inverse_diag(diag_operator): + return linear_operator_diag.LinearOperatorDiag( + 1. / diag_operator.diag, + is_non_singular=diag_operator.is_non_singular, + is_self_adjoint=diag_operator.is_self_adjoint, + is_positive_definite=diag_operator.is_positive_definite, + is_square=True) + + +@linear_operator_algebra.RegisterInverse( + linear_operator_identity.LinearOperatorIdentity) +def _inverse_identity(identity_operator): + return identity_operator + + +@linear_operator_algebra.RegisterInverse( + linear_operator_identity.LinearOperatorScaledIdentity) +def _inverse_scaled_identity(identity_operator): + return linear_operator_identity.LinearOperatorScaledIdentity( + num_rows=identity_operator._num_rows, # pylint: disable=protected-access + multiplier=1. / identity_operator.multiplier, + is_non_singular=identity_operator.is_non_singular, + is_self_adjoint=True, + is_positive_definite=identity_operator.is_positive_definite, + is_square=True) + + +@linear_operator_algebra.RegisterInverse( + linear_operator_block_diag.LinearOperatorBlockDiag) +def _inverse_block_diag(block_diag_operator): + # We take the inverse of each block on the diagonal. + return linear_operator_block_diag.LinearOperatorBlockDiag( + operators=[ + operator.inverse() for operator in block_diag_operator.operators], + is_non_singular=block_diag_operator.is_non_singular, + is_self_adjoint=block_diag_operator.is_self_adjoint, + is_positive_definite=block_diag_operator.is_positive_definite, + is_square=True) + + +@linear_operator_algebra.RegisterInverse( + linear_operator_kronecker.LinearOperatorKronecker) +def _inverse_kronecker(kronecker_operator): + # Inverse decomposition of a Kronecker product is the Kronecker product + # of inverse decompositions. + return linear_operator_kronecker.LinearOperatorKronecker( + operators=[ + operator.inverse() for operator in kronecker_operator.operators], + is_non_singular=kronecker_operator.is_non_singular, + is_self_adjoint=kronecker_operator.is_self_adjoint, + is_positive_definite=kronecker_operator.is_positive_definite, + is_square=True) + + +@linear_operator_algebra.RegisterInverse( + linear_operator_circulant.LinearOperatorCirculant) +def _inverse_circulant(circulant_operator): + # Inverting the spectrum is sufficient to get the inverse. + return linear_operator_circulant.LinearOperatorCirculant( + spectrum=1. / circulant_operator.spectrum, + is_non_singular=circulant_operator.is_non_singular, + is_self_adjoint=circulant_operator.is_self_adjoint, + is_positive_definite=circulant_operator.is_positive_definite, + is_square=True) diff --git a/tensorflow/python/ops/linalg/linalg.py b/tensorflow/python/ops/linalg/linalg.py index ac4fd4ebc6..eebe741337 100644 --- a/tensorflow/python/ops/linalg/linalg.py +++ b/tensorflow/python/ops/linalg/linalg.py @@ -21,6 +21,7 @@ from __future__ import print_function # go/tf-wildcard-import # pylint: disable=wildcard-import,unused-import from tensorflow.python.ops.linalg import cholesky_registrations as _cholesky_registrations +from tensorflow.python.ops.linalg import inverse_registrations as _inverse_registrations from tensorflow.python.ops.linalg import linear_operator_algebra as _linear_operator_algebra from tensorflow.python.ops.linalg import matmul_registrations as _matmul_registrations from tensorflow.python.ops.linalg.linalg_impl import * diff --git a/tensorflow/python/ops/linalg/linear_operator.py b/tensorflow/python/ops/linalg/linear_operator.py index 6be81f4b34..4c99e86dc5 100644 --- a/tensorflow/python/ops/linalg/linear_operator.py +++ b/tensorflow/python/ops/linalg/linear_operator.py @@ -847,6 +847,31 @@ class LinearOperator(object): return self._solvevec(rhs, adjoint=adjoint) + def inverse(self, name="inverse"): + """Returns the Inverse of this `LinearOperator`. + + Given `A` representing this `LinearOperator`, return a `LinearOperator` + representing `A^-1`. + + Args: + name: A name scope to use for ops added by this method. + + Returns: + `LinearOperator` representing inverse of this matrix. + + Raises: + ValueError: When the `LinearOperator` is not hinted to be `non_singular`. + """ + if self.is_square is False: # pylint: disable=g-bool-id-comparison + raise ValueError("Cannot take the Inverse: This operator represents " + "a non square matrix.") + if self.is_non_singular is False: # pylint: disable=g-bool-id-comparison + raise ValueError("Cannot take the Inverse: This operator represents " + "a singular matrix.") + + with self._name_scope(name): + return linear_operator_algebra.inverse(self) + def cholesky(self, name="cholesky"): """Returns a Cholesky factor as a `LinearOperator`. diff --git a/tensorflow/python/ops/linalg/linear_operator_algebra.py b/tensorflow/python/ops/linalg/linear_operator_algebra.py index 7b99066e4c..c1513fdb38 100644 --- a/tensorflow/python/ops/linalg/linear_operator_algebra.py +++ b/tensorflow/python/ops/linalg/linear_operator_algebra.py @@ -27,6 +27,7 @@ from tensorflow.python.util import tf_inspect _CHOLESKY_DECOMPS = {} _MATMUL = {} +_INVERSES = {} def _registered_function(type_list, registry): @@ -55,6 +56,11 @@ def _registered_matmul(type_a, type_b): return _registered_function([type_a, type_b], _MATMUL) +def _registered_inverse(type_a): + """Get the Cholesky function registered for class a.""" + return _registered_function([type_a], _INVERSES) + + def cholesky(lin_op_a, name=None): """Get the Cholesky factor associated to lin_op_a. @@ -103,6 +109,29 @@ def matmul(lin_op_a, lin_op_b, name=None): return matmul_fn(lin_op_a, lin_op_b) +def inverse(lin_op_a, name=None): + """Get the Inverse associated to lin_op_a. + + Args: + lin_op_a: The LinearOperator to decompose. + name: Name to use for this operation. + + Returns: + A LinearOperator that represents the inverse of `lin_op_a`. + + Raises: + NotImplementedError: If no Inverse method is defined for the LinearOperator + type of `lin_op_a`. + """ + inverse_fn = _registered_inverse(type(lin_op_a)) + if inverse_fn is None: + raise ValueError("No inverse registered for {}".format( + type(lin_op_a))) + + with ops.name_scope(name, "Inverse"): + return inverse_fn(lin_op_a) + + class RegisterCholesky(object): """Decorator to register a Cholesky implementation function. @@ -189,3 +218,45 @@ class RegisterMatmul(object): self._key[1].__name__)) _MATMUL[self._key] = matmul_fn return matmul_fn + + +class RegisterInverse(object): + """Decorator to register an Inverse implementation function. + + Usage: + + @linear_operator_algebra.RegisterInverse(lin_op.LinearOperatorIdentity) + def _inverse_identity(lin_op_a): + # Return the identity matrix. + """ + + def __init__(self, lin_op_cls_a): + """Initialize the LinearOperator registrar. + + Args: + lin_op_cls_a: the class of the LinearOperator to decompose. + """ + self._key = (lin_op_cls_a,) + + def __call__(self, inverse_fn): + """Perform the Inverse registration. + + Args: + inverse_fn: The function to use for the Inverse. + + Returns: + inverse_fn + + Raises: + TypeError: if inverse_fn is not a callable. + ValueError: if a Inverse function has already been registered for + the given argument classes. + """ + if not callable(inverse_fn): + raise TypeError( + "inverse_fn must be callable, received: {}".format(inverse_fn)) + if self._key in _INVERSES: + raise ValueError("Inverse({}) has already been registered to: {}".format( + self._key[0].__name__, _INVERSES[self._key])) + _INVERSES[self._key] = inverse_fn + return inverse_fn diff --git a/tensorflow/python/ops/linalg/linear_operator_test_util.py b/tensorflow/python/ops/linalg/linear_operator_test_util.py index e50f572b5f..a957c84dc1 100644 --- a/tensorflow/python/ops/linalg/linear_operator_test_util.py +++ b/tensorflow/python/ops/linalg/linear_operator_test_util.py @@ -336,6 +336,22 @@ class LinearOperatorDerivedClassTest(test.TestCase): self._skip_if_tests_to_skip_contains("solve_with_broadcast") self._test_solve(with_batch=False) + def _test_inverse(self): + for use_placeholder in self._use_placeholder_options: + for build_info in self._operator_build_infos: + for dtype in self._dtypes_to_test: + with self.session(graph=ops.Graph()) as sess: + sess.graph.seed = random_seed.DEFAULT_GRAPH_SEED + operator, mat = self._operator_and_matrix( + build_info, dtype, use_placeholder=use_placeholder) + op_inverse_v, mat_inverse_v = sess.run([ + operator.inverse().to_dense(), linalg.inv(mat)]) + self.assertAC(op_inverse_v, mat_inverse_v) + + def test_inverse(self): + self._skip_if_tests_to_skip_contains("inverse") + self._test_inverse() + def test_trace(self): self._skip_if_tests_to_skip_contains("trace") for use_placeholder in self._use_placeholder_options: @@ -463,7 +479,14 @@ class NonSquareLinearOperatorDerivedClassTest(LinearOperatorDerivedClassTest): @property def _tests_to_skip(self): """List of test names to skip.""" - return ["cholesky", "solve", "solve_with_broadcast", "det", "log_abs_det"] + return [ + "cholesky", + "inverse", + "solve", + "solve_with_broadcast", + "det", + "log_abs_det" + ] @property def _operator_build_infos(self): diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-block-diag.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-block-diag.pbtxt index 773c74e64d..c7a50969b5 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-block-diag.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-block-diag.pbtxt @@ -95,6 +95,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant.pbtxt index 533544d21f..3900c752c8 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant.pbtxt @@ -116,6 +116,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant2-d.pbtxt index e3926eb6d4..7b876099af 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant2-d.pbtxt @@ -116,6 +116,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant3-d.pbtxt index ba209df782..5bddba8e79 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant3-d.pbtxt @@ -116,6 +116,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-composition.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-composition.pbtxt index 081fb0e08b..62ba8bb59e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-composition.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-composition.pbtxt @@ -95,6 +95,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-diag.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-diag.pbtxt index 2014a04301..0803feeabd 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-diag.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-diag.pbtxt @@ -95,6 +95,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-full-matrix.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-full-matrix.pbtxt index 9a87ae9687..6def32864b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-full-matrix.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-full-matrix.pbtxt @@ -91,6 +91,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-identity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-identity.pbtxt index 33afb835ce..dbf1ac82d3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-identity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-identity.pbtxt @@ -92,6 +92,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-inversion.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-inversion.pbtxt new file mode 100644 index 0000000000..6a3fe4dd66 --- /dev/null +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-inversion.pbtxt @@ -0,0 +1,142 @@ +path: "tensorflow.linalg.LinearOperatorInversion" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "batch_shape" + mtype: "" + } + member { + name: "domain_dimension" + mtype: "" + } + member { + name: "dtype" + mtype: "" + } + member { + name: "graph_parents" + mtype: "" + } + member { + name: "is_non_singular" + mtype: "" + } + member { + name: "is_positive_definite" + mtype: "" + } + member { + name: "is_self_adjoint" + mtype: "" + } + member { + name: "is_square" + mtype: "" + } + member { + name: "name" + mtype: "" + } + member { + name: "operator" + mtype: "" + } + member { + name: "range_dimension" + mtype: "" + } + member { + name: "shape" + mtype: "" + } + member { + name: "tensor_rank" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'operator\', \'is_non_singular\', \'is_self_adjoint\', \'is_positive_definite\', \'is_square\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\', \'None\'], " + } + member_method { + name: "add_to_tensor" + argspec: "args=[\'self\', \'x\', \'name\'], varargs=None, keywords=None, defaults=[\'add_to_tensor\'], " + } + member_method { + name: "assert_non_singular" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_non_singular\'], " + } + member_method { + name: "assert_positive_definite" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_positive_definite\'], " + } + member_method { + name: "assert_self_adjoint" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_self_adjoint\'], " + } + member_method { + name: "batch_shape_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'batch_shape_tensor\'], " + } + member_method { + name: "cholesky" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'cholesky\'], " + } + member_method { + name: "determinant" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'det\'], " + } + member_method { + name: "diag_part" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'diag_part\'], " + } + member_method { + name: "domain_dimension_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " + } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } + member_method { + name: "log_abs_determinant" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " + } + member_method { + name: "matmul" + argspec: "args=[\'self\', \'x\', \'adjoint\', \'adjoint_arg\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'matmul\'], " + } + member_method { + name: "matvec" + argspec: "args=[\'self\', \'x\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'matvec\'], " + } + member_method { + name: "range_dimension_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'range_dimension_tensor\'], " + } + member_method { + name: "shape_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'shape_tensor\'], " + } + member_method { + name: "solve" + argspec: "args=[\'self\', \'rhs\', \'adjoint\', \'adjoint_arg\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'solve\'], " + } + member_method { + name: "solvevec" + argspec: "args=[\'self\', \'rhs\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'solve\'], " + } + member_method { + name: "tensor_rank_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'tensor_rank_tensor\'], " + } + member_method { + name: "to_dense" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'to_dense\'], " + } + member_method { + name: "trace" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'trace\'], " + } +} diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-kronecker.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-kronecker.pbtxt index a9078c8ab5..85d902b977 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-kronecker.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-kronecker.pbtxt @@ -95,6 +95,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-low-rank-update.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-low-rank-update.pbtxt index 4cfa3bb30d..638d82a599 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-low-rank-update.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-low-rank-update.pbtxt @@ -115,6 +115,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-lower-triangular.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-lower-triangular.pbtxt index a87649133f..ab1b04bd3c 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-lower-triangular.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-lower-triangular.pbtxt @@ -91,6 +91,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-scaled-identity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-scaled-identity.pbtxt index 3265646784..961969aac5 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-scaled-identity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-scaled-identity.pbtxt @@ -96,6 +96,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-zeros.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-zeros.pbtxt index 49d8890c89..e76738a964 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-zeros.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-zeros.pbtxt @@ -91,6 +91,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator.pbtxt index c89dc067b3..b35cd69da4 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator.pbtxt @@ -90,6 +90,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt index 9f7b422fab..5e49b75c31 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt @@ -36,6 +36,10 @@ tf_module { name: "LinearOperatorIdentity" mtype: "" } + member { + name: "LinearOperatorInversion" + mtype: "" + } member { name: "LinearOperatorKronecker" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-block-diag.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-block-diag.pbtxt index 773c74e64d..c7a50969b5 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-block-diag.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-block-diag.pbtxt @@ -95,6 +95,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant.pbtxt index 533544d21f..3900c752c8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant.pbtxt @@ -116,6 +116,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant2-d.pbtxt index e3926eb6d4..7b876099af 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant2-d.pbtxt @@ -116,6 +116,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant3-d.pbtxt index ba209df782..5bddba8e79 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant3-d.pbtxt @@ -116,6 +116,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-composition.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-composition.pbtxt index 081fb0e08b..62ba8bb59e 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-composition.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-composition.pbtxt @@ -95,6 +95,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-diag.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-diag.pbtxt index 2014a04301..0803feeabd 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-diag.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-diag.pbtxt @@ -95,6 +95,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-full-matrix.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-full-matrix.pbtxt index 9a87ae9687..6def32864b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-full-matrix.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-full-matrix.pbtxt @@ -91,6 +91,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-identity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-identity.pbtxt index 33afb835ce..dbf1ac82d3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-identity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-identity.pbtxt @@ -92,6 +92,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-inversion.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-inversion.pbtxt new file mode 100644 index 0000000000..6a3fe4dd66 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-inversion.pbtxt @@ -0,0 +1,142 @@ +path: "tensorflow.linalg.LinearOperatorInversion" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "batch_shape" + mtype: "" + } + member { + name: "domain_dimension" + mtype: "" + } + member { + name: "dtype" + mtype: "" + } + member { + name: "graph_parents" + mtype: "" + } + member { + name: "is_non_singular" + mtype: "" + } + member { + name: "is_positive_definite" + mtype: "" + } + member { + name: "is_self_adjoint" + mtype: "" + } + member { + name: "is_square" + mtype: "" + } + member { + name: "name" + mtype: "" + } + member { + name: "operator" + mtype: "" + } + member { + name: "range_dimension" + mtype: "" + } + member { + name: "shape" + mtype: "" + } + member { + name: "tensor_rank" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'operator\', \'is_non_singular\', \'is_self_adjoint\', \'is_positive_definite\', \'is_square\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\', \'None\'], " + } + member_method { + name: "add_to_tensor" + argspec: "args=[\'self\', \'x\', \'name\'], varargs=None, keywords=None, defaults=[\'add_to_tensor\'], " + } + member_method { + name: "assert_non_singular" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_non_singular\'], " + } + member_method { + name: "assert_positive_definite" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_positive_definite\'], " + } + member_method { + name: "assert_self_adjoint" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_self_adjoint\'], " + } + member_method { + name: "batch_shape_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'batch_shape_tensor\'], " + } + member_method { + name: "cholesky" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'cholesky\'], " + } + member_method { + name: "determinant" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'det\'], " + } + member_method { + name: "diag_part" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'diag_part\'], " + } + member_method { + name: "domain_dimension_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " + } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } + member_method { + name: "log_abs_determinant" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " + } + member_method { + name: "matmul" + argspec: "args=[\'self\', \'x\', \'adjoint\', \'adjoint_arg\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'matmul\'], " + } + member_method { + name: "matvec" + argspec: "args=[\'self\', \'x\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'matvec\'], " + } + member_method { + name: "range_dimension_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'range_dimension_tensor\'], " + } + member_method { + name: "shape_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'shape_tensor\'], " + } + member_method { + name: "solve" + argspec: "args=[\'self\', \'rhs\', \'adjoint\', \'adjoint_arg\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'solve\'], " + } + member_method { + name: "solvevec" + argspec: "args=[\'self\', \'rhs\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'solve\'], " + } + member_method { + name: "tensor_rank_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'tensor_rank_tensor\'], " + } + member_method { + name: "to_dense" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'to_dense\'], " + } + member_method { + name: "trace" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'trace\'], " + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-kronecker.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-kronecker.pbtxt index a9078c8ab5..85d902b977 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-kronecker.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-kronecker.pbtxt @@ -95,6 +95,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-low-rank-update.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-low-rank-update.pbtxt index 4cfa3bb30d..638d82a599 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-low-rank-update.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-low-rank-update.pbtxt @@ -115,6 +115,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-lower-triangular.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-lower-triangular.pbtxt index a87649133f..ab1b04bd3c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-lower-triangular.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-lower-triangular.pbtxt @@ -91,6 +91,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-scaled-identity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-scaled-identity.pbtxt index 3265646784..961969aac5 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-scaled-identity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-scaled-identity.pbtxt @@ -96,6 +96,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-zeros.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-zeros.pbtxt index 49d8890c89..e76738a964 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-zeros.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-zeros.pbtxt @@ -91,6 +91,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator.pbtxt index c89dc067b3..b35cd69da4 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator.pbtxt @@ -90,6 +90,10 @@ tf_class { name: "domain_dimension_tensor" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } member_method { name: "log_abs_determinant" argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt index 3e1e2e3d54..f9119cdd5f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt @@ -36,6 +36,10 @@ tf_module { name: "LinearOperatorIdentity" mtype: "" } + member { + name: "LinearOperatorInversion" + mtype: "" + } member { name: "LinearOperatorKronecker" mtype: "" -- GitLab From 4b2b456ea4353864478e608a587ea9b5f7eb696c Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Wed, 9 Jan 2019 15:00:20 -0800 Subject: [PATCH 0463/2345] Remove a deprecation warning from tf.saved_model.save PiperOrigin-RevId: 228594973 --- tensorflow/python/saved_model/save.py | 2 +- tensorflow/python/saved_model/utils_impl.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/saved_model/save.py b/tensorflow/python/saved_model/save.py index d79b5fe3a6..9db6d03ed0 100644 --- a/tensorflow/python/saved_model/save.py +++ b/tensorflow/python/saved_model/save.py @@ -222,7 +222,7 @@ def _normalize_outputs(outputs, function_name, signature_key): def _tensor_dict_to_tensorinfo(tensor_dict): - return {key: utils_impl.build_tensor_info(value) + return {key: utils_impl.build_tensor_info_internal(value) for key, value in tensor_dict.items()} diff --git a/tensorflow/python/saved_model/utils_impl.py b/tensorflow/python/saved_model/utils_impl.py index d43cfdf2b3..a82007fd54 100644 --- a/tensorflow/python/saved_model/utils_impl.py +++ b/tensorflow/python/saved_model/utils_impl.py @@ -60,6 +60,11 @@ def build_tensor_info(tensor): """ if context.executing_eagerly(): raise RuntimeError("build_tensor_info is not supported in Eager mode.") + return build_tensor_info_internal(tensor) + + +def build_tensor_info_internal(tensor): + """Utility function to build TensorInfo proto from a Tensor.""" tensor_info = meta_graph_pb2.TensorInfo( dtype=dtypes.as_dtype(tensor.dtype).as_datatype_enum, tensor_shape=tensor.get_shape().as_proto()) -- GitLab From 00a074f82ebaebb436830b13978b3c24e514869d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 15:04:45 -0800 Subject: [PATCH 0464/2345] Precalculate eager op's cache key, so this somewhat expensive operation doesn't have to be repeated for each inference. PiperOrigin-RevId: 228595957 --- .../core/common_runtime/eager/attr_builder.cc | 13 ++++++++++++- tensorflow/core/common_runtime/eager/attr_builder.h | 11 +++++++++-- .../core/common_runtime/eager/attr_builder_test.cc | 13 +++++++++++++ tensorflow/lite/delegates/flex/kernel.cc | 6 ++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/common_runtime/eager/attr_builder.cc b/tensorflow/core/common_runtime/eager/attr_builder.cc index a750f8cbba..689b04274f 100644 --- a/tensorflow/core/common_runtime/eager/attr_builder.cc +++ b/tensorflow/core/common_runtime/eager/attr_builder.cc @@ -125,6 +125,7 @@ Status AttrTypeMapForOp(const char* op_name, const AttrTypeMap** out, template <> \ AttrBuilder& AttrBuilder::Set(StringPiece attr_name, value_type&& value) { \ value_field.push_back(std::make_pair(string(attr_name), value)); \ + cached_cache_key_ = absl::nullopt; \ return *this; \ } @@ -231,7 +232,17 @@ inline tensorflow::Fprint128 CacheKeyHelper(StringPiece s, uint64 b) { } // namespace -tensorflow::Fprint128 AttrBuilder::CacheKey(const string& device) const { +tensorflow::Fprint128 AttrBuilder::CacheKey(const string& device) { + if (!cached_cache_key_ || device != device_for_cached_cache_key_) { + cached_cache_key_ = BuildCacheKeyForDevice(device); + device_for_cached_cache_key_ = device; + } + + return *cached_cache_key_; +} + +tensorflow::Fprint128 AttrBuilder::BuildCacheKeyForDevice( + const string& device) const { tensorflow::Fprint128 f = tensorflow::Fingerprint128(op_name_); f = tensorflow::FingerprintCat128(f, tensorflow::Fingerprint128(device)); if (node_def_ != nullptr) { diff --git a/tensorflow/core/common_runtime/eager/attr_builder.h b/tensorflow/core/common_runtime/eager/attr_builder.h index 5e0172dfd3..aa64b5f59b 100644 --- a/tensorflow/core/common_runtime/eager/attr_builder.h +++ b/tensorflow/core/common_runtime/eager/attr_builder.h @@ -28,6 +28,7 @@ limitations under the License. #include "tensorflow/core/framework/types.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/gtl/inlined_vector.h" +#include "tensorflow/core/lib/gtl/optional.h" #include "tensorflow/core/platform/fingerprint.h" #include "tensorflow/core/util/tensor_slice_reader_cache.h" @@ -74,7 +75,7 @@ Status AttrTypeByName(const AttrTypeMap& m, const string& attr_name, // AttrBuilder a; // a.NumInputs(2); // a.Set("T", TF_FLOAT); -// uint64 cache_key = a.CacheKey("cpu:0"); +// tensorflow::Fprint128 cache_key = a.CacheKey("cpu:0"); // const NodeDef& n = a.BuildNodeDef(); // // Note that all calls to Set and NumInputs should happen before calling @@ -100,10 +101,11 @@ class AttrBuilder { AttrBuilder& Set(StringPiece attr_name, T&& value) { MayBeInitializeNodeDef(); SetInAttrValueMap(node_def_->mutable_attr(), string(attr_name), value); + cached_cache_key_ = absl::nullopt; return *this; } - tensorflow::Fprint128 CacheKey(const string& device) const; + tensorflow::Fprint128 CacheKey(const string& device); void FillAttrValueMap(AttrValueMap* m) const { FillAttrValueMap(m, true); } const NodeDef& BuildNodeDef(); @@ -112,6 +114,8 @@ class AttrBuilder { template using AttrVec = tensorflow::gtl::InlinedVector, 2>; + tensorflow::Fprint128 BuildCacheKeyForDevice(const string& device) const; + void MayBeInitializeNodeDef(); // Fill `m` with the attr-value pairs set via AttrBuilder::Set() so far, as // well as any default attr-value pairs from the associated op_def, if there @@ -148,6 +152,9 @@ class AttrBuilder { int num_inputs_; std::unique_ptr node_def_; bool node_def_finalized_; + + absl::optional cached_cache_key_; + string device_for_cached_cache_key_; }; // namespace tensorflow template <> diff --git a/tensorflow/core/common_runtime/eager/attr_builder_test.cc b/tensorflow/core/common_runtime/eager/attr_builder_test.cc index 220cc6f5ce..8245660bfc 100644 --- a/tensorflow/core/common_runtime/eager/attr_builder_test.cc +++ b/tensorflow/core/common_runtime/eager/attr_builder_test.cc @@ -67,5 +67,18 @@ TEST(AttrTypeMap, Lookup) { EXPECT_NE(is_list, 0); } +TEST(AttrTypeMap, CacheKey) { + AttrBuilder a("op_name"); + a.NumInputs(2); + a.Set("T", TF_FLOAT); + tensorflow::Fprint128 cache_key = a.CacheKey("cpu:0"); + + ASSERT_FALSE(cache_key == a.CacheKey("cpu:1")); + ASSERT_TRUE(cache_key == a.CacheKey("cpu:0")); + + a.Set("x", 1.0); + ASSERT_FALSE(cache_key == a.CacheKey("cpu:0")); +} + } // namespace } // namespace tensorflow diff --git a/tensorflow/lite/delegates/flex/kernel.cc b/tensorflow/lite/delegates/flex/kernel.cc index d2853cf9f8..2e0fc22ad6 100644 --- a/tensorflow/lite/delegates/flex/kernel.cc +++ b/tensorflow/lite/delegates/flex/kernel.cc @@ -245,6 +245,12 @@ class OpNode { op_->MutableAttrs()->Set(attr.first, attr.second); } + // Precalculating a cache key saves about 10% of inference time for very + // small models. + tensorflow::Device* device = op_->Device(); + op_->MutableAttrs()->CacheKey(device == nullptr ? "unspecified" + : device->name()); + return tensorflow::Status::OK(); } -- GitLab From d7cde5bd2cf5d0d7a69773422716590b79c7bed2 Mon Sep 17 00:00:00 2001 From: Yuefeng Zhou Date: Wed, 9 Jan 2019 15:18:43 -0800 Subject: [PATCH 0465/2345] Local collective all reduce with eager mode: wraps invocations to collective allreduce into a defun; Only broadcast send/recv initial values on the first replica of each worker. PiperOrigin-RevId: 228598348 --- .../python/collective_all_reduce_strategy.py | 71 ++++++++++++------- .../collective_all_reduce_strategy_test.py | 26 ++++--- .../python/distribute/cross_device_ops.py | 15 +--- .../python/distribute/cross_device_utils.py | 28 +++++--- 4 files changed, 85 insertions(+), 55 deletions(-) diff --git a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py index 39756b32c5..a3538dfc50 100644 --- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py +++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py @@ -31,6 +31,7 @@ from tensorflow.python.distribute import multi_worker_util from tensorflow.python.distribute import numpy_dataset from tensorflow.python.distribute import values from tensorflow.python.eager import context +from tensorflow.python.eager import tape from tensorflow.python.framework import ops from tensorflow.python.ops import array_ops from tensorflow.python.ops import collective_ops @@ -91,6 +92,7 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): self._collective_keys = cross_device_utils.CollectiveKeys() self._initialize_local(local_devices) + # TODO(yuefengz): remove num_gpus_per_worker from CollectiveAllReduce. self._cross_device_ops = cross_device_ops_lib.CollectiveAllReduce( num_workers=self._num_workers, num_gpus_per_worker=num_gpus_per_worker, @@ -166,16 +168,17 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): else: device_map = colocate_with.device_map logical_device = colocate_with.logical_device - group_size = device_map.num_replicas_in_graph * self._num_workers - group_key = self._collective_keys.get_group_key(self.worker_devices) def _real_mirrored_creator(devices, *args, **kwargs): """Creates one MirroredVariable on the current worker.""" - value_list = [] unique_var_name = ops.get_default_graph().unique_name( kwargs["name"], mark_as_used=False).rstrip("/") + # pylint: disable=protected-access collective_instance_key = self._collective_keys.get_instance_key( key_id=unique_var_name) + # Only the first device participles in the broadcast of initial values. + group_key = self._collective_keys.get_group_key([devices[0]]) + group_size = self._num_workers if "initial_value" not in kwargs: raise ValueError("Initial value must be specified.") initial_value = kwargs["initial_value"] @@ -184,9 +187,33 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): else: initial_value_fn = lambda: initial_value + value_list = [] for i, d in enumerate(devices): - with ops.device(d): - if i > 0: + with ops.init_scope(), ops.device(d): + if i == 0: + # The initial value fn makes sure variables all initialized to + # same values. The first device of the chief worker will send their + # variable values to other workers. + def _overridden_initial_value_fn(device=d, index=i): # pylint: disable=g-missing-docstring + with ops.device(device): + initial_value = initial_value_fn() + assert not callable(initial_value) + initial_value = ops.convert_to_tensor(initial_value) + + assert index == 0, index + if self._num_workers > 1: + if self._is_chief: + bcast_send = collective_ops.broadcast_send( + initial_value, initial_value.shape, initial_value.dtype, + group_size, group_key, collective_instance_key) + with ops.control_dependencies([bcast_send]): + return array_ops.identity(initial_value) + else: + return collective_ops.broadcast_recv( + initial_value.shape, initial_value.dtype, group_size, + group_key, collective_instance_key) + return initial_value + else: # 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 @@ -194,30 +221,22 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): # name as the absolute name of the variable. kwargs["name"] = "%s/replica_%d/" % (var0name, i) - # The initial value fn makes sure variables all initialized to - # same values. The first device of the chief worker will send their - # variable values to other devices and other workers. - def _overridden_initial_value_fn(device=d, index=i): # pylint: disable=g-missing-docstring - with ops.device(device): - initial_value = initial_value_fn() - assert not callable(initial_value) - initial_value = ops.convert_to_tensor(initial_value) - - if self._is_chief and index == 0: - bcast_send = collective_ops.broadcast_send( - initial_value, initial_value.shape, initial_value.dtype, - group_size, group_key, collective_instance_key) - with ops.control_dependencies([bcast_send]): - return array_ops.identity(initial_value) - else: - return collective_ops.broadcast_recv( - initial_value.shape, initial_value.dtype, group_size, - group_key, collective_instance_key) + # Variables on non-first replica get initial values from the + # variables created on the first device of each worker. + def _overridden_initial_value_fn(device=d, index=i): + assert index > 0 + with ops.device(device): + if context.executing_eagerly(): + return array_ops.identity(value_list[0].value()) + else: + 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): - v = next_creator(*args, **kwargs) + # Don't record operations (e.g. other variable reads) during + # variable creation. + with tape.stop_recording(): + v = next_creator(*args, **kwargs) if i == 0: actual_var_name = v.name.split(":")[0] 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 c3e9c55e96..682da44f5d 100644 --- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py +++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py @@ -192,6 +192,7 @@ class CollectiveAllReduceStrategyTestBase( image = random_ops.random_uniform([2, 28, 28]) label = random_ops.random_uniform([2, 1], maxval=10, dtype=dtypes.int32) logits = model(image, training=True) + # TODO(yuefengz): make loss a callable for eager mode. loss = losses.sparse_softmax_cross_entropy(labels=label, logits=logits) optimizer = adam.AdamOptimizer(learning_rate=1e-4) train_op = optimizer.minimize(loss, @@ -403,24 +404,31 @@ class LocalCollectiveAllReduceStrategy( strategy_test_lib.TwoDeviceDistributionTestBase, parameterized.TestCase): - def testMinimizeLossGraph(self, num_gpus=2): + @combinations.generate( + combinations.combine( + mode=['graph', 'eager'], num_gpus=[2, 4], required_gpus=2)) + def testMinimizeLoss(self, num_gpus): # Collective ops doesn't support strategy with one device. if context.num_gpus() < num_gpus: self.skipTest('Not enough GPUs') - self._test_minimize_loss_graph(None, None, num_gpus) + if context.executing_eagerly(): + strategy, _, _ = self._get_test_object(None, None, num_gpus) + self._test_minimize_loss_eager(strategy) + else: + self._test_minimize_loss_graph(None, None, num_gpus) - def testComplexModel(self, num_gpus=2): - # Collective ops doesn't support strategy with one device. + @combinations.generate( + combinations.combine(mode=['graph'], num_gpus=[2, 4], required_gpus=2)) + def testComplexModel(self, num_gpus): if context.num_gpus() < num_gpus: self.skipTest('Not enough GPUs') self._test_complex_model(None, None, num_gpus) + @combinations.generate( + combinations.combine(mode=['graph', 'eager'], required_gpus=2)) def testMakeInputFnIterator(self, num_gpus=2): - # Collective ops doesn't support strategy with one device. - if context.num_gpus() < num_gpus: - self.skipTest('Not enough GPUs') - dataset_fn = lambda: dataset_ops.Dataset.range(10) - expected_values = [[i, i+1] for i in range(0, 10, 2)] + dataset_fn = lambda: dataset_ops.Dataset.range(5 * num_gpus) + expected_values = [range(i, i + num_gpus) for i in range(0, 10, num_gpus)] input_fn = self._input_fn_to_test_input_context( dataset_fn, diff --git a/tensorflow/python/distribute/cross_device_ops.py b/tensorflow/python/distribute/cross_device_ops.py index 9575301d97..9729302c6d 100644 --- a/tensorflow/python/distribute/cross_device_ops.py +++ b/tensorflow/python/distribute/cross_device_ops.py @@ -323,6 +323,9 @@ class ReductionToOneDeviceCrossDeviceOps(CrossDeviceOps): assert check_destinations(destinations) devices = get_devices_from(destinations) reduce_to_device = self.reduce_to_device or devices[0] + logging.log_first_n( + logging.INFO, + "Reduce to %s then broadcast to %r." % (reduce_to_device, devices), 10) reduced = _simple_reduce(per_replica_value, reduce_to_device, self.accumulation_fn, reduce_op) return self.broadcast(reduced, destinations) @@ -839,9 +842,6 @@ class CollectiveAllReduce(CrossDeviceOps): if cross_device_utils.contains_indexed_slices(per_replica_value): raise ValueError( "`IndexSlices` is not supported for Collective All-Reduce.") - if context.executing_eagerly(): - raise ValueError( - "Eager execution is not supported for Collective All-Reduce") all_reduced = self._batch_all_reduce(reduce_op, [per_replica_value])[0] device_map, logical_device = get_device_map_from(destinations) @@ -865,9 +865,6 @@ class CollectiveAllReduce(CrossDeviceOps): if cross_device_utils.contains_indexed_slices(value_destination_pairs): raise ValueError( "`IndexSlices` is not supported for Collective All-Reduce.") - if context.executing_eagerly(): - raise ValueError( - "Eager execution is not supported for Collective All-Reduce") all_devices_match = _all_devices_match(value_destination_pairs) if all_devices_match: @@ -886,9 +883,6 @@ class CollectiveAllReduce(CrossDeviceOps): def _batch_all_reduce(self, reduce_op, per_replica_values): """All-reduce across all workers in a batch.""" - if context.executing_eagerly(): - raise ValueError( - "Eager execution with collective ops is not supported yet.") logging.log_first_n( logging.INFO, "Collective All-reduce invoked with batches size = %d, " @@ -949,12 +943,9 @@ def _has_dgx1_like_links(gpu_links): def _choose_all_reduce_algorithm(device_links): if _has_dgx1_like_links(device_links): - logging.info("Configured hierarchical_copy with num_packs=%d", - len(device_links)) return AllReduceCrossDeviceOps( "hierarchical_copy", num_packs=len(device_links)) else: - logging.info("Configured nccl all-reduce.") return AllReduceCrossDeviceOps("nccl", num_packs=1) diff --git a/tensorflow/python/distribute/cross_device_utils.py b/tensorflow/python/distribute/cross_device_utils.py index 5b4b3a6f97..e8066dd467 100644 --- a/tensorflow/python/distribute/cross_device_utils.py +++ b/tensorflow/python/distribute/cross_device_utils.py @@ -23,6 +23,8 @@ import threading from tensorflow.python.distribute import all_reduce from tensorflow.python.distribute import values as value_lib +from tensorflow.python.eager import context +from tensorflow.python.eager import def_function from tensorflow.python.framework import device as pydev from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops @@ -353,15 +355,25 @@ def build_collective_reduce(input_tensors, num_devices = len(devices) group_key = collective_keys.get_group_key(devices) instance_key = collective_keys.get_instance_key() - out_tensors = [] subdiv_offsets = [0] # TODO(tucker): maybe support non-default subdiv spec - for d in range(num_devices): - with ops.device(devices[d]): - reduce_op = collective_ops.all_reduce( - input_tensors[d], group_size, group_key, instance_key, reduction_op, - unary_op, subdiv_offsets) - out_tensors.append(reduce_op) - return out_tensors + + def collective_all_reduce(): + """Call collective allreduce.""" + assert not context.executing_eagerly() + out_tensors = [] + for d in range(num_devices): + with ops.device(devices[d]): + reduce_op = collective_ops.all_reduce( + input_tensors[d], group_size, group_key, instance_key, reduction_op, + unary_op, subdiv_offsets) + out_tensors.append(reduce_op) + return out_tensors + + if context.executing_eagerly(): + # Collective ops will block unless they are executed concurrently such as in + # a graph or a defun. + collective_all_reduce = def_function.function(collective_all_reduce) + return collective_all_reduce() def sum_grad_and_var_all_reduce(grad_and_vars, -- GitLab From 615182adb3d01fd8357e574bd8920c0995c6bbb8 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 15:42:02 -0800 Subject: [PATCH 0466/2345] Fix issue with Subclassed models and generator input. PiperOrigin-RevId: 228602224 --- tensorflow/python/keras/callbacks.py | 9 +--- tensorflow/python/keras/callbacks_test.py | 42 ------------------- .../python/keras/engine/training_generator.py | 4 -- .../keras/engine/training_generator_test.py | 21 ++++------ 4 files changed, 8 insertions(+), 68 deletions(-) diff --git a/tensorflow/python/keras/callbacks.py b/tensorflow/python/keras/callbacks.py index 589cd992d6..b9074669eb 100644 --- a/tensorflow/python/keras/callbacks.py +++ b/tensorflow/python/keras/callbacks.py @@ -123,14 +123,6 @@ def configure_callbacks(callbacks, 'metrics': callback_metrics, } callback_list.set_params(callback_params) - - if (do_validation and not model._distribution_strategy and - not model.run_eagerly): - # Need to create the eval_function before start of the first epoch - # because TensorBoard callback on_epoch_begin adds summary to the - # list of fetches of the eval_function - callback_model._make_eval_function() - callback_list.model.stop_training = False return callback_list # pylint: enable=protected-access @@ -1373,6 +1365,7 @@ class TensorBoard(Callback): self._epoch = epoch # pylint: disable=protected-access # add the histogram summary op if it should run this epoch + self.model._make_eval_function() if self.merged not in self.model._eval_function.fetches: self.model._eval_function.fetches.append(self.merged) self.model._eval_function.fetch_callbacks[ diff --git a/tensorflow/python/keras/callbacks_test.py b/tensorflow/python/keras/callbacks_test.py index ef469c5e4f..d5ffdf789a 100644 --- a/tensorflow/python/keras/callbacks_test.py +++ b/tensorflow/python/keras/callbacks_test.py @@ -31,7 +31,6 @@ import numpy as np from tensorflow.core.framework import summary_pb2 from tensorflow.python import keras -from tensorflow.python.framework import ops from tensorflow.python.framework import random_seed from tensorflow.python.framework import test_util from tensorflow.python.keras import keras_parameterized @@ -1397,47 +1396,6 @@ class KerasCallbacksTest(test.TestCase): callbacks=cbks, epochs=1) - @test_util.run_deprecated_v1 - def test_fit_generator_with_callback(self): - - class TestCallback(keras.callbacks.Callback): - - def set_model(self, model): - # Check the model operations for the optimizer operations that - # the _make_train_function adds under a named scope for the - # optimizer. This ensurs the full model is populated before the - # set_model callback is called. - optimizer_name_scope = 'training/' + model.optimizer.__class__.__name__ - graph_def = ops.get_default_graph().as_graph_def() - for node in graph_def.node: - if node.name.startswith(optimizer_name_scope): - return - raise RuntimeError('The optimizer operations are not present in the ' - 'model graph when the Callback.set_model function ' - 'is called') - np.random.seed(1337) - - def generator(): - x = np.random.randn(10, 100).astype(np.float32) - y = np.random.randn(10, 10).astype(np.float32) - while True: - yield x, y - - with self.cached_session(): - model = testing_utils.get_small_sequential_mlp( - num_hidden=10, num_classes=10, input_dim=100) - model.compile( - loss='categorical_crossentropy', - optimizer='sgd', - metrics=['accuracy']) - model.fit_generator( - generator(), - steps_per_epoch=2, - epochs=1, - validation_data=generator(), - validation_steps=2, - callbacks=[TestCallback()], - verbose=0) if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/engine/training_generator.py b/tensorflow/python/keras/engine/training_generator.py index d1699b2827..07de934259 100644 --- a/tensorflow/python/keras/engine/training_generator.py +++ b/tensorflow/python/keras/engine/training_generator.py @@ -430,12 +430,8 @@ def _make_enqueued_generator(generator, def _make_execution_function(model, mode, class_weight=None): """Makes function to run one step of model execution.""" if mode == ModeKeys.TRAIN: - if not context.executing_eagerly(): - model._make_fit_function() f = functools.partial(model.train_on_batch, class_weight=class_weight) elif mode == ModeKeys.TEST: - if not context.executing_eagerly(): - model._make_eval_function() f = model.test_on_batch else: # Match signature of other modes to allow diff --git a/tensorflow/python/keras/engine/training_generator_test.py b/tensorflow/python/keras/engine/training_generator_test.py index 90c45dfcb7..6b754c18b3 100644 --- a/tensorflow/python/keras/engine/training_generator_test.py +++ b/tensorflow/python/keras/engine/training_generator_test.py @@ -66,8 +66,7 @@ class TestGeneratorMethods(keras_parameterized.TestCase): @unittest.skipIf( os.name == 'nt', 'use_multiprocessing=True does not work on windows properly.') - # TODO(b/120940700): Bug with subclassed model inputs. - @keras_parameterized.run_with_all_model_types(exclude_models='subclass') + @keras_parameterized.run_with_all_model_types @keras_parameterized.run_all_keras_modes def test_fit_generator_method(self): model = testing_utils.get_small_mlp( @@ -107,8 +106,7 @@ class TestGeneratorMethods(keras_parameterized.TestCase): @unittest.skipIf( os.name == 'nt', 'use_multiprocessing=True does not work on windows properly.') - # TODO(b/120940700): Bug with subclassed model inputs. - @keras_parameterized.run_with_all_model_types(exclude_models='subclass') + @keras_parameterized.run_with_all_model_types @keras_parameterized.run_all_keras_modes def test_evaluate_generator_method(self): model = testing_utils.get_small_mlp( @@ -173,8 +171,7 @@ class TestGeneratorMethods(keras_parameterized.TestCase): max_queue_size=10, workers=0) - # TODO(b/120940700): Bug with subclassed model inputs. - @keras_parameterized.run_with_all_model_types(exclude_models='subclass') + @keras_parameterized.run_with_all_model_types @keras_parameterized.run_all_keras_modes def test_generator_methods_with_sample_weights(self): model = testing_utils.get_small_mlp( @@ -208,8 +205,7 @@ class TestGeneratorMethods(keras_parameterized.TestCase): max_queue_size=10, use_multiprocessing=False) - # TODO(b/120940700): Bug with subclassed model inputs. - @keras_parameterized.run_with_all_model_types(exclude_models='subclass') + @keras_parameterized.run_with_all_model_types @keras_parameterized.run_all_keras_modes def test_generator_methods_invalid_use_case(self): @@ -249,8 +245,7 @@ class TestGeneratorMethods(keras_parameterized.TestCase): max_queue_size=10, use_multiprocessing=False) - # TODO(b/120940700): Bug with subclassed model inputs. - @keras_parameterized.run_with_all_model_types(exclude_models='subclass') + @keras_parameterized.run_with_all_model_types @keras_parameterized.run_all_keras_modes def test_generator_input_to_fit_eval_predict(self): val_data = np.ones([10, 10], np.float32), np.ones([10, 1], np.float32) @@ -275,8 +270,7 @@ class TestGeneratorMethods(keras_parameterized.TestCase): class TestGeneratorMethodsWithSequences(keras_parameterized.TestCase): - # TODO(b/120940700): Bug with subclassed model inputs. - @keras_parameterized.run_with_all_model_types(exclude_models='subclass') + @keras_parameterized.run_with_all_model_types @keras_parameterized.run_all_keras_modes def test_training_with_sequences(self): @@ -307,8 +301,7 @@ class TestGeneratorMethodsWithSequences(keras_parameterized.TestCase): workers=0, use_multiprocessing=False) - # TODO(b/120940700): Bug with subclassed model inputs. - @keras_parameterized.run_with_all_model_types(exclude_models='subclass') + @keras_parameterized.run_with_all_model_types @keras_parameterized.run_all_keras_modes def test_sequence_input_to_fit_eval_predict(self): val_data = np.ones([10, 10], np.float32), np.ones([10, 1], np.float32) -- GitLab From ac40a68cd5fac2227fb6c4086b2eb01a7dc726c4 Mon Sep 17 00:00:00 2001 From: Samantha Andow Date: Wed, 26 Dec 2018 08:58:44 -0800 Subject: [PATCH 0467/2345] Fix iterable bug Co-authored-by: melissagrueter Co-authored-by: irenedea --- tensorflow/java/src/gen/cc/op_specs.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/java/src/gen/cc/op_specs.cc b/tensorflow/java/src/gen/cc/op_specs.cc index 4f5a491d25..4024efedef 100644 --- a/tensorflow/java/src/gen/cc/op_specs.cc +++ b/tensorflow/java/src/gen/cc/op_specs.cc @@ -91,11 +91,6 @@ class TypeResolver { Type TypeResolver::TypeOf(const OpDef_ArgDef& arg_def, bool* iterable_out) { *iterable_out = false; - if (!arg_def.number_attr().empty()) { - // when number_attr is set, argument has to be a list of tensors - *iterable_out = true; - visited_attrs_.insert(std::make_pair(arg_def.number_attr(), Type::Int())); - } Type type = Type::Wildcard(); if (arg_def.type() != DataType::DT_INVALID) { type = Type::ForDataType(arg_def.type()); @@ -122,6 +117,11 @@ Type TypeResolver::TypeOf(const OpDef_ArgDef& arg_def, bool* iterable_out) { LOG(FATAL) << "Cannot resolve data type of argument \"" << arg_def.name() << "\" in operation \"" << op_def_.name() << "\""; } + if (!arg_def.number_attr().empty()) { + // when number_attr is set, argument has to be a list of tensors + *iterable_out = true; + visited_attrs_.insert(std::make_pair(arg_def.number_attr(), Type::Int())); + } return type; } -- GitLab From fe13148bf6afcb85d71c05391d168302d8d4bf2d Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 9 Jan 2019 16:24:30 -0800 Subject: [PATCH 0468/2345] Change TPU Cluster Resolver doc string to clarify current / future features PiperOrigin-RevId: 228610010 --- .../cluster_resolver/tpu_cluster_resolver.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py index 529a443412..72b9990701 100644 --- a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py @@ -192,11 +192,12 @@ class TPUClusterResolver(ClusterResolver): for the IP addresses and ports of each Cloud TPU listed. Args: - tpu: Either a string, or a list of strings corresponding to the TPUs to - use. If the single string is the empty string, the string 'local', or a - string that begins with 'grpc://' or '/bns', then it is assumed to not - correspond with a Cloud TPU and will instead be passed as the session - master and no ClusterSpec propagation will be done. + tpu: A string corresponding to the TPU to use. If the string is the empty + string, the string 'local', or a string that begins with 'grpc://' or + '/bns', then it is assumed to not correspond with a Cloud TPU and will + instead be passed as the session master and no ClusterSpec propagation + will be done. In the future, this may also support a list of strings + when multiple Cloud TPUs are used. zone: Zone where the TPUs are located. If omitted or empty, we will assume that the zone of the TPU is the same as the zone of the GCE VM, which we will try to discover from the GCE metadata service. -- GitLab From 4bb6da8e92da8d75456ee00b264e4c02877c5d7d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 16:35:23 -0800 Subject: [PATCH 0469/2345] Enable TPUEstimator to ignore non predict mode exporters rather than just hard failing. PiperOrigin-RevId: 228611812 --- tensorflow/contrib/tpu/python/tpu/tpu_estimator.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py index 2317300041..2df5b3e4eb 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py @@ -2186,7 +2186,9 @@ class TPUEstimator(estimator_lib.Estimator): 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 besides the one on CPU. + serving on TPU besides the one on CPU. 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. 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 @@ -2277,10 +2279,9 @@ class TPUEstimator(estimator_lib.Estimator): export_tags=None, check_variables=True): if self._export_to_tpu and mode != model_fn_lib.ModeKeys.PREDICT: - raise NotImplementedError( - 'TPUEstimator only handles mode PREDICT for exporting ' - 'when `export_to_tpu` is `True`; ' - 'got {}.'.format(mode)) + logging.warning('TPUEstimator only handles mode PREDICT for exporting ' + 'when `export_to_tpu` is `True`; Mode {} will be ignored ' + 'for TPU.'.format(mode)) (super(TPUEstimator, self)._add_meta_graph_for_mode( builder, @@ -2291,7 +2292,7 @@ class TPUEstimator(estimator_lib.Estimator): export_tags=export_tags, check_variables=check_variables)) - if self._export_to_tpu: + 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] } -- GitLab From 1a768c9dc3895b3188b19d36a630f0e8ba5c85a0 Mon Sep 17 00:00:00 2001 From: Yunxing Dai Date: Wed, 9 Jan 2019 16:46:17 -0800 Subject: [PATCH 0470/2345] Populate entry parameter's dynamic dimension when using XlaBuilder::SetDynamicBinding Since now that we have is_dynamic_dimension field, we can set the field when calling SetDynamicBinding. ShapeInference will soon start populating this field based on the parameter shape. PiperOrigin-RevId: 228613510 --- tensorflow/compiler/xla/client/xla_builder.cc | 23 +++++++++++++++++++ .../compiler/xla/client/xla_builder_test.cc | 20 ++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/tensorflow/compiler/xla/client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_builder.cc index 7a04c0ec69..59e156eb72 100644 --- a/tensorflow/compiler/xla/client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_builder.cc @@ -247,6 +247,29 @@ Status XlaBuilder::SetDynamicBinding(int64 dynamic_size_param_num, int64 target_param_num, ShapeIndex target_param_index, int64 target_dim_num) { + bool param_exists = false; + for (HloInstructionProto& instr : instructions_) { + if (instr.opcode() == HloOpcodeString(HloOpcode::kParameter) && + instr.parameter_number() == target_param_num) { + param_exists = true; + Shape param_shape(instr.shape()); + Shape* param_shape_ptr = ¶m_shape; + for (int64 index : target_param_index) { + param_shape_ptr = param_shape_ptr->mutable_tuple_shapes(index); + } + param_shape_ptr->set_dynamic_dimension(target_dim_num, + /*is_dynamic=*/true); + *instr.mutable_shape() = param_shape.ToProto(); + } + } + + if (!param_exists) { + return InvalidArgument( + "Asked to mark parameter %lld as dynamic sized parameter, but the " + "doesn't exists", + target_param_num); + } + TF_RETURN_IF_ERROR(dynamic_parameter_binding_.Bind( DynamicParameterBinding::DynamicParameter{dynamic_size_param_num, dynamic_size_param_index}, diff --git a/tensorflow/compiler/xla/client/xla_builder_test.cc b/tensorflow/compiler/xla/client/xla_builder_test.cc index ba929a1200..abc11b4732 100644 --- a/tensorflow/compiler/xla/client/xla_builder_test.cc +++ b/tensorflow/compiler/xla/client/xla_builder_test.cc @@ -446,6 +446,26 @@ TEST_F(XlaBuilderTest, ProtoMatches) { EXPECT_EQ(c0_string, c1_string); } +TEST_F(XlaBuilderTest, DynamicParameter) { + std::vector computations; + XlaBuilder b("builder"); + Shape tuple_param_shape = ShapeUtil::MakeTupleShape( + {ShapeUtil::MakeShape(F32, {5}), ShapeUtil::MakeShape(F32, {6})}); + auto p0 = Parameter(&b, 0, tuple_param_shape, "p0"); + Parameter(&b, 1, ShapeUtil::MakeShape(U32, {}), "p1"); + ASSERT_IS_OK(b.SetDynamicBinding(/*dynamic_size_param_num=*/1, + /*dynamic_size_param_index=*/{}, + /*target_param_num=*/0, + /*target_param_index=*/{1}, + /*target_dim_num=*/0)); + TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b, /*root=*/p0)); + const Shape& param_shape = module->entry_computation() + ->parameter_instruction(0) + ->shape() + .tuple_shapes(1); + EXPECT_TRUE(param_shape.is_dynamic_dimension(0)); +} + TEST_F(XlaBuilderTest, AfterAllWithNonTokenOperands) { XlaBuilder b(TestName()); AfterAll(&b, {CreateToken(&b), ConstantR0(&b, 1.0)}); -- GitLab From 8e55a2f3fe768451b0e1ac8ae657f5ecfcdd0123 Mon Sep 17 00:00:00 2001 From: Dimitris Vardoulakis Date: Wed, 9 Jan 2019 16:55:33 -0800 Subject: [PATCH 0471/2345] [TF/XLA] Fix bug in AR/CRS combiner. Don't recur to the arguments when comparing cross-module AllReduces for equality. PiperOrigin-RevId: 228615009 --- tensorflow/compiler/xla/service/ar_crs_combiner.cc | 12 +++++++++--- .../compiler/xla/service/ar_crs_combiner_test.cc | 5 +++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/xla/service/ar_crs_combiner.cc b/tensorflow/compiler/xla/service/ar_crs_combiner.cc index 4a227d3b5c..35a32f537b 100644 --- a/tensorflow/compiler/xla/service/ar_crs_combiner.cc +++ b/tensorflow/compiler/xla/service/ar_crs_combiner.cc @@ -190,6 +190,15 @@ bool ArCrsCombiner::InstructionsComputeSameValue( if (opcode1 != i2->opcode() || operands1.size() != i2->operands().size()) { return false; } + auto eq_computations = [](const HloComputation* a, const HloComputation* b) { + return *a == *b; + }; + if (i1->IsCrossModuleAllReduce()) { + return i1->Identical(*i2, + /*eq_operands=*/std::equal_to(), + eq_computations, + /*layout_sensitive=*/false); + } visited_pairs->emplace(min_uid, max_uid); for (int i = 0; i < operands1.size(); ++i) { auto operand1 = operands1[i]; @@ -215,9 +224,6 @@ bool ArCrsCombiner::InstructionsComputeSameValue( // InstructionsComputeSameValue earlier. auto eq_instructions = [](const HloInstruction* i1, const HloInstruction* i2) -> bool { return true; }; - auto eq_computations = [](const HloComputation* a, const HloComputation* b) { - return *a == *b; - }; return i1->Identical(*i2, eq_instructions, eq_computations, /*layout_sensitive=*/false); } diff --git a/tensorflow/compiler/xla/service/ar_crs_combiner_test.cc b/tensorflow/compiler/xla/service/ar_crs_combiner_test.cc index caa57296f4..b12b63b2dd 100644 --- a/tensorflow/compiler/xla/service/ar_crs_combiner_test.cc +++ b/tensorflow/compiler/xla/service/ar_crs_combiner_test.cc @@ -360,6 +360,7 @@ HloModule foobar ENTRY %entrycomp (p: bf16[]) -> (f32[], f32[]) { %p = bf16[] parameter(0) + %constant.bf16 = bf16[] constant(1) %all-reduce.ar.1 = bf16[] all-reduce(%p), @@ -377,7 +378,7 @@ ENTRY %entrycomp (p: bf16[]) -> (f32[], f32[]) { sharding={maximal device=0} %all-reduce.ar.2 = bf16[] - all-reduce(%p), + all-reduce(%constant.bf16), replica_groups={{0},{1}}, all_reduce_id=1, to_apply=%sum.bf16, @@ -407,7 +408,7 @@ ENTRY %entrycomp (p: bf16[]) -> (f32[], f32[]) { EXPECT_TRUE(changed); EXPECT_THAT(module->entry_computation()->root_instruction(), op::Tuple(op::AllReduce(op::Convert(op::Parameter())), - op::AllReduce(op::Convert(op::Parameter())))); + op::AllReduce(op::Convert(op::Constant())))); auto crs_after = module->entry_computation()->root_instruction()->operands()[0]; auto replica_groups_after = crs_after->replica_groups(); -- GitLab From 7f9c7afd8829d20df25ee04f1999074290d7a45a Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Wed, 9 Jan 2019 16:58:27 -0800 Subject: [PATCH 0472/2345] Fix AllOutputValuesKnown logic. PiperOrigin-RevId: 228615404 --- .../core/grappler/costs/graph_properties.cc | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/grappler/costs/graph_properties.cc b/tensorflow/core/grappler/costs/graph_properties.cc index 863a5087ae..e869f7830c 100644 --- a/tensorflow/core/grappler/costs/graph_properties.cc +++ b/tensorflow/core/grappler/costs/graph_properties.cc @@ -1087,15 +1087,20 @@ class SymbolicShapeRefiner { c->output_tensor_protos.size() < ic->num_outputs()) { return false; } else { + // Checks if we can get output value via either output_tensor_proto or + // output_tensors_as_shapes. for (int i = 0; i < ic->num_outputs(); i++) { - if (c->output_tensor_protos.size() <= i || - c->output_tensor_protos[i] == nullptr) { - return false; + if (c->output_tensor_protos.size() > i && + c->output_tensor_protos[i] != nullptr) { + continue; } - if (c->output_tensors_as_shapes.size() <= i || - !ic->FullyDefined(c->output_tensors_as_shapes[i])) { - return false; + if (c->output_tensors_as_shapes.size() > i && + ic->FullyDefined(c->output_tensors_as_shapes[i])) { + continue; } + + // Unknown for output[i]. + return false; } } return true; -- GitLab From c2aa5390828b39863f9baaa2e5baad34788c7dec Mon Sep 17 00:00:00 2001 From: Jian Li Date: Wed, 9 Jan 2019 17:11:46 -0800 Subject: [PATCH 0473/2345] Update basic rnn test case. PiperOrigin-RevId: 228617690 --- tensorflow/lite/kernels/basic_rnn_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/basic_rnn_test.cc b/tensorflow/lite/kernels/basic_rnn_test.cc index c71849fff3..9eb20444a6 100644 --- a/tensorflow/lite/kernels/basic_rnn_test.cc +++ b/tensorflow/lite/kernels/basic_rnn_test.cc @@ -240,7 +240,7 @@ class HybridRNNOpModel : public RNNOpModel { TensorType tensor_type_; - void SetWeights(int weights_idx, std::vector f) { + void SetWeights(int weights_idx, const std::vector& f) { if (tensor_type_ == TensorType_UINT8) { SymmetricQuantizeAndPopulate(weights_idx, f); } else { -- GitLab From 12f810aa76194935959cc0d414f7de6eeaa234b9 Mon Sep 17 00:00:00 2001 From: Bixia Zheng Date: Wed, 9 Jan 2019 17:14:32 -0800 Subject: [PATCH 0474/2345] [XLA:GPU] Enhance unrolling heuristics for column reduction. Previously, we enable unrolling only when the reduce operands are of small data types. This change adds a simple analysis to count the number of tensors that can be vectorized and can't be vectorized in order to decide whether unrolling is beneficial for the kernel. Add test cases. PiperOrigin-RevId: 228618121 --- .../xla/service/gpu/ir_emitter_unnested.cc | 106 ++++++++++++-- .../xla/service/gpu/ir_emitter_unnested.h | 8 +- .../gpu/tests/gpu_kernel_tiling_test.cc | 129 +++++++++++++++++- 3 files changed, 228 insertions(+), 15 deletions(-) diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc index 2bc4912155..34ddeb1d41 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc @@ -3403,11 +3403,101 @@ std::tuple GetReductionToVectorDimensions( return std::make_tuple(num_reduced_major, num_kept, num_reduced_minor); } +// Returns true if all the transitive users of hlo before hitting users in +// use_chain_endings are elementwise operations. +bool AreUsersElementwise(const HloInstruction* hlo, + const ConstHloInstructionSet& use_chain_endings) { + return absl::c_all_of(hlo->users(), [&](const HloInstruction* user) { + return use_chain_endings.count(user) || + (user->IsElementwise() && + AreUsersElementwise(user, use_chain_endings)); + }); +} + +// Returns the number of fusion inputs that have the same dimension as the +// given shape, and involve in only elementwise operations. +int64 NumInputsInvolveInOnlyElementwiseOps( + const HloInstruction* unnested_hlo, const Shape& op_shape, + const ConstHloInstructionSet& use_chain_endings) { + return absl::c_count_if( + unnested_hlo->fused_parameters(), [&](const HloInstruction* parameter) { + const Shape& parameter_shape = parameter->shape(); + return ShapeUtil::SameDimensions(op_shape, parameter_shape) && + AreUsersElementwise(parameter, use_chain_endings); + }); +} + +// Returns the number of fusion inputs that have more elements than the given +// shape. +int64 NumInputsWithMoreElementsThan(const HloInstruction* unnested_hlo, + const Shape& shape) { + int64 num_elements = ShapeUtil::ElementsIn(shape); + return absl::c_count_if( + unnested_hlo->fused_parameters(), [&](const HloInstruction* parameter) { + return ShapeUtil::ElementsIn(parameter->shape()) > num_elements; + }); +} + +// The benefit of unrolling a kInput fusion that is a column reduction comes +// from the vectorization of non-reduction fusion outputs and fusion inputs. +// On the other hand, unrolling can also introduce factors that can cause +// the kernel to run slower. This routine uses a simple heuristic to estimate +// the benefit as well as the overhead of unrolling in order to decide whether +// unrolling is beneficial for the given kInput fusion. +bool IsUnrollingColumnReductionBeneficial(const HloInstruction* unnested_hlo, + const Shape& input_shape, + int64 num_kept) { + // TODO(b/122468062): Need further investigate to see whether we can + // remove the constraint on IsPowerOfTwo. + if (!IsPowerOfTwo(static_cast(num_kept))) { + return false; + } + + if (unnested_hlo->opcode() == HloOpcode::kReduce) { + return true; + } + + CHECK_EQ(unnested_hlo->opcode(), HloOpcode::kFusion); + int64 can_be_vectorized = 0; + int64 cannot_be_vectorized = 0; + const HloInstruction* fused_root = unnested_hlo->fused_expression_root(); + ConstHloInstructionSet use_chain_endings; + if (fused_root->opcode() == HloOpcode::kReduce) { + use_chain_endings.insert(fused_root); + // Atomic.add of the reduction result can't be vectorized. + cannot_be_vectorized++; + } else { + CHECK_EQ(fused_root->opcode(), HloOpcode::kTuple); + for (const HloInstruction* instr : fused_root->operands()) { + if (instr->opcode() == HloOpcode::kReduce) { + // Atomic.add of the reduction result can't be vectorized. + cannot_be_vectorized++; + } else { + // Write of the non-reduction result can be vectorized. + can_be_vectorized++; + } + use_chain_endings.insert(instr); + } + } + // Fusion inputs that have the same dimension as the reduce input and + // only involve in elementwise operations can be vectorized. + can_be_vectorized += NumInputsInvolveInOnlyElementwiseOps( + unnested_hlo, input_shape, use_chain_endings); + // Fusion inputs with more elements than the reduce op input must participate + // in non-elementwise operations and we assume that they are not vectorizable + // for the purpose of estimating the benefit of unrolling. If the kernel is + // unrolled even with such an assumption, and the accesses to those inputs + // turn out to be vectorizable, the compiler will still vectorize them. + cannot_be_vectorized += + NumInputsWithMoreElementsThan(unnested_hlo, input_shape); + return can_be_vectorized >= cannot_be_vectorized; +} + } // namespace std::tuple IrEmitterUnnested::ComputeMappingSchemeAndReductionKind( - const HloInstruction* first_reduce) { + const HloInstruction* unnested_hlo, const HloInstruction* first_reduce) { int64 depth = 1; int64 height = 1; int64 width = 1; @@ -3437,15 +3527,6 @@ IrEmitterUnnested::ComputeMappingSchemeAndReductionKind( height = num_reduced_major; width = num_kept; is_row_reduction = false; - // Assume unrolling is beneficial only when we can vectorize the loads - // of small data types. - auto is_unrolling_beneficial = [&] { - // TODO(b/122468062): Need further investigate to see whether we can - // remove the constraint on IsPowerOfTwo. - return IsPowerOfTwo(static_cast(num_kept)) && - primitive_util::BitWidth( - first_reduce->operand(0)->shape().element_type()) <= 16; - }; // Column reduction without transpose doesn't require communication among // threads processing elements in the same tile. The current implementation // only support the use of one hardware thread block to process one block of @@ -3454,7 +3535,8 @@ IrEmitterUnnested::ComputeMappingSchemeAndReductionKind( // num_threads_x and tile_size_x to allow a bigger hardware thread block. int64 hw_threads_per_block_limit = ThreadsPerBlockLimit(ir_emitter_context_->device_description()); - if (is_unrolling_beneficial()) { + if (IsUnrollingColumnReductionBeneficial(unnested_hlo, input_shape, + num_kept)) { tile_size_x = std::min(2 * hw_threads_per_block_limit, num_kept); num_threads_x = tile_size_x / 2; dilated_x = false; @@ -3539,7 +3621,7 @@ Status IrEmitterUnnested::EmitReductionToVector(HloInstruction* unnested_hlo) { bool is_row_reduction; llvm_ir::KernelMappingScheme mapping_scheme; std::tie(mapping_scheme, is_row_reduction) = - ComputeMappingSchemeAndReductionKind(first_reduce); + ComputeMappingSchemeAndReductionKind(unnested_hlo, first_reduce); ReductionCodegenInfo reduction_info(&mapping_scheme, is_row_reduction); KernelCodeGenerator kernel_generator( /*tile_element_generator=*/ diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h index eb0ea90e14..21b842bb2c 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h @@ -217,9 +217,13 @@ class IrEmitterUnnested : public IrEmitter { Status EmitReductionToVector(HloInstruction* unnested_hlo); // Computes the KernelMappingScheme for the reduce HLO and indicates whether - // the reduction is a row reduction. + // the reduction is a row reduction. For an un-fused reduce op, unnested_hlo + // and first_reduce are the same instruction. For a kInput fusion, + // unnested_hlo is the fusion instruction while first_reduce is the first + // reduce op. std::tuple - ComputeMappingSchemeAndReductionKind(const HloInstruction* first_reduce); + ComputeMappingSchemeAndReductionKind(const HloInstruction* unnested_hlo, + const HloInstruction* first_reduce); // Emits code for an in-place scatter, modifying `thunk`s launch dimensions in // the process. `scatter` may be fused, scatter indices are taken from diff --git a/tensorflow/compiler/xla/service/gpu/tests/gpu_kernel_tiling_test.cc b/tensorflow/compiler/xla/service/gpu/tests/gpu_kernel_tiling_test.cc index 8004ebdc8e..869724db60 100644 --- a/tensorflow/compiler/xla/service/gpu/tests/gpu_kernel_tiling_test.cc +++ b/tensorflow/compiler/xla/service/gpu/tests/gpu_kernel_tiling_test.cc @@ -270,7 +270,7 @@ TEST_F(GpuKernelTilingTest, TransposedInputWithoutUnsafeUseTiled) { kind=kLoop, calls=fused_computation })"; - // Check that a call to llvm.nvvm.barrier0 is not generated. + // Check that a call to llvm.nvvm.barrier0 is generated. auto hlo_module = ParseHloString(kHloString, ConfigWithoutLayoutAssignment()).ValueOrDie(); CompileAndVerifyIr(std::move(hlo_module), @@ -284,6 +284,133 @@ TEST_F(GpuKernelTilingTest, TransposedInputWithoutUnsafeUseTiled) { EXPECT_TRUE(RunAndCompareNoHloPasses(kHloString, ErrorSpec{0.0})); } +TEST_F(GpuKernelTilingTest, ColumnReductionWithPowerOf2OutputElementsUnrolled) { + const char *const kHloString = R"( + HloModule column_reduce_powerof2 + + reduction { + x = f32[] parameter(0) + y = f32[] parameter(1) + ROOT add = f32[] add(x, y) + } + + ENTRY kernel_entry { + constant0 = f32[] constant(0) + arg1 = f16[1024,512]{1,0} parameter(0) + arg1_conv = f32[1024,512]{1,0} convert(arg1) + ROOT reduce = f32[512]{0} reduce(arg1_conv, constant0), dimensions={0}, to_apply=reduction + })"; + + // Check that two calls to llvm.nvvm.atomic are generated. + auto hlo_module = + ParseHloString(kHloString, ConfigWithoutLayoutAssignment()).ValueOrDie(); + CompileAndVerifyIr(std::move(hlo_module), + R"( +; CHECK-LABEL: define void @fusion +; CHECK: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK-NOT: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK: } +)", + /*match_optimized_ir=*/true); + // Check that the kernel runs correctly. + EXPECT_TRUE(RunAndCompareNoHloPasses(kHloString, ErrorSpec{1.0e-5, 1.0e-5})); +} + +TEST_F(GpuKernelTilingTest, + ColumnReductionWithInputLargerThenReduceInputNotUnrolled) { + const char *const kHloString = R"( + HloModule larger_than_reduce_input_parameter + + reduction22 { + x = f32[] parameter(0) + y = f32[] parameter(1) + ROOT add = f32[] add(x, y) + } + + fused_computation { + constant0 = f32[] constant(0) + arg.1 = f16[1024,512]{1,0} parameter(0) + arg.2 = f16[1027,513]{1,0} parameter(1) + arg1.conv = f32[1024,512]{1,0} convert(arg.1) + arg2.conv = f32[1027,513]{1,0} convert(arg.2) + slice2 = f32[1024,512]{1,0} slice(arg2.conv), slice={[2:1026], [1:513]} + add2 = f32[1024,512]{1,0} add(arg1.conv, slice2) + ROOT reduce = f32[512]{0} reduce(add2, constant0), dimensions={0}, + to_apply=reduction22 + } + + ENTRY kernel_entry { + arg1 = f16[1024,512]{1,0} parameter(0) + arg2 = f16[1027,513]{1,0} parameter(1) + ROOT fusion = f32[512]{0} fusion(arg1, arg2), kind=kInput, + calls=fused_computation + })"; + + // Check that one call to llvm.nvvm.atomic is generated. + auto hlo_module = + ParseHloString(kHloString, ConfigWithoutLayoutAssignment()).ValueOrDie(); + CompileAndVerifyIr(std::move(hlo_module), + R"( +; CHECK-LABEL: define void @fusion +; CHECK: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK-NOT: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK: } +)", + /*match_optimized_ir=*/true); + // Check that the kernel runs correctly. + EXPECT_TRUE(RunAndCompareNoHloPasses(kHloString, ErrorSpec{1.0e-5, 1.0e-5})); +} + +TEST_F(GpuKernelTilingTest, ColumnReductionMOFUnrolled) { + const char *const kHloString = R"( + HloModule column_reduce_powerof2_mof + + reduction22 { + x = f32[] parameter(0) + y = f32[] parameter(1) + ROOT add = f32[] add(x, y) + } + + fused_computation { + constant0 = f32[] constant(0) + arg.1 = f16[1024,512]{1,0} parameter(0) + arg.2 = f16[1024,512]{1,0} parameter(1) + arg1.conv = f32[1024,512]{1,0} convert(arg.1) + arg2.conv = f32[1024,512]{1,0} convert(arg.2) + reduce1 = f32[512]{0} reduce(arg1.conv, constant0), dimensions={0}, + to_apply=reduction22 + reduce2 = f32[512]{0} reduce(arg2.conv, constant0), dimensions={0}, + to_apply=reduction22 + add = f32[1024,512]{1,0} add(arg1.conv, arg2.conv) + ROOT tuple = (f32[512]{0}, f32[512]{0}, f32[1024,512]{1,0}) + tuple(reduce1, reduce2, add) + } + + ENTRY kernel_entry { + arg1 = f16[1024,512]{1,0} parameter(0) + arg2 = f16[1024,512]{1,0} parameter(1) + ROOT fusion = (f32[512]{0}, f32[512]{0}, f32[1024,512]{1,0}) + fusion(arg1, arg2), kind=kInput, calls=fused_computation + })"; + + // Check that four calls to llvm.nvvm.atomic are generated. + auto hlo_module = + ParseHloString(kHloString, ConfigWithoutLayoutAssignment()).ValueOrDie(); + CompileAndVerifyIr(std::move(hlo_module), + R"( +; CHECK-LABEL: define void @fusion +; CHECK: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK-NOT: call float @llvm.nvvm.atomic.load.add.f32.p0f32 +; CHECK: } +)", + /*match_optimized_ir=*/true); + // Check that the kernel runs correctly. + EXPECT_TRUE(RunAndCompareNoHloPasses(kHloString, ErrorSpec{1.0e-5, 1.0e-5})); +} } // namespace } // namespace gpu } // namespace xla -- GitLab From 84bf8020f3a99dc49116417a6430c7a51e349e14 Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Wed, 9 Jan 2019 17:25:33 -0800 Subject: [PATCH 0475/2345] Set device on Identity ops representing inlined function inputs/outputs. This fixes a strange multi-GPU performance issue where the identities from inlined cond_v2 branch functions (part of the control flow lowering pass) were being placed alone on a GPU, creating unnecessary device traffic. Ideally the placer wouldn't be this stupid or grappler would remove the useless identities, but this fix is easier for now. This also adds a simple benchmark testing nested function calls, which currently are inlined. This benchmark doesn't expose the original problem (all of the ops, including the identities added by inlining, are placed on a single device regardless), but I've included it in case it catches any future regressions. I also increased the number of iterations, as I found this made the results more stable. PiperOrigin-RevId: 228619628 --- tensorflow/core/common_runtime/function.cc | 4 ++++ tensorflow/python/eager/benchmarks_test.py | 25 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/common_runtime/function.cc b/tensorflow/core/common_runtime/function.cc index 24a0eee0df..48f32df275 100644 --- a/tensorflow/core/common_runtime/function.cc +++ b/tensorflow/core/common_runtime/function.cc @@ -104,6 +104,10 @@ static Node* AddIdentity(Graph* g, Endpoint input) { NodeDef ndef; ndef.set_name(g->NewName(kNodeLabel)); ndef.set_op("Identity"); + // NOTE(skyewm): we explicitly set the device here to address a multi-GPU + // performance issue where this Identity would be placed alone on a GPU, + // causing unnecessary device traffic. See b/122483225 for details. + ndef.set_device(input.node->def().device()); ndef.add_input(input.name()); AddNodeAttr("T", BaseType(input.dtype()), &ndef); Status s; diff --git a/tensorflow/python/eager/benchmarks_test.py b/tensorflow/python/eager/benchmarks_test.py index 9191c8e689..62c4a12cbf 100644 --- a/tensorflow/python/eager/benchmarks_test.py +++ b/tensorflow/python/eager/benchmarks_test.py @@ -140,7 +140,7 @@ class MicroBenchmarks(test.Benchmark): self._m_2_by_2 = random_ops.random_uniform((2, 2)) self._m_100_by_784 = random_ops.random_uniform((100, 784)) self._num_iters_2_by_2 = 30000 - self._num_iters_100_by_784 = 1000 + self._num_iters_100_by_784 = 30000 def _run(self, func, num_iters, execution_mode=None): # call func to maybe warm up the GPU @@ -370,6 +370,19 @@ class MicroBenchmarks(test.Benchmark): func = lambda: f(m, m, transpose_b=transpose_b) self._run(func, num_iters, execution_mode=execution_mode) + def _benchmark_nested_defun_matmul(self, m, transpose_b, num_iters): + inner = function.defun(math_ops.matmul) + + @function.defun + def outer(a, b, c, transpose_b): + return math_ops.matmul(inner(a, b, transpose_b=transpose_b), c) + + func = lambda: outer(m, m, m, transpose_b=transpose_b) + # Warmup before benchmark + for _ in range(1000): + func() + self._run(func, num_iters) + def _benchmark_defun_matmul_forward_backward(self, m, transpose_b, @@ -525,6 +538,11 @@ class MicroBenchmarks(test.Benchmark): num_iters=self._num_iters_2_by_2, execution_mode=context.ASYNC) + def benchmark_nested_defun_matmul_2_by_2(self): + m = self._m_2_by_2.cpu() + self._benchmark_nested_defun_matmul( + m, transpose_b=False, num_iters=self._num_iters_2_by_2) + # Benchmarks for AA.T, A of dimension 100 by 784. def benchmark_np_matmul_100_by_784(self): self._benchmark_np_matmul( @@ -614,6 +632,11 @@ class MicroBenchmarks(test.Benchmark): self._benchmark_defun_matmul( m, transpose_b=True, num_iters=self._num_iters_100_by_784) + def benchmark_nested_defun_matmul_100_by_784(self): + m = self._m_100_by_784.gpu() + self._benchmark_nested_defun_matmul( + m, transpose_b=True, num_iters=self._num_iters_100_by_784) + def benchmark_defun_without_signature(self): def func(t1, t2, t3, t4, t5, t6, t7, t8): -- GitLab From 0d53e22c582605d4f7f2a26b179a637a2d469206 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 17:30:56 -0800 Subject: [PATCH 0476/2345] Refactor pfor tests into multiple files. PiperOrigin-RevId: 228620325 --- tensorflow/python/ops/parallel_for/BUILD | 47 +- .../python/ops/parallel_for/array_test.py | 274 +++++++ .../ops/parallel_for/control_flow_ops_test.py | 693 ++---------------- .../python/ops/parallel_for/gradients_test.py | 2 + .../python/ops/parallel_for/math_test.py | 405 ++++++++++ .../python/ops/parallel_for/test_util.py | 59 ++ 6 files changed, 830 insertions(+), 650 deletions(-) create mode 100644 tensorflow/python/ops/parallel_for/array_test.py create mode 100644 tensorflow/python/ops/parallel_for/math_test.py create mode 100644 tensorflow/python/ops/parallel_for/test_util.py diff --git a/tensorflow/python/ops/parallel_for/BUILD b/tensorflow/python/ops/parallel_for/BUILD index 07fc9433a2..1028963b1a 100644 --- a/tensorflow/python/ops/parallel_for/BUILD +++ b/tensorflow/python/ops/parallel_for/BUILD @@ -15,11 +15,13 @@ py_library( "control_flow_ops.py", "gradients.py", "pfor.py", + "test_util.py", ], srcs_version = "PY2AND3", deps = [ ":control_flow_ops", ":gradients", + ":test_util", "//tensorflow/python:array_ops", "//tensorflow/python:check_ops", "//tensorflow/python:constant_op", @@ -83,12 +85,25 @@ py_library( ], ) +py_library( + name = "test_util", + srcs = ["test_util.py"], + srcs_version = "PY2AND3", + deps = [ + ":pfor_lib", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_ops", + "//tensorflow/python:util", + "//tensorflow/python:variables", + ], +) + cuda_py_test( name = "control_flow_ops_test", - size = "large", srcs = ["control_flow_ops_test.py"], additional_deps = [ ":control_flow_ops", + ":test_util", "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:gradients", @@ -101,6 +116,34 @@ cuda_py_test( ], ) +cuda_py_test( + name = "array_test", + srcs = ["array_test.py"], + additional_deps = [ + ":control_flow_ops", + ":test_util", + "//tensorflow/python:client_testlib", + "//tensorflow/python:array_ops", + "//tensorflow/python:random_ops", + "//tensorflow/python:util", + "//tensorflow/python/eager:backprop", + ], +) + +cuda_py_test( + name = "math_test", + srcs = ["math_test.py"], + additional_deps = [ + ":control_flow_ops", + ":test_util", + "//tensorflow/python:client_testlib", + "//tensorflow/python:math_ops", + "//tensorflow/python:random_ops", + "//tensorflow/python:util", + ], + tags = ["optonly"], # Too slow in non-opt mode +) + py_library( name = "gradients", srcs = ["gradients.py"], @@ -115,7 +158,6 @@ py_library( cuda_py_test( name = "gradients_test", - size = "large", srcs = ["gradients_test.py"], additional_deps = [ ":control_flow_ops", @@ -128,4 +170,5 @@ cuda_py_test( "//tensorflow/python:random_ops", "//tensorflow/python/ops/losses", ], + tags = ["optonly"], # Too slow in non-opt mode ) diff --git a/tensorflow/python/ops/parallel_for/array_test.py b/tensorflow/python/ops/parallel_for/array_test.py new file mode 100644 index 0000000000..7f0c0f5b99 --- /dev/null +++ b/tensorflow/python/ops/parallel_for/array_test.py @@ -0,0 +1,274 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for vectorization of array kernels.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.eager import backprop +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import test_util +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import nn +from tensorflow.python.ops import random_ops +from tensorflow.python.ops import tensor_array_grad # pylint: disable=unused-import +from tensorflow.python.ops.parallel_for import control_flow_ops as pfor_control_flow_ops +from tensorflow.python.ops.parallel_for.test_util import PForTestCase +from tensorflow.python.platform import test + + +@test_util.run_all_in_graph_and_eager_modes +class ArrayTest(PForTestCase): + + def test_gather(self): + x = random_ops.random_uniform([3, 3, 3]) + + def loop_fn(i): + outputs = [] + x_i = array_ops.gather(x, i) + for y in [x, x_i]: + axes = [0, 2, -1] if y == x else [0] + for axis in axes: + outputs.append(array_ops.gather(y, 2, axis=axis)) + outputs.append(array_ops.gather(y, i, axis=axis)) + outputs.append(array_ops.gather(y, [i], axis=axis)) + outputs.append(array_ops.gather(y, [i, 2], axis=axis)) + outputs.append(array_ops.gather(y, [[2, i], [i, 1]], axis=axis)) + return outputs + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 20) + + def test_shape(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x_i = array_ops.gather(x, i) + return array_ops.shape(x_i), array_ops.shape(x_i, out_type=dtypes.int64) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32, dtypes.int64]) + + def test_size(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x_i = array_ops.gather(x, i) + return array_ops.size(x_i), array_ops.size(x_i, out_type=dtypes.int64) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32, dtypes.int64]) + + def test_rank(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x_i = array_ops.gather(x, i) + return array_ops.rank(x_i) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32]) + + def test_shape_n(self): + x = random_ops.random_uniform([3, 2, 3]) + y = random_ops.random_uniform([3]) + + def loop_fn(i): + x_i = array_ops.gather(x, i) + y_i = array_ops.gather(y, i) + return array_ops.shape_n([x_i, x, y, y_i]), array_ops.shape_n( + [x_i, x, y, y_i], out_type=dtypes.int64) + + self._test_loop_fn( + loop_fn, 3, loop_fn_dtypes=[dtypes.int32] * 4 + [dtypes.int64] * 4) + + def test_reshape(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.reshape(x1, [-1]), array_ops.reshape(x1, [1, 3, 1, -1]) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) + + def test_expand_dims(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.expand_dims( + x1, axis=-1), array_ops.expand_dims( + x1, axis=1) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) + + def test_slice(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.slice(x1, begin=(0, 1), size=(2, 1)) + + self._test_loop_fn(loop_fn, 3) + + def test_tile(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.tile(x1, [2, 1]) + + self._test_loop_fn(loop_fn, 3) + + def test_tile_loop_dependent(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.tile(x1, [i, 1]) + + with self.assertRaisesRegexp(ValueError, "expected to be loop invariant"): + pfor_control_flow_ops.pfor(loop_fn, 2) + + def test_pack(self): + x = random_ops.random_uniform([3, 2, 3]) + y = random_ops.random_uniform([2, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.stack([x1, y], axis=-1) + + self._test_loop_fn(loop_fn, 1) + + def test_unpack(self): + x = random_ops.random_uniform([3, 2, 3, 4]) + + def loop_fn(i): + x_i = array_ops.gather(x, i) + return array_ops.unstack( + x_i, 4, axis=-1), array_ops.unstack( + x_i, 3, axis=1) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 7) + + def test_pad(self): + x = random_ops.random_uniform([3, 2, 3]) + padding = constant_op.constant([[1, 2], [3, 4]]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.pad(x1, padding, mode="CONSTANT") + + self._test_loop_fn(loop_fn, 3) + + def test_split(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.split(x1, 2, axis=0), array_ops.split(x1, 3, axis=-1) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 5) + + def test_split_v(self): + x = random_ops.random_uniform([3, 6, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return (array_ops.split(x1, [2, 1, 3], axis=0), + array_ops.split(x1, [3], axis=-1)) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 4) + + def test_transpose(self): + x = random_ops.random_uniform([3, 2, 3, 4]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.transpose(x1, [2, 1, 0]) + + self._test_loop_fn(loop_fn, 3) + + def test_zeros_like(self): + x = random_ops.random_uniform([3, 2, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + z = array_ops.zeros_like(x1), + return z, z + x1 + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) + + def test_concat_v2(self): + x = random_ops.random_uniform([3, 2, 3]) + y = random_ops.random_uniform([2, 3]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return array_ops.concat( + [x1, x1, y], axis=0), array_ops.concat( + [x1, x1, y], axis=-1) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) + + def test_unary_cwise_ops(self): + for op in [array_ops.identity, array_ops.stop_gradient]: + with backprop.GradientTape(persistent=True) as g: + x = random_ops.random_uniform([3, 5]) + g.watch(x) + + # pylint: disable=cell-var-from-loop + def loop_fn(i): + with g: + x1 = array_ops.gather(x, i) + y = op(x1) + x1 + loss = nn.l2_loss(y) + return op(x), y, g.gradient(loss, x1) + + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 3) + + def test_identity_n(self): + x = random_ops.random_uniform([3, 4]) + + def loop_fn(i): + return array_ops.identity_n([x, array_ops.gather(x, i)]) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) + + def test_matrix_diag_part(self): + x = random_ops.random_uniform([3, 4, 2]) + + def loop_fn(i): + return array_ops.matrix_diag_part(array_ops.gather(x, i)) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32]) + + def test_strided_slice(self): + with backprop.GradientTape(persistent=True) as g: + x = random_ops.random_uniform([3, 3, 4, 4, 2, 2, 2]) + g.watch(x) + + def loop_fn(i): + with g: + x_i = array_ops.gather(x, i) + y = x_i[:2, ::2, 1::3, ..., array_ops.newaxis, 1] + loss = nn.l2_loss(y) + return y, g.gradient(loss, x_i) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/python/ops/parallel_for/control_flow_ops_test.py b/tensorflow/python/ops/parallel_for/control_flow_ops_test.py index 933bddd8cc..1a8f639d43 100644 --- a/tensorflow/python/ops/parallel_for/control_flow_ops_test.py +++ b/tensorflow/python/ops/parallel_for/control_flow_ops_test.py @@ -34,7 +34,6 @@ from tensorflow.python.framework import sparse_tensor from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops from tensorflow.python.ops import bitwise_ops -from tensorflow.python.ops import clip_ops from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import data_flow_ops from tensorflow.python.ops import functional_ops @@ -50,40 +49,13 @@ from tensorflow.python.ops import tensor_array_grad # pylint: disable=unused-im from tensorflow.python.ops import tensor_array_ops from tensorflow.python.ops import variables from tensorflow.python.ops.parallel_for import control_flow_ops as pfor_control_flow_ops +from tensorflow.python.ops.parallel_for.test_util import PForTestCase from tensorflow.python.platform import test from tensorflow.python.util import nest @test_util.run_all_in_graph_and_eager_modes -class PForTest(test.TestCase): - - def _run_targets(self, targets1, targets2=None, run_init=True): - targets1 = nest.flatten(targets1) - targets2 = ([] if targets2 is None else nest.flatten(targets2)) - assert len(targets1) == len(targets2) or not targets2 - if run_init: - init = variables.global_variables_initializer() - self.evaluate(init) - return self.evaluate(targets1 + targets2) - - def run_and_assert_equal(self, targets1, targets2): - outputs = self._run_targets(targets1, targets2) - outputs = nest.flatten(outputs) # flatten SparseTensorValues - n = len(outputs) // 2 - for i in range(n): - if outputs[i + n].dtype != np.object: - self.assertAllClose(outputs[i + n], outputs[i], rtol=1e-4, atol=1e-5) - else: - self.assertAllEqual(outputs[i + n], outputs[i]) - - def _test_loop_fn(self, loop_fn, iters, - loop_fn_dtypes=dtypes.float32, - parallel_iterations=None): - t1 = pfor_control_flow_ops.pfor(loop_fn, iters=iters, - parallel_iterations=parallel_iterations) - t2 = pfor_control_flow_ops.for_loop(loop_fn, loop_fn_dtypes, iters=iters, - parallel_iterations=parallel_iterations) - self.run_and_assert_equal(t1, t2) +class PForTest(PForTestCase): def test_op_conversion_fallback_to_while_loop(self): # Note that we used top_k op for this test. If a converter gets defined for @@ -129,246 +101,7 @@ class PForTest(test.TestCase): @test_util.run_all_in_graph_and_eager_modes -class ArrayTest(PForTest): - - def test_gather(self): - x = random_ops.random_uniform([3, 3, 3]) - - def loop_fn(i): - outputs = [] - x_i = array_ops.gather(x, i) - for y in [x, x_i]: - axes = [0, 2, -1] if y == x else [0] - for axis in axes: - outputs.append(array_ops.gather(y, 2, axis=axis)) - outputs.append(array_ops.gather(y, i, axis=axis)) - outputs.append(array_ops.gather(y, [i], axis=axis)) - outputs.append(array_ops.gather(y, [i, 2], axis=axis)) - outputs.append(array_ops.gather(y, [[2, i], [i, 1]], axis=axis)) - return outputs - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 20) - - def test_shape(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x_i = array_ops.gather(x, i) - return array_ops.shape(x_i), array_ops.shape(x_i, out_type=dtypes.int64) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32, dtypes.int64]) - - def test_size(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x_i = array_ops.gather(x, i) - return array_ops.size(x_i), array_ops.size(x_i, out_type=dtypes.int64) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32, dtypes.int64]) - - def test_rank(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x_i = array_ops.gather(x, i) - return array_ops.rank(x_i) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32]) - - def test_shape_n(self): - x = random_ops.random_uniform([3, 2, 3]) - y = random_ops.random_uniform([3]) - - def loop_fn(i): - x_i = array_ops.gather(x, i) - y_i = array_ops.gather(y, i) - return array_ops.shape_n([x_i, x, y, y_i]), array_ops.shape_n( - [x_i, x, y, y_i], out_type=dtypes.int64) - - self._test_loop_fn( - loop_fn, 3, loop_fn_dtypes=[dtypes.int32] * 4 + [dtypes.int64] * 4) - - def test_reshape(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.reshape(x1, [-1]), array_ops.reshape(x1, [1, 3, 1, -1]) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) - - def test_expand_dims(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.expand_dims( - x1, axis=-1), array_ops.expand_dims( - x1, axis=1) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) - - def test_slice(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.slice(x1, begin=(0, 1), size=(2, 1)) - - self._test_loop_fn(loop_fn, 3) - - def test_tile(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.tile(x1, [2, 1]) - - self._test_loop_fn(loop_fn, 3) - - def test_tile_loop_dependent(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.tile(x1, [i, 1]) - - with self.assertRaisesRegexp(ValueError, "expected to be loop invariant"): - pfor_control_flow_ops.pfor(loop_fn, 2) - - def test_pack(self): - x = random_ops.random_uniform([3, 2, 3]) - y = random_ops.random_uniform([2, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.stack([x1, y], axis=-1) - - self._test_loop_fn(loop_fn, 1) - - def test_unpack(self): - x = random_ops.random_uniform([3, 2, 3, 4]) - - def loop_fn(i): - x_i = array_ops.gather(x, i) - return array_ops.unstack( - x_i, 4, axis=-1), array_ops.unstack( - x_i, 3, axis=1) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 7) - - def test_pad(self): - x = random_ops.random_uniform([3, 2, 3]) - padding = constant_op.constant([[1, 2], [3, 4]]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.pad(x1, padding, mode="CONSTANT") - - self._test_loop_fn(loop_fn, 3) - - def test_split(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.split(x1, 2, axis=0), array_ops.split(x1, 3, axis=-1) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 5) - - def test_split_v(self): - x = random_ops.random_uniform([3, 6, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return (array_ops.split(x1, [2, 1, 3], axis=0), - array_ops.split(x1, [3], axis=-1)) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 4) - - def test_transpose(self): - x = random_ops.random_uniform([3, 2, 3, 4]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.transpose(x1, [2, 1, 0]) - - self._test_loop_fn(loop_fn, 3) - - def test_zeros_like(self): - x = random_ops.random_uniform([3, 2, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - z = array_ops.zeros_like(x1), - return z, z + x1 - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) - - def test_concat_v2(self): - x = random_ops.random_uniform([3, 2, 3]) - y = random_ops.random_uniform([2, 3]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return array_ops.concat( - [x1, x1, y], axis=0), array_ops.concat( - [x1, x1, y], axis=-1) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) - - def test_unary_cwise_ops(self): - for op in [array_ops.identity, array_ops.stop_gradient]: - with backprop.GradientTape(persistent=True) as g: - x = random_ops.random_uniform([3, 5]) - g.watch(x) - - # pylint: disable=cell-var-from-loop - def loop_fn(i): - with g: - x1 = array_ops.gather(x, i) - y = op(x1) + x1 - loss = nn.l2_loss(y) - return op(x), y, g.gradient(loss, x1) - - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 3) - - def test_identity_n(self): - x = random_ops.random_uniform([3, 4]) - - def loop_fn(i): - return array_ops.identity_n([x, array_ops.gather(x, i)]) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) - - def test_matrix_diag_part(self): - x = random_ops.random_uniform([3, 4, 2]) - - def loop_fn(i): - return array_ops.matrix_diag_part(array_ops.gather(x, i)) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32]) - - def test_strided_slice(self): - with backprop.GradientTape(persistent=True) as g: - x = random_ops.random_uniform([3, 3, 4, 4, 2, 2, 2]) - g.watch(x) - - def loop_fn(i): - with g: - x_i = array_ops.gather(x, i) - y = x_i[:2, ::2, 1::3, ..., array_ops.newaxis, 1] - loss = nn.l2_loss(y) - return y, g.gradient(loss, x_i) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) - - -@test_util.run_all_in_graph_and_eager_modes -class BitwiseTest(PForTest): +class BitwiseTest(PForTestCase): def test_unary_cwise(self): for op in [bitwise_ops.invert]: @@ -408,376 +141,7 @@ class BitwiseTest(PForTest): @test_util.run_all_in_graph_and_eager_modes -class MathTest(PForTest): - - def test_unary_cwise_ops(self): - complex_ops = [ - math_ops.angle, - math_ops.imag, - math_ops.complex_abs, - math_ops.real, - math_ops.conj, - ] - real_ops = [ - lambda x: math_ops.acosh(1 + math_ops.square(x)), - math_ops.abs, - math_ops.acos, - math_ops.asin, - math_ops.asinh, - math_ops.atan, - math_ops.atanh, - math_ops.bessel_i0e, - math_ops.bessel_i1e, - math_ops.cos, - math_ops.cosh, - math_ops.digamma, - math_ops.erf, - math_ops.erfc, - math_ops.exp, - math_ops.expm1, - math_ops.inv, - math_ops.is_finite, - math_ops.is_inf, - math_ops.lgamma, - math_ops.log, - math_ops.log1p, - math_ops.neg, - math_ops.negative, - math_ops.reciprocal, - math_ops.rint, - math_ops.round, - math_ops.rsqrt, - math_ops.sigmoid, - math_ops.sign, - math_ops.sin, - math_ops.sinh, - math_ops.sqrt, - math_ops.square, - math_ops.tan, - math_ops.tanh, - math_ops.tanh, - nn.elu, - nn.relu, - nn.relu6, - nn.selu, - nn.softplus, - nn.softsign, - ] - for op in complex_ops + real_ops: - with backprop.GradientTape(persistent=True) as g: - x = random_ops.random_uniform([3, 5]) - g.watch(x) - if op in complex_ops: - y = random_ops.random_uniform([3, 5]) - g.watch(y) - x = math_ops.complex(x, y) - - # pylint: disable=cell-var-from-loop - output_dtypes = [] - def loop_fn(i): - with g: - x1 = array_ops.gather(x, i) - y1 = op(x1) - outputs = [op(x), y1] - if y1.dtype == dtypes.float32: - loss = math_ops.reduce_sum(y1 * y1) - else: - loss = None - if loss is not None: - grad = g.gradient(loss, x1) - if grad is not None: - outputs.append(grad) - del output_dtypes[:] - output_dtypes.extend([t.dtype for t in outputs]) - return outputs - - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=output_dtypes) - - def test_unary_cwise_no_grad(self): - for op in [math_ops.ceil, - math_ops.floor, - math_ops.logical_not]: - x = random_ops.random_uniform([3, 5]) - if op == math_ops.logical_not: - x = x > 0 - - # pylint: disable=cell-var-from-loop - def loop_fn(i): - return op(array_ops.gather(x, i)) - - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=x.dtype) - - def test_binary_cwise_ops(self): - logical_ops = [ - math_ops.logical_and, - math_ops.logical_or, - math_ops.logical_xor - ] - - # Wrapper functions restricting the range of inputs of zeta and polygamma. - def safe_polygamma(x, y): - return math_ops.polygamma( - math_ops.round(clip_ops.clip_by_value(y, 1, 10)), - x * x + 1) - - def safe_zeta(x, y): - return math_ops.zeta(x * x + 1, y * y) - - float_ops = [ - math_ops.add, - math_ops.add_v2, - math_ops.atan2, - math_ops.complex, - math_ops.div, - math_ops.divide, - math_ops.div_no_nan, - math_ops.equal, - math_ops.floor_div, - math_ops.floor_mod, - math_ops.greater, - math_ops.greater_equal, - math_ops.igamma, - math_ops.igammac, - math_ops.igamma_grad_a, - math_ops.less, - math_ops.less_equal, - math_ops.maximum, - math_ops.minimum, - math_ops.mod, - math_ops.multiply, - math_ops.not_equal, - math_ops.pow, - math_ops.squared_difference, - math_ops.subtract, - math_ops.truncate_mod, - safe_polygamma, - safe_zeta, - ] - for op in logical_ops + float_ops: - x = random_ops.random_uniform([7, 3, 5]) - y = random_ops.random_uniform([3, 5]) - if op in logical_ops: - x = x > 0 - y = y > 0 - - output_dtypes = [] - # pylint: disable=cell-var-from-loop - def loop_fn(i): - x1 = array_ops.gather(x, i) - y1 = array_ops.gather(y, i) - outputs = [op(x, y), op(x1, y), op(x, y1), op(x1, y1), op(x1, x1)] - del output_dtypes[:] - output_dtypes.extend([t.dtype for t in outputs]) - return outputs - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=output_dtypes) - - def test_approximate_equal(self): - x = random_ops.random_uniform([3, 5]) - y = random_ops.random_uniform([3, 5]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - y1 = array_ops.gather(y, i) - return math_ops.approximate_equal(x1, y1) - - self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.bool]) - - def test_addn(self): - x = random_ops.random_uniform([2, 3, 5]) - y = random_ops.random_uniform([3, 5]) - z = random_ops.random_uniform([3, 5]) - - def loop_fn(i): - x1 = array_ops.gather(x, i) - return math_ops.add_n([x1, y, z]) - - self._test_loop_fn(loop_fn, 2) - - def test_matmul(self): - for tr_a in (True, False): - for tr_b in (True, False): - for stack_a in (True, False): - for stack_b in (True, False): - shape_a = (5, 3) if tr_a else (3, 5) - if stack_a: - shape_a = (2,) + shape_a - shape_b = (7, 5) if tr_b else (5, 7) - if stack_b: - shape_b = (2,) + shape_b - - x = random_ops.random_uniform(shape_a) - y = random_ops.random_uniform(shape_b) - - # pylint: disable=cell-var-from-loop - def loop_fn(i): - a = array_ops.gather(x, i) if stack_a else x - b = array_ops.gather(y, i) if stack_b else y - return math_ops.matmul(a, b, transpose_a=tr_a, transpose_b=tr_b) - - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 2) - - def test_batch_matmul(self): - for tr_a in (True, False): - for tr_b in (True, False): - for stack_a in (True, False): - for stack_b in (True, False): - shape_a = (4, 5, 3) if tr_a else (4, 3, 5) - if stack_a: - shape_a = (2,) + shape_a - shape_b = (4, 7, 5) if tr_b else (4, 5, 7) - if stack_b: - shape_b = (2,) + shape_b - - x = random_ops.random_uniform(shape_a) - y = random_ops.random_uniform(shape_b) - - # pylint: disable=cell-var-from-loop - def loop_fn(i): - a = array_ops.gather(x, i) if stack_a else x - b = array_ops.gather(y, i) if stack_b else y - return math_ops.matmul(a, b, transpose_a=tr_a, transpose_b=tr_b) - - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 2) - - def test_reduction(self): - x = random_ops.random_uniform([2, 3, 4, 5]) - for op in [ - math_ops.reduce_sum, math_ops.reduce_prod, math_ops.reduce_max, - math_ops.reduce_min - ]: - for axis in ([1], None, [0, 2]): - for keepdims in (True, False): - - # pylint: disable=cell-var-from-loop - def loop_fn(i): - a = array_ops.gather(x, i) - return op(a, axis=axis, keepdims=keepdims) - - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 2) - - def test_cum_sum(self): - x = random_ops.random_uniform([2, 3, 4, 5]) - for axis in (1, -2): - for exclusive in (True, False): - for reverse in (True, False): - - # pylint: disable=cell-var-from-loop - def loop_fn(i): - a = array_ops.gather(x, i) - return math_ops.cumsum( - a, axis=axis, exclusive=exclusive, reverse=reverse) - - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 2) - - def test_cum_prod(self): - x = random_ops.random_uniform([2, 3, 4, 5]) - for axis in (1, -2): - for exclusive in (True, False): - for reverse in (True, False): - - # pylint: disable=cell-var-from-loop - def loop_fn(i): - a = array_ops.gather(x, i) - return math_ops.cumprod( - a, axis=axis, exclusive=exclusive, reverse=reverse) - - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 2) - - def test_bias_add(self): - x_shape = [2, 3, 4, 5, 6] - x = random_ops.random_uniform(x_shape) - for data_format in ("NCHW", "NHWC"): - with backprop.GradientTape(persistent=True) as g: - bias_dim = 2 if data_format == "NCHW" else -1 - bias_shape = x_shape[bias_dim] - bias = random_ops.random_uniform([bias_shape]) - g.watch(bias) - - # pylint: disable=cell-var-from-loop - def loop_fn(i): - with g: - a = array_ops.gather(x, i) - y = nn.bias_add(a, bias, data_format=data_format) - loss = math_ops.reduce_sum(y * y) - return y, g.gradient(loss, bias) - # pylint: enable=cell-var-from-loop - - self._test_loop_fn( - loop_fn, 2, loop_fn_dtypes=[dtypes.float32, dtypes.float32]) - - def test_unsorted_segment_sum(self): - t = random_ops.random_uniform([3, 3, 2]) - segment_ids = constant_op.constant([[0, 0, 2], [0, 1, 2], [2, 2, 2]]) - num_segments = 3 - - def loop_fn(i): - data = array_ops.gather(t, i) - data_0 = array_ops.gather(t, 0) - seg_ids = array_ops.gather(segment_ids, i) - return (math_ops.unsorted_segment_sum(data, seg_ids, num_segments), - math_ops.unsorted_segment_sum(data_0, seg_ids, num_segments)) - - self._test_loop_fn(loop_fn, 3, [dtypes.float32] * 2) - - def test_cast(self): - x = constant_op.constant([[1], [2]]) - y = constant_op.constant([[1.0], [2.0]]) - - def loop_fn(i): - return (math_ops.cast(array_ops.gather(x, i), dtypes.float32), - math_ops.cast(array_ops.gather(y, i), dtypes.int32)) - - self._test_loop_fn( - loop_fn, 2, loop_fn_dtypes=[dtypes.float32, dtypes.int32]) - - def test_tanh_axpy(self): - a = constant_op.constant(3.) - x = random_ops.random_uniform([4, 5]) - y = random_ops.random_uniform([6, 5]) - n = x.shape[0] - - def loop_fn(i): - return math_ops.tanh(a * array_ops.gather(x, i) + array_ops.gather(y, i)) - - self._test_loop_fn(loop_fn, n) - - def test_select(self): - cond = constant_op.constant([True, False]) - a = random_ops.random_uniform([2, 3, 5]) - b = random_ops.random_uniform([2, 3, 5]) - for cond_shape in [2], [2, 3], [2, 3, 5]: - cond = random_ops.random_uniform(cond_shape) > 0.5 - - # pylint: disable=cell-var-from-loop - def loop_fn(i): - a_i = array_ops.gather(a, i) - b_i = array_ops.gather(b, i) - cond_i = array_ops.gather(cond, i) - return array_ops.where(cond_i, a_i, b_i) - - # pylint: enable=cell-var-from-loop - - self._test_loop_fn(loop_fn, 2) - - -@test_util.run_all_in_graph_and_eager_modes -class NNTest(PForTest): +class NNTest(PForTestCase): def test_conv2d(self): x = random_ops.random_uniform([3, 2, 12, 12, 3]) @@ -956,7 +320,7 @@ class NNTest(PForTest): self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32] * 2) -class RandomTest(PForTest): +class RandomTest(PForTestCase): # The random values generated in the two implementations are not guaranteed to # match. So we only check the returned shapes. @@ -1009,8 +373,9 @@ class RandomTest(PForTest): self._test_loop_fn(loop_fn, 5) -class LoggingTest(PForTest): +class LoggingTest(PForTestCase): + @test_util.run_v1_only("b/122612051") def test_print(self): x = random_ops.random_uniform([3, 5]) @@ -1031,8 +396,9 @@ class LoggingTest(PForTest): sess.run(pfor_control_flow_ops.pfor(loop_fn, 3)) -class TensorArrayTest(PForTest): +class TensorArrayTest(PForTestCase): + @test_util.run_v1_only("b/122612051") def test_create_outside_and_read(self): ta = tensor_array_ops.TensorArray( @@ -1043,6 +409,7 @@ class TensorArrayTest(PForTest): self._test_loop_fn(loop_fn, 2, [dtypes.int32] * 2) + @test_util.run_v1_only("b/122612051") def test_create_outside_and_gather(self): ta = tensor_array_ops.TensorArray( @@ -1053,6 +420,7 @@ class TensorArrayTest(PForTest): self._test_loop_fn(loop_fn, 2, [dtypes.int32] * 2) + @test_util.run_v1_only("b/122612051") def test_create_outside_and_write_and_scatter(self): t = tensor_array_ops.TensorArray(dtypes.int32, 10, clear_after_read=False) @@ -1074,6 +442,7 @@ class TensorArrayTest(PForTest): output2 = self._run_targets(out2) self.assertAllClose(output2, output1) + @test_util.run_v1_only("b/122612051") def test_create_inside_and_write(self): def loop_fn(i): @@ -1085,6 +454,7 @@ class TensorArrayTest(PForTest): self._test_loop_fn(loop_fn, 3, [dtypes.int32] * 2) + @test_util.run_v1_only("b/122612051") def test_create_inside_and_scatter(self): def loop_fn(i): @@ -1097,6 +467,7 @@ class TensorArrayTest(PForTest): self._test_loop_fn(loop_fn, 3, [dtypes.int32] * 2) + @test_util.run_v1_only("b/122612051") def test_create_inside_and_read(self): def loop_fn(i): @@ -1109,6 +480,7 @@ class TensorArrayTest(PForTest): self._test_loop_fn(loop_fn, 2, [dtypes.int32] * 3) + @test_util.run_v1_only("b/122612051") def test_create_inside_and_gather(self): def loop_fn(i): @@ -1121,6 +493,7 @@ class TensorArrayTest(PForTest): self._test_loop_fn(loop_fn, 2, [dtypes.int32] * 3) + @test_util.run_v1_only("b/122612051") def test_grad(self): x = random_ops.random_uniform([3, 2]) ta = tensor_array_ops.TensorArray( @@ -1140,8 +513,9 @@ class TensorArrayTest(PForTest): self.assertAllClose(actual_grad, computed_grad) -class StackTest(PForTest): +class StackTest(PForTestCase): + @test_util.run_v1_only("b/122612051") def test_stack_inside_loop_invariant(self): def loop_fn(_): @@ -1157,6 +531,7 @@ class StackTest(PForTest): self._test_loop_fn(loop_fn, 2, [dtypes.int32] * 2) + @test_util.run_v1_only("b/122612051") def test_stack_inside_push_loop_dependent(self): def loop_fn(i): @@ -1172,6 +547,7 @@ class StackTest(PForTest): self._test_loop_fn(loop_fn, 2, [dtypes.int32] * 2) + @test_util.run_v1_only("b/122612051") def test_stack_outside_pop(self): s = data_flow_ops.stack_v2(max_size=4, elem_type=dtypes.int32) op = data_flow_ops.stack_push_v2(s, 5) @@ -1195,6 +571,7 @@ class StackTest(PForTest): self.assertAllEqual([6, 6], v2) self.assertAllEqual(5, v3) + @test_util.run_v1_only("b/122612051") def test_stack_outside_push(self): s = data_flow_ops.stack_v2(max_size=4, elem_type=dtypes.int32) @@ -1207,7 +584,7 @@ class StackTest(PForTest): # TODO(agarwal): test nested while_loops. This currently requires converting a # tf.cond. -class ControlFlowTest(PForTest): +class ControlFlowTest(PForTestCase): def test_while_outside_loop(self): @@ -1218,6 +595,7 @@ class ControlFlowTest(PForTest): self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32]) + @test_util.run_v1_only("b/122612051") def test_invariant_while(self): def loop_fn(_): @@ -1225,6 +603,7 @@ class ControlFlowTest(PForTest): self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32]) + @test_util.run_v1_only("b/122612051") def test_invariant_while_with_control_dependency(self): def loop_fn(i): @@ -1234,6 +613,7 @@ class ControlFlowTest(PForTest): self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32]) + @test_util.run_v1_only("b/122612051") def test_while_with_stateful_ops(self): def loop_fn(_): @@ -1243,6 +623,7 @@ class ControlFlowTest(PForTest): self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32]) + @test_util.run_v1_only("b/122612051") def test_while_unstacked_condition(self): def loop_fn(i): @@ -1251,6 +632,7 @@ class ControlFlowTest(PForTest): self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32, dtypes.int32]) + @test_util.run_v1_only("b/122612051") def test_while(self): x = random_ops.random_uniform([3, 5]) lengths = constant_op.constant([4, 0, 2]) @@ -1266,6 +648,7 @@ class ControlFlowTest(PForTest): self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.float32]) + @test_util.run_v1_only("b/122612051") def test_while_jacobian(self): x = random_ops.random_uniform([1, 3]) y = random_ops.random_uniform([3, 3]) @@ -1293,6 +676,7 @@ class ControlFlowTest(PForTest): out, expected = sess.run([out, expected_output]) self.assertAllClose(expected, out) + @test_util.run_v1_only("b/122612051") def test_tensor_array_as_loop_variable(self): def loop_fn(i): @@ -1308,6 +692,7 @@ class ControlFlowTest(PForTest): self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32]) + @test_util.run_v1_only("b/122612051") def test_read_tensor_array_partitioned_indices(self): # Note that tensor array values are pfor loop dependent, and the while loop # termination condition is also dependent on pfor iteration. @@ -1325,6 +710,7 @@ class ControlFlowTest(PForTest): self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.int32]) + @test_util.run_v1_only("b/122612051") def test_external_while_loop_grad(self): # Here we test that external while_loops that are extended from inside pfor # (due to gradient calls) are not actually converted. If the below was @@ -1350,6 +736,7 @@ class ControlFlowTest(PForTest): self.assertAllEqual([1, 1, 1], sess.run(pfor_control_flow_ops.pfor(loop_fn, 3))) + @test_util.run_v1_only("b/122612051") def test_tensor_array_grad(self): inp = constant_op.constant(np.random.rand(3, 4, 2), dtype=dtypes.float32) ta = tensor_array_ops.TensorArray(dtypes.float32, size=3) @@ -1447,13 +834,15 @@ def create_dynamic_lstm(cell_fn, batch_size, state_size, max_steps): return pfor_output, tf_output -class RNNTest(PForTest): +class RNNTest(PForTestCase): + @test_util.run_v1_only("b/122612051") def test_dynamic_rnn(self): pfor_outputs, tf_outputs = create_dynamic_lstm(rnn_cell.BasicRNNCell, 3, 5, 7) self.run_and_assert_equal(pfor_outputs, tf_outputs) + @test_util.run_v1_only("b/122612051") def test_dynamic_lstm(self): pfor_outputs, tf_outputs = create_dynamic_lstm(rnn_cell.BasicLSTMCell, 3, 5, 7) @@ -1576,8 +965,9 @@ class Benchmarks(test.Benchmark): self._run(tf_outputs, 100, name="tf_rnn") -class SparseTest(PForTest): +class SparseTest(PForTestCase): + @test_util.run_v1_only("b/122612051") def test_var_loop_len(self): num_iters = array_ops.placeholder(dtypes.int32) @@ -1589,6 +979,7 @@ class SparseTest(PForTest): with self.cached_session() as sess: sess.run(pfor, feed_dict={num_iters: 3}) + @test_util.run_v1_only("b/122612051") def test_sparse_result_none_stacked(self): num_iters = 10 @@ -1605,6 +996,7 @@ class SparseTest(PForTest): manual = sparse_tensor.SparseTensor(indices, values, dense_shapes) self.run_and_assert_equal(pfor, manual) + @test_util.run_v1_only("b/122612051") def test_sparse_result_all_stacked(self): num_iters = 10 @@ -1620,6 +1012,7 @@ class SparseTest(PForTest): (num_iters, num_iters)) self.run_and_assert_equal(pfor, manual) + @test_util.run_v1_only("b/122612051") def test_sparse_result_indices_stacked(self): num_iters = 10 @@ -1634,6 +1027,7 @@ class SparseTest(PForTest): [1] * num_iters, (num_iters, num_iters)) self.run_and_assert_equal(pfor, manual) + @test_util.run_v1_only("b/122612051") def test_sparse_result_values_stacked(self): num_iters = 10 @@ -1648,6 +1042,7 @@ class SparseTest(PForTest): (num_iters, num_iters)) self.run_and_assert_equal(pfor, manual) + @test_util.run_v1_only("b/122612051") def test_sparse_result_shapes_stacked(self): num_iters = 10 @@ -1661,6 +1056,7 @@ class SparseTest(PForTest): [1] * num_iters, (num_iters, num_iters)) self.run_and_assert_equal(pfor, manual) + @test_util.run_v1_only("b/122612051") def test_sparse_result_shapes_stacked_2D(self): num_iters = 10 @@ -1677,7 +1073,7 @@ class SparseTest(PForTest): self.run_and_assert_equal(pfor, manual) -class ParsingTest(PForTest): +class ParsingTest(PForTestCase): def test_decode_csv(self): csv_tensor = constant_op.constant([["1:2:3"], ["::"], ["7:8:9"]]) @@ -1689,6 +1085,7 @@ class ParsingTest(PForTest): self._test_loop_fn(loop_fn, iters=3, loop_fn_dtypes=[dtypes.int32] * 3) + @test_util.run_v1_only("b/122612051") def test_parse_single_example(self): def _int64_feature(*values): diff --git a/tensorflow/python/ops/parallel_for/gradients_test.py b/tensorflow/python/ops/parallel_for/gradients_test.py index 4342833e3e..133e790992 100644 --- a/tensorflow/python/ops/parallel_for/gradients_test.py +++ b/tensorflow/python/ops/parallel_for/gradients_test.py @@ -29,6 +29,7 @@ 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.keras.engine import training as keras_training from tensorflow.python.layers import layers as tf_layers from tensorflow.python.ops import array_ops @@ -338,6 +339,7 @@ def create_fc_per_eg_jacobians(batch_size, activation_size, num_layers): return jacobians, per_eg_jacobians_pfor, per_eg_jacobians_while +@test_util.run_v1_only("b/122612051") class GradientsTest(test.TestCase): def run_and_assert_equal(self, targets1, targets2, atol=1e-4, rtol=1e-4): diff --git a/tensorflow/python/ops/parallel_for/math_test.py b/tensorflow/python/ops/parallel_for/math_test.py new file mode 100644 index 0000000000..db88f4fe03 --- /dev/null +++ b/tensorflow/python/ops/parallel_for/math_test.py @@ -0,0 +1,405 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for vectorization of math kernels.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.eager import backprop +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import test_util +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import clip_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import nn +from tensorflow.python.ops import random_ops +from tensorflow.python.ops import tensor_array_grad # pylint: disable=unused-import +from tensorflow.python.ops.parallel_for.test_util import PForTestCase +from tensorflow.python.platform import test + + +@test_util.run_all_in_graph_and_eager_modes +class MathTest(PForTestCase): + + def test_unary_cwise_ops(self): + complex_ops = [ + math_ops.angle, + math_ops.imag, + math_ops.complex_abs, + math_ops.real, + math_ops.conj, + ] + real_ops = [ + lambda x: math_ops.acosh(1 + math_ops.square(x)), + math_ops.abs, + math_ops.acos, + math_ops.asin, + math_ops.asinh, + math_ops.atan, + math_ops.atanh, + math_ops.bessel_i0e, + math_ops.bessel_i1e, + math_ops.cos, + math_ops.cosh, + math_ops.digamma, + math_ops.erf, + math_ops.erfc, + math_ops.exp, + math_ops.expm1, + math_ops.inv, + math_ops.is_finite, + math_ops.is_inf, + math_ops.lgamma, + math_ops.log, + math_ops.log1p, + math_ops.neg, + math_ops.negative, + math_ops.reciprocal, + math_ops.rint, + math_ops.round, + math_ops.rsqrt, + math_ops.sigmoid, + math_ops.sign, + math_ops.sin, + math_ops.sinh, + math_ops.sqrt, + math_ops.square, + math_ops.tan, + math_ops.tanh, + math_ops.tanh, + nn.elu, + nn.relu, + nn.relu6, + nn.selu, + nn.softplus, + nn.softsign, + ] + for op in complex_ops + real_ops: + with backprop.GradientTape(persistent=True) as g: + x = random_ops.random_uniform([3, 5]) + g.watch(x) + if op in complex_ops: + y = random_ops.random_uniform([3, 5]) + g.watch(y) + x = math_ops.complex(x, y) + + # pylint: disable=cell-var-from-loop + output_dtypes = [] + def loop_fn(i): + with g: + x1 = array_ops.gather(x, i) + y1 = op(x1) + outputs = [op(x), y1] + if y1.dtype == dtypes.float32: + loss = math_ops.reduce_sum(y1 * y1) + else: + loss = None + if loss is not None: + grad = g.gradient(loss, x1) + if grad is not None: + outputs.append(grad) + del output_dtypes[:] + output_dtypes.extend([t.dtype for t in outputs]) + return outputs + + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=output_dtypes) + + def test_unary_cwise_no_grad(self): + for op in [math_ops.ceil, + math_ops.floor, + math_ops.logical_not]: + x = random_ops.random_uniform([3, 5]) + if op == math_ops.logical_not: + x = x > 0 + + # pylint: disable=cell-var-from-loop + def loop_fn(i): + return op(array_ops.gather(x, i)) + + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=x.dtype) + + def test_binary_cwise_ops(self): + logical_ops = [ + math_ops.logical_and, + math_ops.logical_or, + math_ops.logical_xor + ] + + # Wrapper functions restricting the range of inputs of zeta and polygamma. + def safe_polygamma(x, y): + return math_ops.polygamma( + math_ops.round(clip_ops.clip_by_value(y, 1, 10)), + x * x + 1) + + def safe_zeta(x, y): + return math_ops.zeta(x * x + 1, y * y) + + float_ops = [ + math_ops.add, + math_ops.add_v2, + math_ops.atan2, + math_ops.complex, + math_ops.div, + math_ops.divide, + math_ops.div_no_nan, + math_ops.equal, + math_ops.floor_div, + math_ops.floor_mod, + math_ops.greater, + math_ops.greater_equal, + math_ops.igamma, + math_ops.igammac, + math_ops.igamma_grad_a, + math_ops.less, + math_ops.less_equal, + math_ops.maximum, + math_ops.minimum, + math_ops.mod, + math_ops.multiply, + math_ops.not_equal, + math_ops.pow, + math_ops.squared_difference, + math_ops.subtract, + math_ops.truncate_mod, + safe_polygamma, + safe_zeta, + ] + for op in logical_ops + float_ops: + x = random_ops.random_uniform([7, 3, 5]) + y = random_ops.random_uniform([3, 5]) + if op in logical_ops: + x = x > 0 + y = y > 0 + + output_dtypes = [] + # pylint: disable=cell-var-from-loop + def loop_fn(i): + x1 = array_ops.gather(x, i) + y1 = array_ops.gather(y, i) + outputs = [op(x, y), op(x1, y), op(x, y1), op(x1, y1), op(x1, x1)] + del output_dtypes[:] + output_dtypes.extend([t.dtype for t in outputs]) + return outputs + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=output_dtypes) + + def test_approximate_equal(self): + x = random_ops.random_uniform([3, 5]) + y = random_ops.random_uniform([3, 5]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + y1 = array_ops.gather(y, i) + return math_ops.approximate_equal(x1, y1) + + self._test_loop_fn(loop_fn, 3, loop_fn_dtypes=[dtypes.bool]) + + def test_addn(self): + x = random_ops.random_uniform([2, 3, 5]) + y = random_ops.random_uniform([3, 5]) + z = random_ops.random_uniform([3, 5]) + + def loop_fn(i): + x1 = array_ops.gather(x, i) + return math_ops.add_n([x1, y, z]) + + self._test_loop_fn(loop_fn, 2) + + def test_matmul(self): + for tr_a in (True, False): + for tr_b in (True, False): + for stack_a in (True, False): + for stack_b in (True, False): + shape_a = (5, 3) if tr_a else (3, 5) + if stack_a: + shape_a = (2,) + shape_a + shape_b = (7, 5) if tr_b else (5, 7) + if stack_b: + shape_b = (2,) + shape_b + + x = random_ops.random_uniform(shape_a) + y = random_ops.random_uniform(shape_b) + + # pylint: disable=cell-var-from-loop + def loop_fn(i): + a = array_ops.gather(x, i) if stack_a else x + b = array_ops.gather(y, i) if stack_b else y + return math_ops.matmul(a, b, transpose_a=tr_a, transpose_b=tr_b) + + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 2) + + def test_batch_matmul(self): + for tr_a in (True, False): + for tr_b in (True, False): + for stack_a in (True, False): + for stack_b in (True, False): + shape_a = (4, 5, 3) if tr_a else (4, 3, 5) + if stack_a: + shape_a = (2,) + shape_a + shape_b = (4, 7, 5) if tr_b else (4, 5, 7) + if stack_b: + shape_b = (2,) + shape_b + + x = random_ops.random_uniform(shape_a) + y = random_ops.random_uniform(shape_b) + + # pylint: disable=cell-var-from-loop + def loop_fn(i): + a = array_ops.gather(x, i) if stack_a else x + b = array_ops.gather(y, i) if stack_b else y + return math_ops.matmul(a, b, transpose_a=tr_a, transpose_b=tr_b) + + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 2) + + def test_reduction(self): + x = random_ops.random_uniform([2, 3, 4, 5]) + for op in [ + math_ops.reduce_sum, math_ops.reduce_prod, math_ops.reduce_max, + math_ops.reduce_min + ]: + for axis in ([1], None, [0, 2]): + for keepdims in (True, False): + + # pylint: disable=cell-var-from-loop + def loop_fn(i): + a = array_ops.gather(x, i) + return op(a, axis=axis, keepdims=keepdims) + + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 2) + + def test_cum_sum(self): + x = random_ops.random_uniform([2, 3, 4, 5]) + for axis in (1, -2): + for exclusive in (True, False): + for reverse in (True, False): + + # pylint: disable=cell-var-from-loop + def loop_fn(i): + a = array_ops.gather(x, i) + return math_ops.cumsum( + a, axis=axis, exclusive=exclusive, reverse=reverse) + + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 2) + + def test_cum_prod(self): + x = random_ops.random_uniform([2, 3, 4, 5]) + for axis in (1, -2): + for exclusive in (True, False): + for reverse in (True, False): + + # pylint: disable=cell-var-from-loop + def loop_fn(i): + a = array_ops.gather(x, i) + return math_ops.cumprod( + a, axis=axis, exclusive=exclusive, reverse=reverse) + + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 2) + + def test_bias_add(self): + x_shape = [2, 3, 4, 5, 6] + x = random_ops.random_uniform(x_shape) + for data_format in ("NCHW", "NHWC"): + with backprop.GradientTape(persistent=True) as g: + bias_dim = 2 if data_format == "NCHW" else -1 + bias_shape = x_shape[bias_dim] + bias = random_ops.random_uniform([bias_shape]) + g.watch(bias) + + # pylint: disable=cell-var-from-loop + def loop_fn(i): + with g: + a = array_ops.gather(x, i) + y = nn.bias_add(a, bias, data_format=data_format) + loss = math_ops.reduce_sum(y * y) + return y, g.gradient(loss, bias) + # pylint: enable=cell-var-from-loop + + self._test_loop_fn( + loop_fn, 2, loop_fn_dtypes=[dtypes.float32, dtypes.float32]) + + def test_unsorted_segment_sum(self): + t = random_ops.random_uniform([3, 3, 2]) + segment_ids = constant_op.constant([[0, 0, 2], [0, 1, 2], [2, 2, 2]]) + num_segments = 3 + + def loop_fn(i): + data = array_ops.gather(t, i) + data_0 = array_ops.gather(t, 0) + seg_ids = array_ops.gather(segment_ids, i) + return (math_ops.unsorted_segment_sum(data, seg_ids, num_segments), + math_ops.unsorted_segment_sum(data_0, seg_ids, num_segments)) + + self._test_loop_fn(loop_fn, 3, [dtypes.float32] * 2) + + def test_cast(self): + x = constant_op.constant([[1], [2]]) + y = constant_op.constant([[1.0], [2.0]]) + + def loop_fn(i): + return (math_ops.cast(array_ops.gather(x, i), dtypes.float32), + math_ops.cast(array_ops.gather(y, i), dtypes.int32)) + + self._test_loop_fn( + loop_fn, 2, loop_fn_dtypes=[dtypes.float32, dtypes.int32]) + + def test_tanh_axpy(self): + a = constant_op.constant(3.) + x = random_ops.random_uniform([4, 5]) + y = random_ops.random_uniform([6, 5]) + n = x.shape[0] + + def loop_fn(i): + return math_ops.tanh(a * array_ops.gather(x, i) + array_ops.gather(y, i)) + + self._test_loop_fn(loop_fn, n) + + def test_select(self): + cond = constant_op.constant([True, False]) + a = random_ops.random_uniform([2, 3, 5]) + b = random_ops.random_uniform([2, 3, 5]) + for cond_shape in [2], [2, 3], [2, 3, 5]: + cond = random_ops.random_uniform(cond_shape) > 0.5 + + # pylint: disable=cell-var-from-loop + def loop_fn(i): + a_i = array_ops.gather(a, i) + b_i = array_ops.gather(b, i) + cond_i = array_ops.gather(cond, i) + return array_ops.where(cond_i, a_i, b_i) + + # pylint: enable=cell-var-from-loop + + self._test_loop_fn(loop_fn, 2) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/python/ops/parallel_for/test_util.py b/tensorflow/python/ops/parallel_for/test_util.py new file mode 100644 index 0000000000..7b4ef2239e --- /dev/null +++ b/tensorflow/python/ops/parallel_for/test_util.py @@ -0,0 +1,59 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Test utility.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.python.framework import dtypes +from tensorflow.python.ops import variables +from tensorflow.python.ops.parallel_for import control_flow_ops as pfor_control_flow_ops +from tensorflow.python.platform import test +from tensorflow.python.util import nest + + +class PForTestCase(test.TestCase): + """Base class for test cases.""" + + def _run_targets(self, targets1, targets2=None, run_init=True): + targets1 = nest.flatten(targets1) + targets2 = ([] if targets2 is None else nest.flatten(targets2)) + assert len(targets1) == len(targets2) or not targets2 + if run_init: + init = variables.global_variables_initializer() + self.evaluate(init) + return self.evaluate(targets1 + targets2) + + def run_and_assert_equal(self, targets1, targets2): + outputs = self._run_targets(targets1, targets2) + outputs = nest.flatten(outputs) # flatten SparseTensorValues + n = len(outputs) // 2 + for i in range(n): + if outputs[i + n].dtype != np.object: + self.assertAllClose(outputs[i + n], outputs[i], rtol=1e-4, atol=1e-5) + else: + self.assertAllEqual(outputs[i + n], outputs[i]) + + def _test_loop_fn(self, loop_fn, iters, + loop_fn_dtypes=dtypes.float32, + parallel_iterations=None): + t1 = pfor_control_flow_ops.pfor(loop_fn, iters=iters, + parallel_iterations=parallel_iterations) + t2 = pfor_control_flow_ops.for_loop(loop_fn, loop_fn_dtypes, iters=iters, + parallel_iterations=parallel_iterations) + self.run_and_assert_equal(t1, t2) -- GitLab From d852dec831a4a4c30c3018187e52e25010bf9711 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 17:42:18 -0800 Subject: [PATCH 0477/2345] Use %zu format specifier for size_t printf argument. This fixes -Wformat error in 32-bit Android build. PiperOrigin-RevId: 228621967 --- tensorflow/core/lib/core/status.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/lib/core/status.cc b/tensorflow/core/lib/core/status.cc index 3076c09337..0b63f66f6d 100644 --- a/tensorflow/core/lib/core/status.cc +++ b/tensorflow/core/lib/core/status.cc @@ -200,7 +200,7 @@ Status StatusGroup::as_status() const { const std::pair& b) { return a.second < b.second; }); fmt.push_back( - strings::Printf("Combined status information from %lu operations:\n", + strings::Printf("Combined status information from %zu operations:\n", num_ok_ + children_.size())); for (const auto& p : count_vec) { -- GitLab From 0b1b49fd5a39a5834d897dc61068e68f0649e313 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 9 Jan 2019 17:45:02 -0800 Subject: [PATCH 0478/2345] Go: Update generated wrapper functions for TensorFlow ops. PiperOrigin-RevId: 228622263 --- tensorflow/go/op/wrappers.go | 52 ++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index 208c15d75a..6f6fb793a0 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -7477,6 +7477,19 @@ func Conv2DBackpropFilterUseCudnnOnGpu(value bool) Conv2DBackpropFilterAttr { } } +// Conv2DBackpropFilterExplicitPaddings sets the optional explicit_paddings attribute to value. +// +// value: If `padding` is `"EXPLICIT"`, the list of explicit padding amounts. For the ith +// dimension, the amount of padding inserted before and after the dimension is +// `explicit_paddings[2 * i]` and `explicit_paddings[2 * i + 1]`, respectively. If +// `padding` is not `"EXPLICIT"`, `explicit_paddings` must be empty. +// If not specified, defaults to <> +func Conv2DBackpropFilterExplicitPaddings(value []int64) Conv2DBackpropFilterAttr { + return func(m optionalAttr) { + m["explicit_paddings"] = value + } +} + // Conv2DBackpropFilterDataFormat sets the optional data_format attribute to value. // // value: Specify the data format of the input and output data. With the @@ -8176,7 +8189,7 @@ func RegexReplaceReplaceGlobal(value bool) RegexReplaceAttr { // Arguments: // input: The text to be processed. // pattern: The regular expression to match the input. -// rewrite: The rewrite to be applied to the matched expresion. +// rewrite: The rewrite to be applied to the matched expression. // // Returns The text after applying pattern and rewrite. func RegexReplace(scope *Scope, input tf.Output, pattern tf.Output, rewrite tf.Output, optional ...RegexReplaceAttr) (output tf.Output) { @@ -11605,15 +11618,12 @@ func NthElement(scope *Scope, input tf.Output, n tf.Output, optional ...NthEleme // // Arguments: // -// segment_ids: A tensor whose shape is a prefix of `data.shape`.END -// } -// out_arg { -// name: "output" -// description: < +func Conv2DBackpropInputExplicitPaddings(value []int64) Conv2DBackpropInputAttr { + return func(m optionalAttr) { + m["explicit_paddings"] = value + } +} + // Conv2DBackpropInputDataFormat sets the optional data_format attribute to value. // // value: Specify the data format of the input and output data. With the @@ -35308,6 +35331,19 @@ func Conv2DUseCudnnOnGpu(value bool) Conv2DAttr { } } +// Conv2DExplicitPaddings sets the optional explicit_paddings attribute to value. +// +// value: If `padding` is `"EXPLICIT"`, the list of explicit padding amounts. For the ith +// dimension, the amount of padding inserted before and after the dimension is +// `explicit_paddings[2 * i]` and `explicit_paddings[2 * i + 1]`, respectively. If +// `padding` is not `"EXPLICIT"`, `explicit_paddings` must be empty. +// If not specified, defaults to <> +func Conv2DExplicitPaddings(value []int64) Conv2DAttr { + return func(m optionalAttr) { + m["explicit_paddings"] = value + } +} + // Conv2DDataFormat sets the optional data_format attribute to value. // // value: Specify the data format of the input and output data. With the -- GitLab From 767a1fe74600fadee070fbaa88cbb5f5917170bd Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Wed, 9 Jan 2019 18:31:55 -0800 Subject: [PATCH 0479/2345] Delete unused ServiceExecutableRunOptions::xla_intra_op_thread_pool; NFC PiperOrigin-RevId: 228627868 --- tensorflow/compiler/xla/client/local_client.cc | 5 ++--- tensorflow/compiler/xla/service/hlo_runner.cc | 4 +--- tensorflow/compiler/xla/service/service.cc | 4 +--- .../xla/service/service_executable_run_options.h | 14 +++----------- .../compiler/xla/tests/xla_hlo_profile_test.cc | 5 ++--- 5 files changed, 9 insertions(+), 23 deletions(-) diff --git a/tensorflow/compiler/xla/client/local_client.cc b/tensorflow/compiler/xla/client/local_client.cc index 049cd15738..48b5f94538 100644 --- a/tensorflow/compiler/xla/client/local_client.cc +++ b/tensorflow/compiler/xla/client/local_client.cc @@ -164,9 +164,8 @@ StatusOr LocalExecutable::Run( // ExecutableRunOptions.eigen_intra_op_thread_pool. // *) The thread pool used for XLA CPU ops is from // backend_->eigen_intra_op_thread_pool(). - ServiceExecutableRunOptions service_options( - run_options, backend_->StreamBorrower(), - backend_->eigen_intra_op_thread_pool()); + ServiceExecutableRunOptions service_options(run_options, + backend_->StreamBorrower()); if (executable_->dumping_snapshot()) { return ExecuteAndDump(&service_options, arguments); diff --git a/tensorflow/compiler/xla/service/hlo_runner.cc b/tensorflow/compiler/xla/service/hlo_runner.cc index 5a9b820a9d..d7d66ae1c4 100644 --- a/tensorflow/compiler/xla/service/hlo_runner.cc +++ b/tensorflow/compiler/xla/service/hlo_runner.cc @@ -383,9 +383,7 @@ ServiceExecutableRunOptions HloRunner::GetServiceRunOptionsForDevice( if (device_assignment != nullptr) { run_options.set_device_assignment(device_assignment); } - return ServiceExecutableRunOptions( - run_options, backend().StreamBorrower(), - /*xla_intra_op_thread_pool=*/backend().eigen_intra_op_thread_pool()); + return ServiceExecutableRunOptions(run_options, backend().StreamBorrower()); } Backend& HloRunner::backend() { diff --git a/tensorflow/compiler/xla/service/service.cc b/tensorflow/compiler/xla/service/service.cc index 4a8d272261..2732d498d8 100644 --- a/tensorflow/compiler/xla/service/service.cc +++ b/tensorflow/compiler/xla/service/service.cc @@ -553,9 +553,7 @@ StatusOr Service::ExecuteAndRegisterResult( options.set_intra_op_thread_pool( backend->eigen_intra_op_thread_pool_device()); options.set_device_assignment(&device_assignment); - run_options.emplace_back( - options, backend->StreamBorrower(), - /*xla_intra_op_thread_pool=*/backend->eigen_intra_op_thread_pool()); + run_options.emplace_back(options, backend->StreamBorrower()); } if (options_.number_of_replicas() == 1) { diff --git a/tensorflow/compiler/xla/service/service_executable_run_options.h b/tensorflow/compiler/xla/service/service_executable_run_options.h index dbfed628bf..6bee671056 100644 --- a/tensorflow/compiler/xla/service/service_executable_run_options.h +++ b/tensorflow/compiler/xla/service/service_executable_run_options.h @@ -32,12 +32,10 @@ class ServiceExecutableRunOptions { ServiceExecutableRunOptions() : ServiceExecutableRunOptions(ExecutableRunOptions()) {} - explicit ServiceExecutableRunOptions( - ExecutableRunOptions run_options, StreamBorrower borrow_stream = nullptr, - tensorflow::thread::ThreadPool* xla_intra_op_thread_pool = nullptr) + explicit ServiceExecutableRunOptions(ExecutableRunOptions run_options, + StreamBorrower borrow_stream = nullptr) : run_options_(std::move(run_options)), - borrow_stream_(std::move(borrow_stream)), - xla_intra_op_thread_pool_(xla_intra_op_thread_pool) {} + borrow_stream_(std::move(borrow_stream)) {} // Returns reference or pointer to `ExecutableRunOptions` member. const ExecutableRunOptions& run_options() const { return run_options_; } @@ -56,15 +54,9 @@ class ServiceExecutableRunOptions { : Status(tensorflow::error::UNIMPLEMENTED, "No stream cache"); } - // Returns reference to thread pool for execution of XLA ops on CPU backend. - tensorflow::thread::ThreadPool* xla_intra_op_thread_pool() const { - return xla_intra_op_thread_pool_; - } - private: ExecutableRunOptions run_options_; StreamBorrower borrow_stream_; - tensorflow::thread::ThreadPool* xla_intra_op_thread_pool_; }; } // namespace xla diff --git a/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc b/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc index 1538f2afba..c7337e8caa 100644 --- a/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc +++ b/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc @@ -174,9 +174,8 @@ void ExecuteAndFetchProfile(string* profile_output, LocalClient* client, exec_run_options.set_allocator(backend->memory_allocator()); exec_run_options.set_intra_op_thread_pool( backend->eigen_intra_op_thread_pool_device()); - ServiceExecutableRunOptions run_options( - exec_run_options, /*borrow_stream=*/nullptr, - backend->eigen_intra_op_thread_pool()); + ServiceExecutableRunOptions run_options(exec_run_options, + /*borrow_stream=*/nullptr); std::vector args = {&lhs_arg, &rhs_arg}; TF_ASSERT_OK_AND_ASSIGN( auto execution_result, -- GitLab From 07b238033996f813381f1e1a0410c877c13c6c94 Mon Sep 17 00:00:00 2001 From: Aurelien Geron Date: Thu, 10 Jan 2019 12:41:40 +0800 Subject: [PATCH 0480/2345] Rename Function to ConcreteFunction --- .../python/mirrored_strategy_multigpu_test.py | 2 +- tensorflow/python/eager/def_function.py | 9 ++-- tensorflow/python/eager/function.py | 50 ++++++++++--------- tensorflow/python/eager/wrap_function.py | 2 +- tensorflow/python/keras/backend.py | 2 +- .../saved_model/function_deserialization.py | 12 ++--- tensorflow/python/saved_model/save.py | 2 +- 7 files changed, 42 insertions(+), 37 deletions(-) diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py index 59d711ae01..2925b8c88c 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py @@ -1266,7 +1266,7 @@ class MirroredStrategyDefunTest(test.TestCase): self.evaluate(device_result)) for defun in defuns: - # PolymorphicFunctions are specialized to the current device stack, so + # `PolymorphicFunction`s are specialized to the current device stack, so # call_for_each has one trace per device. To check that the expected set # of variables was accessed on each trace, we first retrieve each # device-specific graph function. diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 4c22a12c1c..b3f361b92d 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -408,7 +408,8 @@ class PolymorphicFunction(object): return self._function_spec def get_initialization_function(self, *args, **kwargs): - """Returns a `Function` object which initializes this function's variables. + """Returns a `ConcreteFunction` object which initializes this function's + variables. Requires that this function hasn't been accessed yet through either calling it or calling get_concrete_function. Fails if we cannot build an initializer @@ -420,7 +421,8 @@ class PolymorphicFunction(object): **kwargs: keyword arguments to the python callable. Returns: - A `Function` object which initializes the variables of this function. + A `ConcreteFunction` object which initializes the variables of this + function. Raises: RuntimeError: if called after the variables have been initialized. @@ -468,7 +470,8 @@ class PolymorphicFunction(object): # pylint: enable=protected-access def get_concrete_function(self, *args, **kwargs): - """Returns a `Function` object specialized to inputs and execution context. + """Returns a `ConcreteFunction` object specialized to inputs and execution + context. If this `PolymorphicFunction` was created with an `input_signature`, `args` and `kwargs` may be omitted. With an input signature there is only one diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 83cd140158..3bd7df9502 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -271,8 +271,8 @@ class _EagerDefinedFunction(object): def call(self, ctx, args): """Calls this function with `args` as inputs. - Function execution respects device annotations only if the function won't - be compiled with xla. + `ConcreteFunction` execution respects device annotations only if the + function won't be compiled with xla. Args: ctx: a Context object @@ -340,15 +340,15 @@ class _EagerDefinedFunction(object): return outputs -class Function(object): +class ConcreteFunction(object): """Callable object encapsulating a function definition and its gradient. - `Function` is a callable that encapsulates a function definition and + `ConcreteFunction` is a callable that encapsulates a function definition and is differentiable under `tf.GradientTape` objects. """ def __init__(self, func_graph, attrs=None, signature=None): - """Initialize a Function. + """Initialize a `ConcreteFunction`. Args: func_graph: An instance of FuncGraph: the function body to wrap. @@ -384,8 +384,8 @@ class Function(object): *args: Tensors or Variables. Positional arguments are only accepted when they correspond one-to-one with arguments of the traced Python function. **kwargs: Tensors or Variables specified by name. When - `get_concrete_function` was called to create this `Function`, each - Tensor input was given a name, defaulting to the name of the Python + `get_concrete_function` was called to create this `ConcreteFunction`, + each Tensor input was given a name, defaulting to the name of the Python function's argument but possibly overridden by the `name=` argument to `tf.TensorSpec`. These names become the argument names for the concrete function. @@ -394,7 +394,7 @@ class Function(object): The result of applying the TF function on the given Tensors. Raises: - AssertionError: If this `Function` was not created through + AssertionError: If this `ConcreteFunction` was not created through `get_concrete_function`. ValueError: If arguments contains anything other than Tensors or Variables. @@ -478,7 +478,7 @@ class Function(object): tensor_inputs.append( ops.convert_to_tensor(arg, self._signature[i].dtype)) else: - raise ValueError("All inputs to `Function`s must be Tensors; " + raise ValueError("All inputs to `ConcreteFunction`s must be Tensors; " "on invocation of %s, the %d-th input (%s) was not a " "Tensor." % (self._func_graph.name, i, str(arg))) args = tensor_inputs + self._captured_inputs @@ -504,7 +504,8 @@ class Function(object): return self._build_call_outputs(outputs) def _register_gradient(self, name): - """Registers the gradient for the current Function under the given name. + """Registers the gradient for the current `ConcreteFunction` under the given + name. The gradient rewrites an inference call op to a forward call op, but does not modify a pre-existing forward call op. It then computes the gradient @@ -545,7 +546,7 @@ class Function(object): @property def name(self): - """Function name.""" + """`ConcreteFunction` name.""" return self._inference_function.name @property @@ -672,7 +673,7 @@ class Function(object): grad for grad in func_graph_module.flatten(gradients_wrt_inputs) if grad is not None) backwards_graph.structured_outputs = gradients_wrt_inputs - self._backward_graph_function = Function( + self._backward_graph_function = ConcreteFunction( backwards_graph, attrs=backward_function_attr) forward_function_attr = _parse_func_attrs({ @@ -1087,14 +1088,15 @@ class PolymorphicFunction(object): graph_function = self._get_concrete_function_internal_garbage_collected( *args, **kwargs) # We're returning this concrete function to someone, and they may keep a - # reference to the FuncGraph without keeping a reference to the Function - # object. So we won't clean up the reference cycles manually and instead - # will leave them to Python's garbage collector. + # reference to the FuncGraph without keeping a reference to the + # ConcreteFunction object. So we won't clean up the reference cycles + # manually and instead will leave them to Python's garbage collector. graph_function._garbage_collector.release() # pylint: disable=protected-access return graph_function def get_concrete_function(self, *args, **kwargs): - """Returns a `Function` object specialized to inputs and execution context. + """Returns a `ConcreteFunction` object specialized to inputs and execution + context. Args: *args: inputs to specialize on. @@ -1292,7 +1294,7 @@ class PolymorphicFunction(object): self._function_spec.arg_names[:arglen] + [self._function_spec.vararg_name] * (arglen - len(self._function_spec.arg_names))) - graph_function = Function( + graph_function = ConcreteFunction( func_graph_module.func_graph_from_py_func( self._name, self._python_function, @@ -1311,10 +1313,10 @@ class PolymorphicFunction(object): # Save information about non-Tensor arguments with the concrete # function. Used to serialize PolymorphicFunctions. graph_function._python_call_signature = python_call_signature - # Tell the Function to clean up its graph once it goes out of - # scope. Function does not do this in its constructor since it gets used - # in some places (like Keras) where the FuncGraph lives longer than the - # Function. + # Tell the ConcreteFunction to clean up its graph once it goes out of + # scope. ConcreteFunction does not do this in its constructor since it + # gets used in some places (like Keras) where the FuncGraph lives longer + # than the ConcreteFunction. graph_function._garbage_collector = _FunctionGarbageCollector( graph_function.graph) # pylint: enable=protected-access @@ -1335,7 +1337,7 @@ def register(func, *args, **kwargs): **kwargs: input keyword arguments for the Python function. Returns: - a `Function` object specialized to inputs and execution context. + a `ConcreteFunction` object specialized to inputs and execution context. Raises: ValueError: When the input function is not a defun wrapped python function. @@ -1706,7 +1708,7 @@ def defun_with_attributes(func=None, whitelisted attribute name is allowed. Unwhitelisted attribute name or unsupported value will result into ValueError. `func_name` is also one of the whitelisted argument which is a python string, and sets the name for - this `Function` in the graph. + this `ConcreteFunction` in the graph. autograph: same as defun()'s autograph. Returns: @@ -1828,7 +1830,7 @@ class _PolymorphicFunctionGarbageCollector(object): class _FunctionGarbageCollector(object): - """Cleans up reference cycles when a Function goes out of scope.""" + """Cleans up reference cycles when a `ConcreteFunction` goes out of scope.""" def __init__(self, func_graph): self._func_graph = func_graph diff --git a/tensorflow/python/eager/wrap_function.py b/tensorflow/python/eager/wrap_function.py index 6f978feb3c..61b4876830 100644 --- a/tensorflow/python/eager/wrap_function.py +++ b/tensorflow/python/eager/wrap_function.py @@ -60,7 +60,7 @@ class VariableHolder(object): # TODO(allenl): make this checkpointable -class WrappedFunction(function.Function): +class WrappedFunction(function.ConcreteFunction): """Wraps a tf V1 piece of code in a function.""" def __init__(self, fn_graph, variable_holder, attrs=None, signature=None): diff --git a/tensorflow/python/keras/backend.py b/tensorflow/python/keras/backend.py index 6693243a68..2e53ee4ec9 100644 --- a/tensorflow/python/keras/backend.py +++ b/tensorflow/python/keras/backend.py @@ -3138,7 +3138,7 @@ class EagerExecutionFunction(object): # the relevant subgraph? graph.inputs = self.inputs + list(graph.captures.values()) graph.outputs = self.outputs - graph_fn = eager_function.Function(graph) + graph_fn = eager_function.ConcreteFunction(graph) graph_fn._num_positional_args = len(self.inputs) graph_fn._arg_keywords = [] self._graph_fn = graph_fn diff --git a/tensorflow/python/saved_model/function_deserialization.py b/tensorflow/python/saved_model/function_deserialization.py index dc839cf61c..bc10c81fbe 100644 --- a/tensorflow/python/saved_model/function_deserialization.py +++ b/tensorflow/python/saved_model/function_deserialization.py @@ -60,7 +60,7 @@ def recreate_polymorphic_function( Args: saved_polymorphic_function: SavedPolymorphicFunction proto. - functions: map from function name to Function. + functions: map from function name to `ConcreteFunction`. Returns: A PolymorphicFunction. @@ -123,8 +123,8 @@ def load_function_def_library(library): library: FunctionDefLibrary proto message. Returns: - Map of original function names in the library to instances of `Function` - without captured inputs. + Map of original function names in the library to instances of + `ConcreteFunction` without captured inputs. Raises: ValueError: if functions dependencies have a cycle. @@ -142,7 +142,7 @@ def load_function_def_library(library): copy = _fix_fdef(fdef, name_mapping) func_graph = function_def_lib.function_def_to_graph(copy) - func = function_lib.Function(func_graph) + func = function_lib.ConcreteFunction(func_graph) func.add_to_graph(import_graph) name_mapping[fdef.signature.name] = func.name @@ -209,8 +209,8 @@ def _list_function_deps(fdef): def _clean_function_name(name): """Vanity function to keep the function names comprehensible.""" - # Note: each time a function is wrapped into `function_lib.Function` its - # name becomes "__inference__xyz". + # Note: each time a function is wrapped into `function_lib.ConcreteFunction` + # its name becomes "__inference__xyz". match = re.search(r"^__inference_(.*)_\d+$", name) if match: return match.group(1) diff --git a/tensorflow/python/saved_model/save.py b/tensorflow/python/saved_model/save.py index 9db6d03ed0..89fa845b75 100644 --- a/tensorflow/python/saved_model/save.py +++ b/tensorflow/python/saved_model/save.py @@ -169,7 +169,7 @@ def _canonicalize_signatures(signatures): "converted to concrete functions using " "`f.get_concrete_function(...)`.").format(signature_function)) signature_function = signature_function.get_concrete_function() - elif not isinstance(signature_function, defun.Function): + elif not isinstance(signature_function, defun.ConcreteFunction): raise ValueError( ("Expected a TensorFlow function to generate a signature for, but " "got {}. Python functions may be decorated with " -- GitLab From 10b4e7755c9c3c94cc9adc2d0bd0ffe975d718d7 Mon Sep 17 00:00:00 2001 From: Aurelien Geron Date: Thu, 10 Jan 2019 14:08:58 +0800 Subject: [PATCH 0481/2345] Rename PolymorphicFunction to Function --- .../python/mirrored_strategy_multigpu_test.py | 2 +- tensorflow/python/eager/def_function.py | 36 +++++------ tensorflow/python/eager/function.py | 61 +++++++++---------- tensorflow/python/eager/function_test.py | 8 +-- .../python/keras/engine/training_utils.py | 2 +- .../saved_model/function_deserialization.py | 23 ++++--- .../saved_model/function_serialization.py | 29 +++++---- tensorflow/python/saved_model/load.py | 21 ++++--- .../saved_model/nested_structure_coder.py | 8 +-- tensorflow/python/saved_model/save.py | 43 +++++++------ .../python/training/checkpointable/util.py | 3 +- 11 files changed, 116 insertions(+), 120 deletions(-) diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py index 2925b8c88c..b3b4fa85d3 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py @@ -1266,7 +1266,7 @@ class MirroredStrategyDefunTest(test.TestCase): self.evaluate(device_result)) for defun in defuns: - # `PolymorphicFunction`s are specialized to the current device stack, so + # `Function`s are specialized to the current device stack, so # call_for_each has one trace per device. To check that the expected set # of variables was accessed on each trace, we first retrieve each # device-specific graph function. diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index b3f361b92d..6471841780 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -202,13 +202,13 @@ class UnliftedInitializerVariable(resource_variable_ops.ResourceVariable): self._cached_shape_as_list = None -class PolymorphicFunction(object): +class Function(object): """Wrapper class for the graph functions defined for a Python function. See the documentation for `tf.function` for more information on the semantics of defined functions. - PolymorphicFunction is thread-compatible. + `Function` is thread-compatible. """ def __init__(self, @@ -217,7 +217,7 @@ class PolymorphicFunction(object): input_signature=None, autograph=True, experimental_autograph_options=None): - """Initializes a polymorphic function. + """Initializes a `Function`. Args: python_function: the function to be wrapped. @@ -280,10 +280,10 @@ class PolymorphicFunction(object): def _initialize(self, args, kwds, add_initializers_to=None): """Initializes, on the first call. - Creates two polymorphic functions, one that will allow creation of variables + Creates two `Function`s, one that will allow creation of variables and one that won't. - Additionally runs a trace for the polymorphic function that allows creation + Additionally runs a trace for the `Function` that allows creation of variables. Args: @@ -447,7 +447,7 @@ class PolymorphicFunction(object): @property def _cached_input_signatures(self): - """All input signatures used to call this PolymorphicFunction.""" + """All input signatures used to call this `Function`.""" seen = list() # We are using a list so that: # - the returned collection is deterministic, and @@ -473,15 +473,15 @@ class PolymorphicFunction(object): """Returns a `ConcreteFunction` object specialized to inputs and execution context. - If this `PolymorphicFunction` was created with an `input_signature`, `args` - and `kwargs` may be omitted. With an input signature there is only one - concrete function associated with this `PolymorphicFunction`. + If this `Function` was created with an `input_signature`, `args` and + `kwargs` may be omitted. With an input signature there is only one + concrete function associated with this `Function`. If there is no fixed `input_signature` associated with this - `PolymorphicFunction`, positional and keyword arguments to - `get_concrete_function` follow the same rules as input signature - specification, with `tf.TensorSpec` objects describing `tf.Tensor`s which - will be passed to the concrete function. + `Function`, positional and keyword arguments to `get_concrete_function` + follow the same rules as input signature specification, with `tf.TensorSpec` + objects describing `tf.Tensor`s which will be passed to the concrete + function. Each `tf.Tensor` argument to the concrete function must have a unique name, either because it is the only one associated with a named argument of the @@ -566,8 +566,8 @@ class PolymorphicFunction(object): def __get__(self, instance, owner): """Makes it possible to defun instance methods.""" del owner - # `instance` here is the instance that this `PolymorphicFunction` was - # accessed through; e.g., for + # `instance` here is the instance that this `Function` was accessed through + # e.g., for # # class Foo(object): # @@ -576,10 +576,10 @@ class PolymorphicFunction(object): # ... # # foo = Foo() - # foo.bar() # `foo.bar` is a `PolymorphicFunction` instance + # foo.bar() # `foo.bar` is a `Function` instance # # then `instance` will be `foo` (and `owner` will be `Foo`). We create a - # new instance of PolymorphicFunction here to allow different instances each + # new instance of `Function` here to allow different instances each # to create variables once, thereby allowing methods to be decorated with # tf.function. Keeps a cache to avoid retracing the function every time the # descriptor is accessed. @@ -835,7 +835,7 @@ def function(func=None, name = "function" return tf_decorator.make_decorator( inner_function, - PolymorphicFunction( + Function( inner_function, name, input_signature=input_signature, diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 3bd7df9502..26ad52f59d 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -997,16 +997,16 @@ class FunctionSpec(object): return inputs, {} -class PolymorphicFunction(object): +class Function(object): """Wrapper class for the graph functions defined for a Python function. See the documentation for `defun` for more information on the semantics of defined functions. - PolymorphicFunction class is thread-compatible meaning that minimal - usage of defuns (defining and calling) is thread-safe, but if users call other - methods or invoke the base `python_function` themselves, external - synchronization is necessary. + `Function` class is thread-compatible meaning that minimal usage of defuns + (defining and calling) is thread-safe, but if users call other methods or + invoke the base `python_function` themselves, external synchronization is + necessary. """ def __init__(self, @@ -1015,7 +1015,7 @@ class PolymorphicFunction(object): input_signature=None, attributes=None, autograph=True): - """Initializes a polymorphic function. + """Initializes a `Function`. Args: python_function: the function to be wrapped. @@ -1042,14 +1042,14 @@ class PolymorphicFunction(object): self._name = name self._autograph = autograph self._function_cache = collections.OrderedDict() - self._garbage_collector = _PolymorphicFunctionGarbageCollector( + self._garbage_collector = _FunctionGarbageCollector( self._function_cache) self._function_attributes = attributes or {} self._lock = threading.Lock() # _descriptor_cache is a of instance of a class to an instance-specific - # PolymorphicFunction, used to make sure defun-decorated methods create - # different functions for each instance. + # `Function`, used to make sure defun-decorated methods create different + # functions for each instance. self._descriptor_cache = weakref.WeakKeyDictionary() def __call__(self, *args, **kwargs): @@ -1169,8 +1169,8 @@ class PolymorphicFunction(object): def __get__(self, instance, owner): """Makes it possible to defun instance methods.""" del owner - # `instance` here is the instance that this `PolymorphicFunction` was - # accessed through; e.g., for + # `instance` here is the instance that this `Function` was accessed through + # e.g., for # # class Foo(object): # @@ -1179,26 +1179,25 @@ class PolymorphicFunction(object): # ... # # foo = Foo() - # foo.bar() # `foo.bar` is a `PolymorphicFunction` instance + # foo.bar() # `foo.bar` is a `Function` instance # # then `instance` will be `foo` (and `owner` will be `Foo`). We create a - # new instance of PolymorphicFunction here to allow different instances each + # new instance of `Function` here to allow different instances each # to create variables once, thereby allowing methods to be decorated with # defun. Keeps a cache to avoid retracing the function every time the # descriptor is accessed. if instance not in self._descriptor_cache: if instance is None: return self - # If there is no instance-specific polymorphic func in the cache, - # we construct an instance-specific polymorphic function - # that uses a weak reference to the instance (so that the instance will - # be correctly gc'd). + # If there is no instance-specific `Function` in the cache, we construct + # an instance-specific `Function` that uses a weak reference to the + # instance (so that the instance will be correctly gc'd). # And finally add the wrapped function to the description cache self._descriptor_cache[instance] = class_method_to_instance_method( self, instance) - # Return the cached polymorphic function for the instance + # Return the cached `Function` for the instance return self._descriptor_cache[instance] def _cache_key(self, args, kwargs): @@ -1256,8 +1255,8 @@ class PolymorphicFunction(object): def _maybe_define_function(self, args, kwargs): """Gets a function for these inputs, defining it if necessary. - `args` and `kwargs` can be None if this `PolymorphicFunction` was created - with an `input_signature`. + `args` and `kwargs` can be None if this `Function` was created with an + `input_signature`. Args: args: The varargs for the Python function. @@ -1311,13 +1310,13 @@ class PolymorphicFunction(object): _encode_arg_for_serialization(arg) for arg in args) # pylint: disable=protected-access # Save information about non-Tensor arguments with the concrete - # function. Used to serialize PolymorphicFunctions. + # function. Used to serialize `Function`s. graph_function._python_call_signature = python_call_signature # Tell the ConcreteFunction to clean up its graph once it goes out of # scope. ConcreteFunction does not do this in its constructor since it # gets used in some places (like Keras) where the FuncGraph lives longer # than the ConcreteFunction. - graph_function._garbage_collector = _FunctionGarbageCollector( + graph_function._garbage_collector = _ConcreteFunctionGarbageCollector( graph_function.graph) # pylint: enable=protected-access self._function_cache[cache_key] = graph_function @@ -1325,14 +1324,14 @@ class PolymorphicFunction(object): def register(func, *args, **kwargs): - """Register a specialization of a PolymorphicFunction into the graph. + """Register a specialization of a `Function` into the graph. This won't actually call the function with the inputs, and only put the function definition into graph. Register function with different input param will result into multiple version of functions registered in graph. Args: - func: the PolymorphicFunction instance that generated by a @defun + func: the `Function` instance that generated by a @defun *args: input arguments for the Python function. **kwargs: input keyword arguments for the Python function. @@ -1342,7 +1341,7 @@ def register(func, *args, **kwargs): Raises: ValueError: When the input function is not a defun wrapped python function. """ - if not isinstance(func, PolymorphicFunction): + if not isinstance(func, Function): raise ValueError("Only defun function is allowed to be registered. " "Got type: %s" % type(func)) concrete_func = func.get_concrete_function(*args, **kwargs) @@ -1729,7 +1728,7 @@ def defun_with_attributes(func=None, name = "function" return tf_decorator.make_decorator( function, - PolymorphicFunction( + Function( function, name, input_signature=input_signature, @@ -1761,7 +1760,7 @@ class _WeakrefSelf(object): def class_method_to_instance_method(original_function, instance): - """Constructs a new PolymorphicFunction with `self` bound.""" + """Constructs a new `Function` with `self` bound.""" weak_instance = weakref.ref(instance) # Note: while we could bind to a weakref proxy instead, that causes the @@ -1769,8 +1768,8 @@ def class_method_to_instance_method(original_function, instance): bound_method = types_lib.MethodType(original_function.python_function, _WeakrefSelf(weak_instance)) - # original_function is expected to be of one of the two PolymorphicFunction - # types (defined either in function.py or def_function.py). + # original_function is expected to be of one of the two `Function` types + # (defined either in function.py or def_function.py). assert hasattr(original_function, "_name") assert hasattr(original_function, "_autograph") assert hasattr(original_function, "_input_signature") @@ -1812,7 +1811,7 @@ def class_method_to_instance_method(original_function, instance): return wrapped_instance_func -class _PolymorphicFunctionGarbageCollector(object): +class _FunctionGarbageCollector(object): """Cleans up cycles when a defun goes out of scope.""" def __init__(self, cache): @@ -1829,7 +1828,7 @@ class _PolymorphicFunctionGarbageCollector(object): pass -class _FunctionGarbageCollector(object): +class _ConcreteFunctionGarbageCollector(object): """Cleans up reference cycles when a `ConcreteFunction` goes out of scope.""" def __init__(self, func_graph): diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index 1966b259bf..3bb4f2452c 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -966,8 +966,8 @@ class FunctionTest(test.TestCase, parameterized.TestCase): self.assertAllEqual([[3.0]], self.evaluate(y)) # Break the reference cycle between the MiniModel and the defun: - # MiniModel --(through its `call` method)--> PolymorphicFunction - # PolymorphicFunction --(instancemethod on MiniModel)--> MiniModel + # `MiniModel` --(through its `call` method)--> `Function` + # `Function` --(instancemethod on `MiniModel`)--> `MiniModel` del model.call # Note: The ConfigProto below unfortunately only configures graph @@ -1535,7 +1535,7 @@ class FunctionTest(test.TestCase, parameterized.TestCase): t = constant_op.constant([[1.0, 2.0], [3.0, 4.0]]) add(t, t) - def testRegisterPolymorphicFunction(self): + def testRegisterFunction(self): @function.defun def add(x, y): return math_ops.add(x, y) @@ -1818,7 +1818,7 @@ class FunctionTest(test.TestCase, parameterized.TestCase): self.assertAllEqual(instance_call_one, instance_call_two) self.assertAllEqual(instance_call_one, class_call) - def testDecoratedMethodUniquePolymorphicFuncPerInstance(self): + def testDecoratedMethodUniqueFunctionPerInstance(self): m = DefunnedMiniModel() n = DefunnedMiniModel() diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py index 949f1e400d..6072d09428 100644 --- a/tensorflow/python/keras/engine/training_utils.py +++ b/tensorflow/python/keras/engine/training_utils.py @@ -1402,7 +1402,7 @@ def trace_model_call(model, input_signature=None): ValueError: if input signature cannot be inferred from the model. """ if input_signature is None: - if isinstance(model.call, def_function.PolymorphicFunction): + if isinstance(model.call, def_function.Function): input_signature = model.call.input_signature if input_signature is None: diff --git a/tensorflow/python/saved_model/function_deserialization.py b/tensorflow/python/saved_model/function_deserialization.py index bc10c81fbe..f1c43cb7a4 100644 --- a/tensorflow/python/saved_model/function_deserialization.py +++ b/tensorflow/python/saved_model/function_deserialization.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tools for deserializing PolymorphicFunctions.""" +"""Tools for deserializing `Function`s.""" from __future__ import absolute_import from __future__ import division @@ -54,25 +54,24 @@ def _inputs_compatible(args, stored_inputs): return True -def recreate_polymorphic_function( - saved_polymorphic_function, functions): - """Creates a PolymorphicFunction from a SavedPolymorphicFunction. +def recreate_function(saved_function, concrete_functions): + """Creates a `Function` from a `SavedPolymorphicFunction`. Args: - saved_polymorphic_function: SavedPolymorphicFunction proto. - functions: map from function name to `ConcreteFunction`. + saved_function: `SavedPolymorphicFunction` proto. + concrete_functions: map from function name to `ConcreteFunction`. Returns: - A PolymorphicFunction. + A `Function`. """ - # TODO(andresp): Construct a PolymorphicFunction with the cache populated - # instead of creating a new PolymorphicFunction backed by a Python layer to + # TODO(andresp): Construct a `Function` with the cache populated + # instead of creating a new `Function` backed by a Python layer to # glue things together. Current approach is nesting functions deeper for each # serialization cycle. coder = nested_structure_coder.StructureCoder() function_spec_tuple = coder.decode_proto( - saved_polymorphic_function.function_spec_tuple) + saved_function.function_spec_tuple) function_spec = function_lib.FunctionSpec.from_tuple(function_spec_tuple) # TODO(mdan): We may enable autograph once exceptions are supported. @@ -81,8 +80,8 @@ def recreate_polymorphic_function( """Calls a restored function.""" # TODO(allenl): Functions saved with input_signatures should revive with # input_signatures. - for monomorphic_function in saved_polymorphic_function.monomorphic_function: - function_obj = functions[monomorphic_function.concrete_function] + for monomorphic_function in saved_function.monomorphic_function: + function_obj = concrete_functions[monomorphic_function.concrete_function] canonicalized_original_inputs = coder.decode_proto( monomorphic_function.canonicalized_input) diff --git a/tensorflow/python/saved_model/function_serialization.py b/tensorflow/python/saved_model/function_serialization.py index 014a4ef04c..b645a7c964 100644 --- a/tensorflow/python/saved_model/function_serialization.py +++ b/tensorflow/python/saved_model/function_serialization.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tools for serializing PolymorphicFunctions.""" +"""Tools for serializing `Function`s.""" from __future__ import absolute_import from __future__ import division @@ -24,15 +24,14 @@ from tensorflow.python.saved_model import nested_structure_coder from tensorflow.python.saved_model import saved_object_graph_pb2 -def serialize_polymorphic_function(polymorphic_function, node_ids): - """Build a SavedPolymorphicProto.""" +def serialize_function(function, node_ids): + """Build a SavedPolymorphicFunction proto.""" coder = nested_structure_coder.StructureCoder() proto = saved_object_graph_pb2.SavedPolymorphicFunction() proto.function_spec_tuple.CopyFrom( - coder.encode_structure(polymorphic_function.function_spec.as_tuple())) # pylint: disable=protected-access - for signature, concrete_function in list_all_concrete_functions( - polymorphic_function): + coder.encode_structure(function.function_spec.as_tuple())) # pylint: disable=protected-access + for signature, concrete_function in list_all_concrete_functions(function): bound_inputs = [] try: for capture in concrete_function.captured_inputs: @@ -52,23 +51,23 @@ def serialize_polymorphic_function(polymorphic_function, node_ids): return proto -def list_all_concrete_functions(polymorphic_function): - """Given a polymorphic function, returns all of its concrete functions. +def list_all_concrete_functions(function): + """Given a `Function`, returns all of its concrete functions. Args: - polymorphic_function: Instance of `PolymorphicFunction`. + function: Instance of `Function`. Returns: - A list of tuples in the form (signature, concrete_function), where concrete - function is an instance of `Function`. + A list of tuples in the form (signature, concrete_function), where + `concrete_function` is an instance of `ConcreteFunction`. """ - input_signature = polymorphic_function._input_signature # pylint: disable=protected-access + input_signature = function._input_signature # pylint: disable=protected-access if input_signature is not None: - polymorphic_function.get_concrete_function() + function.get_concrete_function() concrete_functions = [] - for signature in polymorphic_function._cached_input_signatures: # pylint: disable=protected-access + for signature in function._cached_input_signatures: # pylint: disable=protected-access if any(isinstance(arg, defun_lib.UnknownArgument) for arg in signature): continue - concrete_function = polymorphic_function.get_concrete_function(*signature) + concrete_function = function.get_concrete_function(*signature) concrete_functions.append((signature, concrete_function)) return concrete_functions diff --git a/tensorflow/python/saved_model/load.py b/tensorflow/python/saved_model/load.py index bbb1485612..ae71a228ea 100644 --- a/tensorflow/python/saved_model/load.py +++ b/tensorflow/python/saved_model/load.py @@ -42,15 +42,16 @@ class _Loader(object): self._asset_file_def = meta_graph.asset_file_def self._proto = object_graph_proto self._export_dir = export_dir - self._functions = function_deserialization.load_function_def_library( - meta_graph.graph_def.library) + self._concrete_functions = ( + function_deserialization.load_function_def_library( + meta_graph.graph_def.library)) self._load_all() self._bind_function_captures() self._restore_checkpoint() def _bind_function_captures(self): """Setup captured tensors in restored concrete functions.""" - seen_functions = set() + seen_concrete_functions = set() for object_proto in self._proto.nodes: if object_proto.WhichOneof("kind") == "function": for monomorphic_function in object_proto.function.monomorphic_function: @@ -63,18 +64,18 @@ class _Loader(object): for node_id in monomorphic_function.bound_inputs if self._proto.nodes[node_id].WhichOneof("kind") == "variable" ] - if name in seen_functions: - if self._functions[name]._captured_inputs != bound_inputs: # pylint: disable=protected-access + if name in seen_concrete_functions: + if self._concrete_functions[name]._captured_inputs != bound_inputs: # pylint: disable=protected-access raise NotImplementedError( "Function %s is used more than once with different " "captured inputs." % name) else: - seen_functions.add(name) + seen_concrete_functions.add(name) # TODO(andresp): This is only injecting the captured inputs into the # concrete function, note that we did not modify the FuncGraph # itself. - self._functions[name]._captured_inputs = bound_inputs # pylint: disable=protected-access - self._functions[name]._func_graph.variables = bound_variables # pylint: disable=protected-access + self._concrete_functions[name]._captured_inputs = bound_inputs # pylint: disable=protected-access + self._concrete_functions[name]._func_graph.variables = bound_variables # pylint: disable=protected-access def _get_tensor_from_node(self, node_id): obj = self._nodes[node_id] @@ -137,8 +138,8 @@ class _Loader(object): return tracking.TrackableAsset(filename) def _recreate_function(self, proto): - return function_deserialization.recreate_polymorphic_function( - proto, self._functions) + return function_deserialization.recreate_function( + proto, self._concrete_functions) def _recreate_variable(self, proto): # TODO(andresp): Can we use the checkpointed value as initializer? diff --git a/tensorflow/python/saved_model/nested_structure_coder.py b/tensorflow/python/saved_model/nested_structure_coder.py index 410ebda5c1..31ee239f13 100644 --- a/tensorflow/python/saved_model/nested_structure_coder.py +++ b/tensorflow/python/saved_model/nested_structure_coder.py @@ -14,14 +14,14 @@ # ============================================================================== """Module that encodes (decodes) nested structures into (from) protos. -The intended use is to serialize everything needed to restore a -PolymorphicFunction that was saved into a SavedModel. This may include concrete -function inputs and outputs, signatures, function specs, etc. +The intended use is to serialize everything needed to restore a `Function` that +was saved into a SavedModel. This may include concrete function inputs and +outputs, signatures, function specs, etc. Example use: coder = nested_structure_coder.StructureCoder() # Encode into proto. -signature_proto = coder.encode_structure(polymorphic_function.input_signature) +signature_proto = coder.encode_structure(function.input_signature) # Decode into a Python object. restored_signature = coder.decode_proto(signature_proto) """ diff --git a/tensorflow/python/saved_model/save.py b/tensorflow/python/saved_model/save.py index 89fa845b75..31f6aad4cf 100644 --- a/tensorflow/python/saved_model/save.py +++ b/tensorflow/python/saved_model/save.py @@ -68,17 +68,17 @@ class _SaveableView(object): self.nodes = checkpointable_objects self.node_ids = node_ids self.slot_variables = slot_variables - self.polymorphic_functions = util.ObjectIdentityDictionary() + self.functions = util.ObjectIdentityDictionary() - # Also add polymorphic functions as nodes. + # Also add `Function`s as nodes. for obj in self.nodes: - self.polymorphic_functions[obj] = self._list_polymorphic_functions(obj) - for function in self.polymorphic_functions[obj].values(): + self.functions[obj] = self._list_functions(obj) + for function in self.functions[obj].values(): if function not in self.node_ids: self.node_ids[function] = len(self.nodes) self.nodes.append(function) # Force listing the concrete functions for the side effects: - # - populate the cache for polymorphic functions that have an + # - populate the cache for `Function`s that have an # input_signature and have not been called. # - force side effects of creation of concrete functions, e.g. create # variables on first run. @@ -94,20 +94,20 @@ class _SaveableView(object): assert self.node_ids[node] == node_id object_proto = proto.nodes.add() object_proto.slot_variables.extend(self.slot_variables.get(node, ())) - if isinstance(node, def_function.PolymorphicFunction): + if isinstance(node, def_function.Function): continue for child in node._checkpoint_dependencies: # pylint: disable=protected-access child_proto = object_proto.children.add() child_proto.node_id = self.node_ids[child.ref] child_proto.local_name = child.name - for local_name, ref_function in self.polymorphic_functions[node].items(): + for local_name, ref_function in self.functions[node].items(): child_proto = object_proto.children.add() child_proto.node_id = self.node_ids[ref_function] child_proto.local_name = local_name - def _list_polymorphic_functions(self, checkpointable_object): - """Return a dict of polymorphic functions of a checkpointable.""" - polymorphic_functions = dict() + def _list_functions(self, checkpointable_object): + """Return a dict of `Function`s of a checkpointable.""" + functions = dict() for attribute_name in dir(checkpointable_object): try: attribute_value = getattr(checkpointable_object, attribute_name, None) @@ -115,16 +115,16 @@ class _SaveableView(object): # We really don't want to throw an exception just because some object's # attribute accessor is broken. attribute_value = None - if isinstance(attribute_value, def_function.PolymorphicFunction): - polymorphic_functions[attribute_name] = attribute_value - return polymorphic_functions + if isinstance(attribute_value, def_function.Function): + functions[attribute_name] = attribute_value + return functions def _find_function_to_export(saveable_view): """Iterate over `root`'s attributes, finding traced functions.""" exported_function = None previous_attribute_name = None - functions = saveable_view.polymorphic_functions[saveable_view.root] + functions = saveable_view.functions[saveable_view.root] for name, value in sorted(functions.items()): if exported_function is not None: raise ValueError( @@ -158,8 +158,7 @@ def _canonicalize_signatures(signatures): signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: signatures} concrete_signatures = {} for serving_key, signature_function in signatures.items(): - if isinstance(signature_function, (defun.PolymorphicFunction, - def_function.PolymorphicFunction)): + if isinstance(signature_function, (defun.Function, def_function.Function)): input_signature = signature_function._input_signature # pylint: disable=protected-access if input_signature is None: raise ValueError( @@ -547,12 +546,12 @@ def _fill_meta_graph_def(meta_graph_def, saveable_view, signature_functions, # the exported graph (thus the `to_graph` argument). saver = object_saver.freeze(object_map=object_map, to_graph=exported_graph) - # We must instantiate and list all concrete functions of polymorphic functions - # while in eager mode so they end up added to the graph and can later be used - # by the object based saved model. + # We must instantiate and list all concrete functions of `Function`s while in + # eager mode so they end up added to the graph and can later be used by the + # object based saved model. concrete_functions = [] for obj in accessible_objects: - for function in saveable_view.polymorphic_functions[obj].values(): + for function in saveable_view.functions[obj].values(): concrete_functions.extend( function_serialization.list_all_concrete_functions(function)) @@ -613,9 +612,9 @@ def _write_object_proto(obj, proto, asset_file_def_index, node_ids): proto.variable.trainable = obj.trainable proto.variable.dtype = obj.dtype.as_datatype_enum proto.variable.shape.CopyFrom(obj.shape.as_proto()) - elif isinstance(obj, def_function.PolymorphicFunction): + elif isinstance(obj, def_function.Function): proto.function.CopyFrom( - function_serialization.serialize_polymorphic_function(obj, node_ids)) + function_serialization.serialize_function(obj, node_ids)) else: proto.user_object.SetInParent() diff --git a/tensorflow/python/training/checkpointable/util.py b/tensorflow/python/training/checkpointable/util.py index a45263f5c6..4a80b25a98 100644 --- a/tensorflow/python/training/checkpointable/util.py +++ b/tensorflow/python/training/checkpointable/util.py @@ -1748,8 +1748,7 @@ class Checkpoint(tracking.Checkpointable): """ super(Checkpoint, self).__init__() for k, v in sorted(kwargs.items(), key=lambda item: item[0]): - if not isinstance(v, (base.CheckpointableBase, - def_function.PolymorphicFunction)): + if not isinstance(v, (base.CheckpointableBase, def_function.Function)): raise ValueError( ("`Checkpoint` was expecting a checkpointable object (an object " "derived from `CheckpointableBase`), got %s. If you believe this " -- GitLab From ebb3429856441149e41388dfbea59496f8dbf17b Mon Sep 17 00:00:00 2001 From: Penporn Koanantakool Date: Wed, 9 Jan 2019 22:30:09 -0800 Subject: [PATCH 0482/2345] Remove a TODO about moving choose() out of TensorContraction.h since it is now in unsupported/Eigen/CXX11/scr/Tensor/TensorMeta.h. PiperOrigin-RevId: 228648231 --- tensorflow/core/kernels/eigen_spatial_convolutions.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/tensorflow/core/kernels/eigen_spatial_convolutions.h b/tensorflow/core/kernels/eigen_spatial_convolutions.h index 86d8c98ee6..8b19813940 100644 --- a/tensorflow/core/kernels/eigen_spatial_convolutions.h +++ b/tensorflow/core/kernels/eigen_spatial_convolutions.h @@ -1683,8 +1683,6 @@ EIGEN_DEVICE_FUNC kernel_dims[0] = kernelChannels * kernelRows * kernelCols; kernel_dims[1] = kernelFilters; } - // TODO(yangke): choose() is defined in TensorContraction.h -- consider - // moving it to somewhere more "common". return choose( Cond::Layout == ColMajor>(), kernel.reshape(kernel_dims) -- GitLab From 6d3cbd2bcc165f8de064643719991db9753c8dca Mon Sep 17 00:00:00 2001 From: Smit Hinsu Date: Wed, 9 Jan 2019 23:57:12 -0800 Subject: [PATCH 0483/2345] Update TensorRT SegmentGraph function to return node pointers This is in preparation to share the graph segmentation code between XLA and TensorRT. TESTED with existing unit tests PiperOrigin-RevId: 228654815 --- .../contrib/tensorrt/convert/convert_graph.cc | 51 ++++++++++--------- .../contrib/tensorrt/convert/convert_nodes.cc | 15 +++--- .../contrib/tensorrt/convert/convert_nodes.h | 3 +- .../contrib/tensorrt/segment/segment.cc | 17 +++---- tensorflow/contrib/tensorrt/segment/segment.h | 8 +-- .../contrib/tensorrt/segment/segment_test.cc | 5 +- 6 files changed, 53 insertions(+), 46 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index bf2de94e04..eef647473a 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -334,13 +334,12 @@ struct EdgePtrCompare { tensorflow::Status GetEngineInfo( const tensorflow::Graph* g, const tensorflow::grappler::GraphProperties& graph_properties, - const std::set& segment_nodes, + const std::set& segment_nodes, const std::unordered_map& node_map, const std::vector& reverse_topo_order, EngineInfo* info) { - std::vector subgraph_node_ids; // Topologically sorted node ids. - std::set subgraph_node_names = segment_nodes; - std::set added_const_node_ids; // Used to prevent double insertion. + std::vector subgraph_nodes; // Topologically sorted nodes. + std::set added_const_nodes; // Used to prevent double insertion. std::set segment_devices; // Map from src_node_name+port to the unique port numbers of the TRT op, where @@ -352,9 +351,8 @@ tensorflow::Status GetEngineInfo( std::unordered_map input_to_engine_port, output_to_engine_port; for (auto it = reverse_topo_order.rbegin(); it != reverse_topo_order.rend(); ++it) { - const auto& node_name = (*it)->name(); - if (segment_nodes.count(node_name) == 0) continue; - auto node = *it; + const Node* node = *it; + if (segment_nodes.count(node) == 0) continue; auto node_device = node->requested_device(); if (!node_device.empty()) { segment_devices.insert(node_device); @@ -366,8 +364,11 @@ tensorflow::Status GetEngineInfo( << " neither have requested device nor assigned device"; } } + subgraph_nodes.push_back(node); + const int node_id = node->id(); - subgraph_node_ids.push_back(node_id); + const string& node_name = node->name(); + // 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(), @@ -375,7 +376,7 @@ tensorflow::Status GetEngineInfo( std::sort(in_edges.begin(), in_edges.end(), EdgePtrCompare()); for (const auto edge : in_edges) { auto input_node = edge->src(); - if (input_node->IsSource() || segment_nodes.count(input_node->name())) { + if (input_node->IsSource() || segment_nodes.count(input_node)) { continue; } if (edge->IsControlEdge()) { @@ -392,12 +393,11 @@ tensorflow::Status GetEngineInfo( // // Note that the segmenter already ensure that the constant data input // is valid and suppported by the engine. - if (!added_const_node_ids.insert(input_node->id()).second) { + if (!added_const_nodes.insert(input_node).second) { // Already added before. continue; } VLOG(1) << "Adding const node " << input_node->name(); - QCHECK(subgraph_node_names.insert(input_node->name()).second); // Since we already add (duplicate) the const input node to the segment // graphdef, it's now not a data dependency any more, but to make the // dependency correct we still add a control dependency. @@ -428,7 +428,7 @@ tensorflow::Status GetEngineInfo( std::sort(out_edges.begin(), out_edges.end(), EdgePtrCompare()); for (const auto edge : out_edges) { auto output_node = edge->dst(); - if (output_node->IsSink() || segment_nodes.count(output_node->name())) { + if (output_node->IsSink() || segment_nodes.count(output_node)) { continue; } if (edge->IsControlEdge()) { @@ -456,12 +456,11 @@ tensorflow::Status GetEngineInfo( } // For each segment node in topological order. // Construct the const nodes first. - subgraph_node_ids.insert(subgraph_node_ids.begin(), - added_const_node_ids.begin(), - added_const_node_ids.end()); + subgraph_nodes.insert(subgraph_nodes.begin(), added_const_nodes.begin(), + added_const_nodes.end()); TF_RETURN_IF_ERROR(ConvertSegmentToGraphDef( - g, graph_properties, subgraph_node_names, subgraph_node_ids, - &info->connections, &info->segment_graph_def, &info->engine_name)); + g, graph_properties, subgraph_nodes, &info->connections, + &info->segment_graph_def, &info->engine_name)); // TODO(sami): This should not happen once segmenter is updated. if (segment_devices.size() == 1) { info->device = *segment_devices.begin(); @@ -1033,27 +1032,31 @@ tensorflow::Status ConvertAfterShapes(ConversionParams& params) { cudaSetDevice(cuda_device_id); auto status = CreateTRTNode(engine_segments, i, params.max_batch_size, &graph, alloc.get(), &engine_nodes); - // If status is ok, we successfully added the node to the graph and can - // remove segment ops. Otherwise graph is not modified. + string msg = StrCat("TensorRT node ", engine.engine_name, " added for segment ", i, " consisting of ", converted_segments.at(i).first.size(), " nodes"); if (status.ok()) { LOG(INFO) << msg << " succeeded."; - for (auto node_name : converted_segments.at(i).first) { - graph.RemoveNode(node_map.at(node_name)); - } } else { // Graph is not modified. LOG(WARNING) << msg << " failed: " << status << ". Fallback to TF..."; } if (VLOG_IS_ON(1)) { msg = "Segment consists of nodes: "; - for (const string& node_name : converted_segments.at(i).first) { - StrAppend(&msg, node_name, ", "); + for (const Node* node : converted_segments.at(i).first) { + StrAppend(&msg, node->name(), ", "); } VLOG(1) << msg; } + + // If status is ok, we successfully added the node to the graph and can + // remove segment ops. Otherwise graph is not modified. + if (status.ok()) { + for (const Node* node : converted_segments.at(i).first) { + graph.RemoveNode(const_cast(node)); + } + } } cudaSetDevice(old_cuda_device); graph.ToGraphDef(params.output_graph_def); diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 179d631a3e..7b0c4b446d 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -3780,8 +3780,7 @@ tensorflow::Status ConvertGraphDefToEngine( tensorflow::Status ConvertSegmentToGraphDef( const tensorflow::Graph* graph, const tensorflow::grappler::GraphProperties& graph_properties, - const std::set& subgraph_node_names, - const std::vector& subgraph_node_ids, // In topological order + const std::vector& subgraph_nodes, // In topological order std::vector* connections, tensorflow::GraphDef* segment_def, string* common_scope) { std::set marker_nodes; @@ -3855,11 +3854,10 @@ tensorflow::Status ConvertSegmentToGraphDef( std::unordered_map old_to_new_id_map; // Copy internal nodes to new graphdef - string local_scope = graph->FindNodeId(*subgraph_node_ids.begin())->name(); - for (const auto node_id : subgraph_node_ids) { - const auto node = graph->FindNodeId(node_id); + string local_scope = subgraph_nodes.front()->name(); + for (const Node* node : subgraph_nodes) { local_scope = GetCommonNameScope(local_scope, node->name()); - old_to_new_id_map[node_id] = segment_def->node_size(); + old_to_new_id_map[node->id()] = segment_def->node_size(); auto snode = segment_def->add_node(); snode->CopyFrom(node->def()); VLOG(2) << "Copying " << snode->name() << " to subgraph"; @@ -3877,6 +3875,11 @@ tensorflow::Status ConvertSegmentToGraphDef( << placeholder_name; snode->set_input(connection.inside_port, placeholder_name); } + std::set subgraph_node_names; + for (const Node* node : subgraph_nodes) { + subgraph_node_names.insert(node->name()); + } + // Remove control inputs that are not inside the segment. for (int i = 0; i < segment_def->node_size(); ++i) { auto snode = segment_def->mutable_node(i); diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.h b/tensorflow/contrib/tensorrt/convert/convert_nodes.h index 54e19b7395..8f2271ee3f 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.h +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.h @@ -128,8 +128,7 @@ struct EngineInfo { tensorflow::Status ConvertSegmentToGraphDef( const tensorflow::Graph* graph, const tensorflow::grappler::GraphProperties& graph_properties, - const std::set& subgraph_node_names, - const std::vector& subgraph_node_ids, + const std::vector& subgraph_nodes, std::vector* connections, tensorflow::GraphDef* segment_def, string* common_scope); diff --git a/tensorflow/contrib/tensorrt/segment/segment.cc b/tensorflow/contrib/tensorrt/segment/segment.cc index 084a96e0fa..ecaffa3023 100644 --- a/tensorflow/contrib/tensorrt/segment/segment.cc +++ b/tensorflow/contrib/tensorrt/segment/segment.cc @@ -673,10 +673,11 @@ tensorflow::Status SegmentGraph( // --------------------------------- Step 3 --------------------------------- // Convert the segments into the expected return format for (const auto& itr : sg_map) { - const std::set& segment_nodes = - itr.second; + const string& segment_root = itr.first; + // Return format does not require set comparator. + std::set segment_nodes(itr.second.begin(), itr.second.end()); if (VLOG_IS_ON(1)) { - string s = "parent=" + itr.first + ":"; + string s = "parent=" + segment_root + ":"; for (auto node : segment_nodes) s += " " + node->name(); VLOG(1) << "Segment " << segments->size() << ": " << s; } @@ -689,12 +690,10 @@ tensorflow::Status SegmentGraph( } // TODO(sami): Make segmenter placement aware once trtscopes are in place - std::set segment_node_names; - for (auto node : itr.second) segment_node_names.insert(node->name()); - const auto& dev_itr = device_maps.find(itr.first); + const auto& dev_itr = device_maps.find(segment_root); if (dev_itr == device_maps.end() || dev_itr->second.empty()) { VLOG(1) << "No device assigned to segment " << segments->size(); - segments->emplace_back(std::make_pair(segment_node_names, string())); + segments->emplace_back(std::make_pair(segment_nodes, string())); } else if (dev_itr->second.size() > 1) { string s("Segment "); StrAppend(&s, segments->size(), " has multiple devices attached: "); @@ -703,10 +702,10 @@ tensorflow::Status SegmentGraph( } LOG(WARNING) << s << " choosing " << *(dev_itr->second.begin()); segments->emplace_back( - std::make_pair(segment_node_names, *(dev_itr->second.begin()))); + std::make_pair(segment_nodes, *(dev_itr->second.begin()))); } else { segments->emplace_back( - std::make_pair(segment_node_names, *(dev_itr->second.begin()))); + std::make_pair(segment_nodes, *(dev_itr->second.begin()))); } } if (VLOG_IS_ON(1)) { diff --git a/tensorflow/contrib/tensorrt/segment/segment.h b/tensorflow/contrib/tensorrt/segment/segment.h index b9693aad1b..6cc92cdb5d 100644 --- a/tensorflow/contrib/tensorrt/segment/segment.h +++ b/tensorflow/contrib/tensorrt/segment/segment.h @@ -29,10 +29,10 @@ namespace tensorflow { namespace tensorrt { namespace segment { -// Vector of segments, each entry contains a set of node names and a device name -// in the segment. -// TODO(aaroey): use node pointer instead of node name. -using SegmentNodesVector = std::vector, string>>; +// Vector of segments, each entry contains a set of node pointers and a device +// name in the segment. +using SegmentNodesVector = + std::vector, string>>; struct SegmentOptions { // Segment must contain at least this many nodes. diff --git a/tensorflow/contrib/tensorrt/segment/segment_test.cc b/tensorflow/contrib/tensorrt/segment/segment_test.cc index 4805ef9c61..4ac02327ae 100644 --- a/tensorflow/contrib/tensorrt/segment/segment_test.cc +++ b/tensorflow/contrib/tensorrt/segment/segment_test.cc @@ -75,7 +75,10 @@ class SegmentTest : public ::testing::Test { const std::vector>& expected_segments) { EXPECT_EQ(expected_segments.size(), segments.size()); for (int i = 0; i < segments.size(); ++i) { - const auto& segment_node_names = segments[i].first; + std::set segment_node_names; + for (const Node* node : segments[i].first) { + segment_node_names.insert(node->name()); + } const auto& expected = expected_segments[i]; for (const auto& name : expected) { EXPECT_TRUE(segment_node_names.count(name)) -- GitLab From 1e104bb923494b667a0ac8c28dac0d8fe516239c Mon Sep 17 00:00:00 2001 From: Grzegorz Pawelczak Date: Thu, 10 Jan 2019 08:36:05 +0000 Subject: [PATCH 0484/2345] Fix nit in build file --- tensorflow/compiler/xla/service/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index 7be180c624..4b691c7340 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -3484,9 +3484,9 @@ tf_cc_test( srcs = ["sort_simplifier_test.cc"], deps = [ ":hlo_matchers", - ":sort_simplifier", ":pattern_matcher", ":pattern_matcher_gmock", + ":sort_simplifier", "//tensorflow/compiler/xla:test", "//tensorflow/compiler/xla/service:hlo_parser", "//tensorflow/compiler/xla/tests:hlo_test_base", -- GitLab From 439d4ec9568a259f665efd878791530a6f5ca8f2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 10 Jan 2019 01:02:17 -0800 Subject: [PATCH 0485/2345] compat: Update forward compatibility horizon to 2019-01-10 PiperOrigin-RevId: 228661930 --- 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 dfee972cab..638bd445a6 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, 1, 9) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 10) @tf_export("compat.forward_compatible") -- GitLab From 2f7e56a65104aeca0a7fcc98a7b10fb2cff9b1a3 Mon Sep 17 00:00:00 2001 From: Tamara Norman Date: Thu, 10 Jan 2019 02:46:36 -0800 Subject: [PATCH 0486/2345] Remove partitioned_info as argument to initializer in Keras make_variable to work with the v2 initializers PiperOrigin-RevId: 228675005 --- tensorflow/python/keras/engine/base_layer_utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/keras/engine/base_layer_utils.py b/tensorflow/python/keras/engine/base_layer_utils.py index 60d0cf3391..f1b1f42c22 100644 --- a/tensorflow/python/keras/engine/base_layer_utils.py +++ b/tensorflow/python/keras/engine/base_layer_utils.py @@ -25,6 +25,7 @@ from tensorflow.python.framework import ops from tensorflow.python.keras import backend from tensorflow.python.ops import array_ops from tensorflow.python.ops import init_ops +from tensorflow.python.ops import init_ops_v2 from tensorflow.python.ops import variables as tf_variables from tensorflow.python.util import nest @@ -55,7 +56,6 @@ def make_variable(name, shape=None, dtype=dtypes.float32, initializer=None, - partition_info=None, trainable=None, caching_device=None, validate_shape=True, @@ -76,14 +76,12 @@ def make_variable(name, rid of this temporary solution. TODO(fchollet): remove this method when no longer needed. - TODO(fchollet): handle `partitioner` argument. Arguments: name: Variable name. shape: Variable shape. dtype: The type of the variable. Defaults to `self.dtype` or `float32`. initializer: Initializer instance (callable). - partition_info: Not handled at this time. trainable: Whether the variable should be part of the layer's "trainable_variables" (e.g. variables, biases) or "non_trainable_variables" (e.g. BatchNorm mean, stddev). @@ -123,8 +121,9 @@ def make_variable(name, # Instantiate initializer if provided initializer is a type object. if isinstance(initializer, type(init_ops.Initializer)): initializer = initializer(dtype=dtype) - init_val = lambda: initializer( # pylint: disable=g-long-lambda - shape, dtype=dtype, partition_info=partition_info) + elif isinstance(initializer, type(init_ops_v2.Initializer)): + initializer = initializer() + init_val = lambda: initializer(shape, dtype=dtype) variable_dtype = dtype.base_dtype if use_resource is None: use_resource = True -- GitLab From ef088e54734e0412bc47e4f33d1df7c0672fdd87 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 10 Jan 2019 04:21:44 -0800 Subject: [PATCH 0487/2345] Add a test for soft matching of inputs. PiperOrigin-RevId: 228686125 --- tensorflow/python/saved_model/load_test.py | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tensorflow/python/saved_model/load_test.py b/tensorflow/python/saved_model/load_test.py index f7020e4a45..8b34414d25 100644 --- a/tensorflow/python/saved_model/load_test.py +++ b/tensorflow/python/saved_model/load_test.py @@ -29,6 +29,7 @@ from tensorflow.python.framework import dtypes from tensorflow.python.framework import tensor_spec from tensorflow.python.lib.io import file_io from tensorflow.python.ops import variables +from tensorflow.python.saved_model import function_serialization from tensorflow.python.saved_model import load from tensorflow.python.saved_model import save from tensorflow.python.training.checkpointable import tracking @@ -312,6 +313,36 @@ class LoadTest(test.TestCase): x = constant_op.constant(1.0) self.assertAllEqual(imported(x).numpy(), 3.0) + def test_soft_matching(self): + + @def_function.function( + input_signature=[tensor_spec.TensorSpec([None], dtypes.int32)]) + def func(x): + return 2 * x + + root = tracking.Checkpointable() + root.f = func + + self.assertAllEqual([2], root.f(constant_op.constant([1])).numpy()) + self.assertAllEqual([2, 4], root.f(constant_op.constant([1, 2])).numpy()) + + self.assertEqual( + 1, len(function_serialization.list_all_concrete_functions(root.f))) + + imported = self.cycle(root) + + with self.assertRaises(AssertionError): + # We cannot call the function with a constant of shape (). + self.assertEqual(7, imported.f(constant_op.constant(2)).numpy()) + + # TODO(vbardiovsky): When classes are revived with input_signatures, we + # should also check that the calls below are not generating any more + # concrete functions. + self.assertAllEqual([2, 4, 6, 8], + imported.f(constant_op.constant([1, 2, 3, 4])).numpy()) + self.assertAllEqual([2, 4, 6], + imported.f(constant_op.constant([1, 2, 3])).numpy()) + if __name__ == "__main__": test.main() -- GitLab From 2af977ba922fe498e30c1fcc30942709f75d2e78 Mon Sep 17 00:00:00 2001 From: Nutti Date: Thu, 10 Jan 2019 23:07:08 +0900 Subject: [PATCH 0488/2345] Fix: typo in SavedModelBundle.java --- .../java/src/main/java/org/tensorflow/SavedModelBundle.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/java/src/main/java/org/tensorflow/SavedModelBundle.java b/tensorflow/java/src/main/java/org/tensorflow/SavedModelBundle.java index 49594e6b47..e653373f85 100644 --- a/tensorflow/java/src/main/java/org/tensorflow/SavedModelBundle.java +++ b/tensorflow/java/src/main/java/org/tensorflow/SavedModelBundle.java @@ -84,7 +84,7 @@ public class SavedModelBundle implements AutoCloseable { *

This method is a shorthand for: * *

{@code
-   * SavedModelBundler.loader().withTags(tags).load();
+   * SavedModelBundle.loader().withTags(tags).load();
    * }
* * @param exportDir the directory path containing a saved model. -- GitLab From 9ad0810fd55096fc86a58300c5a2710b2f3b5175 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Thu, 10 Jan 2019 08:15:42 -0800 Subject: [PATCH 0489/2345] [XLA:Python] Fix build error in opensource due to xla::int64/protobuf_int64 differences. PiperOrigin-RevId: 228713538 --- tensorflow/compiler/xla/python/local_computation_builder.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/python/local_computation_builder.i b/tensorflow/compiler/xla/python/local_computation_builder.i index cc96d84724..66ecee5c4d 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.i +++ b/tensorflow/compiler/xla/python/local_computation_builder.i @@ -171,7 +171,7 @@ bool HandleStringAttribute(PyObject* o, bool HandleRepeatedInt64Attribute( PyObject* o, const char* attr_name, - tensorflow::protobuf::RepeatedField* field) { + tensorflow::protobuf::RepeatedField* field) { PyObject* seq = PyObject_GetAttrString(o, attr_name); if (!seq) { return false; -- GitLab From 4e462bc347ddadeb939e4861f77fa349c083fe64 Mon Sep 17 00:00:00 2001 From: Karim Nosir Date: Thu, 10 Jan 2019 09:31:51 -0800 Subject: [PATCH 0490/2345] Update unit-test to enable for OSS PiperOrigin-RevId: 228724452 --- tensorflow/lite/models/smartreply/BUILD | 1 - .../lite/models/smartreply/predictor_test.cc | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tensorflow/lite/models/smartreply/BUILD b/tensorflow/lite/models/smartreply/BUILD index a3f4e61cb5..fccbf79eb9 100644 --- a/tensorflow/lite/models/smartreply/BUILD +++ b/tensorflow/lite/models/smartreply/BUILD @@ -58,7 +58,6 @@ tf_cc_test( "//tensorflow/lite/models:testdata/smartreply_samples.tsv", "@tflite_smartreply//:smartreply.tflite", ], - tags = ["no_oss"], deps = [ ":predictor_lib", "//tensorflow/core:test", diff --git a/tensorflow/lite/models/smartreply/predictor_test.cc b/tensorflow/lite/models/smartreply/predictor_test.cc index 9bdd7b537a..9ce8ce9a77 100644 --- a/tensorflow/lite/models/smartreply/predictor_test.cc +++ b/tensorflow/lite/models/smartreply/predictor_test.cc @@ -31,12 +31,16 @@ namespace custom { namespace smartreply { namespace { -const char kModelName[] = "smartreply_ondevice_model.bin"; const char kSamples[] = "smartreply_samples.tsv"; -string TestDataPath() { +string GetModelFilePath() { + return "third_party/tensorflow/lite/models/testdata/" + "smartreply_ondevice_model.bin"; +} + +string GetSamplesFilePath() { return string(absl::StrCat(tensorflow::testing::TensorFlowSrcRoot(), "/", - "lite/models/testdata/")); + "lite/models/testdata/", kSamples)); } MATCHER_P(IncludeAnyResponesIn, expected_response, "contains the response") { @@ -57,8 +61,7 @@ class PredictorTest : public ::testing::Test { ~PredictorTest() override {} void SetUp() override { - model_ = tflite::FlatBufferModel::BuildFromFile( - absl::StrCat(TestDataPath(), "/", kModelName).c_str()); + model_ = tflite::FlatBufferModel::BuildFromFile(GetModelFilePath().c_str()); ASSERT_NE(model_.get(), nullptr); } @@ -123,7 +126,7 @@ TEST_F(PredictorTest, BatchTest) { int total_triggers = 0; string line; - std::ifstream fin(absl::StrCat(TestDataPath(), "/", kSamples)); + std::ifstream fin(GetSamplesFilePath()); while (std::getline(fin, line)) { const std::vector fields = absl::StrSplit(line, '\t'); if (fields.empty()) { -- GitLab From 73b6d9262ea6d1578374ecb63560ad0fcad13e35 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 10 Jan 2019 09:55:14 -0800 Subject: [PATCH 0491/2345] Split Keras TPU correctness tests by models. PiperOrigin-RevId: 228727689 --- tensorflow/contrib/distribute/python/BUILD | 30 +- ...test.py => keras_correctness_test_base.py} | 261 ++++-------------- .../python/keras_dnn_correctness_test.py | 163 +++++++++++ .../keras_image_model_correctness_test.py | 92 ++++++ 4 files changed, 337 insertions(+), 209 deletions(-) rename tensorflow/contrib/distribute/python/{keras_correctness_test.py => keras_correctness_test_base.py} (58%) create mode 100644 tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py create mode 100644 tensorflow/contrib/distribute/python/keras_image_model_correctness_test.py diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index d4758d7518..4c25f49f16 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -657,7 +657,11 @@ cuda_py_test( py_library( name = "keras_correctness_test_lib", testonly = 1, - srcs = ["keras_correctness_test.py"], + srcs = [ + "keras_correctness_test_base.py", + "keras_dnn_correctness_test.py", + "keras_image_model_correctness_test.py", + ], deps = [ ":combinations", "//tensorflow/contrib/distribute/python:mirrored_strategy", @@ -673,8 +677,9 @@ py_library( ) cuda_py_test( - name = "keras_correctness_test", - srcs = ["keras_correctness_test.py"], + name = "keras_dnn_correctness_test", + size = "medium", + srcs = ["keras_dnn_correctness_test.py"], additional_deps = [ ":keras_correctness_test_lib", ], @@ -690,6 +695,25 @@ cuda_py_test( ], ) +cuda_py_test( + name = "keras_image_model_correctness_test", + size = "medium", + srcs = ["keras_image_model_correctness_test.py"], + additional_deps = [ + ":keras_correctness_test_lib", + ], + # Shard count is set to an odd number to distribute tasks across + # shards more evenly. + shard_count = 31, + tags = [ + "multi_and_single_gpu", + "no_oss", # TODO(b/117919883): Fix python error. + "no_pip", + "no_windows_gpu", + "notsan", + ], +) + py_library( name = "metrics_v1_test_lib", testonly = 1, diff --git a/tensorflow/contrib/distribute/python/keras_correctness_test.py b/tensorflow/contrib/distribute/python/keras_correctness_test_base.py similarity index 58% rename from tensorflow/contrib/distribute/python/keras_correctness_test.py rename to tensorflow/contrib/distribute/python/keras_correctness_test_base.py index 3abdee2c0e..e13984b26d 100644 --- a/tensorflow/contrib/distribute/python/keras_correctness_test.py +++ b/tensorflow/contrib/distribute/python/keras_correctness_test_base.py @@ -30,8 +30,6 @@ from tensorflow.python.eager import context from tensorflow.python.eager import test from tensorflow.python.framework import random_seed 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.training import gradient_descent _RANDOM_SEED = 1337 _EVAL_STEPS = 20 @@ -62,24 +60,13 @@ def all_strategy_combinations_with_graph_mode(): return combinations.combine(distribution=all_strategies, mode=['graph']) -def strategy_and_input_combinations(): - def cnn_model_with_batch_norm(**kwargs): - return _create_cnn_model(with_batch_norm=True, **kwargs) - +def all_strategy_and_input_config_combinations(): return ( combinations.times( combinations.combine(distribution=all_strategies), combinations.combine(mode=['graph', 'eager'], use_numpy=[True, False], - use_validation_data=[True, False]), - combinations.combine(model_with_data=[ - ModelWithData('dnn', _create_dnn_model, _dnn_training_data), - ModelWithData('cnn', _create_cnn_model, _cnn_training_data), - ModelWithData('cnn_batch_norm', - cnn_model_with_batch_norm, - _cnn_training_data, - with_batch_norm=True), - ]))) + use_validation_data=[True, False]))) class MaybeDistributionScope(object): @@ -100,97 +87,6 @@ class MaybeDistributionScope(object): self._scope = None -class ModelWithData(object): - """An object giving a good name in combinations. - - The model_fn must take two arguments: initial_weights and distribution. - """ - - def __init__(self, name, model_fn, data_fn, with_batch_norm=False): - self.name = name - self.model_fn = model_fn - self.data_fn = data_fn - self.with_batch_norm = with_batch_norm - - def __repr__(self): - return self.name - - -def _dnn_training_data(): - # TODO(xiejw): Change this back to 10000, once we support final partial - # batch. - num_samples = 9984 - x_train = np.random.rand(num_samples, 1) - y_train = 3 * x_train - x_train = x_train.astype('float32') - y_train = y_train.astype('float32') - x_predict = [[1.], [2.], [3.], [4.]] - return x_train, y_train, x_predict - - -def _create_dnn_model(initial_weights=None, distribution=None): - with MaybeDistributionScope(distribution): - # We add few non-linear layers to make it non-trivial. - model = keras.Sequential() - model.add(keras.layers.Dense(10, activation='relu', input_shape=(1,))) - model.add(keras.layers.Dense(10, activation='relu')) - model.add(keras.layers.Dense(10, activation='relu')) - model.add(keras.layers.Dense(1)) - - if initial_weights: - model.set_weights(initial_weights) - - model.compile( - loss=keras.losses.mean_squared_error, - optimizer=gradient_descent_keras.SGD(0.5), - metrics=['mse']) - return model - - -def _cnn_training_data(count=_GLOBAL_BATCH_SIZE * _EVAL_STEPS, - shape=(28, 28, 3), num_classes=10): - centers = np.random.randn(num_classes, *shape) - - features = [] - labels = [] - for _ in range(count): - label = np.random.randint(0, num_classes, size=1)[0] - offset = np.random.normal(loc=0, scale=0.1, size=np.prod(shape)) - offset = offset.reshape(shape) - labels.append(label) - features.append(centers[label] + offset) - - x_train = np.asarray(features, dtype=np.float32) - y_train = np.asarray(labels, dtype=np.float32).reshape((count, 1)) - x_predict = x_train - return x_train, y_train, x_predict - - -def _create_cnn_model(initial_weights=None, distribution=None, - with_batch_norm=False): - with MaybeDistributionScope(distribution): - image = keras.layers.Input(shape=(28, 28, 3), name='image') - c1 = keras.layers.Conv2D( - name='conv1', filters=16, kernel_size=(3, 3), strides=(4, 4))( - image) - if with_batch_norm: - c1 = keras.layers.BatchNormalization(name='bn1')(c1) - c1 = keras.layers.MaxPooling2D(pool_size=(2, 2))(c1) - logits = keras.layers.Dense( - 10, activation='softmax', name='pred')( - keras.layers.Flatten()(c1)) - model = keras.Model(inputs=[image], outputs=[logits]) - - if initial_weights: - model.set_weights(initial_weights) - - model.compile( - optimizer=gradient_descent.GradientDescentOptimizer(learning_rate=0.1), - loss='sparse_categorical_crossentropy', - metrics=['sparse_categorical_accuracy']) - return model - - def batch_wrapper(dataset, batch_size, distribution, repeat=None): if repeat: dataset = dataset.repeat(repeat) @@ -350,6 +246,11 @@ def compare_results(results_with_ds, results_without_ds, distribution, msg='Fail to assert {}.'.format(key)) +def should_skip_tpu_with_eager(distribution): + return (context.executing_eagerly() and + isinstance(distribution, tpu_strategy.TPUStrategy)) + + class LearningRateBatchScheduler(keras.callbacks.Callback): def __init__(self, update_freq=None): @@ -364,107 +265,60 @@ class LearningRateBatchScheduler(keras.callbacks.Callback): keras.backend.set_value(self.model.optimizer.lr, lr) -class TestDistributionStrategyCorrectness(test.TestCase, - parameterized.TestCase): +class TestDistributionStrategyCorrectnessBase(test.TestCase, + parameterized.TestCase): + """Model agnostic testing infra to test correctness of Keras models.""" + + def set_up_test_config(self, use_numpy=False, + use_validation_data=False, + with_batch_norm=False): + self.use_numpy = use_numpy + self.use_validation_data = use_validation_data + self.with_batch_norm = with_batch_norm - def _should_skip_tpu_with_eager(self, distribution): - return (context.executing_eagerly() and - isinstance(distribution, tpu_strategy.TPUStrategy)) + keras.backend.set_image_data_format('channels_last') + np.random.seed(_RANDOM_SEED) + random_seed.set_random_seed(_RANDOM_SEED) - @combinations.generate(all_strategy_combinations_with_eager_and_graph_modes()) - def test_metric_correctness(self, distribution): - if self._should_skip_tpu_with_eager(distribution): - self.skipTest('TPUStrategy does not support eager mode now.') + def get_data(self): + num_samples = 10000 + x_train = np.random.randint(0, 2, num_samples) + x_train = np.reshape(x_train, (num_samples, 1)) + y_train = x_train + return (x_train.astype('float32'), y_train.astype('float32'), None) - with self.cached_session(): - keras.backend.set_image_data_format('channels_last') - num_samples = 10000 - - x_train = np.random.randint(0, 2, num_samples) - x_train = np.reshape(x_train, (num_samples, 1)) - y_train = x_train - x_train = x_train.astype('float32') - y_train = y_train.astype('float32') - - # Create identity model. - with distribution.scope(): - model = keras.Sequential() - model.add( - keras.layers.Dense(1, input_shape=(1,), kernel_initializer='ones')) - model.compile( - loss=keras.losses.mean_squared_error, - optimizer=gradient_descent.GradientDescentOptimizer(0.5), - metrics=[keras.metrics.BinaryAccuracy()]) - - batch_size = 64 - batch_size = get_batch_size(batch_size, distribution) - train_dataset = dataset_ops.Dataset.from_tensor_slices((x_train, y_train)) - train_dataset = batch_wrapper(train_dataset, batch_size, distribution) - - history = model.fit(x=train_dataset, epochs=2, steps_per_epoch=10) - self.assertEqual(history.history['binary_accuracy'], [1.0, 1.0]) - - @combinations.generate(all_strategy_combinations_with_eager_and_graph_modes()) - def test_eval_metrics_correctness(self, distribution): - if self._should_skip_tpu_with_eager(distribution): - self.skipTest('TPUStrategy does not support eager mode now.') + def get_model(self, distribution=None): + raise NotImplementedError - with self.cached_session(): - with distribution.scope(): - model = keras.Sequential() - model.add( - keras.layers.Dense( - 3, activation='relu', input_dim=4, kernel_initializer='ones')) - model.add( - keras.layers.Dense( - 1, activation='sigmoid', kernel_initializer='ones')) - model.compile( - loss='mae', - metrics=['accuracy', keras.metrics.BinaryAccuracy()], - optimizer=gradient_descent.GradientDescentOptimizer(0.001)) - - # verify correctness of stateful and stateless metrics. - x = np.ones((100, 4)).astype('float32') - y = np.ones((100, 1)).astype('float32') - dataset = dataset_ops.Dataset.from_tensor_slices((x, y)).repeat() - dataset = batch_wrapper(dataset, 4, distribution) - outs = model.evaluate(dataset, steps=10) - self.assertEqual(outs[1], 1.) - self.assertEqual(outs[2], 1.) - - y = np.zeros((100, 1)).astype('float32') - dataset = dataset_ops.Dataset.from_tensor_slices((x, y)).repeat() - dataset = batch_wrapper(dataset, 4, distribution) - outs = model.evaluate(dataset, steps=10) - self.assertEqual(outs[1], 0.) - self.assertEqual(outs[2], 0.) - - @combinations.generate(strategy_and_input_combinations()) - def test_correctness(self, distribution, use_numpy, use_validation_data, - model_with_data): - if self._should_skip_tpu_with_eager(distribution): + def skip_unsupported_test_configuration(self, distribution): + if should_skip_tpu_with_eager(distribution): self.skipTest('TPUStrategy does not support eager mode now.') - if context.executing_eagerly() and use_numpy: + if context.executing_eagerly() and self.use_numpy: self.skipTest('Numpy as inputs is not supported with strategy in eager.') - if context.executing_eagerly() and use_validation_data: - self.skipTest('TODO') + if context.executing_eagerly() and self.use_validation_data: + self.skipTest('TODO(hongjunchoi): Add test logic for using validation ' + 'data for eager execution.') + return + def run_correctness_test(self, + distribution, + use_numpy, + use_validation_data, + with_batch_norm=False): with self.cached_session(): - keras.backend.set_image_data_format('channels_last') - np.random.seed(_RANDOM_SEED) - random_seed.set_random_seed(_RANDOM_SEED) + self.set_up_test_config(use_numpy, use_validation_data, with_batch_norm) + self.skip_unsupported_test_configuration(distribution) - model_fn, data_fn = model_with_data.model_fn, model_with_data.data_fn # Train, eval, and predict datasets are created with the same input numpy # arrays. - x_train, y_train, x_predict = data_fn() + x_train, y_train, x_predict = self.get_data() # The model is built once and the initial weights are saved. # This is used to initialize the model for both the distribution and # non-distribution run. - model = model_fn() + model = self.get_model() initial_weights = model.get_weights() def input_fn(dist): @@ -472,15 +326,15 @@ class TestDistributionStrategyCorrectness(test.TestCase, use_numpy, use_validation_data, dist, x_train, y_train, x_predict) results_with_ds = fit_eval_and_predict( - initial_weights, input_fn=input_fn, model_fn=model_fn, + initial_weights, input_fn=input_fn, model_fn=self.get_model, distribution=distribution) results_without_ds = fit_eval_and_predict( - initial_weights, input_fn=input_fn, model_fn=model_fn, + initial_weights, input_fn=input_fn, model_fn=self.get_model, distribution=None) # First, special case, for multi-replica distributed training, batch norm # is not aggregated globally. So it is expected to have different weights. - if (model_with_data.with_batch_norm and + if (self.with_batch_norm and distribution.num_replicas_in_sync > 1): with self.assertRaises(AssertionError): compare_results(results_with_ds, results_without_ds, distribution, @@ -489,21 +343,16 @@ class TestDistributionStrategyCorrectness(test.TestCase, compare_results(results_with_ds, results_without_ds, distribution, testcase=self) - @combinations.generate(all_strategy_combinations_with_graph_mode()) - def test_dynamic_lr(self, distribution): - + def run_dynamic_lr_test(self, distribution): with self.cached_session(): + self.set_up_test_config() + self.skip_unsupported_test_configuration(distribution) - keras.backend.set_image_data_format('channels_last') - np.random.seed(_RANDOM_SEED) - random_seed.set_random_seed(_RANDOM_SEED) - - x_train, y_train, _ = _dnn_training_data() - - model = _create_dnn_model() + x_train, y_train, _ = self.get_data() + model = self.get_model() initial_weights = model.get_weights() - update_freq = None + if (isinstance(distribution, tpu_strategy.TPUStrategy) and distribution.extended.steps_per_run > 1): # For TPUStrategy with steps_per_run > 1, the callback is not invoked @@ -530,10 +379,10 @@ class TestDistributionStrategyCorrectness(test.TestCase, return training_inputs, eval_inputs, predict_inputs results_with_ds = fit_eval_and_predict( - initial_weights, input_fn=input_fn, model_fn=_create_dnn_model, + initial_weights, input_fn=input_fn, model_fn=self.get_model, distribution=distribution) results_without_ds = fit_eval_and_predict( - initial_weights, input_fn=input_fn, model_fn=_create_dnn_model, + initial_weights, input_fn=input_fn, model_fn=self.get_model, distribution=None) compare_results(results_with_ds, results_without_ds, distribution, testcase=self) diff --git a/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py b/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py new file mode 100644 index 0000000000..f21b1c6752 --- /dev/null +++ b/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py @@ -0,0 +1,163 @@ +# 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. +# ============================================================================== +"""Correctness tests for tf.keras DNN model using DistributionStrategy.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.contrib.distribute.python import keras_correctness_test_base +from tensorflow.python import keras +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.eager import test +from tensorflow.python.keras.optimizer_v2 import gradient_descent as gradient_descent_keras +from tensorflow.python.training import gradient_descent + + +class TestDistributionStrategyDnnCorrectness( + keras_correctness_test_base.TestDistributionStrategyCorrectnessBase): + + def get_model(self, initial_weights=None, distribution=None): + with keras_correctness_test_base.MaybeDistributionScope(distribution): + # We add few non-linear layers to make it non-trivial. + model = keras.Sequential() + model.add(keras.layers.Dense(10, activation='relu', input_shape=(1,))) + model.add(keras.layers.Dense(10, activation='relu')) + model.add(keras.layers.Dense(10, activation='relu')) + model.add(keras.layers.Dense(1)) + + if initial_weights: + model.set_weights(initial_weights) + + model.compile( + loss=keras.losses.mean_squared_error, + optimizer=gradient_descent_keras.SGD(0.5), + metrics=['mse']) + return model + + def get_data(self): + # TODO(xiejw): Change this back to 10000, once we support final partial + # batch. + num_samples = 9984 + x_train = np.random.rand(num_samples, 1) + y_train = 3 * x_train + x_train = x_train.astype('float32') + y_train = y_train.astype('float32') + x_predict = [[1.], [2.], [3.], [4.]] + return x_train, y_train, x_predict + + @combinations.generate(keras_correctness_test_base. + all_strategy_and_input_config_combinations()) + def test_dnn_correctness(self, distribution, use_numpy, use_validation_data): + self.run_correctness_test(distribution, use_numpy, use_validation_data) + + @combinations.generate(keras_correctness_test_base. + all_strategy_combinations_with_graph_mode()) + def test_dnn_with_dynamic_learning_rate(self, distribution): + self.run_dynamic_lr_test(distribution) + + +class TestDistributionStrategyDnnMetricCorrectness( + keras_correctness_test_base.TestDistributionStrategyCorrectnessBase): + + def get_model(self, distribution=None): + with distribution.scope(): + model = keras.Sequential() + model.add(keras.layers.Dense(1, + input_shape=(1,), + kernel_initializer='ones')) + model.compile( + loss=keras.losses.mean_squared_error, + optimizer=gradient_descent.GradientDescentOptimizer(0.5), + metrics=[keras.metrics.BinaryAccuracy()]) + return model + + def run_metric_correctness_test(self, distribution): + with self.cached_session(): + self.set_up_test_config() + self.skip_unsupported_test_configuration(distribution) + + x_train, y_train, _ = self.get_data() + model = self.get_model(distribution=distribution) + + batch_size = 64 + batch_size = (keras_correctness_test_base. + get_batch_size(batch_size, distribution)) + train_dataset = dataset_ops.Dataset.from_tensor_slices((x_train, y_train)) + train_dataset = (keras_correctness_test_base. + batch_wrapper(train_dataset, batch_size, distribution)) + + history = model.fit(x=train_dataset, epochs=2, steps_per_epoch=10) + self.assertEqual(history.history['binary_accuracy'], [1.0, 1.0]) + + @combinations.generate(keras_correctness_test_base. + all_strategy_combinations_with_eager_and_graph_modes()) + def test_simple_dnn_metric_correctness(self, distribution): + self.run_metric_correctness_test(distribution) + + +class TestDistributionStrategyDnnMetricEvalCorrectness( + keras_correctness_test_base.TestDistributionStrategyCorrectnessBase): + + def get_model(self, distribution=None): + with distribution.scope(): + model = keras.Sequential() + model.add( + keras.layers.Dense( + 3, activation='relu', input_dim=4, kernel_initializer='ones')) + model.add( + keras.layers.Dense( + 1, activation='sigmoid', kernel_initializer='ones')) + model.compile( + loss='mae', + metrics=['accuracy', keras.metrics.BinaryAccuracy()], + optimizer=gradient_descent.GradientDescentOptimizer(0.001)) + return model + + def run_eval_metrics_correctness_test(self, distribution): + with self.cached_session(): + self.set_up_test_config() + self.skip_unsupported_test_configuration(distribution) + + model = self.get_model(distribution=distribution) + + # verify correctness of stateful and stateless metrics. + x = np.ones((100, 4)).astype('float32') + y = np.ones((100, 1)).astype('float32') + dataset = dataset_ops.Dataset.from_tensor_slices((x, y)).repeat() + dataset = (keras_correctness_test_base. + batch_wrapper(dataset, 4, distribution)) + outs = model.evaluate(dataset, steps=10) + self.assertEqual(outs[1], 1.) + self.assertEqual(outs[2], 1.) + + y = np.zeros((100, 1)).astype('float32') + dataset = dataset_ops.Dataset.from_tensor_slices((x, y)).repeat() + dataset = (keras_correctness_test_base. + batch_wrapper(dataset, 4, distribution)) + outs = model.evaluate(dataset, steps=10) + self.assertEqual(outs[1], 0.) + self.assertEqual(outs[2], 0.) + + @combinations.generate(keras_correctness_test_base. + all_strategy_combinations_with_eager_and_graph_modes()) + def test_identity_model_metric_eval_correctness(self, distribution): + self.run_eval_metrics_correctness_test(distribution) + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/contrib/distribute/python/keras_image_model_correctness_test.py b/tensorflow/contrib/distribute/python/keras_image_model_correctness_test.py new file mode 100644 index 0000000000..f625664372 --- /dev/null +++ b/tensorflow/contrib/distribute/python/keras_image_model_correctness_test.py @@ -0,0 +1,92 @@ +# 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. +# ============================================================================== +"""Correctness tests for tf.keras CNN models using DistributionStrategy.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.contrib.distribute.python import keras_correctness_test_base +from tensorflow.python import keras +from tensorflow.python.eager import test +from tensorflow.python.training import gradient_descent + + +class DistributionStrategyCnnCorrectnessTest( + keras_correctness_test_base.TestDistributionStrategyCorrectnessBase): + + def get_model(self, initial_weights=None, distribution=None): + with keras_correctness_test_base.MaybeDistributionScope(distribution): + image = keras.layers.Input(shape=(28, 28, 3), name='image') + c1 = keras.layers.Conv2D( + name='conv1', filters=16, kernel_size=(3, 3), strides=(4, 4))( + image) + if self.with_batch_norm: + c1 = keras.layers.BatchNormalization(name='bn1')(c1) + c1 = keras.layers.MaxPooling2D(pool_size=(2, 2))(c1) + logits = keras.layers.Dense( + 10, activation='softmax', name='pred')( + keras.layers.Flatten()(c1)) + model = keras.Model(inputs=[image], outputs=[logits]) + + if initial_weights: + model.set_weights(initial_weights) + + model.compile( + optimizer=gradient_descent.GradientDescentOptimizer( + learning_rate=0.1), + loss='sparse_categorical_crossentropy', + metrics=['sparse_categorical_accuracy']) + + return model + + def get_data(self, + count=keras_correctness_test_base._GLOBAL_BATCH_SIZE + * keras_correctness_test_base._EVAL_STEPS, + shape=(28, 28, 3), + num_classes=10): + centers = np.random.randn(num_classes, *shape) + + features = [] + labels = [] + for _ in range(count): + label = np.random.randint(0, num_classes, size=1)[0] + offset = np.random.normal(loc=0, scale=0.1, size=np.prod(shape)) + offset = offset.reshape(shape) + labels.append(label) + features.append(centers[label] + offset) + + x_train = np.asarray(features, dtype=np.float32) + y_train = np.asarray(labels, dtype=np.float32).reshape((count, 1)) + x_predict = x_train + return x_train, y_train, x_predict + + @combinations.generate(keras_correctness_test_base. + all_strategy_and_input_config_combinations()) + def test_cnn_correctness(self, distribution, use_numpy, use_validation_data): + self.run_correctness_test(distribution, use_numpy, use_validation_data) + + @combinations.generate(keras_correctness_test_base. + all_strategy_and_input_config_combinations()) + def test_cnn_with_batch_norm_correctness(self, distribution, use_numpy, + use_validation_data): + self.run_correctness_test(distribution, use_numpy, use_validation_data, + with_batch_norm=True) + + +if __name__ == '__main__': + test.main() -- GitLab From 1210b6506dbde52a5bf5c33d56f3c52dfbd505e7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 10 Jan 2019 10:28:19 -0800 Subject: [PATCH 0492/2345] fix links to renamed markdown documentation PiperOrigin-RevId: 228733282 --- tensorflow/lite/g3doc/apis.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tensorflow/lite/g3doc/apis.md b/tensorflow/lite/g3doc/apis.md index b15159ce41..1a05142bc4 100644 --- a/tensorflow/lite/g3doc/apis.md +++ b/tensorflow/lite/g3doc/apis.md @@ -1,4 +1,3 @@ - # TensorFlow Lite APIs TensorFlow Lite provides programming APIs in C++ and Java, and in both cases @@ -8,8 +7,7 @@ no surprise that the APIs try to avoid unnecessary copies at the expense of convenience. Similarly, consistency with TensorFlow APIs was not an explicit goal and some variance is to be expected. -There is also a Python API for TensorFlow Lite described -[here](../toco/g3doc/python_api.md#interpreter). +There is also a [Python API for TensorFlow Lite](g3doc/convert/python_api.md). ## C++ -- GitLab From 3c4b35297e4b44baaf4cc7ed5b099c586ffa049b Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Thu, 10 Jan 2019 10:44:31 -0800 Subject: [PATCH 0493/2345] Re-apply "Don't check for alignment before calling into a tiled dot (Eigen or LLVM)" with fix PiperOrigin-RevId: 228736280 --- tensorflow/compiler/xla/service/cpu/BUILD | 2 - .../cpu/cpu_eigen_tensor_alignment_test.cc | 38 ------ .../xla/service/cpu/dot_op_emitter.cc | 117 ++++++++++++------ 3 files changed, 77 insertions(+), 80 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index de62aa60aa..a197bdddc8 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -385,7 +385,6 @@ cc_library( srcs = ["dot_op_emitter.cc"], hdrs = [ "dot_op_emitter.h", - "dot_op_emitter_internal.h", ], deps = [ ":cpu_options", @@ -1029,7 +1028,6 @@ tf_cc_test( size = "small", srcs = ["cpu_eigen_tensor_alignment_test.cc"], deps = [ - ":dot_op_emitter", ":ir_emission_utils", ":target_machine_features_fake", "//tensorflow/compiler/xla:test", diff --git a/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc b/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc index 823bdf259c..485769a373 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_eigen_tensor_alignment_test.cc @@ -13,7 +13,6 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h" #include "tensorflow/compiler/xla/service/cpu/ir_emission_utils.h" #include "tensorflow/compiler/xla/service/cpu/target_machine_features_fake.h" #include "tensorflow/compiler/xla/service/hlo_parser.h" @@ -23,48 +22,11 @@ namespace xla { namespace cpu { namespace { -using internal::DotImplementationStrategy; -using internal::DotInfo; -using internal::GetDotImplementationStrategy; - // Test that we don't call into Eigen with tensors too small to be aligned // reliably. class CpuEigenTensorAlignmentTest : public ::testing::Test {}; -TEST_F(CpuEigenTensorAlignmentTest, EigenDotAlignment) { - string hlo_string = R"( -HloModule DotOperation - -ENTRY DotOperation { - arg0 = f32[5,256] parameter(0) - arg1 = f32[256,1024] parameter(1) - ROOT dot = f32[5,1024] dot(arg0, arg1), lhs_contracting_dims={1}, rhs_contracting_dims={0} -} -)"; - - TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr module, - ParseHloString(hlo_string)); - - HloInstruction* dot = module->entry_computation()->root_instruction(); - - TargetMachineFeaturesWithFakeAlignmentLogic target_machine_with_no_alignment( - [](int64 size) { return 1; }); - - EXPECT_EQ(GetDotImplementationStrategy(HloModuleConfig{}, DotInfo(*dot), - target_machine_with_no_alignment), - DotImplementationStrategy::kNaiveLlvmIr); - - TargetMachineFeaturesWithFakeAlignmentLogic - target_machine_with_full_alignment([](int64 size) { - return TargetMachineFeatures::kEigenExpectedTensorAlignment; - }); - - EXPECT_NE(GetDotImplementationStrategy(HloModuleConfig{}, DotInfo(*dot), - target_machine_with_full_alignment), - DotImplementationStrategy::kNaiveLlvmIr); -} - TEST_F(CpuEigenTensorAlignmentTest, EigenConvAlignment) { string hlo_string = R"( HloModule ConvOperation diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc index e8a84ebe6b..b018e0cd46 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc @@ -14,7 +14,6 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/compiler/xla/service/cpu/dot_op_emitter.h" -#include "tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h" #include #include @@ -43,17 +42,63 @@ namespace xla { using llvm_ir::SetToFirstInsertPoint; namespace cpu { - -using internal::DotImplementationStrategy; -using internal::DotInfo; -using internal::GetDotImplementationStrategy; - namespace { // Returns true if we should call into multi-threaded Eigen routines. bool ShouldUseMultiThreadedEigen(const HloModuleConfig& config) { return config.debug_options().xla_cpu_multi_thread_eigen(); } +// Represents a dot operation. We use this in lieu of an `HloInstruction` +// because we want to be able to create this for the "inner" dot operation in a +// batch dot, for which there is no separate HLO instruction. +struct DotInfo { + Shape lhs_shape; + Shape rhs_shape; + Shape result_shape; + DotDimensionNumbers dim_nums; + + explicit DotInfo(const HloInstruction& instr) { + CHECK_EQ(instr.opcode(), HloOpcode::kDot); + lhs_shape = instr.operand(0)->shape(); + rhs_shape = instr.operand(1)->shape(); + result_shape = instr.shape(); + dim_nums = instr.dot_dimension_numbers(); + } +}; + +// Dictates how a dot operation is implemented. +enum class DotImplementationStrategy { + // The dot operation is lowered into LLVM IR that implements a naive nested + // loop that computes the result one element at a time. This is our + // "fallback"; we don't really want this to kick in for any non-trival dot + // operation. + kNaiveLlvmIr, + + // The dot operation is lowered into LLVM IR that implements a tiled + // Matrix*Vector operation. This strategy also allows fusing in a bias add + // into the dot. The matrix can be row major or column major, both are + // supported. + kTiledLlvmIrGemv, + + // The dot operation is lowered into LLVM IR that implemetns a tiled + // Matrix*Matrix operation. No fusions are supported. The two inputs + // and the output have to be row major. + kTiledLlvmIrGemm, + + // The dot operation is lowered into a call into an Eigen routine. No fusions + // are supported today. The two inputs and the output have to be row major. + // However, we do allow transposing either the LHS or the RHS as part of the + // GEMM -- we expose this flexibility as flexibility in the contraction + // dimensions, but we can also see this as flexibility in the input layouts. + kEigen, +}; + +// Returns the implementation strategy for a dot with the configuration +// `dot_info`. +DotImplementationStrategy GetDotImplementationStrategy( + const HloModuleConfig& config, const DotInfo& dot_info, + const TargetMachineFeatures& target_machine_features); + // Helper class for emitting LLVM IR to perform the dot operation. class DotOpEmitter { public: @@ -190,9 +235,8 @@ void DotOpEmitter::EmitTiledLlvmIrGemm() { } int64 size_bytes = m * n * ShapeUtil::ByteSizeOfPrimitiveType(primitive_type); - b_->CreateMemSet( - target, b_->getInt8(0), size_bytes, - target_machine_features_.minimum_alignment_for_allocation(size_bytes)); + b_->CreateMemSet(target, b_->getInt8(0), /*Size=*/size_bytes, + /*Align=*/1); int64 max_target_vector_width = target_machine_features_.vector_register_num_elements( @@ -696,40 +740,34 @@ absl::optional ProfitableToMakeDotOperandColumnMajor( return {}; } -namespace internal { namespace { // Return whether the given shape is rank 2. bool IsRank2(const Shape& shape) { return shape.rank() == 2; } +bool IsSimpleLayout(const Layout& layout) { + return layout.tiles().empty() && layout.format() == DENSE; +} + // In a gemm operation where output = lhs * rhs, check whether the given shapes // are valid for the operation. -bool AreAlignedGemmShapes( - const Shape& lhs_shape, const Shape& rhs_shape, const Shape& output_shape, - const TargetMachineFeatures& target_machine_features) { - // The inputs and the output must - // 1) be matrices with no padding, and - // 2) have an allowed element type. - PrimitiveType output_primitive_type = output_shape.element_type(); - if (!(output_primitive_type == F64 || output_primitive_type == F32 || - output_primitive_type == F16)) { - return false; - } - - if (!(IsRank2(lhs_shape) && IsRank2(rhs_shape) && IsRank2(output_shape))) { - return false; - } - - auto is_aligned = [&](const Shape& shape) { - return GetMinimumAlignmentForArray(shape, target_machine_features) >= - TargetMachineFeatures::kEigenExpectedTensorAlignment; - }; - - if (!is_aligned(lhs_shape) || !is_aligned(rhs_shape) || - !is_aligned(output_shape)) { - return false; +bool AreGemmShapes(const Shape& lhs_shape, const Shape& rhs_shape, + const Shape& output_shape, + const TargetMachineFeatures& target_machine_features) { + CHECK(!lhs_shape.has_layout() || IsSimpleLayout(lhs_shape.layout())) + << lhs_shape.DebugString(); + CHECK(!rhs_shape.has_layout() || IsSimpleLayout(rhs_shape.layout())) + << rhs_shape.DebugString(); + CHECK(!output_shape.has_layout() || IsSimpleLayout(output_shape.layout())) + << output_shape.DebugString(); + + switch (output_shape.element_type()) { + case F64: + case F32: + case F16: + return IsRank2(lhs_shape) && IsRank2(rhs_shape) && IsRank2(output_shape); + default: + return false; } - - return true; } bool IsAlignedGemm(const DotInfo& dot_info, @@ -739,8 +777,8 @@ bool IsAlignedGemm(const DotInfo& dot_info, return false; } - return AreAlignedGemmShapes(dot_info.lhs_shape, dot_info.rhs_shape, - dot_info.result_shape, target_machine_features); + return AreGemmShapes(dot_info.lhs_shape, dot_info.rhs_shape, + dot_info.result_shape, target_machine_features); } bool CanEmitTiledLlvmIrGemm( @@ -781,7 +819,6 @@ bool CanEmitTiledLlvmIrGemm( return true; } -} // namespace DotImplementationStrategy GetDotImplementationStrategy( const HloModuleConfig& config, const DotInfo& dot_info, @@ -806,7 +843,7 @@ DotImplementationStrategy GetDotImplementationStrategy( return DotImplementationStrategy::kNaiveLlvmIr; } -} // namespace internal +} // namespace bool DotImplementationCanHandleTranspose( const HloInstruction& dot_instr, -- GitLab From eae721281d6c5bdd1e84a17de56c1f2cb911fd76 Mon Sep 17 00:00:00 2001 From: Michael Kuperstein Date: Thu, 10 Jan 2019 11:07:54 -0800 Subject: [PATCH 0494/2345] [XLA] Move over some uses of CreateDS / CreateDUS to new API Move over some uses of the R1-index API to use the span-of-scalars one. PiperOrigin-RevId: 228740894 --- .../xla/service/algebraic_simplifier_test.cc | 34 ++++++++++----- .../cpu/cpu_instruction_fusion_test.cc | 43 ++++++++++++------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc index e39321f1f3..916b953cfc 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc @@ -3774,12 +3774,16 @@ TEST_F(AlgebraicSimplifierTest, TrivialDynamicSlice) { HloComputation::Builder builder(TestName()); Shape shape = ShapeUtil::MakeShape(F32, {10, 100, 1000}); + std::vector params; + for (int i = 0; i < 3; ++i) { + params.push_back(builder.AddInstruction(HloInstruction::CreateParameter( + i + 1, ShapeUtil::MakeShape(U32, {}), "slice_indices"))); + } builder.AddInstruction(HloInstruction::CreateDynamicSlice( shape, builder.AddInstruction( HloInstruction::CreateParameter(0, shape, "slice_from")), - builder.AddInstruction(HloInstruction::CreateParameter( - 1, ShapeUtil::MakeShape(U32, {3}), "slice_indices")), + params, /*slice_sizes=*/{10, 100, 1000})); auto computation = m->AddEntryComputation(builder.Build()); @@ -3798,28 +3802,35 @@ TEST_F(AlgebraicSimplifierTest, TrivialDynamicUpdateSlice) { Shape full_shape = ShapeUtil::MakeShape(F32, {10, 100, 1000}); Shape slice_shape = ShapeUtil::MakeShape(F32, {10, 1, 1000}); + std::vector slice_indices, update_indices; + for (int i = 0; i < 3; ++i) { + slice_indices.push_back( + builder.AddInstruction(HloInstruction::CreateParameter( + i + 1, ShapeUtil::MakeShape(U32, {}), "slice_indices"))); + update_indices.push_back( + builder.AddInstruction(HloInstruction::CreateParameter( + i + 5, ShapeUtil::MakeShape(U32, {}), "update_indices"))); + } HloInstruction* slice = builder.AddInstruction(HloInstruction::CreateDynamicSlice( slice_shape, builder.AddInstruction( HloInstruction::CreateParameter(0, full_shape, "slice_from")), - builder.AddInstruction(HloInstruction::CreateParameter( - 1, ShapeUtil::MakeShape(U32, {3}), "slice_indices")), + slice_indices, /*slice_sizes=*/{10, 1, 1000})); builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( slice_shape, builder.AddInstruction( - HloInstruction::CreateParameter(2, slice_shape, "to_update")), - slice, - builder.AddInstruction(HloInstruction::CreateParameter( - 3, ShapeUtil::MakeShape(U32, {3}), "update_indices")))); + HloInstruction::CreateParameter(4, slice_shape, "to_update")), + slice, update_indices)); auto computation = m->AddEntryComputation(builder.Build()); AlgebraicSimplifier simplifier(default_options_); ASSERT_TRUE(simplifier.Run(m.get()).ValueOrDie()); EXPECT_THAT(computation->root_instruction(), - GmockMatch(m::DynamicSlice(m::Parameter(), m::Parameter()))); + GmockMatch(m::DynamicSlice(m::Parameter(), m::Parameter(), + m::Parameter(), m::Parameter()))); } // Test that two consecutive broadcasts can be merged to one. @@ -4480,9 +4491,10 @@ TEST_F(AlgebraicSimplifierTest, DynamicUpdateSliceZeroUpdate) { HloInstruction* const update = builder.AddInstruction( HloInstruction::CreateParameter(1, update_shape, "update")); HloInstruction* const start_indices = builder.AddInstruction( - HloInstruction::CreateConstant(LiteralUtil::CreateR1({0}))); + HloInstruction::CreateConstant(LiteralUtil::CreateR0({}))); builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( - dslice_shape, operand, update, start_indices)); + dslice_shape, operand, update, + std::initializer_list({start_indices}))); const HloComputation* const computation = m->AddEntryComputation(builder.Build()); diff --git a/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc b/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc index 527df0bd1c..46c1d4c38e 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc @@ -332,7 +332,7 @@ TEST_F(OpcodeFusionTest, Exponential_Reshape_Negate) { TEST_F(OpcodeFusionTest, Broadcast_Reshape_DynamicSlice_Tanh) { HloComputation::Builder builder(TestName()); Shape param_shape = ShapeUtil::MakeShape(F32, {8}); - Shape starts_shape = ShapeUtil::MakeShape(F32, {2}); + Shape starts_shape = ShapeUtil::MakeShape(F32, {}); Shape broadcast_shape = ShapeUtil::MakeShape(F32, {1, 8, 8}); Shape reshape_shape = ShapeUtil::MakeShape(F32, {8, 8}); Shape dynamic_slice_shape = ShapeUtil::MakeShape(F32, {4, 4}); @@ -340,13 +340,15 @@ TEST_F(OpcodeFusionTest, Broadcast_Reshape_DynamicSlice_Tanh) { HloInstruction::CreateParameter(0, param_shape, "param")); HloInstruction* param1 = builder.AddInstruction( HloInstruction::CreateParameter(1, starts_shape, "starts")); + HloInstruction* param2 = builder.AddInstruction( + HloInstruction::CreateParameter(2, starts_shape, "starts")); HloInstruction* broadcast2 = builder.AddInstruction( HloInstruction::CreateBroadcast(broadcast_shape, param0, {1})); HloInstruction* reshape3 = builder.AddInstruction( HloInstruction::CreateReshape(reshape_shape, broadcast2)); HloInstruction* dynamic_slice4 = builder.AddInstruction(HloInstruction::CreateDynamicSlice( - dynamic_slice_shape, reshape3, param1, {4, 4})); + dynamic_slice_shape, reshape3, {param1, param2}, {4, 4})); builder.AddInstruction(HloInstruction::CreateUnary( dynamic_slice_shape, HloOpcode::kTanh, dynamic_slice4)); @@ -356,7 +358,8 @@ TEST_F(OpcodeFusionTest, Broadcast_Reshape_DynamicSlice_Tanh) { RunFusionAndCheckOpcodesWereFused( module.get(), {HloOpcode::kTanh, HloOpcode::kDynamicSlice, HloOpcode::kReshape, - HloOpcode::kBroadcast, HloOpcode::kParameter, HloOpcode::kParameter}); + HloOpcode::kBroadcast, HloOpcode::kParameter, HloOpcode::kParameter, + HloOpcode::kParameter}); } TEST_F(OpcodeFusionTest, Broadcast_Negate) { @@ -381,14 +384,16 @@ TEST_F(OpcodeFusionTest, Broadcast_Negate) { TEST_F(OpcodeFusionTest, DynamicSlice_Negate) { HloComputation::Builder builder(TestName()); Shape param_shape = ShapeUtil::MakeShape(F32, {4}); - Shape slice_shape = ShapeUtil::MakeShape(F32, {1}); + Shape slice_shape = ShapeUtil::MakeShape(F32, {}); Shape result_shape = ShapeUtil::MakeShape(F32, {2}); HloInstruction* param0 = builder.AddInstruction( HloInstruction::CreateParameter(0, param_shape, "param")); HloInstruction* param1 = builder.AddInstruction( HloInstruction::CreateParameter(1, slice_shape, "starts")); - HloInstruction* dynamic_slice2 = builder.AddInstruction( - HloInstruction::CreateDynamicSlice(result_shape, param0, param1, {2})); + HloInstruction* dynamic_slice2 = + builder.AddInstruction(HloInstruction::CreateDynamicSlice( + result_shape, param0, + std::initializer_list({param1}), {2})); builder.AddInstruction(HloInstruction::CreateUnary( result_shape, HloOpcode::kNegate, dynamic_slice2)); @@ -548,28 +553,36 @@ TEST_F(OpcodeFusionTest, DynamicSliceWithDynamicUpdateSlice) { Shape full_shape = ShapeUtil::MakeShape(F32, {10, 100, 1000}); Shape slice_shape = ShapeUtil::MakeShape(F32, {10, 1, 1000}); + std::vector slice_indices, update_indices; + for (int i = 0; i < 3; ++i) { + slice_indices.push_back( + builder.AddInstruction(HloInstruction::CreateParameter( + 1 + i, ShapeUtil::MakeShape(U32, {}), "slice_indices"))); + update_indices.push_back( + builder.AddInstruction(HloInstruction::CreateParameter( + 5 + i, ShapeUtil::MakeShape(U32, {}), "update_indices"))); + } HloInstruction* slice = builder.AddInstruction(HloInstruction::CreateDynamicSlice( slice_shape, builder.AddInstruction( HloInstruction::CreateParameter(0, full_shape, "slice_from")), - builder.AddInstruction(HloInstruction::CreateParameter( - 1, ShapeUtil::MakeShape(U32, {3}), "slice_indices")), + slice_indices, /*slice_sizes=*/{10, 1, 1000})); builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( full_shape, builder.AddInstruction( - HloInstruction::CreateParameter(2, full_shape, "to_update")), - slice, - builder.AddInstruction(HloInstruction::CreateParameter( - 3, ShapeUtil::MakeShape(U32, {3}), "update_indices")))); + HloInstruction::CreateParameter(4, full_shape, "to_update")), + slice, update_indices)); module->AddEntryComputation(builder.Build()); RunFusionAndCheckOpcodesWereFused( - module.get(), {HloOpcode::kDynamicSlice, HloOpcode::kDynamicUpdateSlice, - HloOpcode::kParameter, HloOpcode::kParameter, - HloOpcode::kParameter, HloOpcode::kParameter}); + module.get(), + {HloOpcode::kDynamicSlice, HloOpcode::kDynamicUpdateSlice, + HloOpcode::kParameter, HloOpcode::kParameter, HloOpcode::kParameter, + HloOpcode::kParameter, HloOpcode::kParameter, HloOpcode::kParameter, + HloOpcode::kParameter, HloOpcode::kParameter}); } TEST_F(OpcodeFusionTest, MessOfFusibleNodes) { -- GitLab From 8ccb11412614e7d9b702d9a56b6f876af8779f72 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 10 Jan 2019 11:24:15 -0800 Subject: [PATCH 0495/2345] Allows variable targets in gradient tapes. This allows using the native python `max` method on lists of variables inside of gradient tapes. PiperOrigin-RevId: 228743966 --- tensorflow/python/eager/backprop.py | 9 +++++---- tensorflow/python/eager/backprop_test.py | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/tensorflow/python/eager/backprop.py b/tensorflow/python/eager/backprop.py index 7d99f6e95a..42db726a7b 100644 --- a/tensorflow/python/eager/backprop.py +++ b/tensorflow/python/eager/backprop.py @@ -930,11 +930,12 @@ class GradientTape(object): "gradient in order to compute higher order " "derrivatives.", 1) - flat_targets = nest.flatten(target) - for t in flat_targets: + flat_targets = [] + for t in nest.flatten(target): if resource_variable_ops.is_resource_variable(t): - raise ValueError("GradientTape.gradient is not supported for variable " - "targets.") + with self: + t = ops.convert_to_tensor(t) + flat_targets.append(t) flat_sources = nest.flatten(sources) flat_sources = [_handle_or_self(x) for x in flat_sources] diff --git a/tensorflow/python/eager/backprop_test.py b/tensorflow/python/eager/backprop_test.py index 22ae6f74cb..5f4fda8897 100644 --- a/tensorflow/python/eager/backprop_test.py +++ b/tensorflow/python/eager/backprop_test.py @@ -354,6 +354,16 @@ class BackpropTest(test.TestCase): loss += v * v self.assertAllEqual(t.gradient(loss, v), 2.0) + def testPythonMax(self): + x = [resource_variable_ops.ResourceVariable(2.), + resource_variable_ops.ResourceVariable(3.), + resource_variable_ops.ResourceVariable(5.)] + with backprop.GradientTape() as t: + f = max(x) + grad = t.gradient(f, x) + self.assertAllEqual(self.evaluate(f), 5.) + self.assertAllEqual(self.evaluate(grad), [None, None, 1.0]) + def testAutomaticWatchedVariables(self): with backprop.GradientTape() as t: self.assertEqual(0, len(t.watched_variables())) @@ -674,10 +684,8 @@ class BackpropTest(test.TestCase): with backprop.GradientTape() as g: x = variables.Variable([3.0]) y = variables.Variable([2.0]) - with self.assertRaisesRegexp( - ValueError, - 'GradientTape.gradient is not supported for variable targets.'): - g.gradient(x, y) + grad = g.gradient(x, y) + self.assertAllEqual(grad, None) @test_util.run_in_graph_and_eager_modes @test_util.run_v1_only('b/120545219') -- GitLab From 32a8626373897a05195dfb3d895385a7584037cc Mon Sep 17 00:00:00 2001 From: Asim Shankar Date: Thu, 10 Jan 2019 11:26:10 -0800 Subject: [PATCH 0496/2345] [Java]: Correct some comments. PiperOrigin-RevId: 228744342 --- .../org/tensorflow/processor/OperatorProcessor.java | 2 +- .../java/org/tensorflow/op/annotation/Operator.java | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tensorflow/java/src/gen/java/org/tensorflow/processor/OperatorProcessor.java b/tensorflow/java/src/gen/java/org/tensorflow/processor/OperatorProcessor.java index 1b7bcdab35..df1426ad75 100644 --- a/tensorflow/java/src/gen/java/org/tensorflow/processor/OperatorProcessor.java +++ b/tensorflow/java/src/gen/java/org/tensorflow/processor/OperatorProcessor.java @@ -340,7 +340,7 @@ public final class OperatorProcessor extends AbstractProcessor { + "{@link $T @Operator} is exposed\n" + "by this API or one of its subgroup.\n

Example usage:\n

{@code\n"
                     + "try (Graph g = new Graph()) {\n"
-                    + "  Ops ops = new Ops(g);\n"
+                    + "  Ops ops = Ops.create(g);\n"
                     + "  // Operations are typed classes with convenience\n"
                     + "  // builders in Ops.\n"
                     + "  Constant three = ops.constant(3);\n"
diff --git a/tensorflow/java/src/main/java/org/tensorflow/op/annotation/Operator.java b/tensorflow/java/src/main/java/org/tensorflow/op/annotation/Operator.java
index 3782240edb..38f466c574 100644
--- a/tensorflow/java/src/main/java/org/tensorflow/op/annotation/Operator.java
+++ b/tensorflow/java/src/main/java/org/tensorflow/op/annotation/Operator.java
@@ -25,11 +25,11 @@ import java.lang.annotation.Target;
  * Annotation used by classes to make TensorFlow operations conveniently accessible via {@code
  * org.tensorflow.op.Ops}.
  *
- * 

An annotation processor (TODO: not yet implemented) builds the {@code Ops} class by - * aggregating all classes annotated as {@code @Operator}s. Each annotated class must have at - * least one public static factory method named {@code create} that accepts a {@link - * org.tensorflow.op.Scope} as its first argument. The processor then adds a convenience method in - * the {@code Ops} class. For example: + *

An annotation processor ({@code org.tensorflow.processor.OperatorProcessor}) builds the + * {@code Ops} class by aggregating all classes annotated as {@code @Operator}s. Each annotated + * class must have at least one public static factory method named {@code create} that + * accepts a {@link org.tensorflow.op.Scope} as its first argument. The processor then adds a + * convenience method in the {@code Ops} class. For example: * *

{@code
  * @Operator
@@ -45,7 +45,7 @@ import java.lang.annotation.Target;
  * 
{@code
  * import org.tensorflow.op.Ops;
  * ...
- * Ops ops = new Ops(graph);
+ * Ops ops = Ops.create(graph);
  * ...
  * ops.myOp(operand);
  * // and has exactly the same effect as calling
-- 
GitLab


From 519efc7913ef2c6e5271c90727c6a1dc43817efb Mon Sep 17 00:00:00 2001
From: Michael Kuperstein 
Date: Thu, 10 Jan 2019 11:27:18 -0800
Subject: [PATCH 0497/2345] [XLA] Move over more uses of CreateDS / CreateDUS
 to new API

Move over more uses of the R1-index API to use the span-of-scalars one.

PiperOrigin-RevId: 228744562
---
 .../xla/service/hlo_dataflow_analysis_test.cc | 47 +++++++++++--------
 .../xla/service/hlo_evaluator_test.cc         | 28 ++++++-----
 2 files changed, 45 insertions(+), 30 deletions(-)

diff --git a/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc b/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc
index 1d165ac35f..888886865b 100644
--- a/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc
+++ b/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc
@@ -1970,12 +1970,13 @@ TEST_F(DoesNotUseOperandBufferTest, FusedDynamicUpdateSlice) {
 
   // Create a DynamicUpdateSlice instruction of tuple element 1.
   auto starts = builder.AddInstruction(
-      HloInstruction::CreateConstant(LiteralUtil::CreateR1({2})));
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(2)));
   auto update = builder.AddInstruction(HloInstruction::CreateConstant(
       LiteralUtil::CreateR1({2.f, 2.f, 2.f})));
   auto dynamic_update_slice =
       builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice(
-          data_shape, gte1, update, starts));
+          data_shape, gte1, update,
+          std::initializer_list({starts})));
   builder.AddInstruction(
       HloInstruction::CreateTuple({gte0, dynamic_update_slice}));
 
@@ -2012,12 +2013,13 @@ TEST_F(DoesNotUseOperandBufferTest, IndirectUses) {
 
   // Create a DynamicUpdateSlice instruction of tuple element 1.
   auto starts = builder.AddInstruction(
-      HloInstruction::CreateConstant(LiteralUtil::CreateR1({2})));
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(2)));
   auto update = builder.AddInstruction(HloInstruction::CreateConstant(
       LiteralUtil::CreateR1({2.f, 2.f, 2.f})));
   auto dynamic_update_slice =
       builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice(
-          data_shape, gte1, update, starts));
+          data_shape, gte1, update,
+          std::initializer_list({starts})));
   builder.AddInstruction(
       HloInstruction::CreateTuple({gte0, dynamic_update_slice}));
 
@@ -2150,17 +2152,17 @@ TEST_F(CanShareOperandBufferWithUserTest,
 
   auto param = builder.AddInstruction(
       HloInstruction::CreateParameter(0, data_shape, "param0"));
-  auto index = builder.AddInstruction(
-      HloInstruction::CreateConstant(LiteralUtil::CreateR1({0, 0})));
-  auto ds = builder.AddInstruction(
-      HloInstruction::CreateDynamicSlice(slice_shape, param, index, {1, 2, 2}));
+  auto zero = builder.AddInstruction(
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(0)));
+  auto ds = builder.AddInstruction(HloInstruction::CreateDynamicSlice(
+      slice_shape, param, {zero, zero}, {1, 2, 2}));
 
-  auto dus = builder.AddInstruction(
-      HloInstruction::CreateDynamicUpdateSlice(data_shape, param, ds, index));
+  auto dus = builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice(
+      data_shape, param, ds, {zero, zero}));
 
   BuildModule(builder.Build());
   auto fusion = computation_->CreateFusionInstruction(
-      {dus, ds, index}, HloInstruction::FusionKind::kLoop);
+      {dus, ds, zero}, HloInstruction::FusionKind::kLoop);
   RunAnalysis();
 
   EXPECT_TRUE(
@@ -2219,12 +2221,13 @@ TEST_F(CanShareOperandBufferWithUserTest, FusedDynamicUpdateSlice) {
 
   // Create a DynamicUpdateSlice instruction of tuple element 1.
   auto starts = builder.AddInstruction(
-      HloInstruction::CreateConstant(LiteralUtil::CreateR1({2})));
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(2)));
   auto update = builder.AddInstruction(HloInstruction::CreateConstant(
       LiteralUtil::CreateR1({2.f, 2.f, 2.f})));
   auto dynamic_update_slice =
       builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice(
-          data_shape, gte1, update, starts));
+          data_shape, gte1, update,
+          std::initializer_list({starts})));
   builder.AddInstruction(
       HloInstruction::CreateTuple({gte0, dynamic_update_slice}));
 
@@ -2259,12 +2262,13 @@ TEST_F(CanShareOperandBufferWithUserTest,
 
   // Create a DynamicUpdateSlice instruction of tuple element 1.
   auto starts = builder.AddInstruction(
-      HloInstruction::CreateConstant(LiteralUtil::CreateR1({2})));
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(2)));
   auto update = builder.AddInstruction(HloInstruction::CreateConstant(
       LiteralUtil::CreateR1({2.f, 2.f, 2.f})));
   auto dynamic_update_slice =
       builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice(
-          data_shape_bf16, convert1, update, starts));
+          data_shape_bf16, convert1, update,
+          std::initializer_list({starts})));
 
   auto convert2 = builder.AddInstruction(
       HloInstruction::CreateConvert(data_shape, dynamic_update_slice));
@@ -2290,10 +2294,13 @@ TEST_F(CanShareOperandBufferWithUserTest, DynamicUpdateSliceCanShare) {
       HloInstruction::CreateParameter(0, data_shape, "data"));
   auto update = builder.AddInstruction(
       HloInstruction::CreateParameter(1, update_shape, "update"));
-  auto starts = builder.AddInstruction(
-      HloInstruction::CreateParameter(2, starts_shape, "starts"));
+  auto start0 = builder.AddInstruction(
+      HloInstruction::CreateParameter(2, starts_shape, "start0"));
+  auto start1 = builder.AddInstruction(
+      HloInstruction::CreateParameter(3, starts_shape, "start1"));
+
   auto dus = builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice(
-      data_shape, data, update, starts));
+      data_shape, data, update, {start0, start1}));
 
   BuildModuleAndRunAnalysis(builder.Build());
 
@@ -2304,7 +2311,9 @@ TEST_F(CanShareOperandBufferWithUserTest, DynamicUpdateSliceCanShare) {
   EXPECT_FALSE(
       dataflow_analysis_->CanShareOperandBufferWithUser(update, {}, dus, {}));
   EXPECT_FALSE(
-      dataflow_analysis_->CanShareOperandBufferWithUser(starts, {}, dus, {}));
+      dataflow_analysis_->CanShareOperandBufferWithUser(start0, {}, dus, {}));
+  EXPECT_FALSE(
+      dataflow_analysis_->CanShareOperandBufferWithUser(start1, {}, dus, {}));
 }
 
 TEST_F(CanShareOperandBufferWithUserTest, ScatterCanShare) {
diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc
index 319f0db97a..674df0016a 100644
--- a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc
+++ b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc
@@ -1739,12 +1739,14 @@ TEST_P(HloEvaluatorBf16Test, DynamicSlice) {
   HloInstruction* operand = b.AddInstruction(
       HloInstruction::CreateConstant(std::move(operand_literal)));
 
-  auto start_indices = b.AddInstruction(
-      HloInstruction::CreateConstant(LiteralUtil::CreateR1({0, 1})));
+  auto zero = b.AddInstruction(
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(0)));
+  auto one = b.AddInstruction(
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(1)));
 
   Shape shape = ShapeUtil::MakeShape(F32, {2, 3});
-  b.AddInstruction(HloInstruction::CreateDynamicSlice(shape, operand,
-                                                      start_indices, {2, 3}));
+  b.AddInstruction(
+      HloInstruction::CreateDynamicSlice(shape, operand, {zero, one}, {2, 3}));
   m_->AddEntryComputation(b.Build());
 
   Literal result = Evaluate();
@@ -1775,12 +1777,14 @@ TEST_P(HloEvaluatorBf16Test, DynamicSliceModSlice) {
   HloInstruction* operand = b.AddInstruction(
       HloInstruction::CreateConstant(std::move(operand_literal)));
 
-  auto start_indices = b.AddInstruction(
-      HloInstruction::CreateConstant(LiteralUtil::CreateR1({2, 1})));
+  auto two = b.AddInstruction(
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(2)));
+  auto one = b.AddInstruction(
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(1)));
 
   Shape shape = ShapeUtil::MakeShape(F32, {2, 3});
-  b.AddInstruction(HloInstruction::CreateDynamicSlice(shape, operand,
-                                                      start_indices, {2, 3}));
+  b.AddInstruction(
+      HloInstruction::CreateDynamicSlice(shape, operand, {two, one}, {2, 3}));
   m_->AddEntryComputation(b.Build());
 
   Literal result = Evaluate();
@@ -1809,15 +1813,17 @@ TEST_P(HloEvaluatorBf16Test, DynamicSliceUpdate) {
   HloInstruction* operand = b.AddInstruction(
       HloInstruction::CreateConstant(std::move(operand_literal)));
 
-  auto start_indices = b.AddInstruction(
-      HloInstruction::CreateConstant(LiteralUtil::CreateR1({0, 1})));
+  auto zero = b.AddInstruction(
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(0)));
+  auto one = b.AddInstruction(
+      HloInstruction::CreateConstant(LiteralUtil::CreateR0(1)));
 
   auto update = b.AddInstruction(HloInstruction::CreateConstant(
       LiteralUtil::CreateR2({{-2.0, -3.0}, {-6.0, -7.0}})));
 
   Shape shape = ShapeUtil::MakeShape(F64, {2, 3});
   b.AddInstruction(HloInstruction::CreateDynamicUpdateSlice(
-      shape, operand, update, start_indices));
+      shape, operand, update, {zero, one}));
   m_->AddEntryComputation(b.Build());
 
   Literal result = Evaluate();
-- 
GitLab


From 4644d4e5f26cf72905d1ce40194a9733c4f8252f Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 11:29:29 -0800
Subject: [PATCH 0498/2345] [convergence_tools]: Tracing the subset of the ops
 that are in the execution path.

PiperOrigin-RevId: 228744967
---
 .../contrib/tpu/python/tpu/tensor_tracer.py   | 53 +++++++++++++++++--
 .../contrib/tpu/python/tpu/tpu_estimator.py   |  3 +-
 2 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py b/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py
index c7e29860e5..bf492e78a1 100644
--- a/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py
+++ b/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py
@@ -59,6 +59,7 @@ _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'
 _MARKER_SECTION_BEGIN = '!!!!!!! section-begin:'
 _MARKER_SECTION_END = '!!!!!!! section-end:'
@@ -864,7 +865,8 @@ class TensorTracer(object):
     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):
+  def _skip_op(self, op_id, op, user_included, user_excluded,
+               in_exec_path=True):
     """Returns True if we should not trace Op."""
 
     if user_included:
@@ -875,6 +877,10 @@ class TensorTracer(object):
       self._instrument_records[op.name] = TensorTracer.reason(
           op_id, _REASON_USER_EXCLUDED)
       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)
@@ -946,6 +952,40 @@ class TensorTracer(object):
           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:
+          execution_path_operations.add(input_op)
+          traverse_stack.append(input_op)
+    return execution_path_operations
+
   def _pre_tracing(self, graph):
     """Work needs to be done prior to TPU or CPU tracing."""
 
@@ -987,13 +1027,15 @@ class TensorTracer(object):
                                   _TENSOR_TRACER_CHECKPOINT))
     return checkpoint_operations
 
-  def trace_tpu(self, graph, result_tensor, num_replicas=None):
+  def trace_tpu(self, graph, result_tensor, num_replicas=None, fetches=None):
     """Traces the tensors generated by TPU Ops in a TF graph.
 
     Args:
       graph: the graph of Ops executed on the TPU.
       result_tensor: a result tensor of evaluating the graph.
       num_replicas: number of replicas used on the TPU.
+      fetches: the list of fetches given to session.run, used to determine the
+      ops in execution path. If None, the whole graph will be traced.
 
     Returns:
       A tuple (result_tensor_copy, tracing_ops), where:
@@ -1022,6 +1064,10 @@ class TensorTracer(object):
     result_tensor_copy = self._add_replica_id_to_graph(num_replicas,
                                                        result_tensor)
     (operations, succeed, sorted_or_cycle) = self._pre_tracing(graph)
+    # 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)
     tracing_ops = []
     checkpoint_operations = self._get_checkpoints(graph)
 
@@ -1030,7 +1076,8 @@ class TensorTracer(object):
         continue
       user_included = self._is_user_included_op(op)
       user_excluded = self._is_user_excluded_op(op)
-      if self._skip_op(op_id, op, user_included, user_excluded):
+      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]
diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py
index 2df5b3e4eb..075ecbc52a 100644
--- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py
+++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py
@@ -1412,7 +1412,8 @@ class _ModelFnWrapper(object):
       if tensor_tracer.TensorTracer.is_enabled():
         tt = tensor_tracer.TensorTracer()
         loss, tracing_ops = tt.trace_tpu(ops.get_default_graph(), loss,
-                                         self._ctx.num_replicas)
+                                         self._ctx.num_replicas,
+                                         fetches=[loss, train_op])
 
       # We must run train_op to update the variables prior to running the
       # outfeed.
-- 
GitLab


From 53bd138262df6e141c0e196a262f4ae760c2cf5f Mon Sep 17 00:00:00 2001
From: Asim Shankar 
Date: Thu, 10 Jan 2019 11:34:00 -0800
Subject: [PATCH 0499/2345] [Java]: Add place to put unittests for generated Op
 wrappers.

PiperOrigin-RevId: 228745935
---
 tensorflow/java/BUILD                         | 13 ++++++
 .../op/core/GeneratedOperationsTest.java      | 43 +++++++++++++++++++
 2 files changed, 56 insertions(+)
 create mode 100644 tensorflow/java/src/test/java/org/tensorflow/op/core/GeneratedOperationsTest.java

diff --git a/tensorflow/java/BUILD b/tensorflow/java/BUILD
index 10808e162e..af5503f2ad 100644
--- a/tensorflow/java/BUILD
+++ b/tensorflow/java/BUILD
@@ -295,6 +295,19 @@ tf_java_test(
     ],
 )
 
+tf_java_test(
+    name = "GeneratedOperationsTest",
+    size = "small",
+    srcs = ["src/test/java/org/tensorflow/op/core/GeneratedOperationsTest.java"],
+    javacopts = JAVACOPTS,
+    test_class = "org.tensorflow.op.core.GeneratedOperationsTest",
+    deps = [
+        ":tensorflow",
+        ":testutil",
+        "@junit",
+    ],
+)
+
 tf_java_test(
     name = "GradientsTest",
     size = "small",
diff --git a/tensorflow/java/src/test/java/org/tensorflow/op/core/GeneratedOperationsTest.java b/tensorflow/java/src/test/java/org/tensorflow/op/core/GeneratedOperationsTest.java
new file mode 100644
index 0000000000..42d126c3c4
--- /dev/null
+++ b/tensorflow/java/src/test/java/org/tensorflow/op/core/GeneratedOperationsTest.java
@@ -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.
+==============================================================================*/
+
+package org.tensorflow.op.core;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.tensorflow.Graph;
+import org.tensorflow.Operand;
+import org.tensorflow.Session;
+import org.tensorflow.Tensor;
+import org.tensorflow.op.Ops;
+
+@RunWith(JUnit4.class)
+public final class GeneratedOperationsTest {
+
+  @Test
+  public void tensorInputTensorOutput() {
+    try (Graph g = new Graph();
+        Session sess = new Session(g)) {
+      Ops ops = Ops.create(g);
+      Operand x = ops.math().add(ops.constant(1), ops.constant(2));
+      try (Tensor result = sess.runner().fetch(x).run().get(0).expect(Integer.class)) {
+        assertEquals(3, result.intValue());
+      }
+    }
+  }
+}
-- 
GitLab


From 1095ed8fe6808a70edb66d7970e9462db791e690 Mon Sep 17 00:00:00 2001
From: James Ring 
Date: Thu, 10 Jan 2019 11:48:55 -0800
Subject: [PATCH 0500/2345] Add TF_StepId and TF_ExpectedOutputDataType

PiperOrigin-RevId: 228748943
---
 tensorflow/c/kernels.cc      | 9 +++++++++
 tensorflow/c/kernels.h       | 8 ++++++++
 tensorflow/c/kernels_test.cc | 6 ++++++
 3 files changed, 23 insertions(+)

diff --git a/tensorflow/c/kernels.cc b/tensorflow/c/kernels.cc
index 2a4eaecb6c..673aed558a 100644
--- a/tensorflow/c/kernels.cc
+++ b/tensorflow/c/kernels.cc
@@ -158,3 +158,12 @@ void TF_SetOutput(TF_OpKernelContext* ctx, int i, const TF_Tensor* tensor,
     cc_ctx->set_output(i, cc_tensor);
   }
 }
+
+TF_DataType TF_ExpectedOutputDataType(TF_OpKernelContext* ctx, int i) {
+  auto* cc_ctx = reinterpret_cast<::tensorflow::OpKernelContext*>(ctx);
+  return static_cast(cc_ctx->expected_output_dtype(i));
+}
+
+int64_t TF_StepId(TF_OpKernelContext* ctx) {
+  return reinterpret_cast<::tensorflow::OpKernelContext*>(ctx)->step_id();
+}
diff --git a/tensorflow/c/kernels.h b/tensorflow/c/kernels.h
index cefc30bcdf..721a4aca0b 100644
--- a/tensorflow/c/kernels.h
+++ b/tensorflow/c/kernels.h
@@ -111,6 +111,14 @@ TF_CAPI_EXPORT extern void TF_SetOutput(TF_OpKernelContext* ctx, int i,
                                         const TF_Tensor* tensor,
                                         TF_Status* status);
 
+// Returns the expected output data type of the ith output. If i < 0 or
+// i >= TF_NumOutputs(ctx), the program aborts.
+TF_CAPI_EXPORT extern TF_DataType TF_ExpectedOutputDataType(
+    TF_OpKernelContext* ctx, int i);
+
+// Returns the step ID of the given context.
+TF_CAPI_EXPORT extern int64_t TF_StepId(TF_OpKernelContext* ctx);
+
 #ifdef __cplusplus
 } /* end extern "C" */
 #endif
diff --git a/tensorflow/c/kernels_test.cc b/tensorflow/c/kernels_test.cc
index e659ee3c3d..fdeb04c84c 100644
--- a/tensorflow/c/kernels_test.cc
+++ b/tensorflow/c/kernels_test.cc
@@ -41,6 +41,9 @@ static void* MyCreateFunc(TF_OpKernelConstruction* ctx) {
 static void MyComputeFunc(void* kernel, TF_OpKernelContext* ctx) {
   struct MyCustomKernel* s = static_cast(kernel);
   s->compute_called = true;
+  if (ctx != nullptr) {
+    EXPECT_EQ(43, TF_StepId(ctx));
+  }
 }
 
 static void MyDeleteFunc(void* kernel) {
@@ -155,6 +158,8 @@ TEST(TestKernel, TestInputAndOutputCount) {
     TF_SetOutput(ctx, 24, input, s);
     EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(s));
 
+    EXPECT_EQ(TF_UINT8, TF_ExpectedOutputDataType(ctx, 0));
+
     TF_DeleteStatus(s);
     if (input != nullptr) {
       TF_DeleteTensor(input);
@@ -175,6 +180,7 @@ TEST(TestKernel, TestInputAndOutputCount) {
     OpKernelContext::Params p;
     DummyDevice dummy_device(nullptr, false);
     p.device = &dummy_device;
+    p.step_id = 43;
 
     Tensor t(tensorflow::uint8(123));
 
-- 
GitLab


From d5977dfe93b95f6902cb9df28525a1dc50e4704d Mon Sep 17 00:00:00 2001
From: Zhenyu Tan 
Date: Thu, 10 Jan 2019 11:53:59 -0800
Subject: [PATCH 0501/2345] Internal Cleanup.

PiperOrigin-RevId: 228749766
---
 tensorflow/python/keras/BUILD | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD
index 9f8da126bf..fb0161a677 100755
--- a/tensorflow/python/keras/BUILD
+++ b/tensorflow/python/keras/BUILD
@@ -482,13 +482,14 @@ cuda_py_test(
 
 tf_py_test(
     name = "pooling_test",
-    size = "large",
+    size = "medium",
     srcs = ["layers/pooling_test.py"],
     additional_deps = [
         ":keras",
         "@absl_py//absl/testing:parameterized",
         "//tensorflow/python:client_testlib",
     ],
+    shard_count = 8,
 )
 
 tf_py_test(
-- 
GitLab


From 84572bff5cf02b1722ab763982a92be44e7719ef Mon Sep 17 00:00:00 2001
From: Yuefeng Zhou 
Date: Thu, 10 Jan 2019 12:18:36 -0800
Subject: [PATCH 0502/2345] Fix collective_all_reduce_strategy test failure.

PiperOrigin-RevId: 228754463
---
 .../distribute/python/collective_all_reduce_strategy_test.py   | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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 682da44f5d..62ff4b178e 100644
--- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py
+++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py
@@ -426,7 +426,8 @@ class LocalCollectiveAllReduceStrategy(
 
   @combinations.generate(
       combinations.combine(mode=['graph', 'eager'], required_gpus=2))
-  def testMakeInputFnIterator(self, num_gpus=2):
+  def testMakeInputFnIterator(self):
+    num_gpus = 2
     dataset_fn = lambda: dataset_ops.Dataset.range(5 * num_gpus)
     expected_values = [range(i, i + num_gpus) for i in range(0, 10, num_gpus)]
 
-- 
GitLab


From 02630688f2fa239ce1d1551caa8a2f3559c248e3 Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 12:25:01 -0800
Subject: [PATCH 0503/2345] Automated rollback of commit
 73b6d9262ea6d1578374ecb63560ad0fcad13e35

PiperOrigin-RevId: 228755594
---
 tensorflow/contrib/distribute/python/BUILD    |  30 +-
 ...test_base.py => keras_correctness_test.py} | 261 ++++++++++++++----
 .../python/keras_dnn_correctness_test.py      | 163 -----------
 .../keras_image_model_correctness_test.py     |  92 ------
 4 files changed, 209 insertions(+), 337 deletions(-)
 rename tensorflow/contrib/distribute/python/{keras_correctness_test_base.py => keras_correctness_test.py} (58%)
 delete mode 100644 tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py
 delete mode 100644 tensorflow/contrib/distribute/python/keras_image_model_correctness_test.py

diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD
index 4c25f49f16..d4758d7518 100644
--- a/tensorflow/contrib/distribute/python/BUILD
+++ b/tensorflow/contrib/distribute/python/BUILD
@@ -657,11 +657,7 @@ cuda_py_test(
 py_library(
     name = "keras_correctness_test_lib",
     testonly = 1,
-    srcs = [
-        "keras_correctness_test_base.py",
-        "keras_dnn_correctness_test.py",
-        "keras_image_model_correctness_test.py",
-    ],
+    srcs = ["keras_correctness_test.py"],
     deps = [
         ":combinations",
         "//tensorflow/contrib/distribute/python:mirrored_strategy",
@@ -677,9 +673,8 @@ py_library(
 )
 
 cuda_py_test(
-    name = "keras_dnn_correctness_test",
-    size = "medium",
-    srcs = ["keras_dnn_correctness_test.py"],
+    name = "keras_correctness_test",
+    srcs = ["keras_correctness_test.py"],
     additional_deps = [
         ":keras_correctness_test_lib",
     ],
@@ -695,25 +690,6 @@ cuda_py_test(
     ],
 )
 
-cuda_py_test(
-    name = "keras_image_model_correctness_test",
-    size = "medium",
-    srcs = ["keras_image_model_correctness_test.py"],
-    additional_deps = [
-        ":keras_correctness_test_lib",
-    ],
-    # Shard count is set to an odd number to distribute tasks across
-    # shards more evenly.
-    shard_count = 31,
-    tags = [
-        "multi_and_single_gpu",
-        "no_oss",  # TODO(b/117919883): Fix python error.
-        "no_pip",
-        "no_windows_gpu",
-        "notsan",
-    ],
-)
-
 py_library(
     name = "metrics_v1_test_lib",
     testonly = 1,
diff --git a/tensorflow/contrib/distribute/python/keras_correctness_test_base.py b/tensorflow/contrib/distribute/python/keras_correctness_test.py
similarity index 58%
rename from tensorflow/contrib/distribute/python/keras_correctness_test_base.py
rename to tensorflow/contrib/distribute/python/keras_correctness_test.py
index e13984b26d..3abdee2c0e 100644
--- a/tensorflow/contrib/distribute/python/keras_correctness_test_base.py
+++ b/tensorflow/contrib/distribute/python/keras_correctness_test.py
@@ -30,6 +30,8 @@ from tensorflow.python.eager import context
 from tensorflow.python.eager import test
 from tensorflow.python.framework import random_seed
 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.training import gradient_descent
 
 _RANDOM_SEED = 1337
 _EVAL_STEPS = 20
@@ -60,13 +62,24 @@ def all_strategy_combinations_with_graph_mode():
   return combinations.combine(distribution=all_strategies, mode=['graph'])
 
 
-def all_strategy_and_input_config_combinations():
+def strategy_and_input_combinations():
+  def cnn_model_with_batch_norm(**kwargs):
+    return _create_cnn_model(with_batch_norm=True, **kwargs)
+
   return (
       combinations.times(
           combinations.combine(distribution=all_strategies),
           combinations.combine(mode=['graph', 'eager'],
                                use_numpy=[True, False],
-                               use_validation_data=[True, False])))
+                               use_validation_data=[True, False]),
+          combinations.combine(model_with_data=[
+              ModelWithData('dnn', _create_dnn_model, _dnn_training_data),
+              ModelWithData('cnn', _create_cnn_model, _cnn_training_data),
+              ModelWithData('cnn_batch_norm',
+                            cnn_model_with_batch_norm,
+                            _cnn_training_data,
+                            with_batch_norm=True),
+          ])))
 
 
 class MaybeDistributionScope(object):
@@ -87,6 +100,97 @@ class MaybeDistributionScope(object):
       self._scope = None
 
 
+class ModelWithData(object):
+  """An object giving a good name in combinations.
+
+  The model_fn must take two arguments: initial_weights and distribution.
+  """
+
+  def __init__(self, name, model_fn, data_fn, with_batch_norm=False):
+    self.name = name
+    self.model_fn = model_fn
+    self.data_fn = data_fn
+    self.with_batch_norm = with_batch_norm
+
+  def __repr__(self):
+    return self.name
+
+
+def _dnn_training_data():
+  # TODO(xiejw): Change this back to 10000, once we support final partial
+  # batch.
+  num_samples = 9984
+  x_train = np.random.rand(num_samples, 1)
+  y_train = 3 * x_train
+  x_train = x_train.astype('float32')
+  y_train = y_train.astype('float32')
+  x_predict = [[1.], [2.], [3.], [4.]]
+  return x_train, y_train, x_predict
+
+
+def _create_dnn_model(initial_weights=None, distribution=None):
+  with MaybeDistributionScope(distribution):
+    # We add few non-linear layers to make it non-trivial.
+    model = keras.Sequential()
+    model.add(keras.layers.Dense(10, activation='relu', input_shape=(1,)))
+    model.add(keras.layers.Dense(10, activation='relu'))
+    model.add(keras.layers.Dense(10, activation='relu'))
+    model.add(keras.layers.Dense(1))
+
+    if initial_weights:
+      model.set_weights(initial_weights)
+
+    model.compile(
+        loss=keras.losses.mean_squared_error,
+        optimizer=gradient_descent_keras.SGD(0.5),
+        metrics=['mse'])
+    return model
+
+
+def _cnn_training_data(count=_GLOBAL_BATCH_SIZE * _EVAL_STEPS,
+                       shape=(28, 28, 3), num_classes=10):
+  centers = np.random.randn(num_classes, *shape)
+
+  features = []
+  labels = []
+  for _ in range(count):
+    label = np.random.randint(0, num_classes, size=1)[0]
+    offset = np.random.normal(loc=0, scale=0.1, size=np.prod(shape))
+    offset = offset.reshape(shape)
+    labels.append(label)
+    features.append(centers[label] + offset)
+
+  x_train = np.asarray(features, dtype=np.float32)
+  y_train = np.asarray(labels, dtype=np.float32).reshape((count, 1))
+  x_predict = x_train
+  return x_train, y_train, x_predict
+
+
+def _create_cnn_model(initial_weights=None, distribution=None,
+                      with_batch_norm=False):
+  with MaybeDistributionScope(distribution):
+    image = keras.layers.Input(shape=(28, 28, 3), name='image')
+    c1 = keras.layers.Conv2D(
+        name='conv1', filters=16, kernel_size=(3, 3), strides=(4, 4))(
+            image)
+    if with_batch_norm:
+      c1 = keras.layers.BatchNormalization(name='bn1')(c1)
+    c1 = keras.layers.MaxPooling2D(pool_size=(2, 2))(c1)
+    logits = keras.layers.Dense(
+        10, activation='softmax', name='pred')(
+            keras.layers.Flatten()(c1))
+    model = keras.Model(inputs=[image], outputs=[logits])
+
+    if initial_weights:
+      model.set_weights(initial_weights)
+
+    model.compile(
+        optimizer=gradient_descent.GradientDescentOptimizer(learning_rate=0.1),
+        loss='sparse_categorical_crossentropy',
+        metrics=['sparse_categorical_accuracy'])
+  return model
+
+
 def batch_wrapper(dataset, batch_size, distribution, repeat=None):
   if repeat:
     dataset = dataset.repeat(repeat)
@@ -246,11 +350,6 @@ def compare_results(results_with_ds, results_without_ds, distribution,
         msg='Fail to assert {}.'.format(key))
 
 
-def should_skip_tpu_with_eager(distribution):
-  return (context.executing_eagerly() and
-          isinstance(distribution, tpu_strategy.TPUStrategy))
-
-
 class LearningRateBatchScheduler(keras.callbacks.Callback):
 
   def __init__(self, update_freq=None):
@@ -265,60 +364,107 @@ class LearningRateBatchScheduler(keras.callbacks.Callback):
     keras.backend.set_value(self.model.optimizer.lr, lr)
 
 
-class TestDistributionStrategyCorrectnessBase(test.TestCase,
-                                              parameterized.TestCase):
-  """Model agnostic testing infra to test correctness of Keras models."""
-
-  def set_up_test_config(self, use_numpy=False,
-                         use_validation_data=False,
-                         with_batch_norm=False):
-    self.use_numpy = use_numpy
-    self.use_validation_data = use_validation_data
-    self.with_batch_norm = with_batch_norm
+class TestDistributionStrategyCorrectness(test.TestCase,
+                                          parameterized.TestCase):
 
-    keras.backend.set_image_data_format('channels_last')
-    np.random.seed(_RANDOM_SEED)
-    random_seed.set_random_seed(_RANDOM_SEED)
+  def _should_skip_tpu_with_eager(self, distribution):
+    return (context.executing_eagerly() and
+            isinstance(distribution, tpu_strategy.TPUStrategy))
 
-  def get_data(self):
-    num_samples = 10000
-    x_train = np.random.randint(0, 2, num_samples)
-    x_train = np.reshape(x_train, (num_samples, 1))
-    y_train = x_train
-    return (x_train.astype('float32'), y_train.astype('float32'), None)
+  @combinations.generate(all_strategy_combinations_with_eager_and_graph_modes())
+  def test_metric_correctness(self, distribution):
+    if self._should_skip_tpu_with_eager(distribution):
+      self.skipTest('TPUStrategy does not support eager mode now.')
 
-  def get_model(self, distribution=None):
-    raise NotImplementedError
+    with self.cached_session():
+      keras.backend.set_image_data_format('channels_last')
+      num_samples = 10000
+
+      x_train = np.random.randint(0, 2, num_samples)
+      x_train = np.reshape(x_train, (num_samples, 1))
+      y_train = x_train
+      x_train = x_train.astype('float32')
+      y_train = y_train.astype('float32')
+
+      # Create identity model.
+      with distribution.scope():
+        model = keras.Sequential()
+        model.add(
+            keras.layers.Dense(1, input_shape=(1,), kernel_initializer='ones'))
+        model.compile(
+            loss=keras.losses.mean_squared_error,
+            optimizer=gradient_descent.GradientDescentOptimizer(0.5),
+            metrics=[keras.metrics.BinaryAccuracy()])
+
+      batch_size = 64
+      batch_size = get_batch_size(batch_size, distribution)
+      train_dataset = dataset_ops.Dataset.from_tensor_slices((x_train, y_train))
+      train_dataset = batch_wrapper(train_dataset, batch_size, distribution)
+
+      history = model.fit(x=train_dataset, epochs=2, steps_per_epoch=10)
+      self.assertEqual(history.history['binary_accuracy'], [1.0, 1.0])
+
+  @combinations.generate(all_strategy_combinations_with_eager_and_graph_modes())
+  def test_eval_metrics_correctness(self, distribution):
+    if self._should_skip_tpu_with_eager(distribution):
+      self.skipTest('TPUStrategy does not support eager mode now.')
 
-  def skip_unsupported_test_configuration(self, distribution):
-    if should_skip_tpu_with_eager(distribution):
+    with self.cached_session():
+      with distribution.scope():
+        model = keras.Sequential()
+        model.add(
+            keras.layers.Dense(
+                3, activation='relu', input_dim=4, kernel_initializer='ones'))
+        model.add(
+            keras.layers.Dense(
+                1, activation='sigmoid', kernel_initializer='ones'))
+        model.compile(
+            loss='mae',
+            metrics=['accuracy', keras.metrics.BinaryAccuracy()],
+            optimizer=gradient_descent.GradientDescentOptimizer(0.001))
+
+      # verify correctness of stateful and stateless metrics.
+      x = np.ones((100, 4)).astype('float32')
+      y = np.ones((100, 1)).astype('float32')
+      dataset = dataset_ops.Dataset.from_tensor_slices((x, y)).repeat()
+      dataset = batch_wrapper(dataset, 4, distribution)
+      outs = model.evaluate(dataset, steps=10)
+      self.assertEqual(outs[1], 1.)
+      self.assertEqual(outs[2], 1.)
+
+      y = np.zeros((100, 1)).astype('float32')
+      dataset = dataset_ops.Dataset.from_tensor_slices((x, y)).repeat()
+      dataset = batch_wrapper(dataset, 4, distribution)
+      outs = model.evaluate(dataset, steps=10)
+      self.assertEqual(outs[1], 0.)
+      self.assertEqual(outs[2], 0.)
+
+  @combinations.generate(strategy_and_input_combinations())
+  def test_correctness(self, distribution, use_numpy, use_validation_data,
+                       model_with_data):
+    if self._should_skip_tpu_with_eager(distribution):
       self.skipTest('TPUStrategy does not support eager mode now.')
 
-    if context.executing_eagerly() and self.use_numpy:
+    if context.executing_eagerly() and use_numpy:
       self.skipTest('Numpy as inputs is not supported with strategy in eager.')
 
-    if context.executing_eagerly() and self.use_validation_data:
-      self.skipTest('TODO(hongjunchoi): Add test logic for using validation '
-                    'data for eager execution.')
-    return
+    if context.executing_eagerly() and use_validation_data:
+      self.skipTest('TODO')
 
-  def run_correctness_test(self,
-                           distribution,
-                           use_numpy,
-                           use_validation_data,
-                           with_batch_norm=False):
     with self.cached_session():
-      self.set_up_test_config(use_numpy, use_validation_data, with_batch_norm)
-      self.skip_unsupported_test_configuration(distribution)
+      keras.backend.set_image_data_format('channels_last')
+      np.random.seed(_RANDOM_SEED)
+      random_seed.set_random_seed(_RANDOM_SEED)
 
+      model_fn, data_fn = model_with_data.model_fn, model_with_data.data_fn
       # Train, eval, and predict datasets are created with the same input numpy
       # arrays.
-      x_train, y_train, x_predict = self.get_data()
+      x_train, y_train, x_predict = data_fn()
 
       # The model is built once and the initial weights are saved.
       # This is used to initialize the model for both the distribution and
       # non-distribution run.
-      model = self.get_model()
+      model = model_fn()
       initial_weights = model.get_weights()
 
       def input_fn(dist):
@@ -326,15 +472,15 @@ class TestDistributionStrategyCorrectnessBase(test.TestCase,
             use_numpy, use_validation_data, dist, x_train, y_train, x_predict)
 
       results_with_ds = fit_eval_and_predict(
-          initial_weights, input_fn=input_fn, model_fn=self.get_model,
+          initial_weights, input_fn=input_fn, model_fn=model_fn,
           distribution=distribution)
       results_without_ds = fit_eval_and_predict(
-          initial_weights, input_fn=input_fn, model_fn=self.get_model,
+          initial_weights, input_fn=input_fn, model_fn=model_fn,
           distribution=None)
 
       # First, special case, for multi-replica distributed training, batch norm
       # is not aggregated globally. So it is expected to have different weights.
-      if (self.with_batch_norm and
+      if (model_with_data.with_batch_norm and
           distribution.num_replicas_in_sync > 1):
         with self.assertRaises(AssertionError):
           compare_results(results_with_ds, results_without_ds, distribution,
@@ -343,16 +489,21 @@ class TestDistributionStrategyCorrectnessBase(test.TestCase,
         compare_results(results_with_ds, results_without_ds, distribution,
                         testcase=self)
 
-  def run_dynamic_lr_test(self, distribution):
+  @combinations.generate(all_strategy_combinations_with_graph_mode())
+  def test_dynamic_lr(self, distribution):
+
     with self.cached_session():
-      self.set_up_test_config()
-      self.skip_unsupported_test_configuration(distribution)
 
-      x_train, y_train, _ = self.get_data()
-      model = self.get_model()
+      keras.backend.set_image_data_format('channels_last')
+      np.random.seed(_RANDOM_SEED)
+      random_seed.set_random_seed(_RANDOM_SEED)
+
+      x_train, y_train, _ = _dnn_training_data()
+
+      model = _create_dnn_model()
       initial_weights = model.get_weights()
-      update_freq = None
 
+      update_freq = None
       if (isinstance(distribution, tpu_strategy.TPUStrategy) and
           distribution.extended.steps_per_run > 1):
         # For TPUStrategy with steps_per_run > 1, the callback is not invoked
@@ -379,10 +530,10 @@ class TestDistributionStrategyCorrectnessBase(test.TestCase,
         return training_inputs, eval_inputs, predict_inputs
 
       results_with_ds = fit_eval_and_predict(
-          initial_weights, input_fn=input_fn, model_fn=self.get_model,
+          initial_weights, input_fn=input_fn, model_fn=_create_dnn_model,
           distribution=distribution)
       results_without_ds = fit_eval_and_predict(
-          initial_weights, input_fn=input_fn, model_fn=self.get_model,
+          initial_weights, input_fn=input_fn, model_fn=_create_dnn_model,
           distribution=None)
       compare_results(results_with_ds, results_without_ds, distribution,
                       testcase=self)
diff --git a/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py b/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py
deleted file mode 100644
index f21b1c6752..0000000000
--- a/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py
+++ /dev/null
@@ -1,163 +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.
-# ==============================================================================
-"""Correctness tests for tf.keras DNN model using DistributionStrategy."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import numpy as np
-
-from tensorflow.contrib.distribute.python import combinations
-from tensorflow.contrib.distribute.python import keras_correctness_test_base
-from tensorflow.python import keras
-from tensorflow.python.data.ops import dataset_ops
-from tensorflow.python.eager import test
-from tensorflow.python.keras.optimizer_v2 import gradient_descent as gradient_descent_keras
-from tensorflow.python.training import gradient_descent
-
-
-class TestDistributionStrategyDnnCorrectness(
-    keras_correctness_test_base.TestDistributionStrategyCorrectnessBase):
-
-  def get_model(self, initial_weights=None, distribution=None):
-    with keras_correctness_test_base.MaybeDistributionScope(distribution):
-      # We add few non-linear layers to make it non-trivial.
-      model = keras.Sequential()
-      model.add(keras.layers.Dense(10, activation='relu', input_shape=(1,)))
-      model.add(keras.layers.Dense(10, activation='relu'))
-      model.add(keras.layers.Dense(10, activation='relu'))
-      model.add(keras.layers.Dense(1))
-
-      if initial_weights:
-        model.set_weights(initial_weights)
-
-      model.compile(
-          loss=keras.losses.mean_squared_error,
-          optimizer=gradient_descent_keras.SGD(0.5),
-          metrics=['mse'])
-      return model
-
-  def get_data(self):
-    # TODO(xiejw): Change this back to 10000, once we support final partial
-    # batch.
-    num_samples = 9984
-    x_train = np.random.rand(num_samples, 1)
-    y_train = 3 * x_train
-    x_train = x_train.astype('float32')
-    y_train = y_train.astype('float32')
-    x_predict = [[1.], [2.], [3.], [4.]]
-    return x_train, y_train, x_predict
-
-  @combinations.generate(keras_correctness_test_base.
-                         all_strategy_and_input_config_combinations())
-  def test_dnn_correctness(self, distribution, use_numpy, use_validation_data):
-    self.run_correctness_test(distribution, use_numpy, use_validation_data)
-
-  @combinations.generate(keras_correctness_test_base.
-                         all_strategy_combinations_with_graph_mode())
-  def test_dnn_with_dynamic_learning_rate(self, distribution):
-    self.run_dynamic_lr_test(distribution)
-
-
-class TestDistributionStrategyDnnMetricCorrectness(
-    keras_correctness_test_base.TestDistributionStrategyCorrectnessBase):
-
-  def get_model(self, distribution=None):
-    with distribution.scope():
-      model = keras.Sequential()
-      model.add(keras.layers.Dense(1,
-                                   input_shape=(1,),
-                                   kernel_initializer='ones'))
-      model.compile(
-          loss=keras.losses.mean_squared_error,
-          optimizer=gradient_descent.GradientDescentOptimizer(0.5),
-          metrics=[keras.metrics.BinaryAccuracy()])
-    return model
-
-  def run_metric_correctness_test(self, distribution):
-    with self.cached_session():
-      self.set_up_test_config()
-      self.skip_unsupported_test_configuration(distribution)
-
-      x_train, y_train, _ = self.get_data()
-      model = self.get_model(distribution=distribution)
-
-      batch_size = 64
-      batch_size = (keras_correctness_test_base.
-                    get_batch_size(batch_size, distribution))
-      train_dataset = dataset_ops.Dataset.from_tensor_slices((x_train, y_train))
-      train_dataset = (keras_correctness_test_base.
-                       batch_wrapper(train_dataset, batch_size, distribution))
-
-      history = model.fit(x=train_dataset, epochs=2, steps_per_epoch=10)
-      self.assertEqual(history.history['binary_accuracy'], [1.0, 1.0])
-
-  @combinations.generate(keras_correctness_test_base.
-                         all_strategy_combinations_with_eager_and_graph_modes())
-  def test_simple_dnn_metric_correctness(self, distribution):
-    self.run_metric_correctness_test(distribution)
-
-
-class TestDistributionStrategyDnnMetricEvalCorrectness(
-    keras_correctness_test_base.TestDistributionStrategyCorrectnessBase):
-
-  def get_model(self, distribution=None):
-    with distribution.scope():
-      model = keras.Sequential()
-      model.add(
-          keras.layers.Dense(
-              3, activation='relu', input_dim=4, kernel_initializer='ones'))
-      model.add(
-          keras.layers.Dense(
-              1, activation='sigmoid', kernel_initializer='ones'))
-      model.compile(
-          loss='mae',
-          metrics=['accuracy', keras.metrics.BinaryAccuracy()],
-          optimizer=gradient_descent.GradientDescentOptimizer(0.001))
-    return model
-
-  def run_eval_metrics_correctness_test(self, distribution):
-    with self.cached_session():
-      self.set_up_test_config()
-      self.skip_unsupported_test_configuration(distribution)
-
-      model = self.get_model(distribution=distribution)
-
-      # verify correctness of stateful and stateless metrics.
-      x = np.ones((100, 4)).astype('float32')
-      y = np.ones((100, 1)).astype('float32')
-      dataset = dataset_ops.Dataset.from_tensor_slices((x, y)).repeat()
-      dataset = (keras_correctness_test_base.
-                 batch_wrapper(dataset, 4, distribution))
-      outs = model.evaluate(dataset, steps=10)
-      self.assertEqual(outs[1], 1.)
-      self.assertEqual(outs[2], 1.)
-
-      y = np.zeros((100, 1)).astype('float32')
-      dataset = dataset_ops.Dataset.from_tensor_slices((x, y)).repeat()
-      dataset = (keras_correctness_test_base.
-                 batch_wrapper(dataset, 4, distribution))
-      outs = model.evaluate(dataset, steps=10)
-      self.assertEqual(outs[1], 0.)
-      self.assertEqual(outs[2], 0.)
-
-  @combinations.generate(keras_correctness_test_base.
-                         all_strategy_combinations_with_eager_and_graph_modes())
-  def test_identity_model_metric_eval_correctness(self, distribution):
-    self.run_eval_metrics_correctness_test(distribution)
-
-
-if __name__ == '__main__':
-  test.main()
diff --git a/tensorflow/contrib/distribute/python/keras_image_model_correctness_test.py b/tensorflow/contrib/distribute/python/keras_image_model_correctness_test.py
deleted file mode 100644
index f625664372..0000000000
--- a/tensorflow/contrib/distribute/python/keras_image_model_correctness_test.py
+++ /dev/null
@@ -1,92 +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.
-# ==============================================================================
-"""Correctness tests for tf.keras CNN models using DistributionStrategy."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import numpy as np
-
-from tensorflow.contrib.distribute.python import combinations
-from tensorflow.contrib.distribute.python import keras_correctness_test_base
-from tensorflow.python import keras
-from tensorflow.python.eager import test
-from tensorflow.python.training import gradient_descent
-
-
-class DistributionStrategyCnnCorrectnessTest(
-    keras_correctness_test_base.TestDistributionStrategyCorrectnessBase):
-
-  def get_model(self, initial_weights=None, distribution=None):
-    with keras_correctness_test_base.MaybeDistributionScope(distribution):
-      image = keras.layers.Input(shape=(28, 28, 3), name='image')
-      c1 = keras.layers.Conv2D(
-          name='conv1', filters=16, kernel_size=(3, 3), strides=(4, 4))(
-              image)
-      if self.with_batch_norm:
-        c1 = keras.layers.BatchNormalization(name='bn1')(c1)
-      c1 = keras.layers.MaxPooling2D(pool_size=(2, 2))(c1)
-      logits = keras.layers.Dense(
-          10, activation='softmax', name='pred')(
-              keras.layers.Flatten()(c1))
-      model = keras.Model(inputs=[image], outputs=[logits])
-
-      if initial_weights:
-        model.set_weights(initial_weights)
-
-      model.compile(
-          optimizer=gradient_descent.GradientDescentOptimizer(
-              learning_rate=0.1),
-          loss='sparse_categorical_crossentropy',
-          metrics=['sparse_categorical_accuracy'])
-
-    return model
-
-  def get_data(self,
-               count=keras_correctness_test_base._GLOBAL_BATCH_SIZE
-               * keras_correctness_test_base._EVAL_STEPS,
-               shape=(28, 28, 3),
-               num_classes=10):
-    centers = np.random.randn(num_classes, *shape)
-
-    features = []
-    labels = []
-    for _ in range(count):
-      label = np.random.randint(0, num_classes, size=1)[0]
-      offset = np.random.normal(loc=0, scale=0.1, size=np.prod(shape))
-      offset = offset.reshape(shape)
-      labels.append(label)
-      features.append(centers[label] + offset)
-
-    x_train = np.asarray(features, dtype=np.float32)
-    y_train = np.asarray(labels, dtype=np.float32).reshape((count, 1))
-    x_predict = x_train
-    return x_train, y_train, x_predict
-
-  @combinations.generate(keras_correctness_test_base.
-                         all_strategy_and_input_config_combinations())
-  def test_cnn_correctness(self, distribution, use_numpy, use_validation_data):
-    self.run_correctness_test(distribution, use_numpy, use_validation_data)
-
-  @combinations.generate(keras_correctness_test_base.
-                         all_strategy_and_input_config_combinations())
-  def test_cnn_with_batch_norm_correctness(self, distribution, use_numpy,
-                                           use_validation_data):
-    self.run_correctness_test(distribution, use_numpy, use_validation_data,
-                              with_batch_norm=True)
-
-
-if __name__ == '__main__':
-  test.main()
-- 
GitLab


From a76a7e8545c6f0255d7607ca817fa92ed7b08349 Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 12:29:21 -0800
Subject: [PATCH 0504/2345] Use CUB-based row reduction for sparse_xent_op. The
 Eigen implementation seems to trigger a bug on Volta GPUs.

PiperOrigin-RevId: 228756250
---
 tensorflow/core/kernels/BUILD                 |  5 ++-
 tensorflow/core/kernels/sparse_xent_op.cc     |  9 +++--
 tensorflow/core/kernels/sparse_xent_op.h      | 29 ++++++++++++++--
 .../core/kernels/sparse_xent_op_gpu.cu.cc     | 34 +++++++++++++++++--
 4 files changed, 65 insertions(+), 12 deletions(-)

diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD
index 5794ba520e..1a20e8628e 100644
--- a/tensorflow/core/kernels/BUILD
+++ b/tensorflow/core/kernels/BUILD
@@ -4487,7 +4487,10 @@ tf_kernel_library(
     deps = SPARSE_DEPS + [
         ":bounds_check",
         "//third_party/eigen3",
-    ],
+    ] + if_cuda([
+        ":reduction_ops",
+        "@cub_archive//:cub",
+    ]),
 )
 
 tf_kernel_library(
diff --git a/tensorflow/core/kernels/sparse_xent_op.cc b/tensorflow/core/kernels/sparse_xent_op.cc
index f84ffd5323..37d4d0661c 100644
--- a/tensorflow/core/kernels/sparse_xent_op.cc
+++ b/tensorflow/core/kernels/sparse_xent_op.cc
@@ -90,9 +90,8 @@ class SparseSoftmaxXentWithLogitsOp : public OpKernel {
             context, CheckInvalidLabelIndex(labels, logits.dim_size(1)));
       }
       functor::SparseXentFunctor functor;
-      functor(context->eigen_device(), logits.matrix(),
-              labels.vec(), scratch.vec(), loss_out->vec(),
-              back_out->matrix());
+      functor(context, logits.matrix(), labels.vec(),
+              scratch.vec(), loss_out->vec(), back_out->matrix());
     }
   }
 };
@@ -102,11 +101,11 @@ class SparseSoftmaxXentWithLogitsOp : public OpKernel {
 namespace functor {
 template 
 struct SparseXentFunctor {
-  void operator()(const CPUDevice& d, typename TTypes::ConstMatrix logits,
+  void operator()(OpKernelContext* ctx, typename TTypes::ConstMatrix logits,
                   typename TTypes::ConstVec labels,
                   typename TTypes::Vec scratch, typename TTypes::Vec loss,
                   typename TTypes::Matrix backprop) {
-    SparseXentEigenImpl::Compute(d, logits, labels,
+    SparseXentEigenImpl::Compute(ctx, logits, labels,
                                                       scratch, loss, backprop);
   }
 };
diff --git a/tensorflow/core/kernels/sparse_xent_op.h b/tensorflow/core/kernels/sparse_xent_op.h
index 6ba7931ab5..5e462424ed 100644
--- a/tensorflow/core/kernels/sparse_xent_op.h
+++ b/tensorflow/core/kernels/sparse_xent_op.h
@@ -18,6 +18,7 @@ limitations under the License.
 // Functor definition for SparseXentOp, must be compilable by nvcc.
 
 #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
+#include "tensorflow/core/framework/op_kernel.h"
 #include "tensorflow/core/framework/tensor_types.h"
 #include "tensorflow/core/kernels/bounds_check.h"
 #include "tensorflow/core/platform/macros.h"
@@ -128,6 +129,26 @@ class SparseXentGradGenerator {
 
 namespace functor {
 
+template 
+struct RowMaxReduction {
+  // Computes the maximum across the rows of logits
+  //
+  // logits: batch_size, num_classes.
+  // maximum: temporary tensor, dims: batch_size, 1
+  static inline void Compute(OpKernelContext* ctx,
+                             typename TTypes::ConstMatrix logits,
+                             typename TTypes::Vec maximum) {
+#if !defined(EIGEN_HAS_INDEX_LIST)
+    Eigen::array along_row;
+    along_row[0] = 1;
+#else
+    Eigen::IndexList > along_row;
+#endif
+    Device d = ctx->eigen_device();
+    To32Bit(maximum).device(d) = To32Bit(logits).maximum(along_row);
+  }
+};
+
 // Functor used by SparseXentOp to do the computations.
 template 
 struct SparseXentFunctor {
@@ -138,7 +159,7 @@ struct SparseXentFunctor {
   // scratch: temporary tensor, dims: batch_size, 1
   // loss: output tensor for the loss, dims: batch_size.
   // backprop: output tensor for the backprop, dims: batch_size, num_classes.
-  void operator()(const Device& d, typename TTypes::ConstMatrix logits,
+  void operator()(OpKernelContext* ctx, typename TTypes::ConstMatrix logits,
                   typename TTypes::ConstVec labels,
                   typename TTypes::Vec scratch, typename TTypes::Vec loss,
                   typename TTypes::Matrix backprop);
@@ -149,7 +170,8 @@ struct SparseXentFunctor {
 // specializations for both device types.
 template 
 struct SparseXentEigenImpl {
-  static void Compute(const Device& d, typename TTypes::ConstMatrix logits,
+  static void Compute(OpKernelContext* ctx,
+                      typename TTypes::ConstMatrix logits,
                       typename TTypes::ConstVec labels,
                       typename TTypes::Vec scratch,
                       typename TTypes::Vec loss,
@@ -188,8 +210,9 @@ struct SparseXentEigenImpl {
 #endif
 
     // scratch = max_logits along classes.
-    To32Bit(scratch).device(d) = To32Bit(logits).maximum(along_class);
+    RowMaxReduction::Compute(ctx, logits, scratch);
 
+    Device d = ctx->eigen_device();
     // backprop = logits - max_logits.
     To32Bit(backprop).device(d) =
         To32Bit(logits) -
diff --git a/tensorflow/core/kernels/sparse_xent_op_gpu.cu.cc b/tensorflow/core/kernels/sparse_xent_op_gpu.cu.cc
index d053966028..5fe15352c3 100644
--- a/tensorflow/core/kernels/sparse_xent_op_gpu.cu.cc
+++ b/tensorflow/core/kernels/sparse_xent_op_gpu.cu.cc
@@ -20,22 +20,50 @@ limitations under the License.
 #include "tensorflow/core/kernels/sparse_xent_op.h"
 
 #include "tensorflow/core/framework/tensor_types.h"
+#include "tensorflow/core/kernels/reduction_gpu_kernels.cu.h"
+#include "tensorflow/core/kernels/reduction_ops_common.h"
 #include "tensorflow/core/platform/types.h"
 
 namespace tensorflow {
 
 typedef Eigen::GpuDevice GPUDevice;
 
+namespace functor {
+
+// Partial specialization for a GPUDevice, that uses the CUB implementation
+// from reduction_gpu_kernels.cu.h.
+template 
+struct RowMaxReduction {
+  // Computes the maximum across the rows of logits
+  //
+  // logits: batch_size, num_classes.
+  // maximum: temporary tensor, dims: batch_size, 1
+  static inline void Compute(OpKernelContext* ctx,
+                             typename TTypes::ConstMatrix logits,
+                             typename TTypes::Vec maximum) {
+    const int kBatchDim = 0;
+    const int kClassDim = 1;
+    const int rows = logits.dimension(kBatchDim);
+    const int cols = logits.dimension(kClassDim);
+
+    typedef const Eigen::array::Tensor::Index, 1>& ReductionAxes;
+    Constants constants;
+    cub::Max op;
+    functor::ReduceImpl(
+        ctx, maximum.data(), logits.data(), 2, rows, cols, 1, 1, constants.kOne,
+        op);
+  }
+};
+
 // Partial specialization for a GPUDevice, that uses the Eigen implementation
 // from XentEigenImpl.
-namespace functor {
 template 
 struct SparseXentFunctor {
-  void operator()(const GPUDevice& d, typename TTypes::ConstMatrix logits,
+  void operator()(OpKernelContext* ctx, typename TTypes::ConstMatrix logits,
                   typename TTypes::ConstVec labels,
                   typename TTypes::Vec scratch, typename TTypes::Vec loss,
                   typename TTypes::Matrix backprop) {
-    SparseXentEigenImpl::Compute(d, logits, labels,
+    SparseXentEigenImpl::Compute(ctx, logits, labels,
                                                       scratch, loss, backprop);
   }
 };
-- 
GitLab


From 0b3c3c55e177b35d38ba33170ebe2baa3f5badff Mon Sep 17 00:00:00 2001
From: Sanjoy Das 
Date: Thu, 10 Jan 2019 12:30:40 -0800
Subject: [PATCH 0505/2345] Be resilient towards graphs without source/sink
 connectivity.

PiperOrigin-RevId: 228756459
---
 tensorflow/compiler/jit/mark_for_compilation_pass.cc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tensorflow/compiler/jit/mark_for_compilation_pass.cc b/tensorflow/compiler/jit/mark_for_compilation_pass.cc
index 6618e3a58a..50afec020d 100644
--- a/tensorflow/compiler/jit/mark_for_compilation_pass.cc
+++ b/tensorflow/compiler/jit/mark_for_compilation_pass.cc
@@ -677,6 +677,11 @@ Status MarkForCompilationPass::Run(
   VLOG(1) << "flags->tf_xla_auto_jit = " << flags->tf_xla_auto_jit;
   const FunctionLibraryDefinition* fld = options.flib_def;
 
+  // Deadness analysis expects a graph with source and sink edges properly
+  // connected but sometimes the incoming graph does not follow this invariant.
+  // So fix up the source and sink edges before calling into deadness analysis.
+  FixupSourceAndSinkEdges(options.graph->get());
+
   std::unique_ptr deadness;
   {
     XLA_SCOPED_LOGGING_TIMER_LEVEL("DeadnessAnalysis", 1);
-- 
GitLab


From 9c45814aac694a6454d312225121dd4c4cd33de9 Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 12:45:38 -0800
Subject: [PATCH 0506/2345] Update documentation of `tf.distribute.Strategy
 methods` to clarify and/or correct whether they expect global or per-replica
 batching.

PiperOrigin-RevId: 228758901
---
 .../python/collective_all_reduce_strategy.py  |  8 ++++
 .../distribute/python/mirrored_strategy.py    | 26 +++++++++++
 .../distribute/python/one_device_strategy.py  |  1 +
 .../python/parameter_server_strategy.py       | 26 +++++++++++
 .../contrib/distribute/python/tpu_strategy.py |  9 +++-
 .../python/distribute/distribute_lib.py       | 44 ++++++++++++-------
 .../python/distribute/mirrored_strategy.py    |  8 ++++
 .../distribute/parameter_server_strategy.py   |  8 ++++
 8 files changed, 113 insertions(+), 17 deletions(-)

diff --git a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py
index a3538dfc50..eee0754325 100644
--- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py
+++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py
@@ -372,4 +372,12 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended):
   # 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.
+
+    `distribute_dataset` and `make_input_fn_iterator` assume per-replica
+    batching.
+
+    Returns:
+      Boolean.
+    """
     return True
diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy.py b/tensorflow/contrib/distribute/python/mirrored_strategy.py
index 5fa36fb402..2e23a51ee5 100644
--- a/tensorflow/contrib/distribute/python/mirrored_strategy.py
+++ b/tensorflow/contrib/distribute/python/mirrored_strategy.py
@@ -104,6 +104,31 @@ class MirroredStrategy(distribute_lib.DistributionStrategy):
                                 auto_shard_dataset)
     super(MirroredStrategy, self).__init__(extended)
 
+  # Override to change the documentation to reflect the different handling of
+  # global vs. local batch size between core and contrib.
+  def make_dataset_iterator(self, dataset):  # pylint: disable=useless-super-delegation
+    """Makes an iterator for input provided via `dataset`.
+
+    NOTE: The batch size of the `dataset` argument is treated differently for
+    this contrib version of `MirroredStrategy`.
+
+    Data from the given dataset will be distributed evenly across all the
+    compute replicas. We will assume that the input dataset is batched by the
+    per-replica batch size.
+
+    The user could also use `make_input_fn_iterator` if they want to
+    customize which input is fed to which replica/worker etc.
+
+    Args:
+      dataset: `tf.data.Dataset` that will be distributed evenly across all
+        replicas.
+
+    Returns:
+      An `tf.distribute.InputIterator` which returns inputs for each step of the
+      computation.  User should call `initialize` on the returned iterator.
+    """
+    return super(MirroredStrategy, self).make_dataset_iterator(dataset)
+
   # Override to change the documentation to reflect the different handling of
   # global vs. local batch size between core and contrib.
   def experimental_make_numpy_iterator(  # pylint: disable=useless-super-delegation
@@ -180,4 +205,5 @@ class MirroredExtended(CoreMirroredExtended):
   # TODO(priyag): Delete this once all strategies use global batch size.
   @property
   def _global_batch_size(self):
+    """The contrib version of Mirrored strategy uses per-replica batch size."""
     return False
diff --git a/tensorflow/contrib/distribute/python/one_device_strategy.py b/tensorflow/contrib/distribute/python/one_device_strategy.py
index 34b0c31087..836cb7cc41 100644
--- a/tensorflow/contrib/distribute/python/one_device_strategy.py
+++ b/tensorflow/contrib/distribute/python/one_device_strategy.py
@@ -199,6 +199,7 @@ class OneDeviceExtended(distribute_lib.DistributionStrategyExtended):
   # TODO(priyag): Delete this once all strategies use global batch size.
   @property
   def _global_batch_size(self):
+    """Global and per-replica batching are equivalent for OneDeviceStrategy."""
     return True
 
 
diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy.py b/tensorflow/contrib/distribute/python/parameter_server_strategy.py
index 0785427c2c..0cefef7545 100644
--- a/tensorflow/contrib/distribute/python/parameter_server_strategy.py
+++ b/tensorflow/contrib/distribute/python/parameter_server_strategy.py
@@ -89,6 +89,31 @@ class ParameterServerStrategy(distribute_lib.DistributionStrategy):
     super(ParameterServerStrategy, self).__init__(
         ParameterServerExtended(self, num_gpus_per_worker))
 
+  # Override to change the documentation to reflect the different handling of
+  # global vs. local batch size between core and contrib.
+  def make_dataset_iterator(self, dataset):  # pylint: disable=useless-super-delegation
+    """Makes an iterator for input provided via `dataset`.
+
+    NOTE: The batch size of the `dataset` argument is treated differently for
+    this contrib version of `ParameterServerStrategy`.
+
+    Data from the given dataset will be distributed evenly across all the
+    compute replicas. We will assume that the input dataset is batched by the
+    per-replica batch size.
+
+    The user could also use `make_input_fn_iterator` if they want to
+    customize which input is fed to which replica/worker etc.
+
+    Args:
+      dataset: `tf.data.Dataset` that will be distributed evenly across all
+        replicas.
+
+    Returns:
+      An `tf.distribute.InputIterator` which returns inputs for each step of the
+      computation.  User should call `initialize` on the returned iterator.
+    """
+    return super(ParameterServerStrategy, self).make_dataset_iterator(dataset)
+
   # Override to change the documentation to reflect the different handling of
   # global vs. local batch size between core and contrib.
   def experimental_make_numpy_iterator(  # pylint: disable=useless-super-delegation
@@ -143,4 +168,5 @@ class ParameterServerExtended(CoreParameterServerExtended):
   # TODO(priyag): Delete this once all strategies use global batch size.
   @property
   def _global_batch_size(self):
+    """The contrib version of PS strategy uses per-replica batch size."""
     return False
diff --git a/tensorflow/contrib/distribute/python/tpu_strategy.py b/tensorflow/contrib/distribute/python/tpu_strategy.py
index 3f89c5869e..518c704b89 100644
--- a/tensorflow/contrib/distribute/python/tpu_strategy.py
+++ b/tensorflow/contrib/distribute/python/tpu_strategy.py
@@ -295,7 +295,6 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended):
 
   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)
 
@@ -667,6 +666,14 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended):
   # 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.
+
+    `distribute_dataset` and `make_input_fn_iterator` assume per-replica
+    batching.
+
+    Returns:
+      Boolean.
+    """
     return True
 
 
diff --git a/tensorflow/python/distribute/distribute_lib.py b/tensorflow/python/distribute/distribute_lib.py
index 31213ab647..5fe77e5478 100644
--- a/tensorflow/python/distribute/distribute_lib.py
+++ b/tensorflow/python/distribute/distribute_lib.py
@@ -210,12 +210,14 @@ class _SameScopeAgainContext(object):
 # TODO(yuefengz): add more replication modes.
 @tf_export("distribute.InputReplicationMode")
 class InputReplicationMode(enum.Enum):
-  """Replication mode for input function."""
+  """Replication mode for input function.
 
-  # The input function will be called on each worker independently, creating as
-  # many input pipelines as number of workers. Replicas will dequeue from the
-  # local Dataset on their worker. Distribution Strategy doesn't manage any
-  # state sharing between such separate input pipelines.
+  * `PER_WORKER`: The input function will be called on each worker
+    independently, creating as many input pipelines as number of workers.
+    Replicas will dequeue from the local Dataset on their worker.
+    `tf.distribute.Strategy` doesn't manage any state sharing between such
+    separate input pipelines.
+  """
   PER_WORKER = "PER_WORKER"
 
 
@@ -353,7 +355,8 @@ class DistributionStrategy(object):
     ```
 
     Args:
-      dataset_fn: A function that returns a `tf.data.Dataset`.
+      dataset_fn: A function that returns a `tf.data.Dataset` with per-replica
+        batching.
 
     Returns:
       A `PerReplicaDataset` that will produce data for each replica.
@@ -390,28 +393,36 @@ class DistributionStrategy(object):
     """Returns an iterator split across replicas created from an input function.
 
     The `input_fn` should take an `tf.distribute.InputContext` object where
-    information about input sharding can be accessed:
+    information about batching and input sharding can be accessed:
 
     ```
     def input_fn(input_context):
-      d = tf.data.Dataset.from_tensors([[1.]]).repeat()
+      batch_size = input_context.get_per_replica_batch_size(global_batch_size)
+      d = tf.data.Dataset.from_tensors([[1.]]).repeat().batch(batch_size)
       return d.shard(input_context.num_input_pipelines,
                      input_context.input_pipeline_id)
     with strategy.scope():
-      iterator = strategy.make_input_fn_iterator(
-          input_fn)
-      replica_results = strategy.extended.call_for_each_replica(
-          replica_fn, iterator.get_next())
+      iterator = strategy.make_input_fn_iterator(input_fn)
+      replica_results = strategy.experimental_run(replica_fn, iterator)
     ```
 
+    The `tf.data.Dataset` returned by `input_fn` should have a per-replica
+    batch size, which may be computed using
+    `input_context.get_per_replica_batch_size`.
+
     Args:
-      input_fn: A function that returns a `tf.data.Dataset`. This function is
-        expected to take an `tf.distribute.InputContext` object.
+      input_fn: A function taking a `tf.distribute.InputContext` object and
+        returning a `tf.data.Dataset`.
       replication_mode: an enum value of `tf.distribute.InputReplicationMode`.
-        Only `PER_WORKER` is supported currently.
+        Only `PER_WORKER` is supported currently, which means there will be
+        a single call to `input_fn` per worker. Replicas will dequeue from the
+        local `tf.data.Dataset` on their worker.
 
     Returns:
-      An iterator object that can be initialized and fetched next element.
+      An iterator object that should first be `.initialize()`-ed. It may then
+      either be passed to `strategy.experimental_run()` or you can
+      `iterator.get_next()` to get the next value to pass to
+      `strategy.extended.call_for_each_replica()`.
     """
     if replication_mode != InputReplicationMode.PER_WORKER:
       raise ValueError(
@@ -1813,6 +1824,7 @@ class _DefaultDistributionExtended(DistributionStrategyExtended):
   # TODO(priyag): Delete this once all strategies use global batch size.
   @property
   def _global_batch_size(self):
+    """Global and per-replica batching are equivalent for this strategy."""
     return True
 
 
diff --git a/tensorflow/python/distribute/mirrored_strategy.py b/tensorflow/python/distribute/mirrored_strategy.py
index c0a39d4b55..1ed7eef1ed 100644
--- a/tensorflow/python/distribute/mirrored_strategy.py
+++ b/tensorflow/python/distribute/mirrored_strategy.py
@@ -782,6 +782,14 @@ class MirroredExtended(distribute_lib.DistributionStrategyExtended):
   # 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.
+
+    `distribute_dataset` and `make_input_fn_iterator` assume per-replica
+    batching.
+
+    Returns:
+      Boolean.
+    """
     return True
 
 
diff --git a/tensorflow/python/distribute/parameter_server_strategy.py b/tensorflow/python/distribute/parameter_server_strategy.py
index ac5ee6f589..9e865082f1 100644
--- a/tensorflow/python/distribute/parameter_server_strategy.py
+++ b/tensorflow/python/distribute/parameter_server_strategy.py
@@ -536,4 +536,12 @@ class ParameterServerStrategyExtended(
   # 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.
+
+    `distribute_dataset` and `make_input_fn_iterator` assume per-replica
+    batching.
+
+    Returns:
+      Boolean.
+    """
     return True
-- 
GitLab


From a23420e75ddf38b92c93fe80e0d83ea7b70b11b1 Mon Sep 17 00:00:00 2001
From: Karim Nosir 
Date: Thu, 10 Jan 2019 12:47:37 -0800
Subject: [PATCH 0507/2345] Enable test in OSS

PiperOrigin-RevId: 228759239
---
 tensorflow/lite/kernels/BUILD | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD
index 50deac9bb6..7a4b6b8644 100644
--- a/tensorflow/lite/kernels/BUILD
+++ b/tensorflow/lite/kernels/BUILD
@@ -585,9 +585,6 @@ tf_cc_test(
     name = "bidirectional_sequence_rnn_test",
     size = "small",
     srcs = ["bidirectional_sequence_rnn_test.cc"],
-    tags = [
-        "tflite_not_portable",
-    ],
     deps = [
         ":builtin_ops",
         "//tensorflow/lite:framework",
-- 
GitLab


From b1876c7c25cb1d40c79a77c21f1ae05deca36cc4 Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 12:52:05 -0800
Subject: [PATCH 0508/2345] Migrate to new names: * get_distribution_strategy
 -> get_strategy * has_distribution_strategy -> has_strategy

PiperOrigin-RevId: 228759939
---
 tensorflow/contrib/distribute/__init__.py             | 2 ++
 tensorflow/contrib/optimizer_v2/optimizer_v2.py       | 3 +--
 tensorflow/python/distribute/input_lib.py             | 2 +-
 tensorflow/python/distribute/values.py                | 4 ++--
 tensorflow/python/eager/tape.py                       | 4 ++--
 tensorflow/python/framework/test_util.py              | 2 +-
 tensorflow/python/keras/engine/training.py            | 4 ++--
 tensorflow/python/keras/layers/normalization.py       | 9 +++++----
 tensorflow/python/keras/optimizer_v2/optimizer_v2.py  | 8 +++-----
 tensorflow/python/keras/optimizers.py                 | 2 +-
 tensorflow/python/training/optimizer.py               | 5 ++---
 tensorflow/python/training/session_manager.py         | 2 +-
 tensorflow/python/training/slot_creator.py            | 6 ++----
 tensorflow/python/training/sync_replicas_optimizer.py | 3 +--
 14 files changed, 26 insertions(+), 30 deletions(-)

diff --git a/tensorflow/contrib/distribute/__init__.py b/tensorflow/contrib/distribute/__init__.py
index 4c3f9b8f02..1bcc453a7e 100644
--- a/tensorflow/contrib/distribute/__init__.py
+++ b/tensorflow/contrib/distribute/__init__.py
@@ -64,7 +64,9 @@ _allowed_symbols = [
     'get_distribution_strategy',
     'get_loss_reduction',
     'get_replica_context',
+    'get_strategy',
     'has_distribution_strategy',
+    'has_strategy',
     'in_cross_replica_context',
     'require_replica_context',
     'run_standard_tensorflow_server',
diff --git a/tensorflow/contrib/optimizer_v2/optimizer_v2.py b/tensorflow/contrib/optimizer_v2/optimizer_v2.py
index 7fb23abc38..1323ed014c 100644
--- a/tensorflow/contrib/optimizer_v2/optimizer_v2.py
+++ b/tensorflow/contrib/optimizer_v2/optimizer_v2.py
@@ -843,8 +843,7 @@ class OptimizerV2(optimizer_v1.Optimizer):
       scale_loss_by_num_replicas = (
           distribute_lib.get_loss_reduction() == ds_reduce_util.ReduceOp.MEAN)
     if scale_loss_by_num_replicas:
-      num_replicas = \
-        distribute_ctx.get_distribution_strategy().num_replicas_in_sync
+      num_replicas = distribute_ctx.get_strategy().num_replicas_in_sync
       if num_replicas > 1:
         loss_value *= 1. / num_replicas
     return loss_value
diff --git a/tensorflow/python/distribute/input_lib.py b/tensorflow/python/distribute/input_lib.py
index 022595146d..c64eea1604 100644
--- a/tensorflow/python/distribute/input_lib.py
+++ b/tensorflow/python/distribute/input_lib.py
@@ -689,7 +689,7 @@ class MultiStepContext(object):
       if reduce_op is None:
         self._last_step_outputs[name] = output
       else:
-        distribution = distribution_strategy_context.get_distribution_strategy()
+        distribution = distribution_strategy_context.get_strategy()
         self._last_step_outputs[name] = distribution.reduce(reduce_op, output)
     else:
       assert reduce_op is not None
diff --git a/tensorflow/python/distribute/values.py b/tensorflow/python/distribute/values.py
index a9dcabdab6..2a57d5cfc7 100644
--- a/tensorflow/python/distribute/values.py
+++ b/tensorflow/python/distribute/values.py
@@ -411,11 +411,11 @@ def _assign_on_device(device, variable, tensor):
 
 
 def _assert_strategy(strategy):
-  if not distribution_strategy_context.has_distribution_strategy():
+  if not distribution_strategy_context.has_strategy():
     raise RuntimeError(
         'Need to be inside "with strategy.scope()" for %s' %
         (strategy,))
-  current_strategy = distribution_strategy_context.get_distribution_strategy()
+  current_strategy = distribution_strategy_context.get_strategy()
   if current_strategy is not strategy:
     raise RuntimeError(
         "Mixing different tf.distribute.Strategy objects: %s is not %s" %
diff --git a/tensorflow/python/eager/tape.py b/tensorflow/python/eager/tape.py
index e501b403a3..56b68b9eea 100644
--- a/tensorflow/python/eager/tape.py
+++ b/tensorflow/python/eager/tape.py
@@ -61,7 +61,7 @@ def watch(tape, tensor):
 
 def watch_variable(tape, variable):
   """Marks this variable to be watched by the given tape."""
-  strategy = distribution_strategy_context.get_distribution_strategy()
+  strategy = distribution_strategy_context.get_strategy()
   if distribution_strategy_context.get_replica_context():
     variables = [strategy.extended.value_container(variable)]
   else:
@@ -76,7 +76,7 @@ def variable_accessed(variable):
   Args:
     variable: variable to be watched.
   """
-  strategy = distribution_strategy_context.get_distribution_strategy()
+  strategy = distribution_strategy_context.get_strategy()
   if distribution_strategy_context.get_replica_context():
     variables = [strategy.extended.value_container(variable)]
   else:
diff --git a/tensorflow/python/framework/test_util.py b/tensorflow/python/framework/test_util.py
index 21d21cc7f4..6d01d3bf54 100644
--- a/tensorflow/python/framework/test_util.py
+++ b/tensorflow/python/framework/test_util.py
@@ -735,7 +735,7 @@ def assert_no_garbage_created(f):
     """Sets DEBUG_SAVEALL, runs the test, and checks for new garbage."""
     # Force-load `distribution_strategy_context` to prevent GC at
     # test time when using eager. Remove once b/117329403 is resolved.
-    tape.distribution_strategy_context.get_distribution_strategy()
+    tape.distribution_strategy_context.get_strategy()
 
     gc.disable()
     previous_debug_flags = gc.get_debug()
diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py
index a65c2b6413..0b1743af38 100644
--- a/tensorflow/python/keras/engine/training.py
+++ b/tensorflow/python/keras/engine/training.py
@@ -216,14 +216,14 @@ class Model(Network):
       self._distribution_strategy = distribute
       self._compile_distribution = True
     else:
-      if distribution_strategy_context.has_distribution_strategy():
+      if distribution_strategy_context.has_strategy():
         # When the user builds the model in the DS scope and cross replica
         # context we want distribution strategy to be set but when building the
         # replica copies of the models internally we should not be compiling
         # with distribution strategy and use the default compilation path.
         if distribution_strategy_context.in_cross_replica_context():
           self._distribution_strategy = (
-              distribution_strategy_context.get_distribution_strategy())
+              distribution_strategy_context.get_strategy())
 
     # Validate that arguments passed by the user to `compile` are supported by
     # DistributionStrategy.
diff --git a/tensorflow/python/keras/layers/normalization.py b/tensorflow/python/keras/layers/normalization.py
index 98bc10881b..c174c8ddd6 100644
--- a/tensorflow/python/keras/layers/normalization.py
+++ b/tensorflow/python/keras/layers/normalization.py
@@ -417,8 +417,8 @@ class BatchNormalizationV2(Layer):
       # since TPUStrategy does not implement replica local variables.
       # Remove this hack once we support TPULocalVariables.
       is_tpu_strategy = False
-      if distribution_strategy_context.has_distribution_strategy():
-        distribute = distribution_strategy_context.get_distribution_strategy()
+      if distribution_strategy_context.has_strategy():
+        distribute = distribution_strategy_context.get_strategy()
         if distribute.__class__.__name__ == 'TPUStrategy':
           is_tpu_strategy = True
 
@@ -474,7 +474,7 @@ class BatchNormalizationV2(Layer):
       momentum = ops.convert_to_tensor(self.momentum)
     if training_value or training_value is None:
       if distribution_strategy_context.in_cross_replica_context():
-        strategy = distribution_strategy_context.get_distribution_strategy()
+        strategy = distribution_strategy_context.get_strategy()
         mean_update = strategy.extended.update(
             self.moving_mean, self._assign_moving_average,
             (mean, self.momentum))
@@ -666,7 +666,8 @@ class BatchNormalizationV2(Layer):
         scale, offset = _compose_transforms(r, d, scale, offset)
 
       if distribution_strategy_context.in_cross_replica_context():
-        strategy = distribution_strategy_context.get_distribution_strategy()
+        strategy = distribution_strategy_context.get_strategy()
+
         def _do_update(var, value):
           """Compute the updates for mean and variance."""
           if in_eager_mode and not self.trainable:
diff --git a/tensorflow/python/keras/optimizer_v2/optimizer_v2.py b/tensorflow/python/keras/optimizer_v2/optimizer_v2.py
index 98f87a41ae..894af66f5d 100644
--- a/tensorflow/python/keras/optimizer_v2/optimizer_v2.py
+++ b/tensorflow/python/keras/optimizer_v2/optimizer_v2.py
@@ -290,8 +290,7 @@ class OptimizerV2(checkpointable.CheckpointableBase):
   @staticmethod
   def _scale_loss(loss_value):
     if distribute_lib.get_loss_reduction() == ds_reduce_util.ReduceOp.MEAN:
-      num_replicas = \
-        distribute_ctx.get_distribution_strategy().num_replicas_in_sync
+      num_replicas = distribute_ctx.get_strategy().num_replicas_in_sync
       if num_replicas > 1:
         loss_value *= (1. / num_replicas)
     return loss_value
@@ -349,7 +348,7 @@ class OptimizerV2(checkpointable.CheckpointableBase):
     """
     grads_and_vars = _filter_grads(grads_and_vars)
     var_list = [v for (_, v) in grads_and_vars]
-    if distribute_ctx.has_distribution_strategy():
+    if distribute_ctx.has_strategy():
       reduced_grads = merge_grads(grads_and_vars)
       grads_and_vars = zip(reduced_grads, var_list)
 
@@ -900,8 +899,7 @@ def _var_key(var):
   """
 
   # pylint: disable=protected-access
-  if distribute_ctx.has_distribution_strategy() and hasattr(
-      var, "_primary_var"):
+  if distribute_ctx.has_strategy() and hasattr(var, "_primary_var"):
     var = var._primary_var
   if hasattr(var, "op"):
     return var._shared_name
diff --git a/tensorflow/python/keras/optimizers.py b/tensorflow/python/keras/optimizers.py
index 12168e5574..82fb555b57 100644
--- a/tensorflow/python/keras/optimizers.py
+++ b/tensorflow/python/keras/optimizers.py
@@ -740,7 +740,7 @@ class TFOptimizer(Optimizer, checkpointable.CheckpointableBase):
     return self.optimizer.compute_gradients(loss, params)
 
   def get_updates(self, loss, params):
-    if distribution_strategy_context.has_distribution_strategy():
+    if distribution_strategy_context.has_strategy():
       self.updates = []
 
       if not params:
diff --git a/tensorflow/python/training/optimizer.py b/tensorflow/python/training/optimizer.py
index c6cc0b6044..8076ed31bf 100644
--- a/tensorflow/python/training/optimizer.py
+++ b/tensorflow/python/training/optimizer.py
@@ -521,8 +521,7 @@ class Optimizer(
   @staticmethod
   def _scale_loss(loss_value):
     if distribute_lib.get_loss_reduction() == ds_reduce_util.ReduceOp.MEAN:
-      num_replicas = \
-        distribute_ctx.get_distribution_strategy().num_replicas_in_sync
+      num_replicas = distribute_ctx.get_strategy().num_replicas_in_sync
       if num_replicas > 1:
         loss_value *= (1. / num_replicas)
     return loss_value
@@ -816,7 +815,7 @@ class Optimizer(
     v = self._non_slot_dict.get(key, None)
     if v is None:
       self._maybe_initialize_checkpointable()
-      distribution_strategy = distribute_ctx.get_distribution_strategy()
+      distribution_strategy = distribute_ctx.get_strategy()
       with distribution_strategy.colocate_vars_with(colocate_with):
         if eager:
           restored_initial_value = self._preload_simple_restoration(
diff --git a/tensorflow/python/training/session_manager.py b/tensorflow/python/training/session_manager.py
index cd22e7e7de..7bd0891e35 100644
--- a/tensorflow/python/training/session_manager.py
+++ b/tensorflow/python/training/session_manager.py
@@ -186,7 +186,7 @@ class SessionManager(object):
     # This is required to so that we initialize the TPU device before
     # restoring from checkpoint since we'll be placing variables on the device
     # and TPUInitialize wipes out the memory of the device.
-    strategy = distribution_strategy_context.get_distribution_strategy()
+    strategy = distribution_strategy_context.get_strategy()
     if strategy and hasattr(strategy.extended,
                             "_experimental_initialize_system"):
       strategy.extended._experimental_initialize_system()  # pylint: disable=protected-access
diff --git a/tensorflow/python/training/slot_creator.py b/tensorflow/python/training/slot_creator.py
index bc1137e200..abe1253b00 100644
--- a/tensorflow/python/training/slot_creator.py
+++ b/tensorflow/python/training/slot_creator.py
@@ -121,8 +121,7 @@ def create_slot(primary, val, name, colocate_with_primary=True):
     prefix = primary.op.name
   with variable_scope.variable_scope(None, prefix + "/" + name):
     if colocate_with_primary:
-      distribution_strategy = (
-          distribution_strategy_context.get_distribution_strategy())
+      distribution_strategy = distribution_strategy_context.get_strategy()
       with distribution_strategy.colocate_vars_with(primary):
         return _create_slot_var(primary, val, "", validate_shape, None, None)
     else:
@@ -159,8 +158,7 @@ def create_slot_with_initializer(primary, initializer, shape, dtype, name,
     prefix = primary.op.name
   with variable_scope.variable_scope(None, prefix + "/" + name):
     if colocate_with_primary:
-      distribution_strategy = (
-          distribution_strategy_context.get_distribution_strategy())
+      distribution_strategy = distribution_strategy_context.get_strategy()
       with distribution_strategy.colocate_vars_with(primary):
         return _create_slot_var(primary, initializer, "", validate_shape, shape,
                                 dtype)
diff --git a/tensorflow/python/training/sync_replicas_optimizer.py b/tensorflow/python/training/sync_replicas_optimizer.py
index cd4590db7f..0079ecc98b 100644
--- a/tensorflow/python/training/sync_replicas_optimizer.py
+++ b/tensorflow/python/training/sync_replicas_optimizer.py
@@ -260,8 +260,7 @@ class SyncReplicasOptimizer(optimizer.Optimizer):
     # local_anchor op will be placed on this worker task by default.
     local_anchor = control_flow_ops.no_op()
     # Colocating local_step variable prevents it being placed on the PS.
-    distribution_strategy = (
-        distribution_strategy_context.get_distribution_strategy())
+    distribution_strategy = distribution_strategy_context.get_strategy()
     with distribution_strategy.colocate_vars_with(local_anchor):
       self._local_step = variable_scope.variable(
           initial_value=0,
-- 
GitLab


From 0418e820833f88bc3545644d0aed81f51022a359 Mon Sep 17 00:00:00 2001
From: Shashi Shekhar 
Date: Thu, 10 Jan 2019 13:09:58 -0800
Subject: [PATCH 0509/2345] TfLite calibration library.

TfLite calibration library to capture activation ranges of tensors.

PiperOrigin-RevId: 228762955
---
 .../lite/tools/optimize/calibration_common.h  |  61 ++++
 .../lite/tools/optimize/calibration_logger.h  |  85 +++++
 .../lite/tools/optimize/calibration_reader.cc |  55 +++
 .../lite/tools/optimize/calibration_reader.h  |  55 +++
 tensorflow/lite/tools/optimize/calibrator.cc  | 345 ++++++++++++++++++
 tensorflow/lite/tools/optimize/calibrator.h   |  64 ++++
 .../lite/tools/optimize/calibrator_test.cc    | 189 ++++++++++
 .../tools/optimize/logging_op_resolver.cc     |  61 ++++
 .../lite/tools/optimize/logging_op_resolver.h |  59 +++
 .../optimize/logging_op_resolver_test.cc      | 141 +++++++
 .../lite/tools/optimize/node_info_delegate.cc |  66 ++++
 .../lite/tools/optimize/node_info_delegate.h  |  67 ++++
 .../tools/optimize/node_info_delegate_test.cc | 152 ++++++++
 13 files changed, 1400 insertions(+)
 create mode 100644 tensorflow/lite/tools/optimize/calibration_common.h
 create mode 100644 tensorflow/lite/tools/optimize/calibration_logger.h
 create mode 100644 tensorflow/lite/tools/optimize/calibration_reader.cc
 create mode 100644 tensorflow/lite/tools/optimize/calibration_reader.h
 create mode 100644 tensorflow/lite/tools/optimize/calibrator.cc
 create mode 100644 tensorflow/lite/tools/optimize/calibrator.h
 create mode 100644 tensorflow/lite/tools/optimize/calibrator_test.cc
 create mode 100644 tensorflow/lite/tools/optimize/logging_op_resolver.cc
 create mode 100644 tensorflow/lite/tools/optimize/logging_op_resolver.h
 create mode 100644 tensorflow/lite/tools/optimize/logging_op_resolver_test.cc
 create mode 100644 tensorflow/lite/tools/optimize/node_info_delegate.cc
 create mode 100644 tensorflow/lite/tools/optimize/node_info_delegate.h
 create mode 100644 tensorflow/lite/tools/optimize/node_info_delegate_test.cc

diff --git a/tensorflow/lite/tools/optimize/calibration_common.h b/tensorflow/lite/tools/optimize/calibration_common.h
new file mode 100644
index 0000000000..1ff2d3f18a
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/calibration_common.h
@@ -0,0 +1,61 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_COMMON_H_
+#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_COMMON_H_
+
+#include 
+#include 
+
+#include "tensorflow/lite/mutable_op_resolver.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+using BuiltinOperatorKey = std::pair;
+
+using BuiltinOpsSet = std::unordered_set<
+    BuiltinOperatorKey,
+    op_resolver_hasher::OperatorKeyHasher>;
+
+template 
+class BuiltinOpsMap
+    : public std::unordered_map<
+          BuiltinOperatorKey, T,
+          op_resolver_hasher::OperatorKeyHasher> {};
+
+// An alias for |TfLiteRegistration.invoke|.
+using KernelEvalFuncPtr = TfLiteStatus (*)(TfLiteContext*, TfLiteNode*);
+
+enum class OperatorTensorType { kNone, kInput, kOutput, kIntermediate };
+
+// Information about an operator in the TfLite graph.
+struct OperatorInfo {
+  int node_index;
+  std::string name;
+  BuiltinOperator builtin_op_code;
+  bool is_custom_op;
+  std::vector inputs;
+  std::vector outputs;
+  // Inputs that need to be logged.
+  std::vector loggable_inputs;
+  // Outputs that need to be logged.
+  std::vector loggable_outputs;
+  const TfLiteRegistration* registration;
+};
+
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
+#endif  // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_COMMON_H_
diff --git a/tensorflow/lite/tools/optimize/calibration_logger.h b/tensorflow/lite/tools/optimize/calibration_logger.h
new file mode 100644
index 0000000000..8fd380423a
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/calibration_logger.h
@@ -0,0 +1,85 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_LOGGER_H_
+#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_LOGGER_H_
+
+#include 
+
+#include "tensorflow/lite/c/c_api_internal.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+class MinMax {
+ public:
+  void Update(const float* values, size_t tensor_size) {
+    // TODO(shashishekhar): Really slow implementation, optimize
+    if (tensor_size <= 0) return;
+
+    if (!has_values_) {
+      min_ = max_ = values[0];
+      has_values_ = true;
+      return;
+    }
+
+    // We are only logging absolute min/max here.
+    // TODO(shashishekhar): Make it possible to use weighted/moving average.
+    for (size_t i = 0; i < tensor_size; i++) {
+      float val = values[i];
+      if (min_ > val) {
+        min_ = val;
+      } else if (max_ < val) {
+        max_ = val;
+      }
+    }
+  }
+
+  bool HasValues() const { return has_values_; }
+
+  TfLiteStatus Get(float* min_val, float* max_val) const {
+    if (!has_values_) return kTfLiteError;
+    *min_val = min_;
+    *max_val = max_;
+    return kTfLiteOk;
+  }
+
+ private:
+  bool has_values_;
+  float min_, max_;
+};
+
+// Captures min max values for tensors.
+class Logger {
+ public:
+  // Log the value for tensor at |tensor_index| which has |tensor_values|
+  void LogTensorValue(int tensor_index, const float* tensor_values,
+                      size_t tensor_size) {
+    tensor_id_to_stats_map_[tensor_index].Update(tensor_values, tensor_size);
+  }
+
+  // Returns a map from tensor_index -> observed min max values.
+  const std::unordered_map& GetCalibrationValues() const {
+    return tensor_id_to_stats_map_;
+  }
+
+ private:
+  std::unordered_map tensor_id_to_stats_map_;
+};
+
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
+
+#endif  // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_LOGGER_H_
diff --git a/tensorflow/lite/tools/optimize/calibration_reader.cc b/tensorflow/lite/tools/optimize/calibration_reader.cc
new file mode 100644
index 0000000000..b01a62bd6c
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/calibration_reader.cc
@@ -0,0 +1,55 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include "tensorflow/lite/tools/optimize/calibration_reader.h"
+
+#include "absl/memory/memory.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+TfLiteStatus CalibrationReader::GetTensorStatsAsMap(
+    std::unordered_map* tensor_id_to_stats_map) const {
+  tensor_id_to_stats_map->clear();
+  for (const auto& tensorid_stat : logger_->GetCalibrationValues()) {
+    auto minmax = tensorid_stat.second;
+    CalibrationReader::CalibrationStats stats;
+    TF_LITE_ENSURE_STATUS(minmax.Get(&stats.min, &stats.max));
+    tensor_id_to_stats_map->insert({tensorid_stat.first, stats});
+  }
+
+  return kTfLiteOk;
+}
+
+TfLiteStatus CalibrationReader::AddCalibrationToModel(ModelT* model) const {
+  if (!model || model->subgraphs.empty()) {
+    return kTfLiteError;
+  }
+  const auto& subgraph = model->subgraphs[0];
+  for (const auto& tensorid_stat : logger_->GetCalibrationValues()) {
+    auto minmax = tensorid_stat.second;
+    float min, max;
+    TF_LITE_ENSURE_STATUS(minmax.Get(&min, &max));
+    auto quant_params = absl::make_unique();
+    quant_params->min.push_back(min);
+    quant_params->max.push_back(max);
+    subgraph->tensors[tensorid_stat.first]->quantization =
+        std::move(quant_params);
+  }
+
+  return kTfLiteOk;
+}
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
diff --git a/tensorflow/lite/tools/optimize/calibration_reader.h b/tensorflow/lite/tools/optimize/calibration_reader.h
new file mode 100644
index 0000000000..af0da1bb38
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/calibration_reader.h
@@ -0,0 +1,55 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_READER_H_
+#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_READER_H_
+
+#include 
+
+#include "tensorflow/lite/context.h"
+#include "tensorflow/lite/model.h"
+#include "tensorflow/lite/tools/optimize/calibration_logger.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+// Warning: This is not a public API and subject to change.
+//
+// Reads calibrator data collected by running the interpreter through
+// a calibration set.
+class CalibrationReader {
+ public:
+  struct CalibrationStats {
+    float min;
+    float max;
+  };
+  explicit CalibrationReader(const Logger* logger) : logger_(logger) {}
+
+  // Gets a map from tensor index to recorded calibration values.
+  virtual TfLiteStatus GetTensorStatsAsMap(
+      std::unordered_map* tensor_id_to_stats_map) const;
+
+  // Annotates the tensors in the given model with statistics captured during
+  // calibration.
+  virtual TfLiteStatus AddCalibrationToModel(ModelT* model) const;
+
+  virtual ~CalibrationReader() {}
+
+ private:
+  const Logger* logger_;
+};
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
+#endif  // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_READER_H_
diff --git a/tensorflow/lite/tools/optimize/calibrator.cc b/tensorflow/lite/tools/optimize/calibrator.cc
new file mode 100644
index 0000000000..0e817f9346
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/calibrator.cc
@@ -0,0 +1,345 @@
+/* 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/tools/optimize/calibrator.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "absl/memory/memory.h"
+#include "tensorflow/lite/core/api/error_reporter.h"
+#include "tensorflow/lite/core/api/op_resolver.h"
+#include "tensorflow/lite/interpreter.h"
+#include "tensorflow/lite/kernels/register.h"
+#include "tensorflow/lite/model.h"
+#include "tensorflow/lite/op_resolver.h"
+#include "tensorflow/lite/schema/schema_generated.h"
+#include "tensorflow/lite/string_util.h"
+#include "tensorflow/lite/tools/optimize/calibration_common.h"
+#include "tensorflow/lite/tools/optimize/calibration_logger.h"
+#include "tensorflow/lite/tools/optimize/calibration_reader.h"
+#include "tensorflow/lite/tools/optimize/logging_op_resolver.h"
+#include "tensorflow/lite/tools/optimize/node_info_delegate.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+
+namespace {
+
+// Calibrator is used to hold information that can be accessed during kernel
+// invocations.
+// TfLite kernel invocations are C functions and cannot look at the global
+// structure of the graph. Calibrator allows the kernel invoke functions to
+// access the global structure of graph and know which node is currently being
+// executed. This also allows us to write a simple kernel invoke wrapper
+// (see LoggingEval) that can work for most builtin ops.
+class Calibrator {
+ public:
+  Calibrator(const std::unordered_map&
+                 node_ptr_opinfo_map,
+             std::unique_ptr logging_op_resolver)
+      : node_ptr_opinfo_map_(node_ptr_opinfo_map),
+        logging_op_resolver_(std::move(logging_op_resolver)) {
+    logger_ = absl::make_unique();
+  }
+
+  // Returns the wrapped kernel invoke function |TfLiteRegistration.invoke|.
+  KernelEvalFuncPtr GetKernelInvoke(const TfLiteNode* node) const;
+
+  // Gets the instance of logger associated with the current context.
+  Logger* GetLogger() const { return logger_.get(); }
+
+  // Gets the operator information about the given TfLiteNode.
+  const OperatorInfo& GetOpInfo(const TfLiteNode* node) const {
+    return node_ptr_opinfo_map_.at(node);
+  }
+
+ private:
+  std::unordered_map node_ptr_opinfo_map_;
+  std::unique_ptr logging_op_resolver_;
+  const std::unordered_map index_opinfo_;
+  std::unique_ptr logger_;
+};
+
+KernelEvalFuncPtr Calibrator::GetKernelInvoke(const TfLiteNode* node) const {
+  auto op_info = node_ptr_opinfo_map_.at(node);
+  return logging_op_resolver_->GetWrappedKernelInvoke(op_info.builtin_op_code,
+                                                      1);
+}
+
+// A registry of |Calibrator| objects per |TfLiteContext|.
+// This global registry is needed to access |Calibrator| objects in the kernel
+// invoke functions i.e. |TfLiteRegistration.invoke|.
+// Kernel invoke functions are C functions that have limited access to
+// |TfLiteContext|. Kernel invoke functions don't have access to global state of
+// graph. That means during a kernel invocation, the function cannot know which
+// node it was invoked for. E.g. in case of a model with |Conv| op at two
+// locations, there is no easy way for the Conv.invoke function to disambiguate
+// the calls.
+//
+// For calibration we solve this problem by creating a map of calibrators
+// per |TfLiteContext|. This map is |GlobalCalibrationRegistry|.
+//
+// This registry is then accessed using a global getter function:
+// |GetCalibratorRegistry|.
+// E.g.
+// TfLiteStatus SomeKernelInvokeFn(TfLiteContext* context, TfLiteNode* node) {
+//   .... code ....
+//   auto registry = GetCalibratorRegistry();
+//   auto calibrator = registry->GetCalibrator(context);
+//   ..... code ....
+//  }
+//
+// This way the kernel invoke functions can get the access to the Calibrator
+// object associated with the |TfLiteContext|.
+class GlobalCalibratorRegistry {
+ public:
+  // Get the |Calibrator| associated with given context, returns null if no
+  // calibrator is associated with the given context.
+  Calibrator* GetCalibrator(const TfLiteContext* context) const {
+    if (calibrator_registry_.find(context) == calibrator_registry_.cend()) {
+      return nullptr;
+    }
+    return calibrator_registry_.at(context).get();
+  }
+
+  // Removes the association between calibrator and context.
+  // Note: This deletes the calibrator as well.
+  void RemoveCalibrator(const TfLiteContext* context) {
+    calibrator_registry_.erase(context);
+  }
+
+  // Creates an instance of |Calibrator|.
+  // Registry owns the |Calibrator| object which can be deleted by calling
+  // |RemoveCalibrator|.
+  TfLiteStatus CreateCalibrator(
+      const TfLiteContext* context,
+      const std::unordered_map& node_to_opinfo,
+      std::unique_ptr logging_op_resolver,
+      Calibrator** calibrator_ptr, ErrorReporter* reporter) {
+    if (calibrator_registry_.find(context) != calibrator_registry_.cend()) {
+      reporter->Report(
+          "Failed to create calibrator, context already registered.");
+      return kTfLiteError;
+    }
+    std::unique_ptr calibrator = absl::make_unique(
+        node_to_opinfo, std::move(logging_op_resolver));
+    calibrator_registry_[context] = std::move(calibrator);
+    *calibrator_ptr = calibrator_registry_.at(context).get();
+    return kTfLiteOk;
+  }
+
+ private:
+  std::unordered_map>
+      calibrator_registry_;
+};
+
+GlobalCalibratorRegistry* GetCalibratorRegistry() {
+  static GlobalCalibratorRegistry* registry = new GlobalCalibratorRegistry();
+  return registry;
+}
+
+// A wrapper implementation for |TfLiteRegistration.invoke| that logs inputs,
+// invokes the wrapped implementation and then logs the outputs.
+TfLiteStatus LoggingEval(TfLiteContext* context, TfLiteNode* node) {
+  Calibrator* calibrator = GetCalibratorRegistry()->GetCalibrator(context);
+
+  if (!calibrator) {
+    context->ReportError(context, "No calibrator found for context.");
+    return kTfLiteError;
+  }
+
+  auto kernel_invoke = calibrator->GetKernelInvoke(node);
+  auto logger = calibrator->GetLogger();
+  auto op_info = calibrator->GetOpInfo(node);
+
+  for (int i : op_info.loggable_inputs) {
+    auto tensor = context->tensors[i];
+    logger->LogTensorValue(i, tensor.data.f, tensor.bytes / sizeof(float));
+  }
+
+  auto status = kernel_invoke(context, node);
+  // TODO(shashishekhar): An intermediate tensor in graph will get logged twice
+  // once as an input and second time as output. This doesn't change the min max
+  // values but is inefficient.
+  // Using moving average will also break this.
+
+  for (int i : op_info.loggable_outputs) {
+    auto tensor = context->tensors[i];
+    logger->LogTensorValue(i, tensor.data.f, tensor.bytes / sizeof(float));
+  }
+
+  return status;
+}
+
+// Returns the loggable tensors. Not all inputs and outputs need to be logged.
+// For example, const weight tensors which have buffers associated with them
+// don't need to be logged.
+std::vector GetLoggableTensorIndices(
+    const std::vector& tensor_indices,
+    const flatbuffers::Vector>* tensors,
+    const flatbuffers::Vector>* tensor_buffers) {
+  std::vector loggable;
+  for (auto tensor_index : tensor_indices) {
+    auto tensor = tensors->Get(tensor_index);
+    auto buffer_index = tensor->buffer();
+    bool has_no_buffer =
+        buffer_index == 0 || (tensor_buffers->Get(buffer_index) == nullptr);
+    if (has_no_buffer && tensor->type() == tflite::TensorType_FLOAT32) {
+      loggable.push_back(tensor_index);
+    }
+  }
+  return loggable;
+}
+
+// Creates a mapping between the static model graph and the runtime TfLiteNode*
+// nodes in the graph for the given context.
+// This is done by querying the TfLiteContext for node and registrations using
+// the |NodeInfoDelegateObserver|.
+TfLiteStatus GetNodeOpInfoMapAndContext(
+    const std::unordered_map& node_to_opinfo,
+    tflite::Interpreter* const interpreter,
+    std::unordered_map* node_ptr_opinfo_map,
+    const TfLiteContext** context
+
+) {
+  NodeInfoDelegateObserver delegate_observer(node_to_opinfo,
+                                             node_ptr_opinfo_map);
+  NodeInfoDelegateParams delegate_params;
+  delegate_params.delegate_observer = &delegate_observer;
+  TfLiteDelegate logging_delegate = CreateNodeInfoDelegate(&delegate_params);
+
+  auto modify_status = interpreter->ModifyGraphWithDelegate(&logging_delegate);
+  if (modify_status != kTfLiteOk) {
+    return kTfLiteError;
+  }
+  *context = delegate_observer.GetContext();
+  return kTfLiteOk;
+}
+
+string GetOpName(const tflite::OperatorCode& opcode) {
+  if (opcode.custom_code() != nullptr) {
+    return opcode.custom_code()->str();
+  }
+  return tflite::EnumNamesBuiltinOperator()[opcode.builtin_code()];
+}
+
+// A |CalibrationReader| that owns the Calibrator.
+class Reader : public CalibrationReader {
+ public:
+  Reader(const TfLiteContext* context, const Logger* logger)
+      : CalibrationReader(logger), context_(context) {}
+
+  ~Reader() override { GetCalibratorRegistry()->RemoveCalibrator(context_); }
+
+ private:
+  const TfLiteContext* context_;
+};
+
+}  // namespace
+
+TfLiteStatus BuildLoggingInterpreter(
+    const FlatBufferModel& model, const OpResolver& op_resolver,
+    std::unique_ptr* interpreter,
+    std::unique_ptr* calibration_reader) {
+  auto tflite_model = model.GetModel();
+  auto subgraphs = tflite_model->subgraphs();
+  auto tensor_buffers = tflite_model->buffers();
+
+  if (subgraphs->size() != 1) {
+    model.error_reporter()->Report(
+        "Only models with a single subgraph are supported, model had %d "
+        "subgraphs",
+        subgraphs->size());
+    return kTfLiteError;
+  }
+
+  // Populate the node index to operator info map.
+  // We want to collect this information so we can use it during runtime to
+  // log details of which inputs and outputs.
+  // At runtime TFLite kernel invoke functions can only look into their
+  // own node in the graph (TFLiteNode*) and some limited context information.
+  auto primary_subgraph = subgraphs->Get(0);
+  auto operator_codes = tflite_model->operator_codes();
+  auto operators = primary_subgraph->operators();
+  auto tensors = primary_subgraph->tensors();
+  std::unordered_map node_to_opinfo;
+  BuiltinOpsSet op_and_versions;
+
+  for (size_t i = 0; i < operators->size(); i++) {
+    OperatorInfo op_info;
+    op_info.node_index = i;
+    auto op = operators->Get(i);
+    auto operator_code = operator_codes->Get(op->opcode_index());
+    op_info.builtin_op_code = operator_code->builtin_code();
+    op_info.name = GetOpName(*operator_code);
+    op_info.is_custom_op = operator_code->custom_code() != nullptr;
+
+    auto op_inputs = op->inputs();
+    auto op_outputs = op->outputs();
+    op_info.inputs = std::vector(op_inputs->begin(), op_inputs->end());
+    op_info.outputs = std::vector(op_outputs->begin(), op_outputs->end());
+    op_info.loggable_inputs =
+        GetLoggableTensorIndices(op_info.inputs, tensors, tensor_buffers);
+    op_info.loggable_outputs =
+        GetLoggableTensorIndices(op_info.outputs, tensors, tensor_buffers);
+    if (!op_info.is_custom_op) {
+      op_info.registration = op_resolver.FindOp(operator_code->builtin_code(),
+                                                operator_code->version());
+    } else {
+      op_info.registration =
+          op_resolver.FindOp(op_info.name.c_str(), operator_code->version());
+    }
+    node_to_opinfo[i] = op_info;
+    op_and_versions.insert({op_info.builtin_op_code, operator_code->version()});
+  }
+
+  // Prepare the logging op resolver to use |LoggingEval| for kernel
+  // invocations.
+  auto logging_op_resolver = absl::make_unique(
+      op_and_versions, op_resolver, LoggingEval);
+  tflite::InterpreterBuilder(model, *logging_op_resolver)(interpreter);
+
+  if (!(*interpreter)) {
+    model.error_reporter()->Report("Failed to construct interpreter");
+    return kTfLiteError;
+  }
+
+  // Compute the mapping between runtime and static graph structure, i.e.
+  // (TfLiteContext, TfLiteNode) -> OperatorInfo
+  std::unordered_map node_ptr_opinfo_map;
+  const TfLiteContext* context = nullptr;
+  GetNodeOpInfoMapAndContext(node_to_opinfo, interpreter->get(),
+                             &node_ptr_opinfo_map, &context);
+
+  Calibrator* calibrator = nullptr;
+  // Register a calibrator object for the context. This can be accessed
+  // during invocations by the logging kernels.
+  TF_LITE_ENSURE_STATUS(GetCalibratorRegistry()->CreateCalibrator(
+      context, node_ptr_opinfo_map, std::move(logging_op_resolver), &calibrator,
+      model.error_reporter()));
+  *calibration_reader = std::unique_ptr(
+      new Reader(context, calibrator->GetLogger()));
+
+  return kTfLiteOk;
+}
+
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
diff --git a/tensorflow/lite/tools/optimize/calibrator.h b/tensorflow/lite/tools/optimize/calibrator.h
new file mode 100644
index 0000000000..ab3cb27eb7
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/calibrator.h
@@ -0,0 +1,64 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATOR_H_
+#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATOR_H_
+
+#include 
+
+#include "flatbuffers/flatbuffers.h"  // TF:flatbuffers
+#include "tensorflow/lite/core/api/op_resolver.h"
+#include "tensorflow/lite/model.h"
+#include "tensorflow/lite/tools/optimize/calibration_reader.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+
+// Warning: This is not a public API and subject to change.
+
+// Builds a interpreter that logs the calibration data in memory.
+// The calibration data can be recovered using |calibration_reader|.
+//
+// Sample usage:
+// std::unique_ptr interpreter;
+// std::unique_ptr calibration_reader;
+// BuiltinOpResolver resolver = ...
+// FlatBufferModel model = ..
+//
+// BuildLoggingInterpreter(model, resolver, &interpreter,
+//  &calibration_reader);
+//
+//
+// * Allocate tensors...
+// * Call interpreter->invoke on calibration dataset.
+//
+// Calibration data can be read either directly by calling
+// std::unordered_map> tensor_index_to_stats;
+// calibration_reader->GetTensorStatsAsMap(&tensor_index_to_stats);
+//
+// or adding calibration data to model itself.
+// ModelT * original_floating_point_model = ...
+// calibration_reader->AddCalibrationToModel(original_floating_point_model);
+//
+TfLiteStatus BuildLoggingInterpreter(
+    const FlatBufferModel& model, const OpResolver& op_resolver,
+    std::unique_ptr* interpreter,
+    std::unique_ptr* calibration_reader);
+
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
+
+#endif  // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATOR_H_
diff --git a/tensorflow/lite/tools/optimize/calibrator_test.cc b/tensorflow/lite/tools/optimize/calibrator_test.cc
new file mode 100644
index 0000000000..bbbcc70fae
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/calibrator_test.cc
@@ -0,0 +1,189 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include 
+
+#include 
+#include 
+#include "tensorflow/lite/kernels/register.h"
+#include "tensorflow/lite/model.h"
+#include "tensorflow/lite/tools/optimize/calibrator.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+namespace {
+
+TEST(CalibratorTest, CalibrationStatsAreCollected) {
+  auto model = FlatBufferModel::BuildFromFile(
+      "third_party/tensorflow/lite/testdata/multi_add.bin");
+  ASSERT_TRUE(model);
+  std::unique_ptr interpreter;
+  std::unique_ptr reader;
+  auto status = BuildLoggingInterpreter(
+      *model, ops::builtin::BuiltinOpResolver{}, &interpreter, &reader);
+  EXPECT_EQ(kTfLiteOk, status);
+
+  ASSERT_TRUE(interpreter);
+  ASSERT_TRUE(reader);
+  std::unordered_map stats;
+  status = reader->GetTensorStatsAsMap(&stats);
+  EXPECT_EQ(kTfLiteOk, status);
+  EXPECT_TRUE(stats.empty());
+
+  status = interpreter->AllocateTensors();
+  ASSERT_EQ(kTfLiteOk, status);
+  // Model does the following:
+  // 0        1       2        3
+  // |        |__ ____|        |
+  // |           |             |
+  // |          Add(tensor:4)  |
+  // |____ ______|______ ______|
+  //      |             |
+  //      Add          Add
+  //      |             |
+  //    Output:5      Output:6
+
+  const size_t tensor_size = 1 * 8 * 8 * 3;
+
+  std::vector ones(tensor_size, 1.0f);
+  // Fill input tensor i with i+1, i.e. input[0] = 1.0f, input[1] = 2.0f,
+  // input[2] = 3.0f
+
+  for (size_t i = 0; i < interpreter->inputs().size(); i++) {
+    int input_tensor_idx = interpreter->inputs()[i];
+    TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
+    ASSERT_EQ(tensor->bytes, tensor_size * sizeof(float));
+    for (size_t j = 0; j < tensor_size; j++) {
+      tensor->data.f[j] = i + 1;
+    }
+  }
+  status = interpreter->Invoke();
+  ASSERT_EQ(kTfLiteOk, status);
+  const float eps = 1e-6f;
+  // Verify that tensor 5: is 6
+  // Verify that tensor 6: is 9
+  TfLiteTensor* tensor = interpreter->tensor(interpreter->outputs()[0]);
+  for (size_t i = 0; i < tensor_size; i++) {
+    EXPECT_NEAR(tensor->data.f[i], 6.0f, eps);
+  }
+  tensor = interpreter->tensor(interpreter->outputs()[1]);
+  for (size_t i = 0; i < tensor_size; i++) {
+    EXPECT_NEAR(tensor->data.f[i], 9.0f, eps);
+  }
+
+  // Verify that min max of tensors.
+  status = reader->GetTensorStatsAsMap(&stats);
+  EXPECT_EQ(kTfLiteOk, status);
+  EXPECT_EQ(7, stats.size());
+  // Check inputs
+  for (int tensor_idx = 0; tensor_idx < 4; tensor_idx++) {
+    EXPECT_NEAR(stats.at(tensor_idx).min, tensor_idx + 1, eps);
+    EXPECT_NEAR(stats.at(tensor_idx).max, tensor_idx + 1, eps);
+  }
+  // Check tensor 4 max.
+  EXPECT_NEAR(stats.at(4).min, 5, eps);
+  EXPECT_NEAR(stats.at(4).max, 5, eps);
+
+  // Check outputs
+  EXPECT_NEAR(stats.at(5).min, 6, eps);
+  EXPECT_NEAR(stats.at(5).max, 6, eps);
+
+  EXPECT_NEAR(stats.at(6).min, 9, eps);
+  EXPECT_NEAR(stats.at(6).max, 9, eps);
+}
+
+TEST(CalibratorTest, MultipleInvokes) {
+  auto model = FlatBufferModel::BuildFromFile(
+      "third_party/tensorflow/lite/testdata/multi_add.bin");
+  ASSERT_TRUE(model);
+  std::unique_ptr interpreter;
+  std::unique_ptr reader;
+  auto status = BuildLoggingInterpreter(
+      *model, ops::builtin::BuiltinOpResolver{}, &interpreter, &reader);
+  EXPECT_EQ(kTfLiteOk, status);
+
+  ASSERT_TRUE(interpreter);
+  ASSERT_TRUE(reader);
+  status = interpreter->AllocateTensors();
+
+  EXPECT_EQ(kTfLiteOk, status);
+  const size_t tensor_size = 1 * 8 * 8 * 3;
+  // Fill input tensor i with i+1, i.e. input[0] = 1.0f, input[1] = 2.0f,
+  // input[2] = 3.0f
+
+  for (size_t i = 0; i < interpreter->inputs().size(); i++) {
+    int input_tensor_idx = interpreter->inputs()[i];
+    TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
+    ASSERT_EQ(tensor->bytes, tensor_size * sizeof(float));
+    for (size_t j = 0; j < tensor_size; j++) {
+      tensor->data.f[j] = i + 1;
+    }
+  }
+  status = interpreter->Invoke();
+  ASSERT_EQ(kTfLiteOk, status);
+  const float eps = 1e-6f;
+  // Verify that min max of tensors.
+  std::unordered_map stats;
+  status = reader->GetTensorStatsAsMap(&stats);
+  EXPECT_EQ(kTfLiteOk, status);
+  EXPECT_EQ(7, stats.size());
+  const float expected_values[7] = {
+      1.0f,  // input 0
+      2.0f,  // input 1
+      3.0f,  // input 2
+      4.0f,  // input 3
+      5.0f,  // Add(1, 2)
+      6.0f,  // Output 5: Add(0, Add(1,2))
+      9.0f,  // Output 6: Add(Add(1,2), 3)
+  };
+  for (int tensor_idx = 0; tensor_idx < 7; tensor_idx++) {
+    EXPECT_NEAR(stats.at(tensor_idx).min, expected_values[tensor_idx], eps);
+    EXPECT_NEAR(stats.at(tensor_idx).max, expected_values[tensor_idx], eps);
+  }
+  // Set input[0][0] = 1.5 and input[0][1] = 0.5 this should change the values
+  // only for input[0] and tensor 4 and ouputs 5, 6.
+  TfLiteTensor* input0 = interpreter->tensor(0);
+  input0->data.f[0] = 1.5f;
+  input0->data.f[1] = 0.5f;
+  status = interpreter->Invoke();
+  ASSERT_EQ(kTfLiteOk, status);
+  status = reader->GetTensorStatsAsMap(&stats);
+  EXPECT_EQ(kTfLiteOk, status);
+  EXPECT_EQ(7, stats.size());
+  EXPECT_NEAR(stats.at(0).min, 0.5f, eps);
+  EXPECT_NEAR(stats.at(0).max, 1.5f, eps);
+
+  for (int tensor_idx = 1; tensor_idx < 5; tensor_idx++) {
+    EXPECT_NEAR(stats.at(tensor_idx).min, expected_values[tensor_idx], eps);
+    EXPECT_NEAR(stats.at(tensor_idx).max, expected_values[tensor_idx], eps);
+  }
+
+  EXPECT_NEAR(stats.at(5).min, 5.5f, eps);
+  EXPECT_NEAR(stats.at(5).max, 6.5f, eps);
+
+  EXPECT_NEAR(stats.at(6).min, 9.0f, eps);
+  EXPECT_NEAR(stats.at(6).max, 9.0f, eps);
+}
+
+}  // namespace
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
+
+int main(int argc, char** argv) {
+  // On Linux, add: FLAGS_logtostderr = true;
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/tensorflow/lite/tools/optimize/logging_op_resolver.cc b/tensorflow/lite/tools/optimize/logging_op_resolver.cc
new file mode 100644
index 0000000000..7633ebb8dd
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/logging_op_resolver.cc
@@ -0,0 +1,61 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include "tensorflow/lite/tools/optimize/logging_op_resolver.h"
+
+#include "absl/memory/memory.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+
+LoggingOpResolver::LoggingOpResolver(const BuiltinOpsSet& ops_to_replace,
+                                     const OpResolver& base_resolver,
+                                     KernelEvalFuncPtr logging_eval_fn) {
+  for (const auto& op_and_version : ops_to_replace) {
+    const TfLiteRegistration* base_registration =
+        base_resolver.FindOp(op_and_version.first, op_and_version.second);
+    BuiltinOperatorKey key = op_and_version;
+    builtin_op_evalfn_map_[key] = base_registration->invoke;
+    std::unique_ptr logging_registation =
+        absl::make_unique(*base_registration);
+    logging_registation->invoke = logging_eval_fn;
+    builtin_op_registration_map_[key] = std::move(logging_registation);
+  }
+}
+
+const TfLiteRegistration* LoggingOpResolver::FindOp(BuiltinOperator op,
+                                                    int version) const {
+  BuiltinOperatorKey key = {op, version};
+  if (builtin_op_registration_map_.find(key) !=
+      builtin_op_registration_map_.end()) {
+    return builtin_op_registration_map_.at(key).get();
+  }
+
+  return nullptr;
+}
+
+KernelEvalFuncPtr LoggingOpResolver::GetWrappedKernelInvoke(BuiltinOperator op,
+                                                            int version) const {
+  return builtin_op_evalfn_map_.at({op, version});
+}
+
+const TfLiteRegistration* LoggingOpResolver::FindOp(const char* op,
+                                                    int version) const {
+  // TODO(b/121374947): Support custom ops as well.
+  return nullptr;
+}
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
diff --git a/tensorflow/lite/tools/optimize/logging_op_resolver.h b/tensorflow/lite/tools/optimize/logging_op_resolver.h
new file mode 100644
index 0000000000..58a3a0fe3c
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/logging_op_resolver.h
@@ -0,0 +1,59 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_LOGGING_OP_RESOLVER_H_
+#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_LOGGING_OP_RESOLVER_H_
+
+#include 
+#include 
+
+#include "tensorflow/lite/core/api/op_resolver.h"
+#include "tensorflow/lite/mutable_op_resolver.h"
+#include "tensorflow/lite/op_resolver.h"
+#include "tensorflow/lite/tools/optimize/calibration_common.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+// A resolver that replaces the kernel invocations with a wrapper
+// eval function.
+class LoggingOpResolver : public OpResolver {
+ public:
+  // Creates an instance of |LoggingOpResolver|.
+  // All |TfLiteRegistration.invoke| functions are replaced by
+  // |logging_eval_fn|.
+  // TODO(shashishekhar): This interface needs to change for custom ops and
+  // BuiltinOps that need special logging implementations.
+  LoggingOpResolver(const BuiltinOpsSet& ops_to_replace,
+                    const OpResolver& base_resolver,
+                    KernelEvalFuncPtr logging_eval_fn);
+
+  const TfLiteRegistration* FindOp(BuiltinOperator op,
+                                   int version) const override;
+
+  KernelEvalFuncPtr GetWrappedKernelInvoke(BuiltinOperator op,
+                                           int version) const;
+  const TfLiteRegistration* FindOp(const char* op, int version) const override;
+
+ private:
+  BuiltinOpsMap>
+      builtin_op_registration_map_;
+  BuiltinOpsMap builtin_op_evalfn_map_;
+};
+
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
+
+#endif  // TENSORFLOW_LITE_TOOLS_OPTIMIZE_LOGGING_OP_RESOLVER_H_
diff --git a/tensorflow/lite/tools/optimize/logging_op_resolver_test.cc b/tensorflow/lite/tools/optimize/logging_op_resolver_test.cc
new file mode 100644
index 0000000000..18c29abec6
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/logging_op_resolver_test.cc
@@ -0,0 +1,141 @@
+/* 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/tools/optimize/logging_op_resolver.h"
+#include 
+#include 
+#include "tensorflow/lite/mutable_op_resolver.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+namespace {
+
+TfLiteStatus ConvPrepare(TfLiteContext* context, TfLiteNode* node) {
+  return kTfLiteOk;
+}
+
+TfLiteStatus ConvEval(TfLiteContext* context, TfLiteNode* node) {
+  return kTfLiteOk;
+}
+
+TfLiteStatus AddPrepare(TfLiteContext* context, TfLiteNode* node) {
+  return kTfLiteOk;
+}
+
+TfLiteStatus AddEval(TfLiteContext* context, TfLiteNode* node) {
+  return kTfLiteOk;
+}
+
+TfLiteStatus WrappingInvoke(TfLiteContext* context, TfLiteNode* node) {
+  return kTfLiteOk;
+}
+
+TEST(LoggingOpResolverTest, KernelInvokesAreReplaced) {
+  MutableOpResolver base_resolver;
+  TfLiteRegistration conv_registration = {
+      .prepare = ConvPrepare,
+      .invoke = ConvEval,
+  };
+  base_resolver.AddBuiltin(BuiltinOperator_CONV_2D, &conv_registration);
+
+  TfLiteRegistration add_registration = {
+      .prepare = AddPrepare,
+      .invoke = AddEval,
+  };
+  base_resolver.AddBuiltin(BuiltinOperator_ADD, &add_registration);
+  BuiltinOpsSet ops_to_replace = {
+      {BuiltinOperator_CONV_2D, /*version*/ 1},
+      {BuiltinOperator_ADD, /*version*/ 1},
+  };
+
+  LoggingOpResolver resolver(ops_to_replace, base_resolver, WrappingInvoke);
+
+  auto reg = resolver.FindOp(BuiltinOperator_CONV_2D, 1);
+
+  EXPECT_EQ(reg->builtin_code, BuiltinOperator_CONV_2D);
+  EXPECT_TRUE(reg->prepare == ConvPrepare);
+  EXPECT_TRUE(reg->invoke == WrappingInvoke);
+
+  reg = resolver.FindOp(BuiltinOperator_ADD, 1);
+
+  EXPECT_EQ(reg->builtin_code, BuiltinOperator_ADD);
+  EXPECT_TRUE(reg->prepare == AddPrepare);
+  EXPECT_TRUE(reg->invoke == WrappingInvoke);
+}
+
+TEST(LoggingOpResolverTest, OriginalKernelInvokesAreRetained) {
+  MutableOpResolver base_resolver;
+  TfLiteRegistration conv_registration = {
+      .prepare = ConvPrepare,
+      .invoke = ConvEval,
+  };
+  base_resolver.AddBuiltin(BuiltinOperator_CONV_2D, &conv_registration);
+
+  TfLiteRegistration add_registration = {
+      .prepare = AddPrepare,
+      .invoke = AddEval,
+  };
+  base_resolver.AddBuiltin(BuiltinOperator_ADD, &add_registration);
+  BuiltinOpsSet ops_to_replace = {
+      {BuiltinOperator_CONV_2D, /*version*/ 1},
+      {BuiltinOperator_ADD, /*version*/ 1},
+  };
+
+  LoggingOpResolver resolver(ops_to_replace, base_resolver, WrappingInvoke);
+  auto kernel_invoke =
+      resolver.GetWrappedKernelInvoke(BuiltinOperator_CONV_2D, 1);
+  EXPECT_TRUE(kernel_invoke == ConvEval);
+  kernel_invoke = resolver.GetWrappedKernelInvoke(BuiltinOperator_ADD, 1);
+  EXPECT_TRUE(kernel_invoke == AddEval);
+}
+
+TEST(LoggingOpResolverTest, OnlyOpsInReplacementSetAreReplaces) {
+  MutableOpResolver base_resolver;
+  TfLiteRegistration conv_registration = {
+      .prepare = ConvPrepare,
+      .invoke = ConvEval,
+  };
+  base_resolver.AddBuiltin(BuiltinOperator_CONV_2D, &conv_registration);
+
+  TfLiteRegistration add_registration = {
+      .prepare = AddPrepare,
+      .invoke = AddEval,
+  };
+  base_resolver.AddBuiltin(BuiltinOperator_ADD, &add_registration);
+  // Only replace conv2d
+  BuiltinOpsSet ops_to_replace = {
+      {BuiltinOperator_CONV_2D, /*version*/ 1},
+  };
+
+  LoggingOpResolver resolver(ops_to_replace, base_resolver, WrappingInvoke);
+  auto reg = resolver.FindOp(BuiltinOperator_CONV_2D, 1);
+  EXPECT_EQ(reg->builtin_code, BuiltinOperator_CONV_2D);
+  EXPECT_TRUE(reg->prepare == ConvPrepare);
+  EXPECT_TRUE(reg->invoke == WrappingInvoke);
+
+  reg = resolver.FindOp(BuiltinOperator_ADD, 1);
+  EXPECT_EQ(nullptr, reg);
+}
+
+}  // namespace
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
+
+int main(int argc, char** argv) {
+  // On Linux, add: FLAGS_logtostderr = true;
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/tensorflow/lite/tools/optimize/node_info_delegate.cc b/tensorflow/lite/tools/optimize/node_info_delegate.cc
new file mode 100644
index 0000000000..ccaa69373f
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/node_info_delegate.cc
@@ -0,0 +1,66 @@
+/* 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/tools/optimize/node_info_delegate.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+
+namespace {
+// The prepare function for delegate that forwards the prepare call to the
+// delegate observer in node info delegate params.
+// The function simply calls a delegate observer OnDelegatePrepareMethod.
+TfLiteStatus NodeInfoDelegatePrepare(TfLiteContext* context,
+                                     TfLiteDelegate* delegate) {
+  if (delegate == nullptr) return TfLiteStatus::kTfLiteError;
+
+  NodeInfoDelegateParams* params =
+      reinterpret_cast(delegate->data_);
+  return params->delegate_observer->OnDelegatePrepareCalled(context);
+}
+}  // namespace
+
+TfLiteDelegate CreateNodeInfoDelegate(NodeInfoDelegateParams* params) {
+  return {.data_ = params,
+          .Prepare = NodeInfoDelegatePrepare,
+          .CopyFromBufferHandle = nullptr,
+          .CopyToBufferHandle = nullptr,
+          .FreeBufferHandle = nullptr};
+}
+
+TfLiteStatus NodeInfoDelegateObserver::OnDelegatePrepareCalled(
+    TfLiteContext* context) {
+  context_ = context;
+  const size_t num_nodes = node_index_opinfo_map_.size();
+  for (size_t node_index = 0; node_index < num_nodes; node_index++) {
+    TfLiteNode* node = nullptr;
+    TfLiteRegistration* reg = nullptr;
+    TF_LITE_ENSURE_STATUS(
+        context->GetNodeAndRegistration(context, node_index, &node, ®));
+    auto op_info = node_index_opinfo_map_.at(node_index);
+    op_info.registration = reg;
+    node_ptr_opinfo_map_->insert({node, op_info});
+  }
+
+  if (node_ptr_opinfo_map_->size() != node_index_opinfo_map_.size()) {
+    // Something wrong.
+    return kTfLiteError;
+  }
+  return kTfLiteOk;
+}
+
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
diff --git a/tensorflow/lite/tools/optimize/node_info_delegate.h b/tensorflow/lite/tools/optimize/node_info_delegate.h
new file mode 100644
index 0000000000..8ee2ce1978
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/node_info_delegate.h
@@ -0,0 +1,67 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_NODE_INFO_DELEGATE_H_
+#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_NODE_INFO_DELEGATE_H_
+
+#include 
+
+#include "tensorflow/lite/context.h"
+#include "tensorflow/lite/tools/optimize/calibration_common.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+
+// An interface for delegate observer that can listen to TfLiteDelegate::Prepare
+// calls.
+class DelegateObserver {
+ public:
+  virtual TfLiteStatus OnDelegatePrepareCalled(TfLiteContext* context) = 0;
+  virtual ~DelegateObserver() {}
+};
+
+// The parameters for the node info delegate.
+struct NodeInfoDelegateParams {
+  DelegateObserver* delegate_observer;
+};
+
+// Creates a delegate with the given |params|.
+TfLiteDelegate CreateNodeInfoDelegate(NodeInfoDelegateParams* params);
+
+// A delegate observer that can construct the map from TfLiteNode* ->
+// OperatorInfo.
+class NodeInfoDelegateObserver : public DelegateObserver {
+ public:
+  NodeInfoDelegateObserver(
+      const std::unordered_map& node_index_to_op,
+      std::unordered_map* node_ptr_opinfo_map)
+      : node_index_opinfo_map_(node_index_to_op),
+        node_ptr_opinfo_map_(node_ptr_opinfo_map) {}
+
+  TfLiteStatus OnDelegatePrepareCalled(TfLiteContext* context) override;
+
+  // Returns the context that was used to called the prepare method.
+  const TfLiteContext* GetContext() const { return context_; }
+
+ private:
+  const TfLiteContext* context_ = nullptr;
+  const std::unordered_map& node_index_opinfo_map_;
+  std::unordered_map* node_ptr_opinfo_map_;
+};
+
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
+#endif  // TENSORFLOW_LITE_TOOLS_OPTIMIZE_NODE_INFO_DELEGATE_H_
diff --git a/tensorflow/lite/tools/optimize/node_info_delegate_test.cc b/tensorflow/lite/tools/optimize/node_info_delegate_test.cc
new file mode 100644
index 0000000000..e762d5c014
--- /dev/null
+++ b/tensorflow/lite/tools/optimize/node_info_delegate_test.cc
@@ -0,0 +1,152 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include 
+
+#include 
+#include 
+#include "tensorflow/lite/kernels/register.h"
+#include "tensorflow/lite/model.h"
+#include "tensorflow/lite/tools/optimize/node_info_delegate.h"
+
+namespace tflite {
+namespace optimize {
+namespace calibration {
+namespace {
+
+class TestDelegateObserver : public DelegateObserver {
+ public:
+  explicit TestDelegateObserver(TfLiteStatus status_to_return)
+      : status_to_return_(status_to_return) {}
+
+  TfLiteStatus OnDelegatePrepareCalled(TfLiteContext* context) override {
+    num_times_called_++;
+    return status_to_return_;
+  }
+  int num_times_called() { return num_times_called_; }
+
+ private:
+  int num_times_called_ = 0;
+  TfLiteStatus status_to_return_;
+};
+
+TEST(NodeInfoDelegateTest, DelegateObserverIsCalled) {
+  TestDelegateObserver observer(kTfLiteOk);
+  NodeInfoDelegateParams params;
+  params.delegate_observer = &observer;
+  auto model = FlatBufferModel::BuildFromFile(
+      "third_party/tensorflow/lite/testdata/multi_add.bin");
+  ASSERT_TRUE(model);
+  std::unique_ptr interpreter;
+  ASSERT_EQ(InterpreterBuilder(*model,
+                               ops::builtin::BuiltinOpResolver{})(&interpreter),
+            kTfLiteOk);
+  ASSERT_TRUE(interpreter);
+  EXPECT_EQ(0, observer.num_times_called());
+  TfLiteDelegate delegate = CreateNodeInfoDelegate(¶ms);
+
+  auto status = interpreter->ModifyGraphWithDelegate(&delegate);
+  EXPECT_EQ(kTfLiteOk, status);
+  EXPECT_EQ(1, observer.num_times_called());
+}
+
+TEST(NodeInfoDelegateTest, ObserverErrorCausesModifyGraphFailure) {
+  // Observer returns error
+  TestDelegateObserver observer(kTfLiteError);
+  NodeInfoDelegateParams params;
+  params.delegate_observer = &observer;
+  auto model = FlatBufferModel::BuildFromFile(
+      "third_party/tensorflow/lite/testdata/multi_add.bin");
+  ASSERT_TRUE(model);
+  std::unique_ptr interpreter;
+  ASSERT_EQ(InterpreterBuilder(*model,
+                               ops::builtin::BuiltinOpResolver{})(&interpreter),
+            kTfLiteOk);
+  ASSERT_TRUE(interpreter);
+  TfLiteDelegate delegate = CreateNodeInfoDelegate(¶ms);
+
+  auto status = interpreter->ModifyGraphWithDelegate(&delegate);
+  EXPECT_EQ(kTfLiteError, status);
+}
+
+TEST(NodeInfoDelegateTest, NodeInfoDelegateObserver) {
+  auto model = FlatBufferModel::BuildFromFile(
+      "third_party/tensorflow/lite/testdata/multi_add.bin");
+  ASSERT_TRUE(model);
+
+  std::unordered_map index_to_opinfo;
+  auto primary_subgraph = model->GetModel()->subgraphs()->Get(0);
+  auto operators = primary_subgraph->operators();
+  auto subgraph_tensors = primary_subgraph->tensors();
+  for (size_t i = 0; i < operators->size(); i++) {
+    OperatorInfo info;
+    auto op_inputs = operators->Get(i)->inputs();
+    auto op_outputs = operators->Get(i)->outputs();
+    info.inputs = std::vector(op_inputs->begin(), op_inputs->end());
+    info.outputs = std::vector(op_outputs->begin(), op_outputs->end());
+    index_to_opinfo[i] = info;
+  }
+
+  std::unordered_map node_to_opinfo;
+  NodeInfoDelegateObserver observer(index_to_opinfo, &node_to_opinfo);
+  NodeInfoDelegateParams params;
+  params.delegate_observer = &observer;
+  std::unique_ptr interpreter;
+  ASSERT_EQ(InterpreterBuilder(*model,
+                               ops::builtin::BuiltinOpResolver{})(&interpreter),
+            kTfLiteOk);
+  ASSERT_TRUE(interpreter);
+
+  TfLiteDelegate delegate = CreateNodeInfoDelegate(¶ms);
+
+  auto status = interpreter->ModifyGraphWithDelegate(&delegate);
+  EXPECT_EQ(kTfLiteOk, status);
+  EXPECT_EQ(index_to_opinfo.size(), node_to_opinfo.size());
+  EXPECT_EQ(interpreter->nodes_size(), node_to_opinfo.size());
+
+  for (const auto& node_and_opinfo : node_to_opinfo) {
+    const TfLiteNode* tflite_node = node_and_opinfo.first;
+    const OperatorInfo& info = node_and_opinfo.second;
+    ASSERT_EQ(tflite_node->inputs->size, info.inputs.size());
+    ASSERT_EQ(tflite_node->outputs->size, info.outputs.size());
+
+    for (size_t input_index = 0; input_index < info.inputs.size();
+         input_index++) {
+      const TfLiteTensor* tflite_tensor =
+          interpreter->tensor(tflite_node->inputs->data[input_index]);
+      EXPECT_EQ(tflite_tensor->name,
+                subgraph_tensors->Get(info.inputs[input_index])->name()->str());
+    }
+
+    for (size_t output_index = 0; output_index < info.outputs.size();
+         output_index++) {
+      const TfLiteTensor* tflite_tensor =
+          interpreter->tensor(tflite_node->outputs->data[output_index]);
+      EXPECT_EQ(
+          tflite_tensor->name,
+          subgraph_tensors->Get(info.outputs[output_index])->name()->str());
+    }
+  }
+}
+
+}  // namespace
+}  // namespace calibration
+}  // namespace optimize
+}  // namespace tflite
+
+int main(int argc, char** argv) {
+  // On Linux, add: FLAGS_logtostderr = true;
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
-- 
GitLab


From adfde389863219e3f2c90e566ba620d48fd44978 Mon Sep 17 00:00:00 2001
From: Trevor Morris 
Date: Thu, 10 Jan 2019 13:18:59 -0800
Subject: [PATCH 0510/2345] Needed this change to compile after rebasing

---
 tensorflow/contrib/tensorrt/resources/trt_resources.h | 2 +-
 tensorflow/contrib/tensorrt/segment/segment.cc        | 7 -------
 2 files changed, 1 insertion(+), 8 deletions(-)

diff --git a/tensorflow/contrib/tensorrt/resources/trt_resources.h b/tensorflow/contrib/tensorrt/resources/trt_resources.h
index 8d877b392f..aac9e5c7bd 100644
--- a/tensorflow/contrib/tensorrt/resources/trt_resources.h
+++ b/tensorflow/contrib/tensorrt/resources/trt_resources.h
@@ -48,7 +48,7 @@ class TRTCalibrationResource : public tensorflow::ResourceBase {
     allocator_.reset();
   }
 
-  string DebugString() const override {
+  string DebugString() override {
     std::stringstream oss;
     using std::dec;
     using std::endl;
diff --git a/tensorflow/contrib/tensorrt/segment/segment.cc b/tensorflow/contrib/tensorrt/segment/segment.cc
index 6f2a7846d7..084a96e0fa 100644
--- a/tensorflow/contrib/tensorrt/segment/segment.cc
+++ b/tensorflow/contrib/tensorrt/segment/segment.cc
@@ -28,7 +28,6 @@ limitations under the License.
 #include "tensorflow/core/lib/core/status.h"
 #include "tensorflow/core/lib/strings/strcat.h"
 #include "tensorflow/core/platform/types.h"
-#include "tensorflow/core/util/device_name_utils.h"
 
 namespace tensorflow {
 namespace tensorrt {
@@ -420,11 +419,6 @@ tensorflow::Status SegmentGraph(
   //    segment but are not eligible, using input/output_candidate_fn to
   //    determine the eligibilities;
   // 3. convert the segment into expected return format and return the result.
-  auto get_device_type = [](const string& device) {
-    DeviceNameUtils::ParsedName parsed_name;
-    DeviceNameUtils::ParseFullName(device, &parsed_name);
-    return parsed_name.type;
-  };
 
   // --------------------------------- Step 1 ---------------------------------
   auto graph = std::unique_ptr(new SimpleGraph(tf_graph));
@@ -436,7 +430,6 @@ tensorflow::Status SegmentGraph(
   std::vector> node_segments;
   for (int i = 0; i < graph->num_node_ids(); ++i) {
     SimpleNode* node = graph->FindNodeId(i);
-
     if (options.exclude_node_list.count(node->name()) != 0) {
       VLOG(1) << "Not a TF-TRT candidate, "
               << "(Op type: " << node->tf_node()->type_string() << "), "
-- 
GitLab


From 1271cb81425692e81a905cb0e7265c5b9c512093 Mon Sep 17 00:00:00 2001
From: Andy Ly 
Date: Thu, 10 Jan 2019 13:10:22 -0800
Subject: [PATCH 0511/2345] [Grappler] Fix updating ports when removing regular
 fanins. Cleaned up some mutation logic and separated out adding and removing
 of regular and controlling fanins.

PiperOrigin-RevId: 228763029
---
 .../core/grappler/mutable_graph_view.cc       | 310 ++++++++------
 tensorflow/core/grappler/mutable_graph_view.h |  78 ++--
 .../core/grappler/mutable_graph_view_test.cc  | 396 +++++++++++-------
 3 files changed, 470 insertions(+), 314 deletions(-)

diff --git a/tensorflow/core/grappler/mutable_graph_view.cc b/tensorflow/core/grappler/mutable_graph_view.cc
index 5df8bd8113..a8a90d1a57 100644
--- a/tensorflow/core/grappler/mutable_graph_view.cc
+++ b/tensorflow/core/grappler/mutable_graph_view.cc
@@ -39,6 +39,22 @@ bool IsTensorIdPortValid(const TensorId& tensor_id) {
   return tensor_id.index() >= Graph::kControlSlot;
 }
 
+bool IsTensorIdRegular(const TensorId& tensor_id) {
+  return tensor_id.index() > Graph::kControlSlot;
+}
+
+bool IsTensorIdControlling(const TensorId& tensor_id) {
+  return tensor_id.index() == Graph::kControlSlot;
+}
+
+bool IsOutputPortRegular(const MutableGraphView::OutputPort& port) {
+  return port.port_id > Graph::kControlSlot;
+}
+
+bool IsOutputPortControlling(const MutableGraphView::OutputPort& port) {
+  return port.port_id == Graph::kControlSlot;
+}
+
 // Determines if node is an Identity where it's first regular input is a Switch
 // node.
 bool IsIdentityConsumingSwitch(const MutableGraphView& graph,
@@ -46,7 +62,10 @@ bool IsIdentityConsumingSwitch(const MutableGraphView& graph,
   if ((IsIdentity(node) || IsIdentityNSingleInput(node)) &&
       node.input_size() > 0) {
     TensorId tensor_id = ParseTensorName(node.input(0));
-    if (tensor_id.index() == Graph::kControlSlot) return false;
+    if (IsTensorIdControlling(tensor_id)) {
+      return false;
+    }
+
     NodeDef* input_node = graph.GetNode(tensor_id.node());
     return IsSwitch(*input_node);
   }
@@ -83,7 +102,7 @@ void MutableGraphView::AddAndDedupFanouts(NodeDef* node) {
   while (pos <= last_pos) {
     TensorId tensor_id = ParseTensorName(node->input(pos));
     absl::string_view input_node_name = tensor_id.node();
-    bool is_control_input = IsControlInput(tensor_id);
+    bool is_control_input = IsTensorIdControlling(tensor_id);
     bool can_dedup_control_with_regular_input =
         CanDedupControlWithRegularInput(*this, input_node_name);
     bool can_dedup_control =
@@ -246,15 +265,13 @@ bool MutableGraphView::UpdateFanoutsInternal(NodeDef* from_node,
   auto control_fanouts =
       GetFanout(GraphView::OutputPort(from_node, Graph::kControlSlot));
 
-  const string from_control_input = absl::StrCat("^", from_node->name());
-  const string to_control_input = absl::StrCat("^", to_node->name());
-
   for (const InputPort& control_port : control_fanouts) {
     // Node can't be control dependency of itself.
     if (control_port.node == to_node) continue;
 
     NodeDef* node = control_port.node;
     modified |= RemoveControllingFaninInternal(node, from_node);
+    // TODO(lyandy): Handle Switch control dependencies.
     modified |= AddFaninInternal(node, {to_node, Graph::kControlSlot});
   }
 
@@ -276,7 +293,7 @@ bool MutableGraphView::AddFaninInternal(NodeDef* node,
                                         const OutputPort& fanin) {
   int num_non_controlling_fanins =
       NumFanins(*node, /*include_controlling_nodes=*/false);
-  bool input_is_control = fanin.port_id == Graph::kControlSlot;
+  bool input_is_control = IsOutputPortControlling(fanin);
   bool can_dedup_control_with_regular_input =
       CanDedupControlWithRegularInput(*this, *fanin.node);
   // Don't add duplicate control dependencies.
@@ -296,12 +313,12 @@ bool MutableGraphView::AddFaninInternal(NodeDef* node,
       input_is_control ? Graph::kControlSlot : num_non_controlling_fanins;
 
   node->add_input(TensorIdToString({fanin.node->name(), fanin.port_id}));
-  if (fanin.port_id > Graph::kControlSlot) {
-    int node_input_size = node->input_size() - 1;
+  if (IsOutputPortRegular(fanin)) {
+    int last_node_input = node->input_size() - 1;
     // If there are control dependencies in node, move newly inserted fanin to
     // be before such control dependencies.
-    if (num_non_controlling_fanins < node_input_size) {
-      node->mutable_input()->SwapElements(node_input_size,
+    if (num_non_controlling_fanins < last_node_input) {
+      node->mutable_input()->SwapElements(last_node_input,
                                           num_non_controlling_fanins);
     }
   }
@@ -327,9 +344,9 @@ bool MutableGraphView::AddFaninInternal(NodeDef* node, const TensorId& fanin) {
   return AddFaninInternal(node, {fanin_node, fanin.index()});
 }
 
-bool MutableGraphView::AddFanin(absl::string_view node_name,
-                                const TensorId& fanin) {
-  if (!IsTensorIdPortValid(fanin)) {
+bool MutableGraphView::AddRegularFanin(absl::string_view node_name,
+                                       const TensorId& fanin) {
+  if (!IsTensorIdRegular(fanin)) {
     return false;
   }
   NodeDef* node = GetNode(node_name);
@@ -339,60 +356,153 @@ bool MutableGraphView::AddFanin(absl::string_view node_name,
   return AddFaninInternal(node, fanin);
 }
 
-bool MutableGraphView::RemoveFanins(NodeDef* node,
-                                    absl::Span fanins) {
-  bool modified = false;
-  auto mutable_inputs = node->mutable_input();
-  int curr_pos = 0;
-  int num_inputs = node->input_size();
-  for (int i = 0; i < num_inputs; ++i) {
-    TensorId tensor_id = ParseTensorName(node->input(i));
-    bool remove_fanin =
-        std::find(fanins.begin(), fanins.end(), tensor_id) != fanins.end();
-    bool update_fanin = !remove_fanin && modified;
-    if (remove_fanin || update_fanin) {
-      OutputPort fanin(nodes()[tensor_id.node()], tensor_id.index());
-
-      InputPort input;
-      input.node = node;
-      input.port_id =
-          tensor_id.index() == Graph::kControlSlot ? Graph::kControlSlot : i;
-
-      auto& fanouts_set = fanouts()[fanin];
-      if (remove_fanin) {
-        fanouts_set.erase(input);
-      } else {
-        // Shift inputs to be retained.
-        if (tensor_id.index() > Graph::kControlSlot) {
-          fanouts_set.erase(input);
-          fanouts_set.insert(InputPort(node, i));
+bool MutableGraphView::AddControllingFanin(absl::string_view node_name,
+                                           const TensorId& fanin) {
+  NodeDef* node = GetNode(node_name);
+  if (node == nullptr) {
+    return false;
+  }
+  NodeDef* fanin_node = GetNode(fanin.node());
+  if (fanin_node == nullptr) {
+    return false;
+  }
+
+  if (!IsSwitch(*fanin_node)) {
+    return AddFaninInternal(node, {fanin_node, Graph::kControlSlot});
+  } else {
+    if (IsTensorIdControlling(fanin)) {
+      // Cannot add a Switch node control dependency.
+      return false;
+    }
+    // We can't anchor control dependencies directly on the switch node: unlike
+    // other nodes only one of the outputs of the switch node will be generated
+    // when the switch node is executed, and we need to make sure the control
+    // dependency is only triggered when the corresponding output is triggered.
+    // We start by looking for an identity node connected to the output of the
+    // switch node, and use it to anchor the control dependency.
+    auto fanouts = GetFanouts(*fanin_node, /*include_controlled_nodes=*/false);
+    for (auto fanout : fanouts) {
+      if (IsIdentity(*fanout.node) || IsIdentityNSingleInput(*fanout.node)) {
+        if (ParseTensorName(fanout.node->input(0)) == fanin) {
+          return AddFaninInternal(node, {fanout.node, Graph::kControlSlot});
         }
-        mutable_inputs->SwapElements(i, curr_pos++);
       }
-      UpdateMaxRegularOutputPortForRemovedFanin(fanin, fanouts_set);
+    }
+    // We haven't found an existing node where we can anchor the control
+    // dependency: add a new identity node.
+    string ctrl_dep_name = AddPrefixToNodeName(
+        absl::StrCat(fanin.node(), "_", fanin.index()), kMutableGraphViewCtrl);
 
+    // Reuse a previously created node, if possible.
+    NodeDef* ctrl_dep_node = GetNode(ctrl_dep_name);
+    if (ctrl_dep_node == nullptr) {
+      NodeDef new_node;
+      new_node.set_name(ctrl_dep_name);
+      new_node.set_op("Identity");
+      new_node.set_device(fanin_node->device());
+      (*new_node.mutable_attr())["T"].set_type(
+          fanin_node->attr().at("T").type());
+      new_node.add_input(TensorIdToString(fanin));
+      ctrl_dep_node = AddNode(std::move(new_node));
+    }
+    return AddFaninInternal(node, {ctrl_dep_node, Graph::kControlSlot});
+  }
+}
+
+bool MutableGraphView::RemoveRegularFaninInternal(NodeDef* node,
+                                                  const TensorId& fanin) {
+  auto remove_input = [this, node](const TensorId& tensor_id, int port,
+                                   bool update_max_port) {
+    OutputPort fanin_port(nodes()[tensor_id.node()], tensor_id.index());
+    InputPort input(node, port);
+
+    absl::flat_hash_set* fanouts_set = &fanouts()[fanin_port];
+    fanouts_set->erase(input);
+    if (update_max_port) {
+      UpdateMaxRegularOutputPortForRemovedFanin(fanin_port, *fanouts_set);
+    }
+    return fanouts_set;
+  };
+
+  auto mutable_inputs = node->mutable_input();
+  bool modified = false;
+  const int num_inputs = node->input_size();
+  int i;
+  int curr_pos = 0;
+  for (i = 0; i < num_inputs; ++i) {
+    TensorId tensor_id = ParseTensorName(node->input(i));
+    if (IsTensorIdControlling(tensor_id)) {
+      break;
+    }
+    if (tensor_id == fanin) {
+      remove_input(tensor_id, i, /*update_max_port=*/true);
       modified = true;
+    } else if (modified) {
+      // Regular inputs will need to have their ports updated.
+      auto fanouts_set = remove_input(tensor_id, i, /*update_max_port=*/false);
+      fanouts_set->insert({node, curr_pos});
+      // Shift inputs to be retained.
+      mutable_inputs->SwapElements(i, curr_pos++);
     } else {
       // Skip inputs to be retained until first modification.
       curr_pos++;
     }
   }
-  if (modified) {
-    mutable_inputs->DeleteSubrange(curr_pos, num_inputs - curr_pos);
+
+  if (modified && curr_pos < i) {
+    // Remove fanins from node inputs.
+    mutable_inputs->DeleteSubrange(curr_pos, i - curr_pos);
   }
+
   return modified;
 }
 
-bool MutableGraphView::RemoveFanin(absl::string_view node_name,
-                                   const TensorId& fanin) {
-  if (!IsTensorIdPortValid(fanin)) {
+bool MutableGraphView::RemoveRegularFanin(absl::string_view node_name,
+                                          const TensorId& fanin) {
+  if (!IsTensorIdRegular(fanin)) {
     return false;
   }
   NodeDef* node = GetNode(node_name);
   if (node == nullptr) {
     return false;
   }
-  return RemoveFanins(node, {fanin});
+  return RemoveRegularFaninInternal(node, fanin);
+}
+
+bool MutableGraphView::RemoveControllingFaninInternal(NodeDef* node,
+                                                      NodeDef* fanin_node) {
+  for (int i = node->input_size() - 1; i >= 0; --i) {
+    TensorId tensor_id = ParseTensorName(node->input(i));
+    if (tensor_id.index() > Graph::kControlSlot) {
+      break;
+    }
+    if (tensor_id.node() == fanin_node->name()) {
+      fanouts()[{fanin_node, Graph::kControlSlot}].erase(
+          {node, Graph::kControlSlot});
+      node->mutable_input()->SwapElements(i, node->input_size() - 1);
+      node->mutable_input()->RemoveLast();
+      return true;
+    }
+  }
+  return false;
+}
+
+bool MutableGraphView::RemoveControllingFaninInternal(
+    NodeDef* node, absl::string_view fanin_node_name) {
+  NodeDef* fanin = GetNode(fanin_node_name);
+  if (fanin == nullptr) {
+    return false;
+  }
+  return RemoveControllingFaninInternal(node, fanin);
+}
+
+bool MutableGraphView::RemoveControllingFanin(
+    absl::string_view node_name, absl::string_view fanin_node_name) {
+  NodeDef* node = GetNode(node_name);
+  if (node == nullptr) {
+    return false;
+  }
+  return RemoveControllingFaninInternal(node, fanin_node_name);
 }
 
 bool MutableGraphView::RemoveAllFanins(absl::string_view node_name,
@@ -432,10 +542,18 @@ bool MutableGraphView::UpdateFanin(absl::string_view node_name,
 
   // When replacing a non control dependency fanin with a control dependency, or
   // vice versa, remove and add, so ports can be updated properly in fanout(s).
-  if (from_fanin.index() == Graph::kControlSlot ||
-      to_fanin.index() == Graph::kControlSlot) {
-    bool modified = RemoveFanins(node, {from_fanin});
-    modified |= AddFaninInternal(node, to_fanin);
+  bool from_fanin_is_control = IsTensorIdControlling(from_fanin);
+  if (from_fanin_is_control || IsTensorIdControlling(to_fanin)) {
+    bool modified = false;
+    if (from_fanin_is_control) {
+      modified |= RemoveControllingFaninInternal(node, from_fanin.node());
+    } else {
+      modified |= RemoveRegularFaninInternal(node, from_fanin);
+    }
+    if (modified) {
+      AddFaninInternal(node, to_fanin);
+    }
+
     return modified;
   }
 
@@ -456,7 +574,7 @@ bool MutableGraphView::UpdateFanin(absl::string_view node_name,
       InputPort old_input;
       old_input.node = node;
       old_input.port_id =
-          from_fanin.index() == Graph::kControlSlot ? Graph::kControlSlot : i;
+          IsTensorIdControlling(from_fanin) ? Graph::kControlSlot : i;
       if (from_fanin_port_fanouts == nullptr) {
         OutputPort from_fanin_port(from_fanin_node, from_fanin.index());
         from_fanin_port_fanouts = &fanouts()[from_fanin_port];
@@ -466,7 +584,7 @@ bool MutableGraphView::UpdateFanin(absl::string_view node_name,
       InputPort new_input;
       new_input.node = node;
       new_input.port_id =
-          to_fanin.index() == Graph::kControlSlot ? Graph::kControlSlot : i;
+          IsTensorIdControlling(to_fanin) ? Graph::kControlSlot : i;
       if (to_fanin_port_fanouts == nullptr) {
         OutputPort to_fanin_port(to_fanin_node, to_fanin.index());
         to_fanin_port_fanouts = &fanouts()[to_fanin_port];
@@ -493,75 +611,6 @@ bool MutableGraphView::UpdateFanin(absl::string_view node_name,
   return modified;
 }
 
-bool MutableGraphView::AddControllingFanin(absl::string_view node_name,
-                                           const TensorId& fanin) {
-  NodeDef* node = GetNode(node_name);
-  if (node == nullptr) {
-    return false;
-  }
-  NodeDef* fanin_node = GetNode(fanin.node());
-  if (fanin_node == nullptr) {
-    return false;
-  }
-  if (fanin.index() == Graph::kControlSlot) {
-    return AddFaninInternal(node, {fanin_node, Graph::kControlSlot});
-  }
-
-  if (!IsSwitch(*fanin_node)) {
-    return AddFaninInternal(node, {fanin_node, Graph::kControlSlot});
-  } else {
-    // We can't anchor control dependencies directly on the switch node: unlike
-    // other nodes only one of the outputs of the switch node will be generated
-    // when the switch node is executed, and we need to make sure the control
-    // dependency is only triggered when the corresponding output is triggered.
-    // We start by looking for an identity node connected to the output of the
-    // switch node, and use it to anchor the control dependency.
-    auto fanouts = GetFanouts(*fanin_node, /*include_controlled_nodes=*/false);
-    for (auto fanout : fanouts) {
-      if (IsIdentity(*fanout.node) || IsIdentityNSingleInput(*fanout.node)) {
-        if (ParseTensorName(fanout.node->input(0)) == fanin) {
-          return AddFaninInternal(node, {fanout.node, Graph::kControlSlot});
-        }
-      }
-    }
-    // We haven't found an existing node where we can anchor the control
-    // dependency: add a new identity node.
-    string ctrl_dep_name = AddPrefixToNodeName(
-        absl::StrCat(fanin.node(), "_", fanin.index()), kMutableGraphViewCtrl);
-
-    NodeDef* ctrl_dep_node = GetNode(ctrl_dep_name);
-    if (ctrl_dep_node == nullptr) {
-      NodeDef new_node;
-      new_node.set_name(ctrl_dep_name);
-      new_node.set_op("Identity");
-      new_node.set_device(fanin_node->device());
-      (*new_node.mutable_attr())["T"].set_type(
-          fanin_node->attr().at("T").type());
-      new_node.add_input(TensorIdToString(fanin));
-      ctrl_dep_node = AddNode(std::move(new_node));
-    }
-    return AddFaninInternal(node, {ctrl_dep_node, Graph::kControlSlot});
-  }
-}
-
-bool MutableGraphView::RemoveControllingFaninInternal(NodeDef* node,
-                                                      NodeDef* fanin_node) {
-  for (int i = node->input_size() - 1; i >= 0; --i) {
-    TensorId tensor_id = ParseTensorName(node->input(i));
-    if (tensor_id.index() > Graph::kControlSlot) {
-      break;
-    }
-    if (tensor_id.node() == fanin_node->name()) {
-      fanouts()[{fanin_node, Graph::kControlSlot}].erase(
-          {node, Graph::kControlSlot});
-      node->mutable_input()->SwapElements(i, node->input_size() - 1);
-      node->mutable_input()->RemoveLast();
-      return true;
-    }
-  }
-  return false;
-}
-
 void MutableGraphView::DeleteNodes(const std::set& nodes_to_delete) {
   for (const string& node_name_to_delete : nodes_to_delete)
     RemoveFaninsInternal(nodes().at(node_name_to_delete),
@@ -575,21 +624,18 @@ void MutableGraphView::RemoveFaninsInternal(NodeDef* deleted_node,
                                             bool keep_controlling_fanins) {
   for (int i = 0; i < deleted_node->input_size(); ++i) {
     TensorId tensor_id = ParseTensorName(deleted_node->input(i));
-    if (keep_controlling_fanins && tensor_id.index() < 0) {
+    if (keep_controlling_fanins && IsTensorIdControlling(tensor_id)) {
       break;
     }
     OutputPort fanin(nodes()[tensor_id.node()], tensor_id.index());
 
     InputPort input;
     input.node = deleted_node;
-    if (tensor_id.index() < 0)
-      input.port_id = Graph::kControlSlot;
-    else
-      input.port_id = i;
-
-    auto& fanouts_set = fanouts()[fanin];
-    fanouts_set.erase(input);
-    UpdateMaxRegularOutputPortForRemovedFanin(fanin, fanouts_set);
+    input.port_id = IsTensorIdControlling(tensor_id) ? Graph::kControlSlot : i;
+
+    absl::flat_hash_set* fanouts_set = &fanouts()[fanin];
+    fanouts_set->erase(input);
+    UpdateMaxRegularOutputPortForRemovedFanin(fanin, *fanouts_set);
   }
 }
 
diff --git a/tensorflow/core/grappler/mutable_graph_view.h b/tensorflow/core/grappler/mutable_graph_view.h
index 93f0958d5e..f07254068d 100644
--- a/tensorflow/core/grappler/mutable_graph_view.h
+++ b/tensorflow/core/grappler/mutable_graph_view.h
@@ -78,39 +78,17 @@ class MutableGraphView : public internal::GraphViewInternal {
   // This will return true iff the nodes are modified.
   bool UpdateFanouts(absl::string_view from_node, absl::string_view to_node);
 
-  // Adds fanin to node `node_name`. If the node or fanin do not exist in the
-  // graph, nothing will be modified in the graph. If fanin is a control
-  // dependency, existing control dependencies will be checked first before
-  // adding. Otherwise fanin will be added after existing non control dependency
-  // inputs.
-  //
-  // This will return true iff the node is modified. If a control dependency
-  // already exists, the node will not be modified.
-  bool AddFanin(absl::string_view node_name, const TensorId& fanin);
-
-  // Removes fanin from node `node_name`. If the node or fanin do not exist in
-  // the graph, nothing will be modified in the graph. If there are multiple
-  // inputs that match the fanin, all of them will be removed.
-  //
-  // This will return true iff the node is modified. If no inputs match the
-  // fanin, the node will not be modified.
-  bool RemoveFanin(absl::string_view node_name, const TensorId& fanin);
-
-  // Removes all fanins from node `node_name`. Control dependencies will be
-  // retained if keep_controlling_fanins is true.
-  //
-  // This will return true iff the node is modified.
-  bool RemoveAllFanins(absl::string_view node_name,
-                       bool keep_controlling_fanins);
-
-  // Replaces all fanins `from_fanin` with `to_fanin` in node `node_name`. If
-  // the fanins or node do not exist, nothing will be modified in the graph.
+  // Adds regular fanin `fanin` to node `node_name`. If the node or fanin do not
+  // exist in the graph, nothing will be modified in the graph. Otherwise fanin
+  // will be added after existing non control dependency fanins. Control
+  // dependencies will be deduped. To add control dependencies, use
+  // AddControllingFanin.
   //
   // This will return true iff the node is modified.
-  bool UpdateFanin(absl::string_view node_name, const TensorId& from_fanin,
-                   const TensorId& to_fanin);
+  bool AddRegularFanin(absl::string_view node_name, const TensorId& fanin);
 
-  // Adds a control dependency to the target node named `node_name`.
+  // Adds control dependency `fanin` to the target node named `node_name`. To
+  // add regular fanins, use AddRegularFanin.
   //
   // Case 1: If the fanin is not a Switch node, the control dependency is simply
   // added to the target node:
@@ -130,6 +108,37 @@ class MutableGraphView : public internal::GraphViewInternal {
   // This will return true iff the node is modified.
   bool AddControllingFanin(absl::string_view node_name, const TensorId& fanin);
 
+  // Removes regular fanin `fanin` from node `node_name`. If the node or fanin
+  // do not exist in the graph, nothing will be modified in the graph. If there
+  // are multiple inputs that match the fanin, all of them will be removed. To
+  // remove controlling fanins, use RemoveControllingFanin.
+  //
+  // This will return true iff the node is modified.
+  bool RemoveRegularFanin(absl::string_view node_name, const TensorId& fanin);
+
+  // Removes control dependency `fanin_node_name` from the target node named
+  // `node_name`. If the node or fanin do not exist in the graph, nothing will
+  // be modified in the graph. To remove regular fanins, use RemoveRegualrFanin.
+  //
+  // This will return true iff the node is modified.
+  bool RemoveControllingFanin(absl::string_view node_name,
+                              absl::string_view fanin_node_name);
+
+  // Removes all fanins from node `node_name`. Control dependencies will be
+  // retained if keep_controlling_fanins is true.
+  //
+  // This will return true iff the node is modified.
+  bool RemoveAllFanins(absl::string_view node_name,
+                       bool keep_controlling_fanins);
+
+  // Replaces all fanins `from_fanin` with `to_fanin` in node `node_name`. If
+  // the fanins or node do not exist, nothing will be modified in the graph.
+  // Control dependencies will be deduped.
+  //
+  // This will return true iff the node is modified.
+  bool UpdateFanin(absl::string_view node_name, const TensorId& from_fanin,
+                   const TensorId& to_fanin);
+
   // Deletes nodes from the graph.
   void DeleteNodes(const std::set& nodes_to_delete);
 
@@ -189,7 +198,14 @@ class MutableGraphView : public internal::GraphViewInternal {
   // Removes any fanin in node that matches to a fanin in fanins.
   //
   // This will return true iff the node is modified.
-  bool RemoveFanins(NodeDef* node, absl::Span fanins);
+  bool RemoveRegularFaninInternal(NodeDef* node, const TensorId& fanin);
+
+  // Removes controlling fanin `fanin_node_name` from node if such controlling
+  // fanin exists.
+  //
+  // This will return true iff the node is modified.
+  bool RemoveControllingFaninInternal(NodeDef* node,
+                                      absl::string_view fanin_node_name);
 
   // Removes controlling fanin `fanin_node` from node if such controlling fanin
   // exists.
diff --git a/tensorflow/core/grappler/mutable_graph_view_test.cc b/tensorflow/core/grappler/mutable_graph_view_test.cc
index 526ca85ec0..2048c67e3e 100644
--- a/tensorflow/core/grappler/mutable_graph_view_test.cc
+++ b/tensorflow/core/grappler/mutable_graph_view_test.cc
@@ -186,10 +186,10 @@ GraphDef SimpleMutateFaninGraph() {
 void CompareNodeInputs(const MutableGraphView& graph, const NodeDef* expected,
                        NodeDef* actual) {
   ASSERT_EQ(actual->input_size(), expected->input_size());
-  int port;
   for (int i = 0; i < actual->input_size(); ++i) {
     EXPECT_EQ(actual->input(i), expected->input(i));
     TensorId tensor_id = ParseTensorName(expected->input(i));
+    int port;
     if (tensor_id.index() == Graph::kControlSlot) {
       port = Graph::kControlSlot;
     } else {
@@ -203,8 +203,9 @@ void CompareNodeInputs(const MutableGraphView& graph, const NodeDef* expected,
   }
 }
 
-void TestAddFanin(absl::string_view node_name, const TensorId& fanin_to_add,
-                  bool modified, const NodeDef* expected_node) {
+void TestAddRegularFanin(absl::string_view node_name,
+                         const TensorId& fanin_to_add, bool modified,
+                         const NodeDef* expected_node) {
   GraphDef graph_def = SimpleMutateFaninGraph();
 
   MutableGraphView graph(&graph_def);
@@ -216,70 +217,71 @@ void TestAddFanin(absl::string_view node_name, const TensorId& fanin_to_add,
     EXPECT_NE(node, nullptr);
   }
 
-  EXPECT_EQ(modified, graph.AddFanin(node_name, fanin_to_add));
+  EXPECT_EQ(modified, graph.AddRegularFanin(node_name, fanin_to_add));
   if (expected_node != nullptr) {
     CompareNodeInputs(graph, expected_node, node);
   }
 }
 
-TEST(MutableGraphViewTest, AddFanin) {
+TEST(MutableGraphViewTest, AddRegularFanin) {
   NodeDef expected_node;
   // Add input to node with 1 input 0 controls.
   expected_node = NDef("", "", {"a", "b:1"});
-  TestAddFanin("foo_1", {"b", 1}, /*modified=*/true, &expected_node);
+  TestAddRegularFanin("foo_1", {"b", 1}, /*modified=*/true, &expected_node);
   // Add input to node with multiple inputs and 0 controls.
   expected_node = NDef("", "", {"b", "a:1", "a:1", "b:2"});
-  TestAddFanin("foo_3", {"b", 2}, /*modified=*/true, &expected_node);
+  TestAddRegularFanin("foo_3", {"b", 2}, /*modified=*/true, &expected_node);
   // Add input to node with 1 input multiple controls.
   expected_node = NDef("", "", {"b", "a", "^c"});
-  TestAddFanin("foo_2", {"a", 0}, /*modified=*/true, &expected_node);
+  TestAddRegularFanin("foo_2", {"a", 0}, /*modified=*/true, &expected_node);
   // Add input to node with multiple inputs and controls.
   expected_node = NDef("", "", {"a", "b:2", "b:2", "a:1", "^d", "^c"});
-  TestAddFanin("foo_4", {"a", 1}, /*modified=*/true, &expected_node);
+  TestAddRegularFanin("foo_4", {"a", 1}, /*modified=*/true, &expected_node);
   // Add input to node with 0 inputs 0 controls.
   expected_node = NDef("", "", {"a:1"});
-  TestAddFanin("foo_5", {"a", 1}, /*modified=*/true, &expected_node);
+  TestAddRegularFanin("foo_5", {"a", 1}, /*modified=*/true, &expected_node);
   // Add input to node with 0 inputs multiple controls.
   expected_node = NDef("", "", {"c:1", "^b", "^a"});
-  TestAddFanin("foo_6", {"c", 1}, /*modified=*/true, &expected_node);
+  TestAddRegularFanin("foo_6", {"c", 1}, /*modified=*/true, &expected_node);
 
   // Add control to node with 1 input 0 controls.
-  expected_node = NDef("", "", {"a", "^b"});
-  TestAddFanin("foo_1", {"b", Graph::kControlSlot}, /*modified=*/true,
-               &expected_node);
+  expected_node = NDef("", "", {"a"});
+  TestAddRegularFanin("foo_1", {"b", Graph::kControlSlot}, /*modified=*/false,
+                      &expected_node);
   // Add control to node with multiple inputs and 0 controls.
-  expected_node = NDef("", "", {"b", "a:1", "a:1", "^c"});
-  TestAddFanin("foo_3", {"c", Graph::kControlSlot}, /*modified=*/true,
-               &expected_node);
+  expected_node = NDef("", "", {"b", "a:1", "a:1"});
+  TestAddRegularFanin("foo_3", {"c", Graph::kControlSlot}, /*modified=*/false,
+                      &expected_node);
   // Add control to node with 1 input multiple controls.
-  expected_node = NDef("", "", {"b", "^a", "^c", "^d"});
-  TestAddFanin("foo_2", {"d", Graph::kControlSlot}, /*modified=*/true,
-               &expected_node);
+  expected_node = NDef("", "", {"b", "^a", "^c"});
+  TestAddRegularFanin("foo_2", {"d", Graph::kControlSlot}, /*modified=*/false,
+                      &expected_node);
   // Add control to node with multiple input multiple controls.
   expected_node = NDef("", "", {"a", "b:2", "b:2", "^c", "^d"});
-  TestAddFanin("foo_4", {"a", Graph::kControlSlot}, /*modified=*/false,
-               &expected_node);
+  TestAddRegularFanin("foo_4", {"a", Graph::kControlSlot},
+                      /*modified=*/false, &expected_node);
   // Add control to node with 0 inputs 0 controls.
-  expected_node = NDef("", "", {"^a"});
-  TestAddFanin("foo_5", {"a", Graph::kControlSlot}, /*modified=*/true,
-               &expected_node);
+  expected_node = NDef("", "", {});
+  TestAddRegularFanin("foo_5", {"a", Graph::kControlSlot}, /*modified=*/false,
+                      &expected_node);
   // Add control to node with 0 inputs multiple controls.
-  expected_node = NDef("", "", {"^a", "^b", "^c"});
-  TestAddFanin("foo_6", {"c", Graph::kControlSlot}, /*modified=*/true,
-               &expected_node);
+  expected_node = NDef("", "", {"^a", "^b"});
+  TestAddRegularFanin("foo_6", {"c", Graph::kControlSlot}, /*modified=*/false,
+                      &expected_node);
   // Add control to node with control that already exists.
   expected_node = NDef("", "", {"b", "^a", "^c"});
-  TestAddFanin("foo_2", {"a", Graph::kControlSlot}, /*modified=*/false,
-               &expected_node);
+  TestAddRegularFanin("foo_2", {"a", Graph::kControlSlot},
+                      /*modified=*/false, &expected_node);
 
   // Add fanin to node where node is missing.
-  TestAddFanin("foo_missing", {"a", 0}, /*modified=*/false, nullptr);
+  TestAddRegularFanin("foo_missing", {"a", 0}, /*modified=*/false, nullptr);
   // Add fanin to node where fanin is missing.
   expected_node = NDef("", "", {"a"});
-  TestAddFanin("foo_1", {"bar_missing", 0}, /*modified=*/false, &expected_node);
+  TestAddRegularFanin("foo_1", {"bar_missing", 0}, /*modified=*/false,
+                      &expected_node);
   // Add fanin to node where node and fanin are missing.
-  TestAddFanin("foo_missing", {"bar_missing", 0}, /*modified=*/false,
-               /*expected_node=*/nullptr);
+  TestAddRegularFanin("foo_missing", {"bar_missing", 0}, /*modified=*/false,
+                      /*expected_node=*/nullptr);
 }
 
 void CheckFanout(const MutableGraphView& graph, const TensorId& fanin,
@@ -292,9 +294,9 @@ void CheckFanout(const MutableGraphView& graph, const TensorId& fanin,
   }
 }
 
-void TestRemoveFanin(absl::string_view node_name,
-                     const TensorId& fanin_to_remove, bool modified,
-                     const NodeDef* expected_node) {
+void TestRemoveRegularFanin(absl::string_view node_name,
+                            const TensorId& fanin_to_remove, bool modified,
+                            const NodeDef* expected_node) {
   GraphDef graph_def = SimpleMutateFaninGraph();
 
   MutableGraphView graph(&graph_def);
@@ -306,7 +308,7 @@ void TestRemoveFanin(absl::string_view node_name,
     EXPECT_NE(nullptr, node);
   }
 
-  EXPECT_EQ(modified, graph.RemoveFanin(node_name, fanin_to_remove));
+  EXPECT_EQ(modified, graph.RemoveRegularFanin(node_name, fanin_to_remove));
   if (expected_node != nullptr) {
     CompareNodeInputs(graph, expected_node, node);
     if (modified) {
@@ -315,63 +317,68 @@ void TestRemoveFanin(absl::string_view node_name,
   }
 }
 
-TEST(MutableGraphViewTest, RemoveFanin) {
+TEST(MutableGraphViewTest, RemoveRegularFanin) {
   NodeDef expected_node;
   // Remove input from node with 1 input 0 controls.
   expected_node = NDef("", "", {});
-  TestRemoveFanin("foo_1", {"a", 0}, /*modified=*/true, &expected_node);
+  TestRemoveRegularFanin("foo_1", {"a", 0}, /*modified=*/true, &expected_node);
   // Remove input from node with multiple inputs and 0 controls.
   expected_node = NDef("", "", {"b"});
-  TestRemoveFanin("foo_3", {"a", 1}, /*modified=*/true, &expected_node);
+  TestRemoveRegularFanin("foo_3", {"a", 1}, /*modified=*/true, &expected_node);
   // Remove input from node with 1 input multiple controls.
   expected_node = NDef("", "", {"^a", "^c"});
-  TestRemoveFanin("foo_2", {"b", 0}, /*modified=*/true, &expected_node);
+  TestRemoveRegularFanin("foo_2", {"b", 0}, /*modified=*/true, &expected_node);
   // Remove input from node with multiple inputs and controls.
   expected_node = NDef("", "", {"a", "^c", "^d"});
-  TestRemoveFanin("foo_4", {"b", 2}, /*modified=*/true, &expected_node);
+  TestRemoveRegularFanin("foo_4", {"b", 2}, /*modified=*/true, &expected_node);
+  // Remove input from node with multiple inputs and controls, and results in
+  // shifting of ports.
+  expected_node = NDef("", "", {"b:2", "b:2", "^c", "^d"});
+  TestRemoveRegularFanin("foo_4", {"a", 0}, /*modified=*/true, &expected_node);
 
   // Remove control from node with 1 input multiple controls.
-  expected_node = NDef("", "", {"b", "^c"});
-  TestRemoveFanin("foo_2", {"a", Graph::kControlSlot}, /*modified=*/true,
-                  &expected_node);
+  expected_node = NDef("", "", {"b", "^a", "^c"});
+  TestRemoveRegularFanin("foo_2", {"a", Graph::kControlSlot},
+                         /*modified=*/false, &expected_node);
   // Remove control from node with multiple input multiple controls.
-  expected_node = NDef("", "", {"a", "b:2", "b:2", "^c"});
-  TestRemoveFanin("foo_4", {"d", Graph::kControlSlot}, /*modified=*/true,
-                  &expected_node);
+  expected_node = NDef("", "", {"a", "b:2", "b:2", "^c", "^d"});
+  TestRemoveRegularFanin("foo_4", {"d", Graph::kControlSlot},
+                         /*modified=*/false, &expected_node);
   // Remove control from node with 0 inputs multiple controls.
-  expected_node = NDef("", "", {"^b"});
-  TestRemoveFanin("foo_6", {"a", Graph::kControlSlot}, /*modified=*/true,
-                  &expected_node);
+  expected_node = NDef("", "", {"^a", "^b"});
+  TestRemoveRegularFanin("foo_6", {"a", Graph::kControlSlot},
+                         /*modified=*/false, &expected_node);
 
   // Remove input from node with 0 inputs 0 controls.
   expected_node = NDef("", "", {});
-  TestRemoveFanin("foo_5", {"a", 1}, /*modified=*/false, &expected_node);
+  TestRemoveRegularFanin("foo_5", {"a", 1}, /*modified=*/false, &expected_node);
   // Remove input from node with 0 inputs multiple controls.
   expected_node = NDef("", "", {"^a", "^b"});
-  TestRemoveFanin("foo_6", {"a", 1}, /*modified=*/false, &expected_node);
+  TestRemoveRegularFanin("foo_6", {"a", 1}, /*modified=*/false, &expected_node);
+
   // Remove control from node with 1 input 0 controls.
   expected_node = NDef("", "", {"a"});
-  TestRemoveFanin("foo_1", {"b", Graph::kControlSlot}, /*modified=*/false,
-                  &expected_node);
+  TestRemoveRegularFanin("foo_1", {"b", Graph::kControlSlot},
+                         /*modified=*/false, &expected_node);
   // Remove control from node with multiple inputs and 0 controls.
   expected_node = NDef("", "", {"b", "a:1", "a:1"});
-  TestRemoveFanin("foo_3", {"c", Graph::kControlSlot}, /*modified=*/false,
-                  &expected_node);
+  TestRemoveRegularFanin("foo_3", {"c", Graph::kControlSlot},
+                         /*modified=*/false, &expected_node);
   // Remove control from node with 0 inputs 0 controls.
   expected_node = NDef("", "", {});
-  TestRemoveFanin("foo_5", {"a", Graph::kControlSlot}, /*modified=*/false,
-                  &expected_node);
+  TestRemoveRegularFanin("foo_5", {"a", Graph::kControlSlot},
+                         /*modified=*/false, &expected_node);
 
   // Remove fanin from node where node is missing.
-  TestRemoveFanin("foo_missing", {"a", 0}, /*modified=*/false,
-                  /*expected_node=*/nullptr);
+  TestRemoveRegularFanin("foo_missing", {"a", 0}, /*modified=*/false,
+                         /*expected_node=*/nullptr);
   // Remove fanin from node where fanin is missing.
   expected_node = NDef("", "", {"a"});
-  TestRemoveFanin("foo_1", {"bar_missing", 0}, /*modified=*/false,
-                  &expected_node);
+  TestRemoveRegularFanin("foo_1", {"bar_missing", 0}, /*modified=*/false,
+                         &expected_node);
   // Remove fanin from node where node and fanin are missing.
-  TestRemoveFanin("foo_missing", {"bar_missing", 0}, /*modified=*/false,
-                  /*expected_node=*/nullptr);
+  TestRemoveRegularFanin("foo_missing", {"bar_missing", 0}, /*modified=*/false,
+                         /*expected_node=*/nullptr);
 }
 
 void TestRemoveAllFanins(absl::string_view node_name,
@@ -540,56 +547,59 @@ TEST(MutableGraphViewTest, DedupControllingFaninsOnGraphInit) {
   MutableGraphView graph(&graph_def);
 
   EXPECT_EQ(graph.graph()->node_size(), 11);
+  NodeDef expected;
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
-  ASSERT_EQ(a->input_size(), 0);
+  expected = NDef("", "", {});
+  CompareNodeInputs(graph, &expected, a);
+
   NodeDef* b = graph.GetNode("b");
   ASSERT_NE(b, nullptr);
-  ASSERT_EQ(b->input_size(), 0);
+  CompareNodeInputs(graph, &expected, b);
+
   NodeDef* c = graph.GetNode("c");
   ASSERT_NE(c, nullptr);
-  ASSERT_EQ(c->input_size(), 0);
+  CompareNodeInputs(graph, &expected, c);
+
   NodeDef* d = graph.GetNode("d");
   ASSERT_NE(d, nullptr);
-  ASSERT_EQ(d->input_size(), 1);
-  EXPECT_EQ(d->input(0), "c:1");
+  expected = NDef("", "", {"c:1"});
+  CompareNodeInputs(graph, &expected, d);
+
   NodeDef* foo_1 = graph.GetNode("foo_1");
   ASSERT_NE(foo_1, nullptr);
-  ASSERT_EQ(foo_1->input_size(), 2);
-  EXPECT_EQ(foo_1->input(0), "a");
-  EXPECT_EQ(foo_1->input(1), "b:1");
+  expected = NDef("", "", {"a", "b:1"});
+  CompareNodeInputs(graph, &expected, foo_1);
+
   NodeDef* foo_2 = graph.GetNode("foo_2");
   ASSERT_NE(foo_2, nullptr);
-  ASSERT_EQ(foo_2->input_size(), 2);
-  EXPECT_EQ(foo_2->input(0), "a");
-  EXPECT_EQ(foo_2->input(1), "^b");
+  expected = NDef("", "", {"a", "^b"});
+  CompareNodeInputs(graph, &expected, foo_2);
+
   NodeDef* foo_3 = graph.GetNode("foo_3");
   ASSERT_NE(foo_3, nullptr);
-  ASSERT_EQ(foo_3->input_size(), 2);
-  EXPECT_EQ(foo_3->input(0), "a");
-  EXPECT_EQ(foo_3->input(1), "b:1");
+  expected = NDef("", "", {"a", "b:1"});
+  CompareNodeInputs(graph, &expected, foo_3);
+
   NodeDef* foo_4 = graph.GetNode("foo_4");
   ASSERT_NE(foo_4, nullptr);
-  ASSERT_EQ(foo_4->input_size(), 2);
-  EXPECT_EQ(foo_4->input(0), "a:2");
-  EXPECT_EQ(foo_4->input(1), "b:1");
+  expected = NDef("", "", {"a:2", "b:1"});
+  CompareNodeInputs(graph, &expected, foo_4);
+
   NodeDef* foo_5 = graph.GetNode("foo_5");
   ASSERT_NE(foo_5, nullptr);
-  ASSERT_EQ(foo_5->input_size(), 2);
-  EXPECT_EQ(foo_5->input(0), "a:2");
-  EXPECT_EQ(foo_5->input(1), "b:1");
+  expected = NDef("", "", {"a:2", "b:1"});
+  CompareNodeInputs(graph, &expected, foo_5);
+
   NodeDef* foo_6 = graph.GetNode("foo_6");
   ASSERT_NE(foo_6, nullptr);
-  ASSERT_EQ(foo_6->input_size(), 2);
-  EXPECT_EQ(foo_6->input(0), "d");
-  EXPECT_EQ(foo_6->input(1), "^d");
+  expected = NDef("", "", {"d", "^d"});
+  CompareNodeInputs(graph, &expected, foo_6);
+
   NodeDef* foo_7 = graph.GetNode("foo_7");
   ASSERT_NE(foo_7, nullptr);
-  ASSERT_EQ(foo_7->input_size(), 4);
-  EXPECT_EQ(foo_7->input(0), "a:3");
-  EXPECT_EQ(foo_7->input(1), "b:2");
-  EXPECT_EQ(foo_7->input(2), "d");
-  EXPECT_EQ(foo_7->input(3), "^d");
+  expected = NDef("", "", {"a:3", "b:2", "d", "^d"});
+  CompareNodeInputs(graph, &expected, foo_7);
 }
 
 TEST(MutableGraphViewTest, DedupControllingFaninsOnAddFanin) {
@@ -601,17 +611,18 @@ TEST(MutableGraphViewTest, DedupControllingFaninsOnAddFanin) {
 
   MutableGraphView graph(&graph_def);
 
-  EXPECT_TRUE(graph.AddFanin("b", {"a", 2}));
+  EXPECT_TRUE(graph.AddRegularFanin("b", {"a", 2}));
+  NodeDef expected;
   NodeDef* b = graph.GetNode("b");
   ASSERT_NE(b, nullptr);
-  ASSERT_EQ(b->input_size(), 1);
-  EXPECT_EQ(b->input(0), "a:2");
+  expected = NDef("", "", {"a:2"});
+  CompareNodeInputs(graph, &expected, b);
 
-  EXPECT_FALSE(graph.AddFanin("c", {"a", Graph::kControlSlot}));
+  EXPECT_FALSE(graph.AddControllingFanin("c", {"a", Graph::kControlSlot}));
   NodeDef* c = graph.GetNode("c");
   ASSERT_NE(c, nullptr);
-  ASSERT_EQ(c->input_size(), 1);
-  EXPECT_EQ(c->input(0), "a:1");
+  expected = NDef("", "", {"a:1"});
+  CompareNodeInputs(graph, &expected, c);
 }
 
 TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnAddFanin) {
@@ -622,32 +633,30 @@ TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnAddFanin) {
 
   MutableGraphView graph(&graph_def);
 
-  EXPECT_TRUE(graph.AddFanin("c", {"b", 2}));
+  EXPECT_TRUE(graph.AddRegularFanin("c", {"b", 2}));
+  NodeDef expected;
   NodeDef* c = graph.GetNode("c");
   ASSERT_NE(c, nullptr);
-  ASSERT_EQ(c->input_size(), 1);
-  EXPECT_EQ(c->input(0), "b:2");
-  EXPECT_TRUE(graph.AddFanin("c", {"b", Graph::kControlSlot}));
-  ASSERT_EQ(c->input_size(), 2);
-  EXPECT_EQ(c->input(0), "b:2");
-  EXPECT_EQ(c->input(1), "^b");
-  EXPECT_FALSE(graph.AddFanin("c", {"b", Graph::kControlSlot}));
-  ASSERT_EQ(c->input_size(), 2);
-  EXPECT_EQ(c->input(0), "b:2");
-  EXPECT_EQ(c->input(1), "^b");
-
-  EXPECT_TRUE(graph.AddFanin("d", {"b", Graph::kControlSlot}));
+  expected = NDef("", "", {"b:2"});
+  CompareNodeInputs(graph, &expected, c);
+  EXPECT_TRUE(graph.AddControllingFanin("c", {"b", Graph::kControlSlot}));
+  expected = NDef("", "", {"b:2", "^b"});
+  CompareNodeInputs(graph, &expected, c);
+  EXPECT_FALSE(graph.AddControllingFanin("c", {"b", Graph::kControlSlot}));
+  expected = NDef("", "", {"b:2", "^b"});
+  CompareNodeInputs(graph, &expected, c);
+
+  EXPECT_TRUE(graph.AddControllingFanin("d", {"b", Graph::kControlSlot}));
   NodeDef* d = graph.GetNode("d");
   ASSERT_NE(d, nullptr);
-  ASSERT_EQ(d->input_size(), 1);
-  EXPECT_EQ(d->input(0), "^b");
-  EXPECT_FALSE(graph.AddFanin("d", {"b", Graph::kControlSlot}));
-  ASSERT_EQ(d->input_size(), 1);
-  EXPECT_EQ(d->input(0), "^b");
-  EXPECT_TRUE(graph.AddFanin("d", {"b", 3}));
-  ASSERT_EQ(d->input_size(), 2);
-  EXPECT_EQ(d->input(0), "b:3");
-  EXPECT_EQ(d->input(1), "^b");
+  expected = NDef("", "", {"^b"});
+  CompareNodeInputs(graph, &expected, d);
+  EXPECT_FALSE(graph.AddControllingFanin("d", {"b", Graph::kControlSlot}));
+  expected = NDef("", "", {"^b"});
+  CompareNodeInputs(graph, &expected, d);
+  EXPECT_TRUE(graph.AddRegularFanin("d", {"b", 3}));
+  expected = NDef("", "", {"b:3", "^b"});
+  CompareNodeInputs(graph, &expected, d);
 }
 
 TEST(MutableGraphViewTest, DedupControllingFaninsOnUpdateFanin) {
@@ -662,8 +671,8 @@ TEST(MutableGraphViewTest, DedupControllingFaninsOnUpdateFanin) {
   EXPECT_TRUE(graph.UpdateFanin("c", {"a", 1}, {"b", 2}));
   NodeDef* c = graph.GetNode("c");
   ASSERT_NE(c, nullptr);
-  ASSERT_EQ(c->input_size(), 1);
-  EXPECT_EQ(c->input(0), "b:2");
+  NodeDef expected = NDef("", "", {"b:2"});
+  CompareNodeInputs(graph, &expected, c);
 }
 
 TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnUpdateFanin) {
@@ -677,23 +686,22 @@ TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnUpdateFanin) {
 
   EXPECT_TRUE(graph.UpdateFanin("d", {"b", Graph::kControlSlot},
                                 {"c", Graph::kControlSlot}));
+  NodeDef expected;
   NodeDef* d = graph.GetNode("d");
   ASSERT_NE(d, nullptr);
-  ASSERT_EQ(d->input_size(), 2);
-  EXPECT_EQ(d->input(0), "c");
-  EXPECT_EQ(d->input(1), "^c");
+  expected = NDef("", "", {"c", "^c"});
+  CompareNodeInputs(graph, &expected, d);
 
   EXPECT_TRUE(graph.UpdateFanin("e", {"b", 0}, {"c", 3}));
   NodeDef* e = graph.GetNode("e");
   ASSERT_NE(e, nullptr);
-  ASSERT_EQ(e->input_size(), 2);
-  EXPECT_EQ(e->input(0), "c:3");
-  EXPECT_EQ(e->input(1), "^c");
+  expected = NDef("", "", {"c:3", "^c"});
+  CompareNodeInputs(graph, &expected, e);
 
   EXPECT_TRUE(graph.UpdateFanin("e", {"c", 3}, {"c", Graph::kControlSlot}));
   ASSERT_NE(e, nullptr);
-  ASSERT_EQ(e->input_size(), 1);
-  EXPECT_EQ(e->input(0), "^c");
+  expected = NDef("", "", {"^c"});
+  CompareNodeInputs(graph, &expected, e);
 }
 
 TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnAddFanin) {
@@ -705,14 +713,17 @@ TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnAddFanin) {
 
   MutableGraphView graph(&graph_def);
 
-  EXPECT_TRUE(graph.AddFanin("c", {"a", 3}));
+  EXPECT_TRUE(graph.AddRegularFanin("c", {"a", 3}));
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
+
   auto fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true);
   EXPECT_EQ(fanouts.size(), 2);
+
   NodeDef* b = graph.GetNode("b");
   ASSERT_NE(b, nullptr);
   EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(b, 0)), 1);
+
   NodeDef* c = graph.GetNode("c");
   ASSERT_NE(c, nullptr);
   EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(c, 0)), 1);
@@ -727,11 +738,13 @@ TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnRemoveFanin) {
 
   MutableGraphView graph(&graph_def);
 
-  EXPECT_TRUE(graph.RemoveFanin("c", {"a", 2}));
+  EXPECT_TRUE(graph.RemoveRegularFanin("c", {"a", 2}));
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
+
   auto fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true);
   EXPECT_EQ(fanouts.size(), 1);
+
   NodeDef* b = graph.GetNode("b");
   ASSERT_NE(b, nullptr);
   EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(b, 0)), 1);
@@ -746,11 +759,13 @@ TEST(MutableGraphViewTest, KeepMaxRegularOutputPortOnRemoveFanin) {
 
   MutableGraphView graph(&graph_def);
 
-  EXPECT_TRUE(graph.RemoveFanin("b", {"a", 1}));
+  EXPECT_TRUE(graph.RemoveRegularFanin("b", {"a", 1}));
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
+
   auto fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true);
   EXPECT_EQ(fanouts.size(), 1);
+
   NodeDef* c = graph.GetNode("c");
   ASSERT_NE(c, nullptr);
   EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(c, 0)), 1);
@@ -768,13 +783,17 @@ TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnUpdateFanin) {
   EXPECT_TRUE(graph.UpdateFanin("c", {"a", 2}, {"b", 3}));
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
+
   auto a_fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true);
   EXPECT_EQ(a_fanouts.size(), 1);
+
   NodeDef* b = graph.GetNode("b");
   ASSERT_NE(b, nullptr);
   EXPECT_EQ(a_fanouts.count(MutableGraphView::InputPort(b, 0)), 1);
+
   auto b_fanouts = graph.GetFanouts(*b, /*include_controlled_nodes=*/true);
   EXPECT_EQ(b_fanouts.size(), 1);
+
   NodeDef* c = graph.GetNode("c");
   ASSERT_NE(c, nullptr);
   EXPECT_EQ(b_fanouts.count(MutableGraphView::InputPort(c, 0)), 1);
@@ -795,12 +814,15 @@ TEST(MutableGraphViewTest, AddControllingFaninMissing) {
   EXPECT_FALSE(graph.AddControllingFanin("c", {"d", Graph::kControlSlot}));
 
   ASSERT_EQ(graph.graph()->node_size(), 2);
+  NodeDef expected;
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
-  ASSERT_EQ(a->input_size(), 0);
+  expected = NDef("", "", {});
+  CompareNodeInputs(graph, &expected, a);
+
   NodeDef* b = graph.GetNode("b");
   ASSERT_NE(b, nullptr);
-  ASSERT_EQ(b->input_size(), 0);
+  CompareNodeInputs(graph, &expected, b);
 }
 
 TEST(MutableGraphViewTest, AddControllingFaninExistingControl) {
@@ -814,13 +836,16 @@ TEST(MutableGraphViewTest, AddControllingFaninExistingControl) {
   EXPECT_FALSE(graph.AddControllingFanin("a", {"b", Graph::kControlSlot}));
 
   ASSERT_EQ(graph.graph()->node_size(), 2);
+  NodeDef expected;
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
-  ASSERT_EQ(a->input_size(), 1);
-  EXPECT_EQ(a->input(0), "^b");
+  expected = NDef("", "", {"^b"});
+  CompareNodeInputs(graph, &expected, a);
+
   NodeDef* b = graph.GetNode("b");
   ASSERT_NE(b, nullptr);
-  ASSERT_EQ(b->input_size(), 0);
+  expected = NDef("", "", {});
+  CompareNodeInputs(graph, &expected, b);
 }
 
 TEST(MutableGraphViewTest, AddControllingFaninNotSwitch) {
@@ -834,13 +859,16 @@ TEST(MutableGraphViewTest, AddControllingFaninNotSwitch) {
   EXPECT_FALSE(graph.AddControllingFanin("a", {"b", 2}));
 
   ASSERT_EQ(graph.graph()->node_size(), 2);
+  NodeDef expected;
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
-  ASSERT_EQ(a->input_size(), 1);
-  EXPECT_EQ(a->input(0), "^b");
+  expected = NDef("", "", {"^b"});
+  CompareNodeInputs(graph, &expected, a);
+
   NodeDef* b = graph.GetNode("b");
   ASSERT_NE(b, nullptr);
-  ASSERT_EQ(b->input_size(), 0);
+  expected = NDef("", "", {});
+  CompareNodeInputs(graph, &expected, b);
 }
 
 TEST(MutableGraphViewTest, AddControllingFaninSwitchWithIdentity) {
@@ -857,8 +885,8 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithIdentity) {
   ASSERT_EQ(graph.graph()->node_size(), 3);
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
-  ASSERT_EQ(a->input_size(), 1);
-  EXPECT_EQ(a->input(0), "^identity");
+  NodeDef expected = NDef("", "", {"^identity"});
+  CompareNodeInputs(graph, &expected, a);
 }
 
 TEST(MutableGraphViewTest, AddControllingFaninSwitchWithNoExistingIdentity) {
@@ -874,14 +902,16 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithNoExistingIdentity) {
   EXPECT_FALSE(graph.AddControllingFanin("a", {"switch", 0}));
 
   ASSERT_EQ(graph.graph()->node_size(), 3);
+  NodeDef expected;
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
-  ASSERT_EQ(a->input_size(), 1);
-  EXPECT_EQ(a->input(0), "^ConstantFoldingCtrl/switch_0");
+  expected = NDef("", "", {"^ConstantFoldingCtrl/switch_0"});
+  CompareNodeInputs(graph, &expected, a);
+
   NodeDef* identity = graph.GetNode("ConstantFoldingCtrl/switch_0");
   ASSERT_NE(identity, nullptr);
-  ASSERT_EQ(identity->input_size(), 1);
-  EXPECT_EQ(identity->input(0), "switch");
+  expected = NDef("", "", {"switch"});
+  CompareNodeInputs(graph, &expected, identity);
   EXPECT_EQ(identity->op(), "Identity");
   EXPECT_EQ(identity->device(), kDevice);
   ASSERT_TRUE(identity->attr().count("T"));
@@ -902,8 +932,72 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithExistingAddedIdentity) {
   ASSERT_EQ(graph.graph()->node_size(), 3);
   NodeDef* a = graph.GetNode("a");
   ASSERT_NE(a, nullptr);
-  ASSERT_EQ(a->input_size(), 1);
-  EXPECT_EQ(a->input(0), "^ConstantFoldingCtrl/switch_0");
+  NodeDef expected = NDef("", "", {"^ConstantFoldingCtrl/switch_0"});
+  CompareNodeInputs(graph, &expected, a);
+}
+
+TEST(MutableGraphViewTest, RemoveControllingFaninMissing) {
+  // Actual node.op() is not important in this test.
+  GraphDef graph_def = test::function::GDef(
+      {
+          NDef("a", "NotImportant", {}, {}),
+          NDef("b", "NotImportant", {}, {}),
+          NDef("c", "NotImportant", {}, {}),
+          NDef("d", "NotImportant", {"^a", "^b"}),
+      },
+      /*funcs=*/{});
+
+  MutableGraphView graph(&graph_def);
+
+  EXPECT_FALSE(graph.RemoveControllingFanin("d", "c"));
+
+  ASSERT_EQ(graph.graph()->node_size(), 4);
+  NodeDef* d = graph.GetNode("d");
+  ASSERT_NE(d, nullptr);
+  NodeDef expected = NDef("", "", {"^a", "^b"});
+  CompareNodeInputs(graph, &expected, d);
+}
+
+TEST(MutableGraphViewTest, RemoveControllingFaninExisting) {
+  // Actual node.op() is not important in this test.
+  GraphDef graph_def = test::function::GDef(
+      {
+          NDef("a", "NotImportant", {}, {}),
+          NDef("b", "NotImportant", {}, {}),
+          NDef("c", "NotImportant", {}, {}),
+          NDef("d", "NotImportant", {"^a", "^b", "^c"}),
+      },
+      /*funcs=*/{});
+
+  MutableGraphView graph(&graph_def);
+
+  EXPECT_TRUE(graph.RemoveControllingFanin("d", "a"));
+  EXPECT_FALSE(graph.RemoveControllingFanin("d", "a"));
+
+  ASSERT_EQ(graph.graph()->node_size(), 4);
+  NodeDef* d = graph.GetNode("d");
+  ASSERT_NE(d, nullptr);
+  NodeDef expected = NDef("", "", {"^c", "^b"});
+  CompareNodeInputs(graph, &expected, d);
+}
+
+TEST(MutableGraphViewTest, RemoveControllingFaninOnRegularFanin) {
+  // Actual node.op() is not important in this test.
+  GraphDef graph_def = test::function::GDef(
+      {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"a"}),
+       NDef("c", "NotImportant", {"a", "b", "^c"})},
+      /*funcs=*/{});
+
+  MutableGraphView graph(&graph_def);
+
+  EXPECT_FALSE(graph.RemoveControllingFanin("c", "a"));
+  EXPECT_FALSE(graph.RemoveControllingFanin("c", "b"));
+
+  ASSERT_EQ(graph.graph()->node_size(), 3);
+  NodeDef* c = graph.GetNode("c");
+  ASSERT_NE(c, nullptr);
+  NodeDef expected = NDef("", "", {"a", "b", "^c"});
+  CompareNodeInputs(graph, &expected, c);
 }
 
 TEST(MutableGraphViewTest, DeleteNodes) {
-- 
GitLab


From 80e29253740b251d7632f613ceb414205248ce93 Mon Sep 17 00:00:00 2001
From: Sanjoy Das 
Date: Thu, 10 Jan 2019 14:06:26 -0800
Subject: [PATCH 0512/2345] [TF:XLA] Bump open source llvm revision to r350820

PiperOrigin-RevId: 228773540
---
 tensorflow/workspace.bzl | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl
index a7f3665a31..06caa200e4 100755
--- a/tensorflow/workspace.bzl
+++ b/tensorflow/workspace.bzl
@@ -500,11 +500,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 = "83a4f199742f3d6892994dd6dc46d6a53019aedaa28590b460c120f3dfc7bc47",
-        strip_prefix = "llvm-671f057a2cf137914be6c786daf8000469adebab",
+        sha256 = "b85f0571c5d2997142cd379eda3def286b26671fabb8453c5d56fee6e0698f52",
+        strip_prefix = "llvm-437f3bdb2355b0f453a4b4824e4b74ed6b6a73a5",
         urls = [
-            "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/671f057a2cf137914be6c786daf8000469adebab.tar.gz",
-            "https://github.com/llvm-mirror/llvm/archive/671f057a2cf137914be6c786daf8000469adebab.tar.gz",
+            "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/437f3bdb2355b0f453a4b4824e4b74ed6b6a73a5.tar.gz",
+            "https://github.com/llvm-mirror/llvm/archive/437f3bdb2355b0f453a4b4824e4b74ed6b6a73a5.tar.gz",
         ],
     )
 
-- 
GitLab


From 462fccab21bc9a6ad0377a0c3998b47291eea279 Mon Sep 17 00:00:00 2001
From: Peter Hawkins 
Date: Thu, 10 Jan 2019 14:38:16 -0800
Subject: [PATCH 0513/2345] [TF:XLA] Fix failure in sort_ops_test when built in
 -UNDEBUG mode.

The current shape function fails to set a shape for the second output tensor.

PiperOrigin-RevId: 228779527
---
 tensorflow/compiler/tf2xla/ops/xla_ops.cc | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/tensorflow/compiler/tf2xla/ops/xla_ops.cc b/tensorflow/compiler/tf2xla/ops/xla_ops.cc
index ab77984684..af641131ed 100644
--- a/tensorflow/compiler/tf2xla/ops/xla_ops.cc
+++ b/tensorflow/compiler/tf2xla/ops/xla_ops.cc
@@ -369,7 +369,11 @@ REGISTER_OP("XlaKeyValueSort")
     .Output("sorted_values: V")
     .Attr("K: realnumbertype")
     .Attr("V: type")
-    .SetShapeFn(shape_inference::UnchangedShape)
+    .SetShapeFn([](shape_inference::InferenceContext* c) {
+      c->set_output(0, c->input(0));
+      c->set_output(1, c->input(1));
+      return Status::OK();
+    })
     .Doc(R"doc(
 Wraps the XLA Sort operator, documented at
  https://www.tensorflow.org/performance/xla/operation_semantics#sort
-- 
GitLab


From 12c9bb638e8e428bccca0ee8f76c26776f75d5b0 Mon Sep 17 00:00:00 2001
From: Saurabh Deoras 
Date: Thu, 10 Jan 2019 14:49:47 -0800
Subject: [PATCH 0514/2345] use smaller slice length for 32-bit arch

Signed-off-by: Saurabh Deoras 
---
 tensorflow/go/attrs.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tensorflow/go/attrs.go b/tensorflow/go/attrs.go
index f86c5737bc..ed1a1f0b54 100644
--- a/tensorflow/go/attrs.go
+++ b/tensorflow/go/attrs.go
@@ -170,7 +170,8 @@ func listAttribute(op *Operation, cname *C.char, meta C.TF_AttrMetadata) (interf
 			}
 			// A []C.int64_t slice backed by C memory.
 			// See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
-			slice := (*[1 << 30]C.int64_t)(unsafe.Pointer(dim))[:numDim:numDim]
+			// Using [1<<27] instead of [1<<30] so it works on 32-bit architecture
+			slice := (*[1 << 27]C.int64_t)(unsafe.Pointer(dim))[:numDim:numDim]
 			list[i] = makeCShape(slice)
 		}
 		return list, nil
-- 
GitLab


From f4fd5aae1d19607497047f5d5ae2d9de3aec4112 Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 14:45:30 -0800
Subject: [PATCH 0515/2345] Introduced eager profiler python api.

PiperOrigin-RevId: 228780846
---
 tensorflow/python/BUILD                  |  2 +
 tensorflow/python/eager/BUILD            | 24 +++++++++
 tensorflow/python/eager/profiler.py      | 69 ++++++++++++++++++++++++
 tensorflow/python/eager/profiler_test.py | 44 +++++++++++++++
 tensorflow/python/pywrap_tfe.i           |  2 +
 5 files changed, 141 insertions(+)
 create mode 100644 tensorflow/python/eager/profiler.py
 create mode 100644 tensorflow/python/eager/profiler_test.py

diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD
index 933ccf1c7b..c5a8994b80 100644
--- a/tensorflow/python/BUILD
+++ b/tensorflow/python/BUILD
@@ -174,6 +174,7 @@ py_library(
         "//tensorflow/python/distribute",
         "//tensorflow/python/distribute:estimator_training",
         "//tensorflow/python/eager:def_function",
+        "//tensorflow/python/eager:profiler",
         "//tensorflow/python/eager:remote",
         "//tensorflow/python/ops/distributions",
         "//tensorflow/python/ops/linalg",
@@ -4117,6 +4118,7 @@ tf_py_wrap_cc(
         "//tensorflow/c:python_api",
         "//tensorflow/c:tf_status_helper",
         "//tensorflow/c/eager:c_api",
+        "//tensorflow/c/eager:c_api_experimental",
         "//tensorflow/core/distributed_runtime/rpc:grpc_rpc_factory_registration",
         "//tensorflow/core/distributed_runtime/rpc:grpc_server_lib",
         "//tensorflow/core/distributed_runtime/rpc:grpc_session",
diff --git a/tensorflow/python/eager/BUILD b/tensorflow/python/eager/BUILD
index 1f1cb22d58..3a35734051 100644
--- a/tensorflow/python/eager/BUILD
+++ b/tensorflow/python/eager/BUILD
@@ -25,6 +25,7 @@ cc_library(
         "//tensorflow/c:c_api",
         "//tensorflow/c:c_api_internal",
         "//tensorflow/c/eager:c_api",
+        "//tensorflow/c/eager:c_api_experimental",
         "//tensorflow/c/eager:c_api_internal",
         "//tensorflow/c/eager:tape",
         "//tensorflow/core:framework",
@@ -55,6 +56,7 @@ py_library(
         ":execute",
         ":function",
         ":graph_only_ops",
+        ":profiler",
         ":tape",
         ":test",
         ":wrap_function",
@@ -89,6 +91,28 @@ py_library(
     ],
 )
 
+py_library(
+    name = "profiler",
+    srcs = ["profiler.py"],
+    srcs_version = "PY2AND3",
+    visibility = ["//tensorflow:internal"],
+    deps = [
+        ":context",
+        "//tensorflow/python:pywrap_tensorflow",
+    ],
+)
+
+cuda_py_test(
+    name = "profiler_test",
+    srcs = ["profiler_test.py"],
+    additional_deps = [
+        ":profiler",
+        ":test",
+        "//tensorflow/core/profiler:protos_all_py",
+        "//tensorflow/python:constant_op",
+    ],
+)
+
 py_library(
     name = "tape",
     srcs = ["tape.py"],
diff --git a/tensorflow/python/eager/profiler.py b/tensorflow/python/eager/profiler.py
new file mode 100644
index 0000000000..016bca3e3c
--- /dev/null
+++ b/tensorflow/python/eager/profiler.py
@@ -0,0 +1,69 @@
+# 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.
+# ==============================================================================
+"""Profiler for eager mode."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import threading
+
+from tensorflow.python import pywrap_tensorflow
+from tensorflow.python.eager import context
+from tensorflow.python.framework import c_api_util
+
+_profiler = None
+_profiler_lock = threading.Lock()
+
+
+def start():
+  """Start profiling.
+
+  Only one active profiling session is allowed.
+
+  Raises:
+    AssertionError: If another profiling session is running.
+  """
+  global _profiler
+  if _profiler is not None:
+    raise AssertionError('Another profiler is running.')
+  with _profiler_lock:
+    _profiler = pywrap_tensorflow.TFE_NewProfiler(context.context()._handle)  # pylint: disable=protected-access
+
+
+def stop():
+  """Stop current profiling session and return its result.
+
+  Returns:
+    A binary string of tensorflow.tfprof.ProfileProto. User can write the string
+    to file for offline analysis by tfprof command-line tools or graphical user
+    interface.
+
+  Raises:
+    AssertionError: If there is no active profiling session.
+  """
+  global _profiler
+  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:
+    pywrap_tensorflow.TFE_DeleteProfiler(_profiler)
+    _profiler = None
+  return result
diff --git a/tensorflow/python/eager/profiler_test.py b/tensorflow/python/eager/profiler_test.py
new file mode 100644
index 0000000000..b3fe2efabe
--- /dev/null
+++ b/tensorflow/python/eager/profiler_test.py
@@ -0,0 +1,44 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests for eager profiler."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from tensorflow.core.profiler import tfprof_log_pb2
+from tensorflow.python.eager import profiler
+from tensorflow.python.eager import test
+from tensorflow.python.framework import constant_op
+from tensorflow.python.framework import test_util
+
+
+class ProfilerTest(test_util.TensorFlowTestCase):
+
+  def test_profile(self):
+    profiler.start()
+    three = constant_op.constant(3)
+    five = constant_op.constant(5)
+    product = three * five
+    self.assertAllEqual(15, product)
+    profile_result = profiler.stop()
+    profile_pb = tfprof_log_pb2.ProfileProto()
+    profile_pb.ParseFromString(profile_result)
+    profile_pb_str = '%s' % profile_pb
+    self.assertTrue('Mul' in profile_pb_str)
+
+
+if __name__ == '__main__':
+  test.main()
diff --git a/tensorflow/python/pywrap_tfe.i b/tensorflow/python/pywrap_tfe.i
index 0620e0345c..d0ba244853 100755
--- a/tensorflow/python/pywrap_tfe.i
+++ b/tensorflow/python/pywrap_tfe.i
@@ -77,6 +77,7 @@ limitations under the License.
 %{
 #include "tensorflow/python/eager/pywrap_tfe.h"
 #include "tensorflow/c/c_api_experimental.h"
+#include "tensorflow/c/eager/c_api_experimental.h"
 %}
 
 %typemap(in) (const void* proto) {
@@ -233,6 +234,7 @@ limitations under the License.
 
 %include "tensorflow/python/eager/pywrap_tfe.h"
 %include "tensorflow/c/c_api_experimental.h"
+%include "tensorflow/c/eager/c_api_experimental.h"
 
 // Clear all typemaps.
 %typemap(out) TF_DataType;
-- 
GitLab


From 4df903f8fff67700567abb68ae46c4cddab67cee Mon Sep 17 00:00:00 2001
From: Pavithra Vijay 
Date: Thu, 10 Jan 2019 14:51:46 -0800
Subject: [PATCH 0516/2345] Splitting metrics_test file - moving confusion
 matrix related metric tests into a separate file.

PiperOrigin-RevId: 228781953
---
 tensorflow/python/keras/BUILD                 |  15 +-
 .../keras/metrics_confusion_matrix_test.py    | 745 ++++++++++++++++++
 tensorflow/python/keras/metrics_test.py       | 713 -----------------
 3 files changed, 759 insertions(+), 714 deletions(-)
 create mode 100644 tensorflow/python/keras/metrics_confusion_matrix_test.py

diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD
index fb0161a677..536acca298 100755
--- a/tensorflow/python/keras/BUILD
+++ b/tensorflow/python/keras/BUILD
@@ -395,7 +395,7 @@ tf_py_test(
 
 tf_py_test(
     name = "metrics_functional_test",
-    size = "medium",
+    size = "small",
     srcs = ["metrics_functional_test.py"],
     additional_deps = [
         ":keras",
@@ -417,6 +417,19 @@ tf_py_test(
     shard_count = 4,
 )
 
+tf_py_test(
+    name = "metrics_confusion_matrix_test",
+    size = "medium",
+    srcs = ["metrics_confusion_matrix_test.py"],
+    additional_deps = [
+        ":keras",
+        "@absl_py//absl/testing:parameterized",
+        "//third_party/py/numpy",
+        "//tensorflow/python:client_testlib",
+    ],
+    shard_count = 4,
+)
+
 tf_py_test(
     name = "applications_test",
     size = "enormous",
diff --git a/tensorflow/python/keras/metrics_confusion_matrix_test.py b/tensorflow/python/keras/metrics_confusion_matrix_test.py
new file mode 100644
index 0000000000..37d2880088
--- /dev/null
+++ b/tensorflow/python/keras/metrics_confusion_matrix_test.py
@@ -0,0 +1,745 @@
+# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests for Keras metrics functions."""
+
+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.framework import constant_op
+from tensorflow.python.framework import dtypes
+from tensorflow.python.framework import test_util
+from tensorflow.python.keras import metrics
+from tensorflow.python.ops import math_ops
+from tensorflow.python.ops import random_ops
+from tensorflow.python.ops import variables
+from tensorflow.python.platform import test
+
+
+@test_util.run_all_in_graph_and_eager_modes
+class FalsePositivesTest(test.TestCase):
+
+  def test_config(self):
+    fp_obj = metrics.FalsePositives(name='my_fp', thresholds=[0.4, 0.9])
+    self.assertEqual(fp_obj.name, 'my_fp')
+    self.assertEqual(len(fp_obj.variables), 1)
+    self.assertEqual(fp_obj.thresholds, [0.4, 0.9])
+
+    # Check save and restore config
+    fp_obj2 = metrics.FalsePositives.from_config(fp_obj.get_config())
+    self.assertEqual(fp_obj2.name, 'my_fp')
+    self.assertEqual(len(fp_obj2.variables), 1)
+    self.assertEqual(fp_obj2.thresholds, [0.4, 0.9])
+
+  def test_unweighted(self):
+    fp_obj = metrics.FalsePositives()
+    self.evaluate(variables.variables_initializer(fp_obj.variables))
+
+    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
+                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
+    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
+                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
+
+    update_op = fp_obj.update_state(y_true, y_pred)
+    self.evaluate(update_op)
+    result = fp_obj.result()
+    self.assertAllClose(7., result)
+
+  def test_weighted(self):
+    fp_obj = metrics.FalsePositives()
+    self.evaluate(variables.variables_initializer(fp_obj.variables))
+    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
+                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
+    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
+                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
+    sample_weight = constant_op.constant((1., 1.5, 2., 2.5))
+    result = fp_obj(y_true, y_pred, sample_weight=sample_weight)
+    self.assertAllClose(14., self.evaluate(result))
+
+  def test_unweighted_with_thresholds(self):
+    fp_obj = metrics.FalsePositives(thresholds=[0.15, 0.5, 0.85])
+    self.evaluate(variables.variables_initializer(fp_obj.variables))
+
+    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
+                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
+    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
+                                   (1, 1, 1, 1)))
+
+    update_op = fp_obj.update_state(y_true, y_pred)
+    self.evaluate(update_op)
+    result = fp_obj.result()
+    self.assertAllClose([7., 4., 2.], result)
+
+  def test_weighted_with_thresholds(self):
+    fp_obj = metrics.FalsePositives(thresholds=[0.15, 0.5, 0.85])
+    self.evaluate(variables.variables_initializer(fp_obj.variables))
+
+    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
+                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
+    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
+                                   (1, 1, 1, 1)))
+    sample_weight = ((1.0, 2.0, 3.0, 5.0), (7.0, 11.0, 13.0, 17.0),
+                     (19.0, 23.0, 29.0, 31.0), (5.0, 15.0, 10.0, 0))
+
+    result = fp_obj(y_true, y_pred, sample_weight=sample_weight)
+    self.assertAllClose([125., 42., 12.], self.evaluate(result))
+
+  def test_threshold_limit(self):
+    with self.assertRaisesRegexp(
+        ValueError,
+        r'Threshold values must be in \[0, 1\]. Invalid values: \[-1, 2\]'):
+      metrics.FalsePositives(thresholds=[-1, 0.5, 2])
+
+    with self.assertRaisesRegexp(
+        ValueError,
+        r'Threshold values must be in \[0, 1\]. Invalid values: \[None\]'):
+      metrics.FalsePositives(thresholds=[None])
+
+
+@test_util.run_all_in_graph_and_eager_modes
+class FalseNegativesTest(test.TestCase):
+
+  def test_config(self):
+    fn_obj = metrics.FalseNegatives(name='my_fn', thresholds=[0.4, 0.9])
+    self.assertEqual(fn_obj.name, 'my_fn')
+    self.assertEqual(len(fn_obj.variables), 1)
+    self.assertEqual(fn_obj.thresholds, [0.4, 0.9])
+
+    # Check save and restore config
+    fn_obj2 = metrics.FalseNegatives.from_config(fn_obj.get_config())
+    self.assertEqual(fn_obj2.name, 'my_fn')
+    self.assertEqual(len(fn_obj2.variables), 1)
+    self.assertEqual(fn_obj2.thresholds, [0.4, 0.9])
+
+  def test_unweighted(self):
+    fn_obj = metrics.FalseNegatives()
+    self.evaluate(variables.variables_initializer(fn_obj.variables))
+
+    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
+                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
+    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
+                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
+
+    update_op = fn_obj.update_state(y_true, y_pred)
+    self.evaluate(update_op)
+    result = fn_obj.result()
+    self.assertAllClose(3., result)
+
+  def test_weighted(self):
+    fn_obj = metrics.FalseNegatives()
+    self.evaluate(variables.variables_initializer(fn_obj.variables))
+    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
+                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
+    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
+                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
+    sample_weight = constant_op.constant((1., 1.5, 2., 2.5))
+    result = fn_obj(y_true, y_pred, sample_weight=sample_weight)
+    self.assertAllClose(5., self.evaluate(result))
+
+  def test_unweighted_with_thresholds(self):
+    fn_obj = metrics.FalseNegatives(thresholds=[0.15, 0.5, 0.85])
+    self.evaluate(variables.variables_initializer(fn_obj.variables))
+
+    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
+                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
+    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
+                                   (1, 1, 1, 1)))
+
+    update_op = fn_obj.update_state(y_true, y_pred)
+    self.evaluate(update_op)
+    result = fn_obj.result()
+    self.assertAllClose([1., 4., 6.], result)
+
+  def test_weighted_with_thresholds(self):
+    fn_obj = metrics.FalseNegatives(thresholds=[0.15, 0.5, 0.85])
+    self.evaluate(variables.variables_initializer(fn_obj.variables))
+
+    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
+                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
+    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
+                                   (1, 1, 1, 1)))
+    sample_weight = ((3.0,), (5.0,), (7.0,), (4.0,))
+
+    result = fn_obj(y_true, y_pred, sample_weight=sample_weight)
+    self.assertAllClose([4., 16., 23.], self.evaluate(result))
+
+
+@test_util.run_all_in_graph_and_eager_modes
+class TrueNegativesTest(test.TestCase):
+
+  def test_config(self):
+    tn_obj = metrics.TrueNegatives(name='my_tn', thresholds=[0.4, 0.9])
+    self.assertEqual(tn_obj.name, 'my_tn')
+    self.assertEqual(len(tn_obj.variables), 1)
+    self.assertEqual(tn_obj.thresholds, [0.4, 0.9])
+
+    # Check save and restore config
+    tn_obj2 = metrics.TrueNegatives.from_config(tn_obj.get_config())
+    self.assertEqual(tn_obj2.name, 'my_tn')
+    self.assertEqual(len(tn_obj2.variables), 1)
+    self.assertEqual(tn_obj2.thresholds, [0.4, 0.9])
+
+  def test_unweighted(self):
+    tn_obj = metrics.TrueNegatives()
+    self.evaluate(variables.variables_initializer(tn_obj.variables))
+
+    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
+                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
+    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
+                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
+
+    update_op = tn_obj.update_state(y_true, y_pred)
+    self.evaluate(update_op)
+    result = tn_obj.result()
+    self.assertAllClose(3., result)
+
+  def test_weighted(self):
+    tn_obj = metrics.TrueNegatives()
+    self.evaluate(variables.variables_initializer(tn_obj.variables))
+    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
+                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
+    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
+                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
+    sample_weight = constant_op.constant((1., 1.5, 2., 2.5))
+    result = tn_obj(y_true, y_pred, sample_weight=sample_weight)
+    self.assertAllClose(4., self.evaluate(result))
+
+  def test_unweighted_with_thresholds(self):
+    tn_obj = metrics.TrueNegatives(thresholds=[0.15, 0.5, 0.85])
+    self.evaluate(variables.variables_initializer(tn_obj.variables))
+
+    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
+                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
+    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
+                                   (1, 1, 1, 1)))
+
+    update_op = tn_obj.update_state(y_true, y_pred)
+    self.evaluate(update_op)
+    result = tn_obj.result()
+    self.assertAllClose([2., 5., 7.], result)
+
+  def test_weighted_with_thresholds(self):
+    tn_obj = metrics.TrueNegatives(thresholds=[0.15, 0.5, 0.85])
+    self.evaluate(variables.variables_initializer(tn_obj.variables))
+
+    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
+                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
+    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
+                                   (1, 1, 1, 1)))
+    sample_weight = ((0.0, 2.0, 3.0, 5.0),)
+
+    result = tn_obj(y_true, y_pred, sample_weight=sample_weight)
+    self.assertAllClose([5., 15., 23.], self.evaluate(result))
+
+
+@test_util.run_all_in_graph_and_eager_modes
+class TruePositivesTest(test.TestCase):
+
+  def test_config(self):
+    tp_obj = metrics.TruePositives(name='my_tp', thresholds=[0.4, 0.9])
+    self.assertEqual(tp_obj.name, 'my_tp')
+    self.assertEqual(len(tp_obj.variables), 1)
+    self.assertEqual(tp_obj.thresholds, [0.4, 0.9])
+
+    # Check save and restore config
+    tp_obj2 = metrics.TruePositives.from_config(tp_obj.get_config())
+    self.assertEqual(tp_obj2.name, 'my_tp')
+    self.assertEqual(len(tp_obj2.variables), 1)
+    self.assertEqual(tp_obj2.thresholds, [0.4, 0.9])
+
+  def test_unweighted(self):
+    tp_obj = metrics.TruePositives()
+    self.evaluate(variables.variables_initializer(tp_obj.variables))
+
+    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
+                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
+    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
+                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
+
+    update_op = tp_obj.update_state(y_true, y_pred)
+    self.evaluate(update_op)
+    result = tp_obj.result()
+    self.assertAllClose(7., result)
+
+  def test_weighted(self):
+    tp_obj = metrics.TruePositives()
+    self.evaluate(variables.variables_initializer(tp_obj.variables))
+    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
+                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
+    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
+                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
+    sample_weight = constant_op.constant((1., 1.5, 2., 2.5))
+    result = tp_obj(y_true, y_pred, sample_weight=sample_weight)
+    self.assertAllClose(12., self.evaluate(result))
+
+  def test_unweighted_with_thresholds(self):
+    tp_obj = metrics.TruePositives(thresholds=[0.15, 0.5, 0.85])
+    self.evaluate(variables.variables_initializer(tp_obj.variables))
+
+    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
+                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
+    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
+                                   (1, 1, 1, 1)))
+
+    update_op = tp_obj.update_state(y_true, y_pred)
+    self.evaluate(update_op)
+    result = tp_obj.result()
+    self.assertAllClose([6., 3., 1.], result)
+
+  def test_weighted_with_thresholds(self):
+    tp_obj = metrics.TruePositives(thresholds=[0.15, 0.5, 0.85])
+    self.evaluate(variables.variables_initializer(tp_obj.variables))
+
+    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
+                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
+    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
+                                   (1, 1, 1, 1)))
+
+    result = tp_obj(y_true, y_pred, sample_weight=37.)
+    self.assertAllClose([222., 111., 37.], self.evaluate(result))
+
+
+@test_util.run_all_in_graph_and_eager_modes
+class PrecisionTest(test.TestCase):
+
+  def test_config(self):
+    p_obj = metrics.Precision(name='my_precision', thresholds=[0.4, 0.9])
+    self.assertEqual(p_obj.name, 'my_precision')
+    self.assertEqual(len(p_obj.variables), 2)
+    self.assertEqual([v.name for v in p_obj.variables],
+                     ['true_positives:0', 'false_positives:0'])
+    self.assertEqual(p_obj.thresholds, [0.4, 0.9])
+
+    # Check save and restore config
+    p_obj2 = metrics.Precision.from_config(p_obj.get_config())
+    self.assertEqual(p_obj2.name, 'my_precision')
+    self.assertEqual(len(p_obj2.variables), 2)
+    self.assertEqual(p_obj2.thresholds, [0.4, 0.9])
+
+  def test_value_is_idempotent(self):
+    p_obj = metrics.Precision(thresholds=[0.3, 0.72])
+    y_pred = random_ops.random_uniform(shape=(10, 3))
+    y_true = random_ops.random_uniform(shape=(10, 3))
+    update_op = p_obj.update_state(y_true, y_pred)
+    self.evaluate(variables.variables_initializer(p_obj.variables))
+
+    # Run several updates.
+    for _ in range(10):
+      self.evaluate(update_op)
+
+    # Then verify idempotency.
+    initial_precision = self.evaluate(p_obj.result())
+    for _ in range(10):
+      self.assertArrayNear(initial_precision, self.evaluate(p_obj.result()),
+                           1e-3)
+
+  def test_unweighted(self):
+    p_obj = metrics.Precision()
+    y_pred = constant_op.constant([1, 0, 1, 0], shape=(1, 4))
+    y_true = constant_op.constant([0, 1, 1, 0], shape=(1, 4))
+    self.evaluate(variables.variables_initializer(p_obj.variables))
+    result = p_obj(y_true, y_pred)
+    self.assertAlmostEqual(0.5, self.evaluate(result))
+
+  def test_unweighted_all_incorrect(self):
+    p_obj = metrics.Precision(thresholds=[0.5])
+    inputs = np.random.randint(0, 2, size=(100, 1))
+    y_pred = constant_op.constant(inputs)
+    y_true = constant_op.constant(1 - inputs)
+    self.evaluate(variables.variables_initializer(p_obj.variables))
+    result = p_obj(y_true, y_pred)
+    self.assertAlmostEqual(0, self.evaluate(result))
+
+  def test_weighted(self):
+    p_obj = metrics.Precision()
+    y_pred = constant_op.constant([[1, 0, 1, 0], [1, 0, 1, 0]])
+    y_true = constant_op.constant([[0, 1, 1, 0], [1, 0, 0, 1]])
+    self.evaluate(variables.variables_initializer(p_obj.variables))
+    result = p_obj(
+        y_true,
+        y_pred,
+        sample_weight=constant_op.constant([[1, 2, 3, 4], [4, 3, 2, 1]]))
+    weighted_tp = 3.0 + 4.0
+    weighted_positives = (1.0 + 3.0) + (4.0 + 2.0)
+    expected_precision = weighted_tp / weighted_positives
+    self.assertAlmostEqual(expected_precision, self.evaluate(result))
+
+  def test_div_by_zero(self):
+    p_obj = metrics.Precision()
+    y_pred = constant_op.constant([0, 0, 0, 0])
+    y_true = constant_op.constant([0, 0, 0, 0])
+    self.evaluate(variables.variables_initializer(p_obj.variables))
+    result = p_obj(y_true, y_pred)
+    self.assertEqual(0, self.evaluate(result))
+
+  def test_unweighted_with_threshold(self):
+    p_obj = metrics.Precision(thresholds=[0.5, 0.7])
+    y_pred = constant_op.constant([1, 0, 0.6, 0], shape=(1, 4))
+    y_true = constant_op.constant([0, 1, 1, 0], shape=(1, 4))
+    self.evaluate(variables.variables_initializer(p_obj.variables))
+    result = p_obj(y_true, y_pred)
+    self.assertArrayNear([0.5, 0.], self.evaluate(result), 0)
+
+  def test_weighted_with_threshold(self):
+    p_obj = metrics.Precision(thresholds=[0.5, 1.])
+    y_true = constant_op.constant([[0, 1], [1, 0]], shape=(2, 2))
+    y_pred = constant_op.constant([[1, 0], [0.6, 0]],
+                                  shape=(2, 2),
+                                  dtype=dtypes.float32)
+    weights = constant_op.constant([[4, 0], [3, 1]],
+                                   shape=(2, 2),
+                                   dtype=dtypes.float32)
+    self.evaluate(variables.variables_initializer(p_obj.variables))
+    result = p_obj(y_true, y_pred, sample_weight=weights)
+    weighted_tp = 0 + 3.
+    weighted_positives = (0 + 3.) + (4. + 0.)
+    expected_precision = weighted_tp / weighted_positives
+    self.assertArrayNear([expected_precision, 0], self.evaluate(result), 1e-3)
+
+  def test_multiple_updates(self):
+    p_obj = metrics.Precision(thresholds=[0.5, 1.])
+    y_true = constant_op.constant([[0, 1], [1, 0]], shape=(2, 2))
+    y_pred = constant_op.constant([[1, 0], [0.6, 0]],
+                                  shape=(2, 2),
+                                  dtype=dtypes.float32)
+    weights = constant_op.constant([[4, 0], [3, 1]],
+                                   shape=(2, 2),
+                                   dtype=dtypes.float32)
+    self.evaluate(variables.variables_initializer(p_obj.variables))
+    update_op = p_obj.update_state(y_true, y_pred, sample_weight=weights)
+    for _ in range(2):
+      self.evaluate(update_op)
+
+    weighted_tp = (0 + 3.) + (0 + 3.)
+    weighted_positives = ((0 + 3.) + (4. + 0.)) + ((0 + 3.) + (4. + 0.))
+    expected_precision = weighted_tp / weighted_positives
+    self.assertArrayNear([expected_precision, 0], self.evaluate(p_obj.result()),
+                         1e-3)
+
+
+@test_util.run_all_in_graph_and_eager_modes
+class RecallTest(test.TestCase):
+
+  def test_config(self):
+    r_obj = metrics.Recall(name='my_recall', thresholds=[0.4, 0.9])
+    self.assertEqual(r_obj.name, 'my_recall')
+    self.assertEqual(len(r_obj.variables), 2)
+    self.assertEqual([v.name for v in r_obj.variables],
+                     ['true_positives:0', 'false_negatives:0'])
+    self.assertEqual(r_obj.thresholds, [0.4, 0.9])
+
+    # Check save and restore config
+    r_obj2 = metrics.Recall.from_config(r_obj.get_config())
+    self.assertEqual(r_obj2.name, 'my_recall')
+    self.assertEqual(len(r_obj2.variables), 2)
+    self.assertEqual(r_obj2.thresholds, [0.4, 0.9])
+
+  def test_value_is_idempotent(self):
+    r_obj = metrics.Recall(thresholds=[0.3, 0.72])
+    y_pred = random_ops.random_uniform(shape=(10, 3))
+    y_true = random_ops.random_uniform(shape=(10, 3))
+    update_op = r_obj.update_state(y_true, y_pred)
+    self.evaluate(variables.variables_initializer(r_obj.variables))
+
+    # Run several updates.
+    for _ in range(10):
+      self.evaluate(update_op)
+
+    # Then verify idempotency.
+    initial_recall = self.evaluate(r_obj.result())
+    for _ in range(10):
+      self.assertArrayNear(initial_recall, self.evaluate(r_obj.result()), 1e-3)
+
+  def test_unweighted(self):
+    r_obj = metrics.Recall()
+    y_pred = constant_op.constant([1, 0, 1, 0], shape=(1, 4))
+    y_true = constant_op.constant([0, 1, 1, 0], shape=(1, 4))
+    self.evaluate(variables.variables_initializer(r_obj.variables))
+    result = r_obj(y_true, y_pred)
+    self.assertAlmostEqual(0.5, self.evaluate(result))
+
+  def test_unweighted_all_incorrect(self):
+    r_obj = metrics.Recall(thresholds=[0.5])
+    inputs = np.random.randint(0, 2, size=(100, 1))
+    y_pred = constant_op.constant(inputs)
+    y_true = constant_op.constant(1 - inputs)
+    self.evaluate(variables.variables_initializer(r_obj.variables))
+    result = r_obj(y_true, y_pred)
+    self.assertAlmostEqual(0, self.evaluate(result))
+
+  def test_weighted(self):
+    r_obj = metrics.Recall()
+    y_pred = constant_op.constant([[1, 0, 1, 0], [0, 1, 0, 1]])
+    y_true = constant_op.constant([[0, 1, 1, 0], [1, 0, 0, 1]])
+    self.evaluate(variables.variables_initializer(r_obj.variables))
+    result = r_obj(
+        y_true,
+        y_pred,
+        sample_weight=constant_op.constant([[1, 2, 3, 4], [4, 3, 2, 1]]))
+    weighted_tp = 3.0 + 1.0
+    weighted_t = (2.0 + 3.0) + (4.0 + 1.0)
+    expected_recall = weighted_tp / weighted_t
+    self.assertAlmostEqual(expected_recall, self.evaluate(result))
+
+  def test_div_by_zero(self):
+    r_obj = metrics.Recall()
+    y_pred = constant_op.constant([0, 0, 0, 0])
+    y_true = constant_op.constant([0, 0, 0, 0])
+    self.evaluate(variables.variables_initializer(r_obj.variables))
+    result = r_obj(y_true, y_pred)
+    self.assertEqual(0, self.evaluate(result))
+
+  def test_unweighted_with_threshold(self):
+    r_obj = metrics.Recall(thresholds=[0.5, 0.7])
+    y_pred = constant_op.constant([1, 0, 0.6, 0], shape=(1, 4))
+    y_true = constant_op.constant([0, 1, 1, 0], shape=(1, 4))
+    self.evaluate(variables.variables_initializer(r_obj.variables))
+    result = r_obj(y_true, y_pred)
+    self.assertArrayNear([0.5, 0.], self.evaluate(result), 0)
+
+  def test_weighted_with_threshold(self):
+    r_obj = metrics.Recall(thresholds=[0.5, 1.])
+    y_true = constant_op.constant([[0, 1], [1, 0]], shape=(2, 2))
+    y_pred = constant_op.constant([[1, 0], [0.6, 0]],
+                                  shape=(2, 2),
+                                  dtype=dtypes.float32)
+    weights = constant_op.constant([[1, 4], [3, 2]],
+                                   shape=(2, 2),
+                                   dtype=dtypes.float32)
+    self.evaluate(variables.variables_initializer(r_obj.variables))
+    result = r_obj(y_true, y_pred, sample_weight=weights)
+    weighted_tp = 0 + 3.
+    weighted_positives = (0 + 3.) + (4. + 0.)
+    expected_recall = weighted_tp / weighted_positives
+    self.assertArrayNear([expected_recall, 0], self.evaluate(result), 1e-3)
+
+  def test_multiple_updates(self):
+    r_obj = metrics.Recall(thresholds=[0.5, 1.])
+    y_true = constant_op.constant([[0, 1], [1, 0]], shape=(2, 2))
+    y_pred = constant_op.constant([[1, 0], [0.6, 0]],
+                                  shape=(2, 2),
+                                  dtype=dtypes.float32)
+    weights = constant_op.constant([[1, 4], [3, 2]],
+                                   shape=(2, 2),
+                                   dtype=dtypes.float32)
+    self.evaluate(variables.variables_initializer(r_obj.variables))
+    update_op = r_obj.update_state(y_true, y_pred, sample_weight=weights)
+    for _ in range(2):
+      self.evaluate(update_op)
+
+    weighted_tp = (0 + 3.) + (0 + 3.)
+    weighted_positives = ((0 + 3.) + (4. + 0.)) + ((0 + 3.) + (4. + 0.))
+    expected_recall = weighted_tp / weighted_positives
+    self.assertArrayNear([expected_recall, 0], self.evaluate(r_obj.result()),
+                         1e-3)
+
+
+@test_util.run_all_in_graph_and_eager_modes
+class SensitivityAtSpecificityTest(test.TestCase, parameterized.TestCase):
+
+  def test_config(self):
+    s_obj = metrics.SensitivityAtSpecificity(
+        0.4, num_thresholds=100, name='sensitivity_at_specificity_1')
+    self.assertEqual(s_obj.name, 'sensitivity_at_specificity_1')
+    self.assertLen(s_obj.variables, 4)
+    self.assertEqual(s_obj.specificity, 0.4)
+    self.assertEqual(s_obj.num_thresholds, 100)
+
+    # Check save and restore config
+    s_obj2 = metrics.SensitivityAtSpecificity.from_config(s_obj.get_config())
+    self.assertEqual(s_obj2.name, 'sensitivity_at_specificity_1')
+    self.assertLen(s_obj2.variables, 4)
+    self.assertEqual(s_obj2.specificity, 0.4)
+    self.assertEqual(s_obj2.num_thresholds, 100)
+
+  def test_value_is_idempotent(self):
+    s_obj = metrics.SensitivityAtSpecificity(0.7)
+    y_pred = random_ops.random_uniform((10, 3),
+                                       maxval=1,
+                                       dtype=dtypes.float32,
+                                       seed=1)
+    y_true = random_ops.random_uniform((10, 3),
+                                       maxval=2,
+                                       dtype=dtypes.int64,
+                                       seed=1)
+    update_op = s_obj.update_state(y_true, y_pred)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+
+    # Run several updates.
+    for _ in range(10):
+      self.evaluate(update_op)
+
+    # Then verify idempotency.
+    initial_sensitivity = self.evaluate(s_obj.result())
+    for _ in range(10):
+      self.assertAlmostEqual(initial_sensitivity, self.evaluate(s_obj.result()),
+                             1e-3)
+
+  def test_unweighted_all_correct(self):
+    s_obj = metrics.SensitivityAtSpecificity(0.7)
+    inputs = np.random.randint(0, 2, size=(100, 1))
+    y_pred = constant_op.constant(inputs, dtype=dtypes.float32)
+    y_true = constant_op.constant(inputs)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+    result = s_obj(y_true, y_pred)
+    self.assertAlmostEqual(1, self.evaluate(result))
+
+  def test_unweighted_high_specificity(self):
+    s_obj = metrics.SensitivityAtSpecificity(0.8)
+    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.1, 0.45, 0.5, 0.8, 0.9]
+    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
+
+    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
+    y_true = constant_op.constant(label_values)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+    result = s_obj(y_true, y_pred)
+    self.assertAlmostEqual(0.8, self.evaluate(result))
+
+  def test_unweighted_low_specificity(self):
+    s_obj = metrics.SensitivityAtSpecificity(0.4)
+    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.01, 0.02, 0.25, 0.26, 0.26]
+    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
+
+    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
+    y_true = constant_op.constant(label_values)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+    result = s_obj(y_true, y_pred)
+    self.assertAlmostEqual(0.6, self.evaluate(result))
+
+  @parameterized.parameters([dtypes.bool, dtypes.int32, dtypes.float32])
+  def test_weighted(self, label_dtype):
+    s_obj = metrics.SensitivityAtSpecificity(0.4)
+    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.01, 0.02, 0.25, 0.26, 0.26]
+    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
+    weight_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
+    y_true = math_ops.cast(label_values, dtype=label_dtype)
+    weights = constant_op.constant(weight_values)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+    result = s_obj(y_true, y_pred, sample_weight=weights)
+    self.assertAlmostEqual(0.675, self.evaluate(result))
+
+  def test_invalid_specificity(self):
+    with self.assertRaisesRegexp(
+        ValueError, r'`specificity` must be in the range \[0, 1\].'):
+      metrics.SensitivityAtSpecificity(-1)
+
+  def test_invalid_num_thresholds(self):
+    with self.assertRaisesRegexp(ValueError, '`num_thresholds` must be > 0.'):
+      metrics.SensitivityAtSpecificity(0.4, num_thresholds=-1)
+
+
+@test_util.run_all_in_graph_and_eager_modes
+class SpecificityAtSensitivityTest(test.TestCase, parameterized.TestCase):
+
+  def test_config(self):
+    s_obj = metrics.SpecificityAtSensitivity(
+        0.4, num_thresholds=100, name='specificity_at_sensitivity_1')
+    self.assertEqual(s_obj.name, 'specificity_at_sensitivity_1')
+    self.assertLen(s_obj.variables, 4)
+    self.assertEqual(s_obj.sensitivity, 0.4)
+    self.assertEqual(s_obj.num_thresholds, 100)
+
+    # Check save and restore config
+    s_obj2 = metrics.SpecificityAtSensitivity.from_config(s_obj.get_config())
+    self.assertEqual(s_obj2.name, 'specificity_at_sensitivity_1')
+    self.assertLen(s_obj2.variables, 4)
+    self.assertEqual(s_obj2.sensitivity, 0.4)
+    self.assertEqual(s_obj2.num_thresholds, 100)
+
+  def test_value_is_idempotent(self):
+    s_obj = metrics.SpecificityAtSensitivity(0.7)
+    y_pred = random_ops.random_uniform((10, 3),
+                                       maxval=1,
+                                       dtype=dtypes.float32,
+                                       seed=1)
+    y_true = random_ops.random_uniform((10, 3),
+                                       maxval=2,
+                                       dtype=dtypes.int64,
+                                       seed=1)
+    update_op = s_obj.update_state(y_true, y_pred)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+
+    # Run several updates.
+    for _ in range(10):
+      self.evaluate(update_op)
+
+    # Then verify idempotency.
+    initial_specificity = self.evaluate(s_obj.result())
+    for _ in range(10):
+      self.assertAlmostEqual(initial_specificity, self.evaluate(s_obj.result()),
+                             1e-3)
+
+  def test_unweighted_all_correct(self):
+    s_obj = metrics.SpecificityAtSensitivity(0.7)
+    inputs = np.random.randint(0, 2, size=(100, 1))
+    y_pred = constant_op.constant(inputs, dtype=dtypes.float32)
+    y_true = constant_op.constant(inputs)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+    result = s_obj(y_true, y_pred)
+    self.assertAlmostEqual(1, self.evaluate(result))
+
+  def test_unweighted_high_sensitivity(self):
+    s_obj = metrics.SpecificityAtSensitivity(0.8)
+    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.1, 0.45, 0.5, 0.8, 0.9]
+    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
+
+    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
+    y_true = constant_op.constant(label_values)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+    result = s_obj(y_true, y_pred)
+    self.assertAlmostEqual(0.4, self.evaluate(result))
+
+  def test_unweighted_low_sensitivity(self):
+    s_obj = metrics.SpecificityAtSensitivity(0.4)
+    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.01, 0.02, 0.25, 0.26, 0.26]
+    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
+
+    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
+    y_true = constant_op.constant(label_values)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+    result = s_obj(y_true, y_pred)
+    self.assertAlmostEqual(0.6, self.evaluate(result))
+
+  @parameterized.parameters([dtypes.bool, dtypes.int32, dtypes.float32])
+  def test_weighted(self, label_dtype):
+    s_obj = metrics.SpecificityAtSensitivity(0.4)
+    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.01, 0.02, 0.25, 0.26, 0.26]
+    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
+    weight_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
+    y_true = math_ops.cast(label_values, dtype=label_dtype)
+    weights = constant_op.constant(weight_values)
+    self.evaluate(variables.variables_initializer(s_obj.variables))
+    result = s_obj(y_true, y_pred, sample_weight=weights)
+    self.assertAlmostEqual(0.4, self.evaluate(result))
+
+  def test_invalid_sensitivity(self):
+    with self.assertRaisesRegexp(
+        ValueError, r'`sensitivity` must be in the range \[0, 1\].'):
+      metrics.SpecificityAtSensitivity(-1)
+
+  def test_invalid_num_thresholds(self):
+    with self.assertRaisesRegexp(ValueError, '`num_thresholds` must be > 0.'):
+      metrics.SpecificityAtSensitivity(0.4, num_thresholds=-1)
+
+
+if __name__ == '__main__':
+  test.main()
diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py
index 42da1dfb99..f902838c11 100644
--- a/tensorflow/python/keras/metrics_test.py
+++ b/tensorflow/python/keras/metrics_test.py
@@ -20,7 +20,6 @@ from __future__ import print_function
 
 import math
 import os
-from absl.testing import parameterized
 import numpy as np
 
 from tensorflow.python.eager import context
@@ -33,8 +32,6 @@ from tensorflow.python.keras import layers
 from tensorflow.python.keras import metrics
 from tensorflow.python.keras import testing_utils
 from tensorflow.python.ops import array_ops
-from tensorflow.python.ops import math_ops
-from tensorflow.python.ops import random_ops
 from tensorflow.python.ops import variables
 from tensorflow.python.platform import test
 from tensorflow.python.training.checkpointable import util as checkpointable_utils
@@ -336,716 +333,6 @@ class KerasAccuracyTest(test.TestCase):
       self.assertAlmostEqual(result, 0.71, 2)  # 2.5/2.7
 
 
-@test_util.run_all_in_graph_and_eager_modes
-class FalsePositivesTest(test.TestCase):
-
-  def test_config(self):
-    fp_obj = metrics.FalsePositives(name='my_fp', thresholds=[0.4, 0.9])
-    self.assertEqual(fp_obj.name, 'my_fp')
-    self.assertEqual(len(fp_obj.variables), 1)
-    self.assertEqual(fp_obj.thresholds, [0.4, 0.9])
-
-    # Check save and restore config
-    fp_obj2 = metrics.FalsePositives.from_config(fp_obj.get_config())
-    self.assertEqual(fp_obj2.name, 'my_fp')
-    self.assertEqual(len(fp_obj2.variables), 1)
-    self.assertEqual(fp_obj2.thresholds, [0.4, 0.9])
-
-  def test_unweighted(self):
-    fp_obj = metrics.FalsePositives()
-    self.evaluate(variables.variables_initializer(fp_obj.variables))
-
-    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
-                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
-    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
-                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
-
-    update_op = fp_obj.update_state(y_true, y_pred)
-    self.evaluate(update_op)
-    result = fp_obj.result()
-    self.assertAllClose(7., result)
-
-  def test_weighted(self):
-    fp_obj = metrics.FalsePositives()
-    self.evaluate(variables.variables_initializer(fp_obj.variables))
-    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
-                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
-    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
-                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
-    sample_weight = constant_op.constant((1., 1.5, 2., 2.5))
-    result = fp_obj(y_true, y_pred, sample_weight=sample_weight)
-    self.assertAllClose(14., self.evaluate(result))
-
-  def test_unweighted_with_thresholds(self):
-    fp_obj = metrics.FalsePositives(thresholds=[0.15, 0.5, 0.85])
-    self.evaluate(variables.variables_initializer(fp_obj.variables))
-
-    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
-                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
-    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
-                                   (1, 1, 1, 1)))
-
-    update_op = fp_obj.update_state(y_true, y_pred)
-    self.evaluate(update_op)
-    result = fp_obj.result()
-    self.assertAllClose([7., 4., 2.], result)
-
-  def test_weighted_with_thresholds(self):
-    fp_obj = metrics.FalsePositives(thresholds=[0.15, 0.5, 0.85])
-    self.evaluate(variables.variables_initializer(fp_obj.variables))
-
-    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
-                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
-    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
-                                   (1, 1, 1, 1)))
-    sample_weight = ((1.0, 2.0, 3.0, 5.0), (7.0, 11.0, 13.0, 17.0),
-                     (19.0, 23.0, 29.0, 31.0), (5.0, 15.0, 10.0, 0))
-
-    result = fp_obj(y_true, y_pred, sample_weight=sample_weight)
-    self.assertAllClose([125., 42., 12.], self.evaluate(result))
-
-  def test_threshold_limit(self):
-    with self.assertRaisesRegexp(
-        ValueError,
-        r'Threshold values must be in \[0, 1\]. Invalid values: \[-1, 2\]'):
-      metrics.FalsePositives(thresholds=[-1, 0.5, 2])
-
-    with self.assertRaisesRegexp(
-        ValueError,
-        r'Threshold values must be in \[0, 1\]. Invalid values: \[None\]'):
-      metrics.FalsePositives(thresholds=[None])
-
-
-@test_util.run_all_in_graph_and_eager_modes
-class FalseNegativesTest(test.TestCase):
-
-  def test_config(self):
-    fn_obj = metrics.FalseNegatives(name='my_fn', thresholds=[0.4, 0.9])
-    self.assertEqual(fn_obj.name, 'my_fn')
-    self.assertEqual(len(fn_obj.variables), 1)
-    self.assertEqual(fn_obj.thresholds, [0.4, 0.9])
-
-    # Check save and restore config
-    fn_obj2 = metrics.FalseNegatives.from_config(fn_obj.get_config())
-    self.assertEqual(fn_obj2.name, 'my_fn')
-    self.assertEqual(len(fn_obj2.variables), 1)
-    self.assertEqual(fn_obj2.thresholds, [0.4, 0.9])
-
-  def test_unweighted(self):
-    fn_obj = metrics.FalseNegatives()
-    self.evaluate(variables.variables_initializer(fn_obj.variables))
-
-    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
-                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
-    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
-                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
-
-    update_op = fn_obj.update_state(y_true, y_pred)
-    self.evaluate(update_op)
-    result = fn_obj.result()
-    self.assertAllClose(3., result)
-
-  def test_weighted(self):
-    fn_obj = metrics.FalseNegatives()
-    self.evaluate(variables.variables_initializer(fn_obj.variables))
-    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
-                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
-    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
-                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
-    sample_weight = constant_op.constant((1., 1.5, 2., 2.5))
-    result = fn_obj(y_true, y_pred, sample_weight=sample_weight)
-    self.assertAllClose(5., self.evaluate(result))
-
-  def test_unweighted_with_thresholds(self):
-    fn_obj = metrics.FalseNegatives(thresholds=[0.15, 0.5, 0.85])
-    self.evaluate(variables.variables_initializer(fn_obj.variables))
-
-    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
-                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
-    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
-                                   (1, 1, 1, 1)))
-
-    update_op = fn_obj.update_state(y_true, y_pred)
-    self.evaluate(update_op)
-    result = fn_obj.result()
-    self.assertAllClose([1., 4., 6.], result)
-
-  def test_weighted_with_thresholds(self):
-    fn_obj = metrics.FalseNegatives(thresholds=[0.15, 0.5, 0.85])
-    self.evaluate(variables.variables_initializer(fn_obj.variables))
-
-    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
-                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
-    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
-                                   (1, 1, 1, 1)))
-    sample_weight = ((3.0,), (5.0,), (7.0,), (4.0,))
-
-    result = fn_obj(y_true, y_pred, sample_weight=sample_weight)
-    self.assertAllClose([4., 16., 23.], self.evaluate(result))
-
-
-@test_util.run_all_in_graph_and_eager_modes
-class TrueNegativesTest(test.TestCase):
-
-  def test_config(self):
-    tn_obj = metrics.TrueNegatives(name='my_tn', thresholds=[0.4, 0.9])
-    self.assertEqual(tn_obj.name, 'my_tn')
-    self.assertEqual(len(tn_obj.variables), 1)
-    self.assertEqual(tn_obj.thresholds, [0.4, 0.9])
-
-    # Check save and restore config
-    tn_obj2 = metrics.TrueNegatives.from_config(tn_obj.get_config())
-    self.assertEqual(tn_obj2.name, 'my_tn')
-    self.assertEqual(len(tn_obj2.variables), 1)
-    self.assertEqual(tn_obj2.thresholds, [0.4, 0.9])
-
-  def test_unweighted(self):
-    tn_obj = metrics.TrueNegatives()
-    self.evaluate(variables.variables_initializer(tn_obj.variables))
-
-    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
-                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
-    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
-                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
-
-    update_op = tn_obj.update_state(y_true, y_pred)
-    self.evaluate(update_op)
-    result = tn_obj.result()
-    self.assertAllClose(3., result)
-
-  def test_weighted(self):
-    tn_obj = metrics.TrueNegatives()
-    self.evaluate(variables.variables_initializer(tn_obj.variables))
-    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
-                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
-    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
-                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
-    sample_weight = constant_op.constant((1., 1.5, 2., 2.5))
-    result = tn_obj(y_true, y_pred, sample_weight=sample_weight)
-    self.assertAllClose(4., self.evaluate(result))
-
-  def test_unweighted_with_thresholds(self):
-    tn_obj = metrics.TrueNegatives(thresholds=[0.15, 0.5, 0.85])
-    self.evaluate(variables.variables_initializer(tn_obj.variables))
-
-    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
-                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
-    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
-                                   (1, 1, 1, 1)))
-
-    update_op = tn_obj.update_state(y_true, y_pred)
-    self.evaluate(update_op)
-    result = tn_obj.result()
-    self.assertAllClose([2., 5., 7.], result)
-
-  def test_weighted_with_thresholds(self):
-    tn_obj = metrics.TrueNegatives(thresholds=[0.15, 0.5, 0.85])
-    self.evaluate(variables.variables_initializer(tn_obj.variables))
-
-    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
-                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
-    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
-                                   (1, 1, 1, 1)))
-    sample_weight = ((0.0, 2.0, 3.0, 5.0),)
-
-    result = tn_obj(y_true, y_pred, sample_weight=sample_weight)
-    self.assertAllClose([5., 15., 23.], self.evaluate(result))
-
-
-@test_util.run_all_in_graph_and_eager_modes
-class TruePositivesTest(test.TestCase):
-
-  def test_config(self):
-    tp_obj = metrics.TruePositives(name='my_tp', thresholds=[0.4, 0.9])
-    self.assertEqual(tp_obj.name, 'my_tp')
-    self.assertEqual(len(tp_obj.variables), 1)
-    self.assertEqual(tp_obj.thresholds, [0.4, 0.9])
-
-    # Check save and restore config
-    tp_obj2 = metrics.TruePositives.from_config(tp_obj.get_config())
-    self.assertEqual(tp_obj2.name, 'my_tp')
-    self.assertEqual(len(tp_obj2.variables), 1)
-    self.assertEqual(tp_obj2.thresholds, [0.4, 0.9])
-
-  def test_unweighted(self):
-    tp_obj = metrics.TruePositives()
-    self.evaluate(variables.variables_initializer(tp_obj.variables))
-
-    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
-                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
-    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
-                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
-
-    update_op = tp_obj.update_state(y_true, y_pred)
-    self.evaluate(update_op)
-    result = tp_obj.result()
-    self.assertAllClose(7., result)
-
-  def test_weighted(self):
-    tp_obj = metrics.TruePositives()
-    self.evaluate(variables.variables_initializer(tp_obj.variables))
-    y_true = constant_op.constant(((0, 1, 0, 1, 0), (0, 0, 1, 1, 1),
-                                   (1, 1, 1, 1, 0), (0, 0, 0, 0, 1)))
-    y_pred = constant_op.constant(((0, 0, 1, 1, 0), (1, 1, 1, 1, 1),
-                                   (0, 1, 0, 1, 0), (1, 1, 1, 1, 1)))
-    sample_weight = constant_op.constant((1., 1.5, 2., 2.5))
-    result = tp_obj(y_true, y_pred, sample_weight=sample_weight)
-    self.assertAllClose(12., self.evaluate(result))
-
-  def test_unweighted_with_thresholds(self):
-    tp_obj = metrics.TruePositives(thresholds=[0.15, 0.5, 0.85])
-    self.evaluate(variables.variables_initializer(tp_obj.variables))
-
-    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
-                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
-    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
-                                   (1, 1, 1, 1)))
-
-    update_op = tp_obj.update_state(y_true, y_pred)
-    self.evaluate(update_op)
-    result = tp_obj.result()
-    self.assertAllClose([6., 3., 1.], result)
-
-  def test_weighted_with_thresholds(self):
-    tp_obj = metrics.TruePositives(thresholds=[0.15, 0.5, 0.85])
-    self.evaluate(variables.variables_initializer(tp_obj.variables))
-
-    y_pred = constant_op.constant(((0.9, 0.2, 0.8, 0.1), (0.2, 0.9, 0.7, 0.6),
-                                   (0.1, 0.2, 0.4, 0.3), (0, 1, 0.7, 0.3)))
-    y_true = constant_op.constant(((0, 1, 1, 0), (1, 0, 0, 0), (0, 0, 0, 0),
-                                   (1, 1, 1, 1)))
-
-    result = tp_obj(y_true, y_pred, sample_weight=37.)
-    self.assertAllClose([222., 111., 37.], self.evaluate(result))
-
-
-@test_util.run_all_in_graph_and_eager_modes
-class PrecisionTest(test.TestCase):
-
-  def test_config(self):
-    p_obj = metrics.Precision(name='my_precision', thresholds=[0.4, 0.9])
-    self.assertEqual(p_obj.name, 'my_precision')
-    self.assertEqual(len(p_obj.variables), 2)
-    self.assertEqual([v.name for v in p_obj.variables],
-                     ['true_positives:0', 'false_positives:0'])
-    self.assertEqual(p_obj.thresholds, [0.4, 0.9])
-
-    # Check save and restore config
-    p_obj2 = metrics.Precision.from_config(p_obj.get_config())
-    self.assertEqual(p_obj2.name, 'my_precision')
-    self.assertEqual(len(p_obj2.variables), 2)
-    self.assertEqual(p_obj2.thresholds, [0.4, 0.9])
-
-  def test_value_is_idempotent(self):
-    p_obj = metrics.Precision(thresholds=[0.3, 0.72])
-    y_pred = random_ops.random_uniform(shape=(10, 3))
-    y_true = random_ops.random_uniform(shape=(10, 3))
-    update_op = p_obj.update_state(y_true, y_pred)
-    self.evaluate(variables.variables_initializer(p_obj.variables))
-
-    # Run several updates.
-    for _ in range(10):
-      self.evaluate(update_op)
-
-    # Then verify idempotency.
-    initial_precision = self.evaluate(p_obj.result())
-    for _ in range(10):
-      self.assertArrayNear(initial_precision, self.evaluate(p_obj.result()),
-                           1e-3)
-
-  def test_unweighted(self):
-    p_obj = metrics.Precision()
-    y_pred = constant_op.constant([1, 0, 1, 0], shape=(1, 4))
-    y_true = constant_op.constant([0, 1, 1, 0], shape=(1, 4))
-    self.evaluate(variables.variables_initializer(p_obj.variables))
-    result = p_obj(y_true, y_pred)
-    self.assertAlmostEqual(0.5, self.evaluate(result))
-
-  def test_unweighted_all_incorrect(self):
-    p_obj = metrics.Precision(thresholds=[0.5])
-    inputs = np.random.randint(0, 2, size=(100, 1))
-    y_pred = constant_op.constant(inputs)
-    y_true = constant_op.constant(1 - inputs)
-    self.evaluate(variables.variables_initializer(p_obj.variables))
-    result = p_obj(y_true, y_pred)
-    self.assertAlmostEqual(0, self.evaluate(result))
-
-  def test_weighted(self):
-    p_obj = metrics.Precision()
-    y_pred = constant_op.constant([[1, 0, 1, 0], [1, 0, 1, 0]])
-    y_true = constant_op.constant([[0, 1, 1, 0], [1, 0, 0, 1]])
-    self.evaluate(variables.variables_initializer(p_obj.variables))
-    result = p_obj(
-        y_true,
-        y_pred,
-        sample_weight=constant_op.constant([[1, 2, 3, 4], [4, 3, 2, 1]]))
-    weighted_tp = 3.0 + 4.0
-    weighted_positives = (1.0 + 3.0) + (4.0 + 2.0)
-    expected_precision = weighted_tp / weighted_positives
-    self.assertAlmostEqual(expected_precision, self.evaluate(result))
-
-  def test_div_by_zero(self):
-    p_obj = metrics.Precision()
-    y_pred = constant_op.constant([0, 0, 0, 0])
-    y_true = constant_op.constant([0, 0, 0, 0])
-    self.evaluate(variables.variables_initializer(p_obj.variables))
-    result = p_obj(y_true, y_pred)
-    self.assertEqual(0, self.evaluate(result))
-
-  def test_unweighted_with_threshold(self):
-    p_obj = metrics.Precision(thresholds=[0.5, 0.7])
-    y_pred = constant_op.constant([1, 0, 0.6, 0], shape=(1, 4))
-    y_true = constant_op.constant([0, 1, 1, 0], shape=(1, 4))
-    self.evaluate(variables.variables_initializer(p_obj.variables))
-    result = p_obj(y_true, y_pred)
-    self.assertArrayNear([0.5, 0.], self.evaluate(result), 0)
-
-  def test_weighted_with_threshold(self):
-    p_obj = metrics.Precision(thresholds=[0.5, 1.])
-    y_true = constant_op.constant([[0, 1], [1, 0]], shape=(2, 2))
-    y_pred = constant_op.constant([[1, 0], [0.6, 0]],
-                                  shape=(2, 2),
-                                  dtype=dtypes.float32)
-    weights = constant_op.constant([[4, 0], [3, 1]],
-                                   shape=(2, 2),
-                                   dtype=dtypes.float32)
-    self.evaluate(variables.variables_initializer(p_obj.variables))
-    result = p_obj(y_true, y_pred, sample_weight=weights)
-    weighted_tp = 0 + 3.
-    weighted_positives = (0 + 3.) + (4. + 0.)
-    expected_precision = weighted_tp / weighted_positives
-    self.assertArrayNear([expected_precision, 0], self.evaluate(result), 1e-3)
-
-  def test_multiple_updates(self):
-    p_obj = metrics.Precision(thresholds=[0.5, 1.])
-    y_true = constant_op.constant([[0, 1], [1, 0]], shape=(2, 2))
-    y_pred = constant_op.constant([[1, 0], [0.6, 0]],
-                                  shape=(2, 2),
-                                  dtype=dtypes.float32)
-    weights = constant_op.constant([[4, 0], [3, 1]],
-                                   shape=(2, 2),
-                                   dtype=dtypes.float32)
-    self.evaluate(variables.variables_initializer(p_obj.variables))
-    update_op = p_obj.update_state(y_true, y_pred, sample_weight=weights)
-    for _ in range(2):
-      self.evaluate(update_op)
-
-    weighted_tp = (0 + 3.) + (0 + 3.)
-    weighted_positives = ((0 + 3.) + (4. + 0.)) + ((0 + 3.) + (4. + 0.))
-    expected_precision = weighted_tp / weighted_positives
-    self.assertArrayNear([expected_precision, 0], self.evaluate(p_obj.result()),
-                         1e-3)
-
-
-@test_util.run_all_in_graph_and_eager_modes
-class RecallTest(test.TestCase):
-
-  def test_config(self):
-    r_obj = metrics.Recall(name='my_recall', thresholds=[0.4, 0.9])
-    self.assertEqual(r_obj.name, 'my_recall')
-    self.assertEqual(len(r_obj.variables), 2)
-    self.assertEqual([v.name for v in r_obj.variables],
-                     ['true_positives:0', 'false_negatives:0'])
-    self.assertEqual(r_obj.thresholds, [0.4, 0.9])
-
-    # Check save and restore config
-    r_obj2 = metrics.Recall.from_config(r_obj.get_config())
-    self.assertEqual(r_obj2.name, 'my_recall')
-    self.assertEqual(len(r_obj2.variables), 2)
-    self.assertEqual(r_obj2.thresholds, [0.4, 0.9])
-
-  def test_value_is_idempotent(self):
-    r_obj = metrics.Recall(thresholds=[0.3, 0.72])
-    y_pred = random_ops.random_uniform(shape=(10, 3))
-    y_true = random_ops.random_uniform(shape=(10, 3))
-    update_op = r_obj.update_state(y_true, y_pred)
-    self.evaluate(variables.variables_initializer(r_obj.variables))
-
-    # Run several updates.
-    for _ in range(10):
-      self.evaluate(update_op)
-
-    # Then verify idempotency.
-    initial_recall = self.evaluate(r_obj.result())
-    for _ in range(10):
-      self.assertArrayNear(initial_recall, self.evaluate(r_obj.result()), 1e-3)
-
-  def test_unweighted(self):
-    r_obj = metrics.Recall()
-    y_pred = constant_op.constant([1, 0, 1, 0], shape=(1, 4))
-    y_true = constant_op.constant([0, 1, 1, 0], shape=(1, 4))
-    self.evaluate(variables.variables_initializer(r_obj.variables))
-    result = r_obj(y_true, y_pred)
-    self.assertAlmostEqual(0.5, self.evaluate(result))
-
-  def test_unweighted_all_incorrect(self):
-    r_obj = metrics.Recall(thresholds=[0.5])
-    inputs = np.random.randint(0, 2, size=(100, 1))
-    y_pred = constant_op.constant(inputs)
-    y_true = constant_op.constant(1 - inputs)
-    self.evaluate(variables.variables_initializer(r_obj.variables))
-    result = r_obj(y_true, y_pred)
-    self.assertAlmostEqual(0, self.evaluate(result))
-
-  def test_weighted(self):
-    r_obj = metrics.Recall()
-    y_pred = constant_op.constant([[1, 0, 1, 0], [0, 1, 0, 1]])
-    y_true = constant_op.constant([[0, 1, 1, 0], [1, 0, 0, 1]])
-    self.evaluate(variables.variables_initializer(r_obj.variables))
-    result = r_obj(
-        y_true,
-        y_pred,
-        sample_weight=constant_op.constant([[1, 2, 3, 4], [4, 3, 2, 1]]))
-    weighted_tp = 3.0 + 1.0
-    weighted_t = (2.0 + 3.0) + (4.0 + 1.0)
-    expected_recall = weighted_tp / weighted_t
-    self.assertAlmostEqual(expected_recall, self.evaluate(result))
-
-  def test_div_by_zero(self):
-    r_obj = metrics.Recall()
-    y_pred = constant_op.constant([0, 0, 0, 0])
-    y_true = constant_op.constant([0, 0, 0, 0])
-    self.evaluate(variables.variables_initializer(r_obj.variables))
-    result = r_obj(y_true, y_pred)
-    self.assertEqual(0, self.evaluate(result))
-
-  def test_unweighted_with_threshold(self):
-    r_obj = metrics.Recall(thresholds=[0.5, 0.7])
-    y_pred = constant_op.constant([1, 0, 0.6, 0], shape=(1, 4))
-    y_true = constant_op.constant([0, 1, 1, 0], shape=(1, 4))
-    self.evaluate(variables.variables_initializer(r_obj.variables))
-    result = r_obj(y_true, y_pred)
-    self.assertArrayNear([0.5, 0.], self.evaluate(result), 0)
-
-  def test_weighted_with_threshold(self):
-    r_obj = metrics.Recall(thresholds=[0.5, 1.])
-    y_true = constant_op.constant([[0, 1], [1, 0]], shape=(2, 2))
-    y_pred = constant_op.constant([[1, 0], [0.6, 0]],
-                                  shape=(2, 2),
-                                  dtype=dtypes.float32)
-    weights = constant_op.constant([[1, 4], [3, 2]],
-                                   shape=(2, 2),
-                                   dtype=dtypes.float32)
-    self.evaluate(variables.variables_initializer(r_obj.variables))
-    result = r_obj(y_true, y_pred, sample_weight=weights)
-    weighted_tp = 0 + 3.
-    weighted_positives = (0 + 3.) + (4. + 0.)
-    expected_recall = weighted_tp / weighted_positives
-    self.assertArrayNear([expected_recall, 0], self.evaluate(result), 1e-3)
-
-  def test_multiple_updates(self):
-    r_obj = metrics.Recall(thresholds=[0.5, 1.])
-    y_true = constant_op.constant([[0, 1], [1, 0]], shape=(2, 2))
-    y_pred = constant_op.constant([[1, 0], [0.6, 0]],
-                                  shape=(2, 2),
-                                  dtype=dtypes.float32)
-    weights = constant_op.constant([[1, 4], [3, 2]],
-                                   shape=(2, 2),
-                                   dtype=dtypes.float32)
-    self.evaluate(variables.variables_initializer(r_obj.variables))
-    update_op = r_obj.update_state(y_true, y_pred, sample_weight=weights)
-    for _ in range(2):
-      self.evaluate(update_op)
-
-    weighted_tp = (0 + 3.) + (0 + 3.)
-    weighted_positives = ((0 + 3.) + (4. + 0.)) + ((0 + 3.) + (4. + 0.))
-    expected_recall = weighted_tp / weighted_positives
-    self.assertArrayNear([expected_recall, 0], self.evaluate(r_obj.result()),
-                         1e-3)
-
-
-@test_util.run_all_in_graph_and_eager_modes
-class SensitivityAtSpecificityTest(test.TestCase, parameterized.TestCase):
-
-  def test_config(self):
-    s_obj = metrics.SensitivityAtSpecificity(
-        0.4, num_thresholds=100, name='sensitivity_at_specificity_1')
-    self.assertEqual(s_obj.name, 'sensitivity_at_specificity_1')
-    self.assertLen(s_obj.variables, 4)
-    self.assertEqual(s_obj.specificity, 0.4)
-    self.assertEqual(s_obj.num_thresholds, 100)
-
-    # Check save and restore config
-    s_obj2 = metrics.SensitivityAtSpecificity.from_config(s_obj.get_config())
-    self.assertEqual(s_obj2.name, 'sensitivity_at_specificity_1')
-    self.assertLen(s_obj2.variables, 4)
-    self.assertEqual(s_obj2.specificity, 0.4)
-    self.assertEqual(s_obj2.num_thresholds, 100)
-
-  def test_value_is_idempotent(self):
-    s_obj = metrics.SensitivityAtSpecificity(0.7)
-    y_pred = random_ops.random_uniform((10, 3),
-                                       maxval=1,
-                                       dtype=dtypes.float32,
-                                       seed=1)
-    y_true = random_ops.random_uniform((10, 3),
-                                       maxval=2,
-                                       dtype=dtypes.int64,
-                                       seed=1)
-    update_op = s_obj.update_state(y_true, y_pred)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-
-    # Run several updates.
-    for _ in range(10):
-      self.evaluate(update_op)
-
-    # Then verify idempotency.
-    initial_sensitivity = self.evaluate(s_obj.result())
-    for _ in range(10):
-      self.assertAlmostEqual(initial_sensitivity, self.evaluate(s_obj.result()),
-                             1e-3)
-
-  def test_unweighted_all_correct(self):
-    s_obj = metrics.SensitivityAtSpecificity(0.7)
-    inputs = np.random.randint(0, 2, size=(100, 1))
-    y_pred = constant_op.constant(inputs, dtype=dtypes.float32)
-    y_true = constant_op.constant(inputs)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-    result = s_obj(y_true, y_pred)
-    self.assertAlmostEqual(1, self.evaluate(result))
-
-  def test_unweighted_high_specificity(self):
-    s_obj = metrics.SensitivityAtSpecificity(0.8)
-    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.1, 0.45, 0.5, 0.8, 0.9]
-    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
-
-    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
-    y_true = constant_op.constant(label_values)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-    result = s_obj(y_true, y_pred)
-    self.assertAlmostEqual(0.8, self.evaluate(result))
-
-  def test_unweighted_low_specificity(self):
-    s_obj = metrics.SensitivityAtSpecificity(0.4)
-    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.01, 0.02, 0.25, 0.26, 0.26]
-    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
-
-    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
-    y_true = constant_op.constant(label_values)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-    result = s_obj(y_true, y_pred)
-    self.assertAlmostEqual(0.6, self.evaluate(result))
-
-  @parameterized.parameters([dtypes.bool, dtypes.int32, dtypes.float32])
-  def test_weighted(self, label_dtype):
-    s_obj = metrics.SensitivityAtSpecificity(0.4)
-    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.01, 0.02, 0.25, 0.26, 0.26]
-    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
-    weight_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-
-    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
-    y_true = math_ops.cast(label_values, dtype=label_dtype)
-    weights = constant_op.constant(weight_values)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-    result = s_obj(y_true, y_pred, sample_weight=weights)
-    self.assertAlmostEqual(0.675, self.evaluate(result))
-
-  def test_invalid_specificity(self):
-    with self.assertRaisesRegexp(
-        ValueError, r'`specificity` must be in the range \[0, 1\].'):
-      metrics.SensitivityAtSpecificity(-1)
-
-  def test_invalid_num_thresholds(self):
-    with self.assertRaisesRegexp(ValueError, '`num_thresholds` must be > 0.'):
-      metrics.SensitivityAtSpecificity(0.4, num_thresholds=-1)
-
-
-@test_util.run_all_in_graph_and_eager_modes
-class SpecificityAtSensitivityTest(test.TestCase, parameterized.TestCase):
-
-  def test_config(self):
-    s_obj = metrics.SpecificityAtSensitivity(
-        0.4, num_thresholds=100, name='specificity_at_sensitivity_1')
-    self.assertEqual(s_obj.name, 'specificity_at_sensitivity_1')
-    self.assertLen(s_obj.variables, 4)
-    self.assertEqual(s_obj.sensitivity, 0.4)
-    self.assertEqual(s_obj.num_thresholds, 100)
-
-    # Check save and restore config
-    s_obj2 = metrics.SpecificityAtSensitivity.from_config(s_obj.get_config())
-    self.assertEqual(s_obj2.name, 'specificity_at_sensitivity_1')
-    self.assertLen(s_obj2.variables, 4)
-    self.assertEqual(s_obj2.sensitivity, 0.4)
-    self.assertEqual(s_obj2.num_thresholds, 100)
-
-  def test_value_is_idempotent(self):
-    s_obj = metrics.SpecificityAtSensitivity(0.7)
-    y_pred = random_ops.random_uniform((10, 3),
-                                       maxval=1,
-                                       dtype=dtypes.float32,
-                                       seed=1)
-    y_true = random_ops.random_uniform((10, 3),
-                                       maxval=2,
-                                       dtype=dtypes.int64,
-                                       seed=1)
-    update_op = s_obj.update_state(y_true, y_pred)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-
-    # Run several updates.
-    for _ in range(10):
-      self.evaluate(update_op)
-
-    # Then verify idempotency.
-    initial_specificity = self.evaluate(s_obj.result())
-    for _ in range(10):
-      self.assertAlmostEqual(initial_specificity, self.evaluate(s_obj.result()),
-                             1e-3)
-
-  def test_unweighted_all_correct(self):
-    s_obj = metrics.SpecificityAtSensitivity(0.7)
-    inputs = np.random.randint(0, 2, size=(100, 1))
-    y_pred = constant_op.constant(inputs, dtype=dtypes.float32)
-    y_true = constant_op.constant(inputs)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-    result = s_obj(y_true, y_pred)
-    self.assertAlmostEqual(1, self.evaluate(result))
-
-  def test_unweighted_high_sensitivity(self):
-    s_obj = metrics.SpecificityAtSensitivity(0.8)
-    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.1, 0.45, 0.5, 0.8, 0.9]
-    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
-
-    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
-    y_true = constant_op.constant(label_values)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-    result = s_obj(y_true, y_pred)
-    self.assertAlmostEqual(0.4, self.evaluate(result))
-
-  def test_unweighted_low_sensitivity(self):
-    s_obj = metrics.SpecificityAtSensitivity(0.4)
-    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.01, 0.02, 0.25, 0.26, 0.26]
-    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
-
-    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
-    y_true = constant_op.constant(label_values)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-    result = s_obj(y_true, y_pred)
-    self.assertAlmostEqual(0.6, self.evaluate(result))
-
-  @parameterized.parameters([dtypes.bool, dtypes.int32, dtypes.float32])
-  def test_weighted(self, label_dtype):
-    s_obj = metrics.SpecificityAtSensitivity(0.4)
-    pred_values = [0.0, 0.1, 0.2, 0.3, 0.4, 0.01, 0.02, 0.25, 0.26, 0.26]
-    label_values = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
-    weight_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-
-    y_pred = constant_op.constant(pred_values, dtype=dtypes.float32)
-    y_true = math_ops.cast(label_values, dtype=label_dtype)
-    weights = constant_op.constant(weight_values)
-    self.evaluate(variables.variables_initializer(s_obj.variables))
-    result = s_obj(y_true, y_pred, sample_weight=weights)
-    self.assertAlmostEqual(0.4, self.evaluate(result))
-
-  def test_invalid_sensitivity(self):
-    with self.assertRaisesRegexp(
-        ValueError, r'`sensitivity` must be in the range \[0, 1\].'):
-      metrics.SpecificityAtSensitivity(-1)
-
-  def test_invalid_num_thresholds(self):
-    with self.assertRaisesRegexp(ValueError, '`num_thresholds` must be > 0.'):
-      metrics.SpecificityAtSensitivity(0.4, num_thresholds=-1)
-
-
 @test_util.run_all_in_graph_and_eager_modes
 class CosineProximityTest(test.TestCase):
 
-- 
GitLab


From 7e3077a22530e253ab31417abb18de5bb60779ef Mon Sep 17 00:00:00 2001
From: Karim Nosir 
Date: Thu, 10 Jan 2019 14:55:53 -0800
Subject: [PATCH 0517/2345] Add the data file to the data section in BUILD.

PiperOrigin-RevId: 228782621
---
 tensorflow/lite/java/BUILD | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tensorflow/lite/java/BUILD b/tensorflow/lite/java/BUILD
index adf7bc9087..a9d4a422c5 100644
--- a/tensorflow/lite/java/BUILD
+++ b/tensorflow/lite/java/BUILD
@@ -121,6 +121,7 @@ java_test(
         "src/testdata/int64.bin",
         "src/testdata/invalid_model.bin",
         "src/testdata/quantized.bin",
+        "src/testdata/string.bin",
         "src/testdata/uint8.bin",
         "src/testdata/with_custom_op.lite",
     ],
-- 
GitLab


From d08fa433af627143e23624e9aaadf32373f4db0e Mon Sep 17 00:00:00 2001
From: Thomas O'Malley 
Date: Thu, 10 Jan 2019 15:27:19 -0800
Subject: [PATCH 0518/2345] Fix error message when Subclassing Network without
 calling super's __init__ first.

PiperOrigin-RevId: 228788216
---
 tensorflow/python/keras/engine/network.py       |  8 +++++---
 tensorflow/python/keras/engine/topology_test.py | 10 ++++++++++
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py
index 8e130aef40..6c3691a30c 100644
--- a/tensorflow/python/keras/engine/network.py
+++ b/tensorflow/python/keras/engine/network.py
@@ -335,9 +335,11 @@ class Network(base_layer.Layer):
     if not getattr(self, '_setattr_tracking', True):
       super(Network, self).__setattr__(name, value)
       return
-    if (isinstance(value, (base_layer.Layer,
-                           data_structures.CheckpointableDataStructure))
-        or checkpointable_layer_utils.has_weights(value)):
+
+    if all(
+        isinstance(v, (base_layer.Layer,
+                       data_structures.CheckpointableDataStructure)) or
+        checkpointable_layer_utils.has_weights(v) for v in nest.flatten(value)):
       try:
         self._is_graph_network
       except AttributeError:
diff --git a/tensorflow/python/keras/engine/topology_test.py b/tensorflow/python/keras/engine/topology_test.py
index e9aeae75ce..6c2835aece 100644
--- a/tensorflow/python/keras/engine/topology_test.py
+++ b/tensorflow/python/keras/engine/topology_test.py
@@ -920,6 +920,16 @@ class TopologyConstructionTest(keras_parameterized.TestCase):
       yaml_str = model.to_yaml()
       keras.models.model_from_yaml(yaml_str)
 
+  def test_subclassed_error_if_init_not_called(self):
+
+    class MyNetwork(network_lib.Network):
+
+      def __init__(self):
+        self._foo = [keras.layers.Dense(10), keras.layers.Dense(10)]
+
+    with self.assertRaisesRegexp(RuntimeError, 'forgot to call'):
+      MyNetwork()
+
 
 class DeferredModeTest(test.TestCase):
 
-- 
GitLab


From d6623c9341cda24851827e5069fec810860f1616 Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 16:08:49 -0800
Subject: [PATCH 0519/2345] A context manager api for eager profiler.

PiperOrigin-RevId: 228796096
---
 tensorflow/python/eager/profiler.py | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/tensorflow/python/eager/profiler.py b/tensorflow/python/eager/profiler.py
index 016bca3e3c..f1d4373085 100644
--- a/tensorflow/python/eager/profiler.py
+++ b/tensorflow/python/eager/profiler.py
@@ -23,6 +23,7 @@ import threading
 from tensorflow.python import pywrap_tensorflow
 from tensorflow.python.eager import context
 from tensorflow.python.framework import c_api_util
+from tensorflow.python.platform import gfile
 
 _profiler = None
 _profiler_lock = threading.Lock()
@@ -67,3 +68,26 @@ def stop():
     pywrap_tensorflow.TFE_DeleteProfiler(_profiler)
     _profiler = None
   return result
+
+
+class Profiler(object):
+  """Context-manager eager profiler api.
+
+  Example usage:
+  ```python
+  with Profiler("/path/to/save/result"):
+    # do some work
+  ```
+  """
+
+  def __init__(self, filename):
+    self._filename = filename
+
+  def __enter__(self):
+    start()
+
+  def __exit__(self, typ, value, tb):
+    result = stop()
+    with gfile.Open(self._filename, 'wb') as f:
+      f.write(result)
+
-- 
GitLab


From 0b3ef83939b9deb2a32c5afae254555f395e0935 Mon Sep 17 00:00:00 2001
From: Andy Ly 
Date: Thu, 10 Jan 2019 16:15:12 -0800
Subject: [PATCH 0520/2345] [Grappler] Update UpdateFanouts and UpdateFanin to
 skip mutations when to_node is a Switch that is being added as a control
 dependency.

PiperOrigin-RevId: 228797109
---
 .../core/grappler/mutable_graph_view.cc       | 115 ++++++-----
 tensorflow/core/grappler/mutable_graph_view.h |  11 +-
 .../core/grappler/mutable_graph_view_test.cc  | 181 ++++++++++++++----
 3 files changed, 209 insertions(+), 98 deletions(-)

diff --git a/tensorflow/core/grappler/mutable_graph_view.cc b/tensorflow/core/grappler/mutable_graph_view.cc
index a8a90d1a57..5acdb02dee 100644
--- a/tensorflow/core/grappler/mutable_graph_view.cc
+++ b/tensorflow/core/grappler/mutable_graph_view.cc
@@ -185,6 +185,9 @@ NodeDef* MutableGraphView::AddNode(NodeDef&& node) {
 
 bool MutableGraphView::UpdateFanouts(absl::string_view from_node,
                                      absl::string_view to_node) {
+  if (from_node == to_node) {
+    return false;
+  }
   NodeDef* from_node_ptr = GetNode(from_node);
   NodeDef* to_node_ptr = GetNode(to_node);
   if (from_node_ptr && to_node_ptr) {
@@ -218,6 +221,35 @@ bool MutableGraphView::UpdateFanoutsInternal(NodeDef* from_node,
     fanouts()[output_port].erase(input_port);
   };
 
+  bool modified = false;
+
+  // For the control fanouts we do not know the input index in a NodeDef,
+  // so we have to traverse all control inputs.
+
+  auto control_fanouts =
+      GetFanout(GraphView::OutputPort(from_node, Graph::kControlSlot));
+
+  bool to_node_is_switch = IsSwitch(*to_node);
+  for (const InputPort& control_port : control_fanouts) {
+    // Node can't be control dependency of itself.
+    if (control_port.node == to_node) continue;
+
+    // Can't add Switch node as a control dependency.
+    if (to_node_is_switch) {
+      // This will only be printed to the log if we are trying to add a Switch
+      // as a control dependency, which if allowed will make the graph invalid.
+      LOG(WARNING) << absl::Substitute(
+          "Can't update fanouts from '$0' to '$1', to node is being added as a "
+          "control dependency when it is a Switch.",
+          from_node, to_node);
+      return false;
+    }
+
+    NodeDef* node = control_port.node;
+    modified |= RemoveControllingFaninInternal(node, from_node);
+    modified |= AddFaninInternal(node, {to_node, Graph::kControlSlot});
+  }
+
   // First we update regular fanouts. For the regular fanouts
   // `input_port:port_id` is the input index in NodeDef.
 
@@ -228,7 +260,6 @@ bool MutableGraphView::UpdateFanoutsInternal(NodeDef* from_node,
   // input to some other node.
   int keep_max_regular_output_port = -1;
 
-  bool modified = false;
   for (const Edge& edge : regular_edges) {
     const OutputPort output_port = edge.src;
     const InputPort input_port = edge.dst;
@@ -259,22 +290,6 @@ bool MutableGraphView::UpdateFanoutsInternal(NodeDef* from_node,
     modified = true;
   }
 
-  // For the control fanouts we do not know the input index in a NodeDef,
-  // so we have to traverse all control inputs.
-
-  auto control_fanouts =
-      GetFanout(GraphView::OutputPort(from_node, Graph::kControlSlot));
-
-  for (const InputPort& control_port : control_fanouts) {
-    // Node can't be control dependency of itself.
-    if (control_port.node == to_node) continue;
-
-    NodeDef* node = control_port.node;
-    modified |= RemoveControllingFaninInternal(node, from_node);
-    // TODO(lyandy): Handle Switch control dependencies.
-    modified |= AddFaninInternal(node, {to_node, Graph::kControlSlot});
-  }
-
   // Because we update all regular fanouts of `from_node`, we can just copy
   // the value `num_regular_outputs`.
   max_regular_output_port()[to_node] = max_regular_output_port()[from_node];
@@ -371,7 +386,7 @@ bool MutableGraphView::AddControllingFanin(absl::string_view node_name,
     return AddFaninInternal(node, {fanin_node, Graph::kControlSlot});
   } else {
     if (IsTensorIdControlling(fanin)) {
-      // Cannot add a Switch node control dependency.
+      // Can't add a Switch node control dependency.
       return false;
     }
     // We can't anchor control dependencies directly on the switch node: unlike
@@ -410,11 +425,10 @@ bool MutableGraphView::AddControllingFanin(absl::string_view node_name,
 }
 
 bool MutableGraphView::RemoveRegularFaninInternal(NodeDef* node,
-                                                  const TensorId& fanin) {
-  auto remove_input = [this, node](const TensorId& tensor_id, int port,
-                                   bool update_max_port) {
-    OutputPort fanin_port(nodes()[tensor_id.node()], tensor_id.index());
-    InputPort input(node, port);
+                                                  const OutputPort& fanin) {
+  auto remove_input = [this, node](const OutputPort& fanin_port,
+                                   int node_input_port, bool update_max_port) {
+    InputPort input(node, node_input_port);
 
     absl::flat_hash_set* fanouts_set = &fanouts()[fanin_port];
     fanouts_set->erase(input);
@@ -434,12 +448,14 @@ bool MutableGraphView::RemoveRegularFaninInternal(NodeDef* node,
     if (IsTensorIdControlling(tensor_id)) {
       break;
     }
-    if (tensor_id == fanin) {
-      remove_input(tensor_id, i, /*update_max_port=*/true);
+    if (tensor_id.node() == fanin.node->name() &&
+        tensor_id.index() == fanin.port_id) {
+      remove_input(fanin, i, /*update_max_port=*/true);
       modified = true;
     } else if (modified) {
       // Regular inputs will need to have their ports updated.
-      auto fanouts_set = remove_input(tensor_id, i, /*update_max_port=*/false);
+      OutputPort fanin_port(nodes()[tensor_id.node()], tensor_id.index());
+      auto fanouts_set = remove_input(fanin_port, i, /*update_max_port=*/false);
       fanouts_set->insert({node, curr_pos});
       // Shift inputs to be retained.
       mutable_inputs->SwapElements(i, curr_pos++);
@@ -466,7 +482,11 @@ bool MutableGraphView::RemoveRegularFanin(absl::string_view node_name,
   if (node == nullptr) {
     return false;
   }
-  return RemoveRegularFaninInternal(node, fanin);
+  NodeDef* fanin_node = GetNode(fanin.node());
+  if (fanin_node == nullptr) {
+    return false;
+  }
+  return RemoveRegularFaninInternal(node, {fanin_node, fanin.index()});
 }
 
 bool MutableGraphView::RemoveControllingFaninInternal(NodeDef* node,
@@ -487,22 +507,17 @@ bool MutableGraphView::RemoveControllingFaninInternal(NodeDef* node,
   return false;
 }
 
-bool MutableGraphView::RemoveControllingFaninInternal(
-    NodeDef* node, absl::string_view fanin_node_name) {
-  NodeDef* fanin = GetNode(fanin_node_name);
-  if (fanin == nullptr) {
-    return false;
-  }
-  return RemoveControllingFaninInternal(node, fanin);
-}
-
 bool MutableGraphView::RemoveControllingFanin(
     absl::string_view node_name, absl::string_view fanin_node_name) {
   NodeDef* node = GetNode(node_name);
   if (node == nullptr) {
     return false;
   }
-  return RemoveControllingFaninInternal(node, fanin_node_name);
+  NodeDef* fanin = GetNode(fanin_node_name);
+  if (fanin == nullptr) {
+    return false;
+  }
+  return RemoveControllingFaninInternal(node, fanin);
 }
 
 bool MutableGraphView::RemoveAllFanins(absl::string_view node_name,
@@ -540,30 +555,36 @@ bool MutableGraphView::UpdateFanin(absl::string_view node_name,
     return false;
   }
 
+  NodeDef* from_fanin_node = GetNode(from_fanin.node());
+  NodeDef* to_fanin_node = GetNode(to_fanin.node());
+  if (from_fanin_node == nullptr || to_fanin_node == nullptr) {
+    return false;
+  }
+
   // When replacing a non control dependency fanin with a control dependency, or
   // vice versa, remove and add, so ports can be updated properly in fanout(s).
   bool from_fanin_is_control = IsTensorIdControlling(from_fanin);
-  if (from_fanin_is_control || IsTensorIdControlling(to_fanin)) {
+  bool to_fanin_is_control = IsTensorIdControlling(to_fanin);
+  if (to_fanin_is_control && IsSwitch(*to_fanin_node)) {
+    // Can't add Switch node as a control dependency.
+    return false;
+  }
+  if (from_fanin_is_control || to_fanin_is_control) {
     bool modified = false;
     if (from_fanin_is_control) {
-      modified |= RemoveControllingFaninInternal(node, from_fanin.node());
+      modified |= RemoveControllingFaninInternal(node, from_fanin_node);
     } else {
-      modified |= RemoveRegularFaninInternal(node, from_fanin);
+      modified |= RemoveRegularFaninInternal(
+          node, {from_fanin_node, from_fanin.index()});
     }
     if (modified) {
-      AddFaninInternal(node, to_fanin);
+      AddFaninInternal(node, {to_fanin_node, to_fanin.index()});
     }
 
     return modified;
   }
 
   // In place mutation, requires no shifting of ports.
-  NodeDef* from_fanin_node = GetNode(from_fanin.node());
-  NodeDef* to_fanin_node = GetNode(to_fanin.node());
-  if (from_fanin_node == nullptr || to_fanin_node == nullptr) {
-    return false;
-  }
-
   string to_fanin_string = TensorIdToString(to_fanin);
   int num_inputs = node->input_size();
   bool modified = false;
diff --git a/tensorflow/core/grappler/mutable_graph_view.h b/tensorflow/core/grappler/mutable_graph_view.h
index f07254068d..0a441d9115 100644
--- a/tensorflow/core/grappler/mutable_graph_view.h
+++ b/tensorflow/core/grappler/mutable_graph_view.h
@@ -195,17 +195,10 @@ class MutableGraphView : public internal::GraphViewInternal {
   // already exists, the node will not be modified.
   bool AddFaninInternal(NodeDef* node, const TensorId& fanin);
 
-  // Removes any fanin in node that matches to a fanin in fanins.
+  // Removes all instances of regular fanin `fanin` from node `node`.
   //
   // This will return true iff the node is modified.
-  bool RemoveRegularFaninInternal(NodeDef* node, const TensorId& fanin);
-
-  // Removes controlling fanin `fanin_node_name` from node if such controlling
-  // fanin exists.
-  //
-  // This will return true iff the node is modified.
-  bool RemoveControllingFaninInternal(NodeDef* node,
-                                      absl::string_view fanin_node_name);
+  bool RemoveRegularFaninInternal(NodeDef* node, const OutputPort& fanin);
 
   // Removes controlling fanin `fanin_node` from node if such controlling fanin
   // exists.
diff --git a/tensorflow/core/grappler/mutable_graph_view_test.cc b/tensorflow/core/grappler/mutable_graph_view_test.cc
index 2048c67e3e..b44e1a360d 100644
--- a/tensorflow/core/grappler/mutable_graph_view_test.cc
+++ b/tensorflow/core/grappler/mutable_graph_view_test.cc
@@ -168,21 +168,6 @@ TEST(MutableGraphViewTest, AddAndUpdateFanoutsWithoutSelfLoops) {
   EXPECT_EQ(new_bar_fanouts.count(MutableGraphView::InputPort(foo_2, -1)), 1);
 }
 
-GraphDef SimpleMutateFaninGraph() {
-  // Actual node.op() is not important in this test.
-  GraphDef graph_def = test::function::GDef(
-      {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {}, {}),
-       NDef("c", "NotImportant", {}, {}), NDef("d", "NotImportant", {}, {}),
-       NDef("foo_1", "NotImportant", {"a"}),
-       NDef("foo_2", "NotImportant", {"b", "^a", "^c"}),
-       NDef("foo_3", "NotImportant", {"b", "a:1", "a:1"}),
-       NDef("foo_4", "NotImportant", {"a", "b:2", "b:2", "^c", "^d"}),
-       NDef("foo_5", "NotImportant", {}),
-       NDef("foo_6", "NotImportant", {"^a", "^b"})},
-      /*funcs=*/{});
-  return graph_def;
-}
-
 void CompareNodeInputs(const MutableGraphView& graph, const NodeDef* expected,
                        NodeDef* actual) {
   ASSERT_EQ(actual->input_size(), expected->input_size());
@@ -203,6 +188,94 @@ void CompareNodeInputs(const MutableGraphView& graph, const NodeDef* expected,
   }
 }
 
+TEST(MutableGraphViewTest, UpdateFanoutsToSwitchWithControlFromSwitch) {
+  GraphDef graph_def = test::function::GDef(
+      {NDef("a", "NotImportant", {}, {}), NDef("b", "Switch", {}, {}),
+       NDef("c", "NotImportant", {}, {}), NDef("d", "NotImportant", {}, {}),
+       NDef("e", "NotImportant", {"c", "b", "^a", "^d"})},
+      /*funcs=*/{});
+
+  MutableGraphView graph(&graph_def);
+
+  EXPECT_FALSE(graph.UpdateFanouts("a", "b"));
+  EXPECT_FALSE(graph.UpdateFanouts("d", "b"));
+
+  EXPECT_EQ(graph.graph()->node_size(), 5);
+
+  NodeDef expected = NDef("", "", {}, {});
+  NodeDef* a = graph.GetNode("a");
+  ASSERT_NE(a, nullptr);
+  CompareNodeInputs(graph, &expected, a);
+
+  NodeDef* b = graph.GetNode("b");
+  ASSERT_NE(b, nullptr);
+  CompareNodeInputs(graph, &expected, b);
+
+  NodeDef* c = graph.GetNode("c");
+  ASSERT_NE(c, nullptr);
+  CompareNodeInputs(graph, &expected, c);
+
+  NodeDef* d = graph.GetNode("d");
+  ASSERT_NE(d, nullptr);
+  CompareNodeInputs(graph, &expected, d);
+
+  NodeDef* e = graph.GetNode("e");
+  ASSERT_NE(e, nullptr);
+  expected = NDef("", "", {"c", "b", "^a", "^d"});
+  CompareNodeInputs(graph, &expected, e);
+}
+
+TEST(MutableGraphViewTest, UpdateFanoutsToSwitchWithNoControlFromSwitch) {
+  GraphDef graph_def = test::function::GDef(
+      {NDef("a", "NotImportant", {}, {}), NDef("b", "Switch", {}, {}),
+       NDef("c", "NotImportant", {}, {}), NDef("d", "NotImportant", {}, {}),
+       NDef("e", "NotImportant", {"c", "b", "^a", "^d"})},
+      /*funcs=*/{});
+
+  MutableGraphView graph(&graph_def);
+
+  EXPECT_TRUE(graph.UpdateFanouts("c", "b"));
+
+  EXPECT_EQ(graph.graph()->node_size(), 5);
+
+  NodeDef expected = NDef("", "", {}, {});
+  NodeDef* a = graph.GetNode("a");
+  ASSERT_NE(a, nullptr);
+  CompareNodeInputs(graph, &expected, a);
+
+  NodeDef* b = graph.GetNode("b");
+  ASSERT_NE(b, nullptr);
+  CompareNodeInputs(graph, &expected, b);
+
+  NodeDef* c = graph.GetNode("c");
+  ASSERT_NE(c, nullptr);
+  CompareNodeInputs(graph, &expected, c);
+
+  NodeDef* d = graph.GetNode("d");
+  ASSERT_NE(d, nullptr);
+  CompareNodeInputs(graph, &expected, d);
+
+  NodeDef* e = graph.GetNode("e");
+  ASSERT_NE(e, nullptr);
+  expected = NDef("", "", {"b", "b", "^a", "^d"});
+  CompareNodeInputs(graph, &expected, e);
+}
+
+GraphDef SimpleMutateFaninGraph() {
+  // Actual node.op() is not important in this test.
+  GraphDef graph_def = test::function::GDef(
+      {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {}, {}),
+       NDef("c", "NotImportant", {}, {}), NDef("d", "NotImportant", {}, {}),
+       NDef("foo_1", "NotImportant", {"a"}),
+       NDef("foo_2", "NotImportant", {"b", "^a", "^c"}),
+       NDef("foo_3", "NotImportant", {"b", "a:1", "a:1"}),
+       NDef("foo_4", "NotImportant", {"a", "b:2", "b:2", "^c", "^d"}),
+       NDef("foo_5", "NotImportant", {}),
+       NDef("foo_6", "NotImportant", {"^a", "^b"})},
+      /*funcs=*/{});
+  return graph_def;
+}
+
 void TestAddRegularFanin(absl::string_view node_name,
                          const TensorId& fanin_to_add, bool modified,
                          const NodeDef* expected_node) {
@@ -525,23 +598,53 @@ TEST(MutableGraphViewTest, UpdateFanin) {
                   /*modified=*/false, /*expected_node=*/nullptr);
 }
 
+void TestUpdateFaninFromFaninToNodeAsSwitchControl(const TensorId& fanin) {
+  string tensor_id_str = TensorIdToString(fanin);
+  GraphDef graph_def = test::function::GDef(
+      {NDef("a", "NotImportant", {}, {}), NDef("b", "Switch", {}, {}),
+       NDef("c", "NotImportant", {tensor_id_str})},
+      /*funcs=*/{});
+
+  MutableGraphView graph(&graph_def);
+
+  EXPECT_FALSE(graph.UpdateFanin("c", fanin, {"b", Graph::kControlSlot}));
+
+  EXPECT_EQ(graph.graph()->node_size(), 3);
+
+  NodeDef expected;
+  NodeDef* a = graph.GetNode("a");
+  ASSERT_NE(a, nullptr);
+  expected = NDef("", "", {});
+  CompareNodeInputs(graph, &expected, a);
+
+  NodeDef* b = graph.GetNode("b");
+  ASSERT_NE(b, nullptr);
+  CompareNodeInputs(graph, &expected, b);
+
+  NodeDef* c = graph.GetNode("c");
+  ASSERT_NE(c, nullptr);
+  expected = NDef("", "", {tensor_id_str});
+  CompareNodeInputs(graph, &expected, c);
+}
+
+TEST(MutableGraphViewTest, UpdateFaninToNodeAsSwitchControl) {
+  TestUpdateFaninFromFaninToNodeAsSwitchControl({"a", 0});
+  TestUpdateFaninFromFaninToNodeAsSwitchControl({"a", 1});
+  TestUpdateFaninFromFaninToNodeAsSwitchControl({"a", Graph::kControlSlot});
+}
+
 TEST(MutableGraphViewTest, DedupControllingFaninsOnGraphInit) {
-  // Actual node.op() is not important in this test.
   GraphDef graph_def = test::function::GDef(
-      {
-          NDef("a", "NotImportant", {}, {}),
-          NDef("b", "NotImportant", {}, {}),
-          NDef("c", "Switch", {}, {}),
-          NDef("d", "Identity", {"c:1"}),
-          NDef("foo_1", "IdentityN", {"a", "b:1", "^b"}),
-          NDef("foo_2", "IdentityN", {"a", "^b", "^b"}),
-          NDef("foo_3", "IdentityN", {"a", "b:1", "^b", "^b"}),
-          NDef("foo_4", "IdentityN", {"a:2", "b:1", "^b", "^b", "^a", "^a"}),
-          NDef("foo_5", "NotImportant", {"a:2", "b:1", "^b", "^b", "^a", "^a"}),
-          NDef("foo_6", "Identity", {"d", "^d"}),
-          NDef("foo_7", "NotImportant",
-               {"a:3", "b:2", "d", "^d", "^d", "^a", "^b", "^a", "^b"}),
-      },
+      {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {}, {}),
+       NDef("c", "Switch", {}, {}), NDef("d", "Identity", {"c:1"}),
+       NDef("foo_1", "IdentityN", {"a", "b:1", "^b"}),
+       NDef("foo_2", "IdentityN", {"a", "^b", "^b"}),
+       NDef("foo_3", "IdentityN", {"a", "b:1", "^b", "^b"}),
+       NDef("foo_4", "IdentityN", {"a:2", "b:1", "^b", "^b", "^a", "^a"}),
+       NDef("foo_5", "NotImportant", {"a:2", "b:1", "^b", "^b", "^a", "^a"}),
+       NDef("foo_6", "Identity", {"d", "^d"}),
+       NDef("foo_7", "NotImportant",
+            {"a:3", "b:2", "d", "^d", "^d", "^a", "^b", "^a", "^b"})},
       /*funcs=*/{});
 
   MutableGraphView graph(&graph_def);
@@ -939,12 +1042,9 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithExistingAddedIdentity) {
 TEST(MutableGraphViewTest, RemoveControllingFaninMissing) {
   // Actual node.op() is not important in this test.
   GraphDef graph_def = test::function::GDef(
-      {
-          NDef("a", "NotImportant", {}, {}),
-          NDef("b", "NotImportant", {}, {}),
-          NDef("c", "NotImportant", {}, {}),
-          NDef("d", "NotImportant", {"^a", "^b"}),
-      },
+      {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {}, {}),
+       NDef("c", "NotImportant", {}, {}),
+       NDef("d", "NotImportant", {"^a", "^b"})},
       /*funcs=*/{});
 
   MutableGraphView graph(&graph_def);
@@ -961,12 +1061,9 @@ TEST(MutableGraphViewTest, RemoveControllingFaninMissing) {
 TEST(MutableGraphViewTest, RemoveControllingFaninExisting) {
   // Actual node.op() is not important in this test.
   GraphDef graph_def = test::function::GDef(
-      {
-          NDef("a", "NotImportant", {}, {}),
-          NDef("b", "NotImportant", {}, {}),
-          NDef("c", "NotImportant", {}, {}),
-          NDef("d", "NotImportant", {"^a", "^b", "^c"}),
-      },
+      {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {}, {}),
+       NDef("c", "NotImportant", {}, {}),
+       NDef("d", "NotImportant", {"^a", "^b", "^c"})},
       /*funcs=*/{});
 
   MutableGraphView graph(&graph_def);
-- 
GitLab


From f497b36f3250a4e26051be3f3616e7ad233f3cc8 Mon Sep 17 00:00:00 2001
From: Sourabh Bajaj 
Date: Thu, 10 Jan 2019 16:17:39 -0800
Subject: [PATCH 0521/2345] Remove the experimental initialize and finalize
 API.

PiperOrigin-RevId: 228797485
---
 .../python/distribute/distribute_lib.py       | 42 -------------------
 ...orflow.distribute.-mirrored-strategy.pbtxt | 16 -------
 .../v1/tensorflow.distribute.-strategy.pbtxt  | 16 -------
 ...orflow.distribute.-mirrored-strategy.pbtxt | 16 -------
 .../v2/tensorflow.distribute.-strategy.pbtxt  | 16 -------
 5 files changed, 106 deletions(-)

diff --git a/tensorflow/python/distribute/distribute_lib.py b/tensorflow/python/distribute/distribute_lib.py
index 5fe77e5478..66112dceda 100644
--- a/tensorflow/python/distribute/distribute_lib.py
+++ b/tensorflow/python/distribute/distribute_lib.py
@@ -505,42 +505,6 @@ class DistributionStrategy(object):
     """DEPRECATED: use extended.broadcast_to() instead."""
     return self._extended.broadcast_to(tensor, destinations)
 
-  @doc_controls.do_not_generate_docs  # Use experimental_initialize() instead.
-  def initialize(self):
-    """DEPRECATED: Use `experimental_initialize()` instead."""
-    return self._extended._initialize()  # pylint: disable=protected-access
-
-  def experimental_initialize(self):
-    """Any initialization to be done before running any computations.
-
-    In eager mode, it executes any initialization as a side effect.
-    In graph mode, it creates the initialization ops and returns them.
-
-    For example, TPU initialize_system ops.
-
-    Returns:
-      A list of ops to execute.
-    """
-    return self._extended._initialize()  # pylint: disable=protected-access
-
-  @doc_controls.do_not_generate_docs  # Use experimental_finalize() instead.
-  def finalize(self):
-    """DEPRECATED: Use `experimental_finalize()` instead."""
-    return self._extended._finalize()  # pylint: disable=protected-access
-
-  def experimental_finalize(self):
-    """Any final actions to be done at the end of all computations.
-
-    In eager mode, it executes any finalize actions as a side effect.
-    In graph mode, it creates the finalize ops and returns them.
-
-    For example, TPU shutdown ops.
-
-    Returns:
-      A list of ops to execute.
-    """
-    return self._extended._finalize()  # pylint: disable=protected-access
-
   @doc_controls.do_not_generate_docs  # DEPRECATED, moving to `extended`
   def run_steps_on_dataset(self, fn, iterator, iterations=1,
                            initial_loop_values=None):
@@ -1171,12 +1135,6 @@ class DistributionStrategyExtended(object):
   def _broadcast_to(self, tensor, destinations):
     raise NotImplementedError("must be implemented in descendants")
 
-  def _initialize(self):
-    return []
-
-  def _finalize(self):
-    return []
-
   def experimental_run_steps_on_iterator(self, fn, iterator, iterations=1,
                                          initial_loop_values=None):
     """Run `fn` with input from `iterator` for `iterations` times.
diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt
index 9c29067b6d..863ee9c210 100644
--- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt
+++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt
@@ -67,14 +67,6 @@ tf_class {
     name: "distribute_dataset"
     argspec: "args=[\'self\', \'dataset_fn\'], varargs=None, keywords=None, defaults=None"
   }
-  member_method {
-    name: "experimental_finalize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
-  member_method {
-    name: "experimental_initialize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "experimental_make_numpy_iterator"
     argspec: "args=[\'self\', \'numpy_input\', \'batch_size\', \'num_epochs\', \'shuffle\', \'session\'], varargs=None, keywords=None, defaults=[\'1\', \'1024\', \'None\'], "
@@ -83,18 +75,10 @@ tf_class {
     name: "experimental_run"
     argspec: "args=[\'self\', \'fn\', \'input_iterator\'], varargs=None, keywords=None, defaults=[\'None\'], "
   }
-  member_method {
-    name: "finalize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "group"
     argspec: "args=[\'self\', \'value\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], "
   }
-  member_method {
-    name: "initialize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "make_dataset_iterator"
     argspec: "args=[\'self\', \'dataset\'], varargs=None, keywords=None, defaults=None"
diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt
index 4aa6f1c4e1..1c06f7873e 100644
--- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt
+++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt
@@ -66,14 +66,6 @@ tf_class {
     name: "distribute_dataset"
     argspec: "args=[\'self\', \'dataset_fn\'], varargs=None, keywords=None, defaults=None"
   }
-  member_method {
-    name: "experimental_finalize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
-  member_method {
-    name: "experimental_initialize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "experimental_make_numpy_iterator"
     argspec: "args=[\'self\', \'numpy_input\', \'batch_size\', \'num_epochs\', \'shuffle\', \'session\'], varargs=None, keywords=None, defaults=[\'1\', \'1024\', \'None\'], "
@@ -82,18 +74,10 @@ tf_class {
     name: "experimental_run"
     argspec: "args=[\'self\', \'fn\', \'input_iterator\'], varargs=None, keywords=None, defaults=[\'None\'], "
   }
-  member_method {
-    name: "finalize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "group"
     argspec: "args=[\'self\', \'value\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], "
   }
-  member_method {
-    name: "initialize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "make_dataset_iterator"
     argspec: "args=[\'self\', \'dataset\'], varargs=None, keywords=None, defaults=None"
diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt
index 9c29067b6d..863ee9c210 100644
--- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt
+++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt
@@ -67,14 +67,6 @@ tf_class {
     name: "distribute_dataset"
     argspec: "args=[\'self\', \'dataset_fn\'], varargs=None, keywords=None, defaults=None"
   }
-  member_method {
-    name: "experimental_finalize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
-  member_method {
-    name: "experimental_initialize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "experimental_make_numpy_iterator"
     argspec: "args=[\'self\', \'numpy_input\', \'batch_size\', \'num_epochs\', \'shuffle\', \'session\'], varargs=None, keywords=None, defaults=[\'1\', \'1024\', \'None\'], "
@@ -83,18 +75,10 @@ tf_class {
     name: "experimental_run"
     argspec: "args=[\'self\', \'fn\', \'input_iterator\'], varargs=None, keywords=None, defaults=[\'None\'], "
   }
-  member_method {
-    name: "finalize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "group"
     argspec: "args=[\'self\', \'value\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], "
   }
-  member_method {
-    name: "initialize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "make_dataset_iterator"
     argspec: "args=[\'self\', \'dataset\'], varargs=None, keywords=None, defaults=None"
diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt
index 4aa6f1c4e1..1c06f7873e 100644
--- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt
+++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt
@@ -66,14 +66,6 @@ tf_class {
     name: "distribute_dataset"
     argspec: "args=[\'self\', \'dataset_fn\'], varargs=None, keywords=None, defaults=None"
   }
-  member_method {
-    name: "experimental_finalize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
-  member_method {
-    name: "experimental_initialize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "experimental_make_numpy_iterator"
     argspec: "args=[\'self\', \'numpy_input\', \'batch_size\', \'num_epochs\', \'shuffle\', \'session\'], varargs=None, keywords=None, defaults=[\'1\', \'1024\', \'None\'], "
@@ -82,18 +74,10 @@ tf_class {
     name: "experimental_run"
     argspec: "args=[\'self\', \'fn\', \'input_iterator\'], varargs=None, keywords=None, defaults=[\'None\'], "
   }
-  member_method {
-    name: "finalize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "group"
     argspec: "args=[\'self\', \'value\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], "
   }
-  member_method {
-    name: "initialize"
-    argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None"
-  }
   member_method {
     name: "make_dataset_iterator"
     argspec: "args=[\'self\', \'dataset\'], varargs=None, keywords=None, defaults=None"
-- 
GitLab


From a7443e6ad57bd60693cf58a9dad3f462fdf5bc3d Mon Sep 17 00:00:00 2001
From: Jared Duke 
Date: Thu, 10 Jan 2019 16:20:26 -0800
Subject: [PATCH 0522/2345] Allow scalar shapes in the Python converter API

Scalars are represented as empty lists in the shape. As the
converter and runtime now support scalars, allow such empty
(scalar) shape values in the Python converter API. Also
add a test to validate conversion using string types.

PiperOrigin-RevId: 228797882
---
 tensorflow/lite/python/BUILD        |  1 +
 tensorflow/lite/python/lite.py      |  5 ++-
 tensorflow/lite/python/lite_test.py | 65 ++++++++++++++++++++++++++---
 3 files changed, 63 insertions(+), 8 deletions(-)

diff --git a/tensorflow/lite/python/BUILD b/tensorflow/lite/python/BUILD
index 54b925cc05..4949d7c92e 100644
--- a/tensorflow/lite/python/BUILD
+++ b/tensorflow/lite/python/BUILD
@@ -72,6 +72,7 @@ py_test(
     name = "lite_test",
     srcs = ["lite_test.py"],
     data = ["@tflite_mobilenet_ssd_quant_protobuf//:tflite_graph.pb"],
+    shard_count = 4,
     srcs_version = "PY2AND3",
     tags = [
         "no_oss",
diff --git a/tensorflow/lite/python/lite.py b/tensorflow/lite/python/lite.py
index 5ffa8c426b..3b0aa02b7c 100644
--- a/tensorflow/lite/python/lite.py
+++ b/tensorflow/lite/python/lite.py
@@ -435,15 +435,16 @@ class TFLiteConverter(object):
     if self._has_valid_tensors():
       for tensor in self._input_tensors:
         shape = tensor.get_shape()
-        if not shape or not shape.as_list():
+        if not shape:
           raise ValueError("Provide an input shape for input array "
                            "'{0}'.".format(_tensor_name(tensor)))
+        # Note that shape_list might be empty for scalar shapes.
         shape_list = 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[0] is None:
+        elif shape_list and shape_list[0] is None:
           self._set_batch_size(batch_size=1)
 
     # Get quantization stats. Ensures there is one stat per name if the stats
diff --git a/tensorflow/lite/python/lite_test.py b/tensorflow/lite/python/lite_test.py
index 1f9c768b44..83fd56bf1d 100644
--- a/tensorflow/lite/python/lite_test.py
+++ b/tensorflow/lite/python/lite_test.py
@@ -113,6 +113,35 @@ class FromSessionTest(test_util.TensorFlowTestCase):
     self.assertTrue(([1, 16, 16, 3] == output_details[0]['shape']).all())
     self.assertEqual((0., 0.), output_details[0]['quantization'])
 
+  def testString(self):
+    in_tensor = array_ops.placeholder(shape=[4], dtype=dtypes.string)
+    out_tensor = array_ops.reshape(in_tensor, shape=[2, 2])
+    sess = session.Session()
+
+    # Convert model and ensure model is not None.
+    converter = lite.TFLiteConverter.from_session(sess, [in_tensor],
+                                                  [out_tensor])
+    tflite_model = converter.convert()
+    self.assertTrue(tflite_model)
+
+    # Check values from converted model.
+    interpreter = Interpreter(model_content=tflite_model)
+    interpreter.allocate_tensors()
+
+    input_details = interpreter.get_input_details()
+    self.assertEqual(1, len(input_details))
+    self.assertEqual('Placeholder', input_details[0]['name'])
+    self.assertEqual(np.object_, input_details[0]['dtype'])
+    self.assertTrue(([4] == input_details[0]['shape']).all())
+
+    output_details = interpreter.get_output_details()
+    self.assertEqual(1, len(output_details))
+    self.assertEqual('Reshape', output_details[0]['name'])
+    self.assertEqual(np.object_, output_details[0]['dtype'])
+    self.assertTrue(([2, 2] == output_details[0]['shape']).all())
+    # TODO(b/122659643): Test setting/getting string data via the python
+    # interpreter API after support has been added.
+
   def testQuantization(self):
     in_tensor_1 = array_ops.placeholder(
         shape=[1, 16, 16, 3], dtype=dtypes.float32, name='inputA')
@@ -223,18 +252,42 @@ class FromSessionTest(test_util.TensorFlowTestCase):
     self.assertEqual('Provide an input shape for input array \'Placeholder\'.',
                      str(error.exception))
 
-  def testSizeEmptyInvalid(self):
+  def testScalarValid(self):
+    # Construct a graph using a scalar (empty shape) input.
     in_tensor = array_ops.placeholder(dtype=dtypes.float32, shape=[])
     out_tensor = in_tensor + in_tensor
     sess = session.Session()
 
-    # Test empty shape.
+    # Test conversion with the scalar input shape.
     converter = lite.TFLiteConverter.from_session(sess, [in_tensor],
                                                   [out_tensor])
-    with self.assertRaises(ValueError) as error:
-      converter.convert()
-    self.assertEqual('Provide an input shape for input array \'Placeholder\'.',
-                     str(error.exception))
+    tflite_model = converter.convert()
+    self.assertTrue(tflite_model)
+
+    # Check values from converted model.
+    interpreter = Interpreter(model_content=tflite_model)
+    interpreter.allocate_tensors()
+
+    input_details = interpreter.get_input_details()
+    self.assertEqual(1, len(input_details))
+    self.assertEqual('Placeholder', input_details[0]['name'])
+    self.assertEqual(np.float32, input_details[0]['dtype'])
+    self.assertTrue(([] == input_details[0]['shape']).all())
+
+    output_details = interpreter.get_output_details()
+    self.assertEqual(1, len(output_details))
+    self.assertEqual('add', output_details[0]['name'])
+    self.assertEqual(np.float32, output_details[0]['dtype'])
+    self.assertTrue(([] == input_details[0]['shape']).all())
+
+    # Validate inference using the scalar inputs/outputs.
+    test_input = np.array(4.0, dtype=np.float32)
+    expected_output = np.array(8.0, dtype=np.float32)
+    interpreter.set_tensor(input_details[0]['index'], test_input)
+    interpreter.invoke()
+
+    output_data = interpreter.get_tensor(output_details[0]['index'])
+    self.assertTrue((expected_output == output_data).all())
 
   def testSizeInvalid(self):
     in_tensor = array_ops.placeholder(
-- 
GitLab


From 63a6b272919a1621b6ecc57b4960047e2c61d654 Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 16:22:20 -0800
Subject: [PATCH 0523/2345] Internal change

PiperOrigin-RevId: 228798154
---
 .../feature_column/feature_column_v2.py       | 18 ++----
 .../feature_column/feature_column_v2_test.py  | 56 ++++++++++++++++++-
 2 files changed, 60 insertions(+), 14 deletions(-)

diff --git a/tensorflow/python/feature_column/feature_column_v2.py b/tensorflow/python/feature_column/feature_column_v2.py
index d4e3fc5d2d..73e3001fc0 100644
--- a/tensorflow/python/feature_column/feature_column_v2.py
+++ b/tensorflow/python/feature_column/feature_column_v2.py
@@ -2780,12 +2780,8 @@ class NumericColumn(
     """See 'FeatureColumn` base class."""
     _check_config_keys(config, cls._fields)
     kwargs = config.copy()
-    # TODO(b/118820158): Simplify if deserialize_keras_object supports None.
-    if config['normalizer_fn']:
-      kwargs['normalizer_fn'] = utils.deserialize_keras_object(
-          config['normalizer_fn'], custom_objects=custom_objects)
-    else:
-      kwargs['normalizer_fn'] = None
+    kwargs['normalizer_fn'] = utils.deserialize_keras_object(
+        config['normalizer_fn'], custom_objects=custom_objects)
     kwargs['dtype'] = dtypes.as_dtype(config['dtype'])
     return cls(**kwargs)
 
@@ -3168,12 +3164,8 @@ class EmbeddingColumn(
     kwargs = config.copy()
     kwargs['categorical_column'] = deserialize_feature_column(
         config['categorical_column'], custom_objects, columns_by_name)
-    # TODO(b/118820158): Simplify if deserialize_keras_object supports None.
-    if config['initializer']:
-      kwargs['initializer'] = utils.deserialize_keras_object(
-          config['initializer'], custom_objects=custom_objects)
-    else:
-      kwargs['initializer'] = None
+    kwargs['initializer'] = utils.deserialize_keras_object(
+        config['initializer'], custom_objects=custom_objects)
     return cls(**kwargs)
 
 
@@ -4681,7 +4673,7 @@ def deserialize_feature_column(config,
           IdentityCategoricalColumn, IndicatorColumn, NumericColumn,
           SequenceCategoricalColumn, SequenceDenseColumn, SharedEmbeddingColumn,
           VocabularyFileCategoricalColumn, VocabularyListCategoricalColumn,
-          WeightedCategoricalColumn
+          WeightedCategoricalColumn, init_ops.TruncatedNormal
       ]
   }
   if columns_by_name is None:
diff --git a/tensorflow/python/feature_column/feature_column_v2_test.py b/tensorflow/python/feature_column/feature_column_v2_test.py
index aeaa8df8b5..137d5e0a8c 100644
--- a/tensorflow/python/feature_column/feature_column_v2_test.py
+++ b/tensorflow/python/feature_column/feature_column_v2_test.py
@@ -40,6 +40,7 @@ from tensorflow.python.framework import ops
 from tensorflow.python.framework import sparse_tensor
 from tensorflow.python.framework import test_util
 from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import init_ops
 from tensorflow.python.ops import lookup_ops
 from tensorflow.python.ops import parsing_ops
 from tensorflow.python.ops import partitioned_variables
@@ -7147,7 +7148,60 @@ class EmbeddingColumnTest(test.TestCase):
                           self.evaluate(predictions))
 
   @test_util.run_deprecated_v1
-  def test_serialization(self):
+  def test_serialization_with_default_initializer(self):
+
+    # Build columns.
+    categorical_column = fc.categorical_column_with_identity(
+        key='aaa', num_buckets=3)
+    embedding_column = fc.embedding_column(categorical_column, dimension=2)
+
+    self.assertEqual([categorical_column], embedding_column.parents)
+
+    config = embedding_column._get_config()
+    self.assertEqual({
+        'categorical_column': {
+            'class_name': 'IdentityCategoricalColumn',
+            'config': {
+                'number_buckets': 3,
+                'key': 'aaa',
+                'default_value': None
+            }
+        },
+        'ckpt_to_load_from': None,
+        'combiner': 'mean',
+        'dimension': 2,
+        'initializer': {
+            'class_name': 'TruncatedNormal',
+            'config': {
+                'dtype': 'float32',
+                'stddev': 0.7071067811865475,
+                'seed': None,
+                'mean': 0.0
+            }
+        },
+        'max_norm': None,
+        'tensor_name_in_ckpt': None,
+        'trainable': True
+    }, config)
+
+    custom_objects = {'TruncatedNormal': init_ops.TruncatedNormal}
+    new_embedding_column = fc.EmbeddingColumn._from_config(
+        config, custom_objects=custom_objects)
+    self.assertEqual(embedding_column._get_config(),
+                     new_embedding_column._get_config())
+    self.assertIsNot(categorical_column,
+                     new_embedding_column.categorical_column)
+
+    new_embedding_column = fc.EmbeddingColumn._from_config(
+        config,
+        custom_objects=custom_objects,
+        columns_by_name={categorical_column.name: categorical_column})
+    self.assertEqual(embedding_column._get_config(),
+                     new_embedding_column._get_config())
+    self.assertIs(categorical_column, new_embedding_column.categorical_column)
+
+  @test_util.run_deprecated_v1
+  def test_serialization_with_custom_initializer(self):
 
     def _initializer(shape, dtype, partition_info):
       del shape, dtype, partition_info
-- 
GitLab


From d2490b91e3f3d7771a58986a2d1446a32053eb19 Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 16:27:21 -0800
Subject: [PATCH 0524/2345] Use Cub-based tensor reduction in multinomial op.
 The Eigen implementation seems to trigger a bug on Volta GPUs.

PiperOrigin-RevId: 228798930
---
 tensorflow/core/kernels/BUILD                    |  7 +++++--
 tensorflow/core/kernels/multinomial_op_gpu.cu.cc | 14 ++++++++++----
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD
index 1a20e8628e..0045c5dd19 100644
--- a/tensorflow/core/kernels/BUILD
+++ b/tensorflow/core/kernels/BUILD
@@ -4982,11 +4982,14 @@ tf_kernel_library(
         ":random_op",
         ":random_ops",
         ":stateless_random_ops",
+        "//third_party/eigen3",
         "//tensorflow/core:framework",
         "//tensorflow/core:lib",
         "//tensorflow/core:lib_internal",
-        "//third_party/eigen3",
-    ],
+    ] + if_cuda([
+        ":reduction_ops",
+        "@cub_archive//:cub",
+    ]),
 )
 
 tf_cuda_cc_test(
diff --git a/tensorflow/core/kernels/multinomial_op_gpu.cu.cc b/tensorflow/core/kernels/multinomial_op_gpu.cu.cc
index 5cc5877cce..62e38694c8 100644
--- a/tensorflow/core/kernels/multinomial_op_gpu.cu.cc
+++ b/tensorflow/core/kernels/multinomial_op_gpu.cu.cc
@@ -22,9 +22,10 @@ limitations under the License.
 #include 
 #include 
 
-#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
 #include "tensorflow/core/framework/tensor_types.h"
 #include "tensorflow/core/kernels/random_op.h"
+#include "tensorflow/core/kernels/reduction_gpu_kernels.cu.h"
+#include "tensorflow/core/kernels/reduction_ops_common.h"
 #include "tensorflow/core/lib/random/philox_random.h"
 #include "tensorflow/core/lib/random/random_distributions.h"
 #include "tensorflow/core/util/cuda_kernel_helper.h"
@@ -67,7 +68,6 @@ struct MultinomialFunctor {
                                                  noises.size(), Dist());
 
 #if defined(EIGEN_HAS_INDEX_LIST)
-    Eigen::IndexList> kTwo;
     Eigen::IndexList bsc;
     bsc.set(0, batch_size);
     bsc.set(1, num_samples);
@@ -80,7 +80,6 @@ struct MultinomialFunctor {
     Eigen::IndexList, int, Eigen::type2index<1>> oso;
     oso.set(1, num_samples);
 #else
-    Eigen::array kTwo{2};
     Eigen::array bsc{batch_size, num_samples, num_classes};
     Eigen::array boc{batch_size, 1, num_classes};
     Eigen::array oso{1, num_samples, 1};
@@ -98,7 +97,14 @@ struct MultinomialFunctor {
         ((-((To32Bit(noises) + 2e-30f).log())).log());
 
     // Max-reduce along classes for each (batch, sample).
-    To32Bit(maxima).device(d) = To32Bit(scores).reshape(bsc).maximum(kTwo);
+    typedef const Eigen::array::Tensor::Index, 1>& ReductionAxes;
+    Constants constants;
+    cub::Max op;
+    functor::ReduceImpl(
+        /*ctx=*/ctx, /*out=*/maxima.data(), /*in=*/scores.data(), /*in_rank=*/2,
+        /*in_dim0=*/batch_size * num_samples,
+        /*in_dim1=*/num_classes, /*in_dim2=*/1, /*out_rank=*/1,
+        /*reduction_axes=*/constants.kOne, /*Op=*/op);
 
     // Necessary for atomicMax() inside the kernel.
     output.device(d) = output.constant(0LL);
-- 
GitLab


From 40874676a61cc1823355016953a6ac6222f8b404 Mon Sep 17 00:00:00 2001
From: Yuefeng Zhou 
Date: Thu, 10 Jan 2019 16:43:49 -0800
Subject: [PATCH 0525/2345] Make nccl work in eager mode: wrap nccl ops in a
 defun; remove control dependencies on NcclAllReduce

PiperOrigin-RevId: 228801431
---
 tensorflow/python/BUILD                       |  2 ++
 .../python/framework/auto_control_deps.py     |  1 +
 tensorflow/python/ops/nccl_ops.py             | 34 ++++++++++++-------
 3 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD
index c5a8994b80..82c542f51d 100644
--- a/tensorflow/python/BUILD
+++ b/tensorflow/python/BUILD
@@ -5869,6 +5869,8 @@ py_library(
     deps = [
         ":framework_for_generated_wrappers",
         ":nccl_ops_gen",
+        "//tensorflow/python/eager:context",
+        "//tensorflow/python/eager:def_function",
     ],
 )
 
diff --git a/tensorflow/python/framework/auto_control_deps.py b/tensorflow/python/framework/auto_control_deps.py
index a7d61417bf..da76a84e55 100644
--- a/tensorflow/python/framework/auto_control_deps.py
+++ b/tensorflow/python/framework/auto_control_deps.py
@@ -35,6 +35,7 @@ ASYNC_STATEFUL_OPS = [
     "CollectiveReduce",
     "CollectiveBcastSend",
     "CollectiveBcastRecv",
+    "NcclAllReduce",
 ]
 
 
diff --git a/tensorflow/python/ops/nccl_ops.py b/tensorflow/python/ops/nccl_ops.py
index 6259ce0f94..6c8685cf63 100644
--- a/tensorflow/python/ops/nccl_ops.py
+++ b/tensorflow/python/ops/nccl_ops.py
@@ -19,6 +19,8 @@ from __future__ import print_function
 
 import threading
 
+from tensorflow.python.eager import context
+from tensorflow.python.eager import def_function
 from tensorflow.python.framework import device
 from tensorflow.python.framework import ops
 from tensorflow.python.ops import gen_nccl_ops
@@ -211,19 +213,27 @@ def _apply_all_reduce(reduction, tensors):
     raise ValueError('Must pass >0 tensors to all reduce operations')
 
   shared_name = _get_shared_name()
-  res = []
 
-  for t in tensors:
-    _check_device(t)
-    with ops.device(t.device):
-      res.append(
-          gen_nccl_ops.nccl_all_reduce(
-              input=t,
-              reduction=reduction,
-              num_devices=len(tensors),
-              shared_name=shared_name))
-
-  return res
+  def _all_reduce():
+    """Call nccl allreduce."""
+    res = []
+    for t in tensors:
+      _check_device(t)
+      with ops.device(t.device):
+        res.append(
+            gen_nccl_ops.nccl_all_reduce(
+                input=t,
+                reduction=reduction,
+                num_devices=len(tensors),
+                shared_name=shared_name))
+    return res
+
+  if context.executing_eagerly():
+    # Nccl ops will block unless they are executed concurrently such as in a
+    # graph or a defun.
+    return def_function.function(_all_reduce)()
+  else:
+    return _all_reduce()
 
 
 def _apply_reduce(reduction, tensors):
-- 
GitLab


From e2760cb89f08c3c31ad0322f5b2d8532d0c11b7d Mon Sep 17 00:00:00 2001
From: Ayush Dubey 
Date: Thu, 10 Jan 2019 16:44:20 -0800
Subject: [PATCH 0526/2345] Enable a soft ordering for collectives which adds
 dependencies as node attrs.

Before this change, OrderCollectives would add control edges between collective
ops to make their execution sequential.  This change introduces a technique of
encoding the ordering information as a node attribute.  In future changes, the
collective executor will parse these changes and enforce a fine-grained
ordering between collective ops.

Additionally, this change also
* restricts ordering to CollectiveReduce nodes,
* and fixes a bug so that this ordering is invoked from both DirectSession and
MasterSession.

PiperOrigin-RevId: 228801501
---
 tensorflow/core/BUILD                         |   1 +
 .../common_runtime/build_graph_options.cc     |  13 ++
 .../core/common_runtime/build_graph_options.h |   6 +
 .../core/common_runtime/direct_session.cc     |  11 +-
 .../common_runtime/graph_execution_state.cc   |   7 +
 .../distributed_runtime/master_session.cc     |  45 ++---
 tensorflow/core/graph/collective_order.cc     | 161 +++++++++++++++---
 tensorflow/core/graph/collective_order.h      |  16 +-
 .../core/graph/collective_order_test.cc       |  71 +++++++-
 tensorflow/core/ops/collective_ops.cc         |   1 +
 10 files changed, 268 insertions(+), 64 deletions(-)

diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD
index e6af9211b5..690fa4a19e 100644
--- a/tensorflow/core/BUILD
+++ b/tensorflow/core/BUILD
@@ -2819,6 +2819,7 @@ tf_cuda_library(
         ":protos_all_cc",
         "//third_party/eigen3",
         "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/container:flat_hash_set",
     ],
 )
 
diff --git a/tensorflow/core/common_runtime/build_graph_options.cc b/tensorflow/core/common_runtime/build_graph_options.cc
index 00f7a8e645..b095fcfa3b 100644
--- a/tensorflow/core/common_runtime/build_graph_options.cc
+++ b/tensorflow/core/common_runtime/build_graph_options.cc
@@ -35,6 +35,19 @@ string BuildGraphOptions::DebugString() const {
   if (collective_graph_key != kNoCollectiveGraphKey) {
     strings::StrAppend(&rv, "\ncollective_graph_key: ", collective_graph_key);
   }
+  string collective_order_str;
+  switch (collective_order) {
+    case GraphCollectiveOrder::kNone:
+      collective_order_str = "none";
+      break;
+    case GraphCollectiveOrder::kEdges:
+      collective_order_str = "edges";
+      break;
+    case GraphCollectiveOrder::kAttrs:
+      collective_order_str = "attrs";
+      break;
+  }
+  strings::StrAppend(&rv, "\ncollective_order: ", collective_order_str);
   return rv;
 }
 
diff --git a/tensorflow/core/common_runtime/build_graph_options.h b/tensorflow/core/common_runtime/build_graph_options.h
index 3d0f242ea5..24b71cc741 100644
--- a/tensorflow/core/common_runtime/build_graph_options.h
+++ b/tensorflow/core/common_runtime/build_graph_options.h
@@ -18,6 +18,7 @@ limitations under the License.
 
 #include 
 
+#include "tensorflow/core/graph/collective_order.h"
 #include "tensorflow/core/platform/types.h"
 #include "tensorflow/core/protobuf/config.pb.h"
 
@@ -34,6 +35,11 @@ struct BuildGraphOptions {
   static const int64 kNoCollectiveGraphKey = 0;
   int64 collective_graph_key = kNoCollectiveGraphKey;
 
+  // If not `kNone`, order all CollectiveReduce operations statically and
+  // deterministically.  If `kEdges`, encode dependencies as explicit control
+  // edges, if `kAttrs` encode as attribute on collective op.
+  GraphCollectiveOrder collective_order = GraphCollectiveOrder::kNone;
+
   string DebugString() const;
 };
 
diff --git a/tensorflow/core/common_runtime/direct_session.cc b/tensorflow/core/common_runtime/direct_session.cc
index 36f1a92aab..8a1c648f61 100644
--- a/tensorflow/core/common_runtime/direct_session.cc
+++ b/tensorflow/core/common_runtime/direct_session.cc
@@ -45,7 +45,6 @@ limitations under the License.
 #include "tensorflow/core/framework/tensor.h"
 #include "tensorflow/core/framework/versions.pb.h"
 #include "tensorflow/core/graph/algorithm.h"
-#include "tensorflow/core/graph/collective_order.h"
 #include "tensorflow/core/graph/graph.h"
 #include "tensorflow/core/graph/graph_constructor.h"
 #include "tensorflow/core/graph/graph_partition.h"
@@ -1192,6 +1191,10 @@ Status DirectSession::CreateExecutors(
   options.use_function_convention = !run_state_args->is_partial_run;
   options.collective_graph_key =
       callable_options.run_options().experimental().collective_graph_key();
+  if (options_.config.experimental()
+          .collective_deterministic_sequential_execution()) {
+    options.collective_order = GraphCollectiveOrder::kEdges;
+  }
 
   std::unique_ptr func_info(new FunctionInfo);
   std::unique_ptr ek(new ExecutorsAndKeys);
@@ -1523,12 +1526,6 @@ Status DirectSession::CreateGraphs(
     CopyGraph(*execution_state->full_graph(), run_state_args->graph.get());
   }
 
-  // Make collective execution order deterministic if needed.
-  if (options_.config.experimental()
-          .collective_deterministic_sequential_execution()) {
-    TF_RETURN_IF_ERROR(OrderCollectives(&client_graph->graph));
-  }
-
   // Partition the graph across devices.
   PartitionOptions popts;
   popts.node_to_loc = [](const Node* node) {
diff --git a/tensorflow/core/common_runtime/graph_execution_state.cc b/tensorflow/core/common_runtime/graph_execution_state.cc
index 9ecbc34f5f..8875883f62 100644
--- a/tensorflow/core/common_runtime/graph_execution_state.cc
+++ b/tensorflow/core/common_runtime/graph_execution_state.cc
@@ -32,6 +32,7 @@ limitations under the License.
 #include "tensorflow/core/framework/tensor.pb.h"
 #include "tensorflow/core/framework/versions.pb.h"
 #include "tensorflow/core/graph/algorithm.h"
+#include "tensorflow/core/graph/collective_order.h"
 #include "tensorflow/core/graph/graph.h"
 #include "tensorflow/core/graph/graph_constructor.h"
 #include "tensorflow/core/graph/subgraph.h"
@@ -819,6 +820,12 @@ Status GraphExecutionState::BuildGraph(const BuildGraphOptions& options,
     }
   }
 
+  // Make collective execution order deterministic if needed.
+  if (options.collective_order != GraphCollectiveOrder::kNone) {
+    TF_RETURN_IF_ERROR(
+        OrderCollectives(optimized_graph.get(), options.collective_order));
+  }
+
   // Copy the extracted graph in order to make its node ids dense,
   // since the local CostModel used to record its stats is sized by
   // the largest node id.
diff --git a/tensorflow/core/distributed_runtime/master_session.cc b/tensorflow/core/distributed_runtime/master_session.cc
index 2bb4375831..48b72fb948 100644
--- a/tensorflow/core/distributed_runtime/master_session.cc
+++ b/tensorflow/core/distributed_runtime/master_session.cc
@@ -292,8 +292,8 @@ class MasterSession::ReffedClientGraph : public core::RefCounted {
     if (tot >= 0.1 * 1048576.0) {
       bytes = strings::Printf("[%.1fMB] ", tot / 1048576.0);
     }
-    return strings::StrCat(bytes, stats.node_name(), " = ",
-                           details.type_string, details.detail_text);
+    return strings::StrCat(bytes, stats.node_name(), " = ", details.type_string,
+                           details.detail_text);
   }
 
   // Send/Recv nodes that are the result of client-added
@@ -1081,17 +1081,18 @@ void CopyAndSortStrings(size_t size,
 }  // namespace
 
 void BuildBuildGraphOptions(const RunStepRequestWrapper& req,
+                            const ConfigProto& config,
                             BuildGraphOptions* opts) {
   CallableOptions* callable_opts = &opts->callable_options;
-  CopyAndSortStrings(req.num_feeds(),
-                     [&req](size_t i) { return req.feed_name(i); },
-                     callable_opts->mutable_feed());
-  CopyAndSortStrings(req.num_fetches(),
-                     [&req](size_t i) { return req.fetch_name(i); },
-                     callable_opts->mutable_fetch());
-  CopyAndSortStrings(req.num_targets(),
-                     [&req](size_t i) { return req.target_name(i); },
-                     callable_opts->mutable_target());
+  CopyAndSortStrings(
+      req.num_feeds(), [&req](size_t i) { return req.feed_name(i); },
+      callable_opts->mutable_feed());
+  CopyAndSortStrings(
+      req.num_fetches(), [&req](size_t i) { return req.fetch_name(i); },
+      callable_opts->mutable_fetch());
+  CopyAndSortStrings(
+      req.num_targets(), [&req](size_t i) { return req.target_name(i); },
+      callable_opts->mutable_target());
 
   if (!req.options().debug_options().debug_tensor_watch_opts().empty()) {
     *callable_opts->mutable_run_options()->mutable_debug_options() =
@@ -1100,19 +1101,23 @@ void BuildBuildGraphOptions(const RunStepRequestWrapper& req,
 
   opts->collective_graph_key =
       req.options().experimental().collective_graph_key();
+  if (config.experimental().collective_deterministic_sequential_execution()) {
+    opts->collective_order = GraphCollectiveOrder::kEdges;
+  }
 }
 
 void BuildBuildGraphOptions(const PartialRunSetupRequest& req,
                             BuildGraphOptions* opts) {
   CallableOptions* callable_opts = &opts->callable_options;
-  CopyAndSortStrings(req.feed_size(), [&req](size_t i) { return req.feed(i); },
-                     callable_opts->mutable_feed());
-  CopyAndSortStrings(req.fetch_size(),
-                     [&req](size_t i) { return req.fetch(i); },
-                     callable_opts->mutable_fetch());
-  CopyAndSortStrings(req.target_size(),
-                     [&req](size_t i) { return req.target(i); },
-                     callable_opts->mutable_target());
+  CopyAndSortStrings(
+      req.feed_size(), [&req](size_t i) { return req.feed(i); },
+      callable_opts->mutable_feed());
+  CopyAndSortStrings(
+      req.fetch_size(), [&req](size_t i) { return req.fetch(i); },
+      callable_opts->mutable_fetch());
+  CopyAndSortStrings(
+      req.target_size(), [&req](size_t i) { return req.target(i); },
+      callable_opts->mutable_target());
 
   // TODO(cais): Add TFDBG support to partial runs.
 }
@@ -1852,7 +1857,7 @@ Status MasterSession::DoRunWithLocalExecution(
 
   // Prepare.
   BuildGraphOptions bgopts;
-  BuildBuildGraphOptions(req, &bgopts);
+  BuildBuildGraphOptions(req, session_opts_.config, &bgopts);
   ReffedClientGraph* rcg = nullptr;
   int64 count;
   TF_RETURN_IF_ERROR(StartStep(bgopts, false, &rcg, &count));
diff --git a/tensorflow/core/graph/collective_order.cc b/tensorflow/core/graph/collective_order.cc
index bd8fd767ee..bbba9264ce 100644
--- a/tensorflow/core/graph/collective_order.cc
+++ b/tensorflow/core/graph/collective_order.cc
@@ -14,55 +14,70 @@ limitations under the License.
 ==============================================================================*/
 #include "tensorflow/core/graph/collective_order.h"
 
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
 #include "tensorflow/core/graph/algorithm.h"
 
 namespace tensorflow {
+namespace {
 
-Status OrderCollectives(Graph* graph) {
-  // `instance_keys[i]` corresponds to `collective_nodes[i]`
-  std::vector collective_nodes;
-  std::vector instance_keys;
-  // node -> set of collectives on which node depends.
-  std::unordered_map> node_dependencies;
+// Find all CollectiveReduce nodes and the existing data dependencies between
+// them.
+Status DiscoverDataDependencies(
+    const Graph* graph, std::vector* collective_nodes,
+    std::vector* instance_keys,
+    absl::flat_hash_map>* data_dependencies) {
   Status s;
-
   // Algorithm: do Reverse DFS starting at sink.  `node_leave` is called when
-  // all parents of `node` have been visited.  At that point, the collectives
-  // on which this node depends on are up to date.  For this node's children,
-  // add all these collectives.  Also, if this node is collective, add as a
-  // dependency for the children.
-  auto node_leave = [&collective_nodes, &instance_keys, &node_dependencies,
+  // all parents of `node` have been visited.  At that point,
+  // `data_dependencies[node]` is a list containing `instance_key` of every
+  // `CollectiveReduce` on which `node` has a data dependency.
+  // For this node's children, add all these instance keys.  Also, if this node
+  // is collective, add as a dependency for the children.
+  auto node_leave = [collective_nodes, instance_keys, data_dependencies,
                      &s](Node* node) {
     int32 instance_key;
-    if (node->IsCollective()) {
+    bool enter_node =
+        node->IsCollective() && node->type_string() == "CollectiveReduce";
+    if (enter_node) {
       Status get_attr_status =
           GetNodeAttr(node->attrs(), "instance_key", &instance_key);
       s.Update(get_attr_status);
-      collective_nodes.push_back(node);
-      instance_keys.push_back(instance_key);
+      collective_nodes->push_back(node);
+      instance_keys->push_back(instance_key);
       VLOG(2) << "collective node " << node->DebugString();
     }
-    const auto& node_deps = node_dependencies[node];
+    const auto& node_deps = (*data_dependencies)[node];
     for (const Edge* out_edge : node->out_edges()) {
-      auto& child_deps = node_dependencies[out_edge->dst()];
+      auto& child_deps = (*data_dependencies)[out_edge->dst()];
       child_deps.insert(node_deps.begin(), node_deps.end());
-      if (node->IsCollective() && s.ok()) {
+      if (enter_node && s.ok()) {
         child_deps.insert(instance_key);
       }
     }
   };
   ReverseDFS(*graph, nullptr, node_leave);
-  if (!s.ok()) return s;
+  return s;
+}
 
-  // For all pairs of collective nodes n1 and n2 on the same device, if n1 does
-  // not depend on n2 and n2 does not depend on n1, then they are potentially
-  // concurrent.  Add an arbitrary, deterministic control edge between them.
+// Given a list of `collective_nodes` and `data_dependencies` between the
+// collective nodes, create control dependencies between concurrent collectives
+// and store in `dependency_edges`.
+// If there exists an edge a -> b then `dependency_edges[a]` contains `b`
+Status CreateControlDependencies(
+    const std::vector& collective_nodes,
+    const std::vector& instance_keys,
+    absl::flat_hash_map>* data_dependencies,
+    absl::flat_hash_map>* dependency_edges) {
+  // If there exists some path a -> ... -> b then `all_paths[a]` contains `b`
+  absl::flat_hash_map> all_paths;
   for (int i = 0; i < collective_nodes.size() - 1; i++) {
-    if (!collective_nodes[i]->IsCollective()) {
+    if (!collective_nodes[i]->IsCollective() ||
+        collective_nodes[i]->type_string() != "CollectiveReduce") {
       return errors::Internal("Unexpected node ",
                               collective_nodes[i]->DebugString());
     }
-    const auto& deps_i = node_dependencies[collective_nodes[i]];
+    const auto& deps_i = (*data_dependencies)[collective_nodes[i]];
     for (int j = i + 1; j < collective_nodes.size(); j++) {
       if (collective_nodes[i]->requested_device() !=
           collective_nodes[j]->requested_device()) {
@@ -74,17 +89,48 @@ Status OrderCollectives(Graph* graph) {
                                 " on 2 nodes with the same device ",
                                 collective_nodes[i]->requested_device());
       }
-      const auto& deps_j = node_dependencies[collective_nodes[j]];
+      const auto& deps_j = (*data_dependencies)[collective_nodes[j]];
       if (deps_i.find(instance_keys[j]) == deps_i.end() &&
           deps_j.find(instance_keys[i]) == deps_j.end()) {
         int src_idx = instance_keys[i] < instance_keys[j] ? i : j;
         int dst_idx = instance_keys[i] < instance_keys[j] ? j : i;
         Node* src_node = collective_nodes[src_idx];
         Node* dst_node = collective_nodes[dst_idx];
-        VLOG(1) << "Adding control edge from node " << src_node->name()
+        VLOG(1) << "Adding control dependency from node " << src_node->name()
                 << " instance " << instance_keys[src_idx] << " to node "
                 << dst_node->name() << " instance " << instance_keys[dst_idx];
-        graph->AddControlEdge(src_node, dst_node);
+        (*dependency_edges)[src_node].insert(dst_node);
+        auto& src_paths = all_paths[src_node];
+        src_paths.insert(dst_node);
+        for (Node* downstream_node : all_paths[dst_node]) {
+          src_paths.insert(downstream_node);
+        }
+      }
+    }
+  }
+
+  // Prune dependency edges so that if there are edges a -> b, b -> c, and a ->
+  // c, then remove a -> c.  This dependency would be handled naturally during
+  // op scheduling.
+  for (int i = 0; i < collective_nodes.size(); ++i) {
+    Node* node = collective_nodes[i];
+    auto& neighbor_set = (*dependency_edges)[node];
+    std::vector neighbor_list(neighbor_set.begin(), neighbor_set.end());
+    // For all n1, n2 in `neighbor_list` if there is a path from n1 -> n2 then
+    // eliminate n2 from `neighbor_set` and `neighbor_list`.  We remove from
+    // `neighbor_list` by replacing with a `nullptr`, hence the `nullptr` checks
+    // below.
+    for (int j = 0; j < neighbor_list.size(); ++j) {
+      Node* n1 = neighbor_list[j];
+      if (n1 == nullptr) continue;
+      auto& n1_paths = all_paths[n1];
+      for (int k = 0; k < neighbor_list.size(); ++k) {
+        Node* n2 = neighbor_list[k];
+        if (j == k || n2 == nullptr) continue;
+        if (n1_paths.find(n2) != n1_paths.end()) {
+          neighbor_set.erase(n2);
+          neighbor_list[k] = nullptr;
+        }
       }
     }
   }
@@ -92,4 +138,65 @@ Status OrderCollectives(Graph* graph) {
   return Status::OK();
 }
 
+// Insert control dependencies defined by `dependency_edges` in `graph`.  If
+// `order_type` is `kEdges`, insert explicit control edges, else if `order_type`
+// is `kAttrs`, encode depdencies as an attribute on collective node.
+Status InsertControlDependencies(
+    Graph* graph, GraphCollectiveOrder order_type,
+    const absl::flat_hash_map>&
+        dependency_edges) {
+  if (order_type == GraphCollectiveOrder::kEdges) {
+    for (const auto& pair : dependency_edges) {
+      Node* src_node = pair.first;
+      for (Node* dst_node : pair.second) {
+        graph->AddControlEdge(src_node, dst_node);
+      }
+    }
+  } else if (order_type == GraphCollectiveOrder::kAttrs) {
+    // `wait_for` is the inverse of `dependency_edges`, i.e. `wait_for[node]`
+    // contains the list of instance keys for which `node` must wait.
+    absl::flat_hash_map> wait_for;
+    for (const auto& pair : dependency_edges) {
+      int32 src_instance;
+      TF_RETURN_IF_ERROR(
+          GetNodeAttr(pair.first->attrs(), "instance_key", &src_instance));
+      for (Node* dst_node : pair.second) {
+        wait_for[dst_node].insert(src_instance);
+      }
+    }
+    for (const auto& pair : wait_for) {
+      std::vector wait_for_list(pair.second.begin(), pair.second.end());
+      pair.first->ClearAttr("wait_for");
+      pair.first->AddAttr("wait_for", wait_for_list);
+    }
+  } else {
+    return errors::Internal("Unexpected GraphCollectiveOrder type ",
+                            static_cast(order_type));
+  }
+  return Status::OK();
+}
+
+}  // namespace
+
+Status OrderCollectives(Graph* graph, GraphCollectiveOrder order_type) {
+  // `instance_keys[i]` corresponds to `collective_nodes[i]`
+  std::vector collective_nodes;
+  std::vector instance_keys;
+  // node -> set of collectives on which node depends.
+  absl::flat_hash_map> data_dependencies;
+  TF_RETURN_IF_ERROR(DiscoverDataDependencies(
+      graph, &collective_nodes, &instance_keys, &data_dependencies));
+
+  if (collective_nodes.empty()) return Status::OK();
+
+  absl::flat_hash_map> dependency_edges;
+  // For all pairs of collective nodes n1 and n2 on the same device, if n1 does
+  // not depend on n2 and n2 does not depend on n1, then they are potentially
+  // concurrent.  Create an arbitrary, deterministic ordering between them.
+  TF_RETURN_IF_ERROR(CreateControlDependencies(
+      collective_nodes, instance_keys, &data_dependencies, &dependency_edges));
+
+  return InsertControlDependencies(graph, order_type, dependency_edges);
+}
+
 }  // namespace tensorflow
diff --git a/tensorflow/core/graph/collective_order.h b/tensorflow/core/graph/collective_order.h
index 66210a2ab4..67a1427a96 100644
--- a/tensorflow/core/graph/collective_order.h
+++ b/tensorflow/core/graph/collective_order.h
@@ -19,11 +19,17 @@ limitations under the License.
 
 namespace tensorflow {
 
-// Introduces control edges between potentially concurrent CollectiveOps to make
-// their execution order deterministic. This may be used to execute collectives
-// in the same order across all workers in a distributed execution, if all
-// workers are executing the same graph.
-Status OrderCollectives(Graph* graph);
+enum class GraphCollectiveOrder { kNone, kEdges, kAttrs };
+
+// Introduces a deterministic execution order between potentially concurrent
+// CollectiveOps.  This may be used to execute collectives in the same order
+// across all workers in a distributed execution, if all workers are executing
+// the same graph.
+// If `order_type` is `kEdges`, introduce the ordering in the form of explicit
+// control edges between collective graph nodes.  If `order_type` is `kAttrs`,
+// add an attribute to the node which may be used by collective executor to
+// ensure the required ordering.
+Status OrderCollectives(Graph* graph, GraphCollectiveOrder order_type);
 
 }  // namespace tensorflow
 
diff --git a/tensorflow/core/graph/collective_order_test.cc b/tensorflow/core/graph/collective_order_test.cc
index b0ced7b007..241c98b549 100644
--- a/tensorflow/core/graph/collective_order_test.cc
+++ b/tensorflow/core/graph/collective_order_test.cc
@@ -59,6 +59,23 @@ void VerifyGraph(const Graph& graph,
               UnorderedElementsAreArray(expected_collective_control_edges));
 }
 
+// Verifies that the `wait_for` attribute on collective nodes matches
+// `wait_for_map`.
+void VerifyAttrs(
+    const Graph& graph,
+    const std::unordered_map> wait_for_map) {
+  for (const Node* node : graph.nodes()) {
+    if (node->IsCollective() ||
+        wait_for_map.find(node->name()) == wait_for_map.end()) {
+      continue;
+    }
+    std::vector wait_for_actual;
+    TF_EXPECT_OK(GetNodeAttr(node->attrs(), "wait_for", &wait_for_actual));
+    auto wait_for_expected = wait_for_map.at(node->name());
+    EXPECT_THAT(wait_for_actual, UnorderedElementsAreArray(wait_for_expected));
+  }
+}
+
 Node* CollectiveReduceNode(GraphDefBuilder* builder, Node* input,
                            const string& name, const string& device,
                            int instance_key) {
@@ -123,11 +140,17 @@ std::unique_ptr InitGraph() {
 // added after calling `OrderCollectives`: c2_0 -> c3_0 and c2_1 -> c3_1.
 TEST(CollectiveOrderTest, SimpleOrder) {
   std::unique_ptr graph = InitGraph();
-  TF_EXPECT_OK(OrderCollectives(graph.get()));
+  TF_EXPECT_OK(OrderCollectives(graph.get(), GraphCollectiveOrder::kEdges));
   VerifyGraph(*graph, {"c1_0", "c1_1", "c2_0", "c2_1", "c3_0", "c3_1"},
               {{"c2_0", "c3_0"}, {"c2_1", "c3_1"}});
 }
 
+TEST(CollectiveOrderTest, SimpleOrderAttr) {
+  std::unique_ptr graph = InitGraph();
+  TF_EXPECT_OK(OrderCollectives(graph.get(), GraphCollectiveOrder::kAttrs));
+  VerifyAttrs(*graph, {{"c3_0", {2}}, {"c3_1", {2}}});
+}
+
 // Initialize the following graph:
 //
 //         a
@@ -162,12 +185,50 @@ std::unique_ptr InitGraph2() {
 }
 
 // Tests that in the graph created by `InitGraph2`, we add the following control
-// edges after calling `OrderCollectives`: c2 -> c3, c3 -> c4, and c2 -> c4.
+// edges after calling `OrderCollectives`: c2 -> c3, c3 -> c4.  c2->c4 is
+// pruned because it follows from the other two edges.
 TEST(CollectiveOrderTest, SimpleOrder2) {
   std::unique_ptr graph = InitGraph2();
-  TF_EXPECT_OK(OrderCollectives(graph.get()));
-  VerifyGraph(*graph, {"c1", "c2", "c3", "c4"},
-              {{"c2", "c3"}, {"c3", "c4"}, {"c2", "c4"}});
+  TF_EXPECT_OK(OrderCollectives(graph.get(), GraphCollectiveOrder::kEdges));
+  VerifyGraph(*graph, {"c1", "c2", "c3", "c4"}, {{"c2", "c3"}, {"c3", "c4"}});
+}
+
+// Initialize the following graph:
+//
+//         w   x   y   z
+//         |   |   |   |
+//         c1  c2  c3  c4
+//
+std::unique_ptr InitGraphForPruning() {
+  GraphDefBuilder builder(GraphDefBuilder::kFailImmediately);
+  const string dev0 = "/job:localhost/replica:0/task:0/device:CPU:0";
+  Node* w = ops::SourceOp("TestParams",
+                          builder.opts().WithName("w").WithDevice(dev0));
+  Node* x = ops::SourceOp("TestParams",
+                          builder.opts().WithName("x").WithDevice(dev0));
+  Node* y = ops::SourceOp("TestParams",
+                          builder.opts().WithName("y").WithDevice(dev0));
+  Node* z = ops::SourceOp("TestParams",
+                          builder.opts().WithName("z").WithDevice(dev0));
+  CollectiveReduceNode(&builder, w, "c1", dev0, 1);
+  CollectiveReduceNode(&builder, x, "c2", dev0, 2);
+  CollectiveReduceNode(&builder, y, "c3", dev0, 3);
+  CollectiveReduceNode(&builder, z, "c4", dev0, 4);
+
+  std::unique_ptr graph = absl::make_unique(OpRegistry::Global());
+  Status s = GraphDefBuilderToGraph(builder, graph.get());
+  if (!s.ok()) {
+    LOG(FATAL) << "Error building graph " << s;
+  }
+  return graph;
+}
+
+// Tests that in the graph created by `InitGraphForPruning`, we only add c1 ->
+// c2, c2 -> c3, c3 -> c4, and other edges are pruned away.
+TEST(CollectiveOrderTest, Pruning) {
+  std::unique_ptr graph = InitGraphForPruning();
+  TF_EXPECT_OK(OrderCollectives(graph.get(), GraphCollectiveOrder::kAttrs));
+  VerifyAttrs(*graph, {{"c4", {3}}, {"c3", {2}}, {"c2", {1}}});
 }
 
 }  // namespace
diff --git a/tensorflow/core/ops/collective_ops.cc b/tensorflow/core/ops/collective_ops.cc
index d6157a69df..e45a8a9b36 100644
--- a/tensorflow/core/ops/collective_ops.cc
+++ b/tensorflow/core/ops/collective_ops.cc
@@ -28,6 +28,7 @@ REGISTER_OP("CollectiveReduce")
     .Attr("merge_op: {'Min', 'Max', 'Mul', 'Add'}")
     .Attr("final_op: {'Id', 'Div'}")
     .Attr("subdiv_offsets: list(int)")
+    .Attr("wait_for: list(int) = []")
     .SetIsStateful()
     .SetShapeFn(shape_inference::UnchangedShape);
 
-- 
GitLab


From 0364aef0ba5385b540632d692fcb2843ef382047 Mon Sep 17 00:00:00 2001
From: "A. Unique TensorFlower" 
Date: Thu, 10 Jan 2019 16:54:28 -0800
Subject: [PATCH 0527/2345] Add registration for (A^-1)^-1 = A.

PiperOrigin-RevId: 228803130
---
 .../python/ops/linalg/inverse_registrations.py       | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/tensorflow/python/ops/linalg/inverse_registrations.py b/tensorflow/python/ops/linalg/inverse_registrations.py
index b89bdd240e..12d1e7554c 100644
--- a/tensorflow/python/ops/linalg/inverse_registrations.py
+++ b/tensorflow/python/ops/linalg/inverse_registrations.py
@@ -40,6 +40,12 @@ def _inverse_linear_operator(linop):
       is_square=linop.is_square)
 
 
+@linear_operator_algebra.RegisterInverse(
+    linear_operator_inversion.LinearOperatorInversion)
+def _inverse_inverse_linear_operator(linop_inversion):
+  return linop_inversion.operator
+
+
 @linear_operator_algebra.RegisterInverse(
     linear_operator_diag.LinearOperatorDiag)
 def _inverse_diag(diag_operator):
@@ -72,7 +78,7 @@ def _inverse_scaled_identity(identity_operator):
 @linear_operator_algebra.RegisterInverse(
     linear_operator_block_diag.LinearOperatorBlockDiag)
 def _inverse_block_diag(block_diag_operator):
-    # We take the inverse of each block on the diagonal.
+  # We take the inverse of each block on the diagonal.
   return linear_operator_block_diag.LinearOperatorBlockDiag(
       operators=[
           operator.inverse() for operator in block_diag_operator.operators],
@@ -85,8 +91,8 @@ def _inverse_block_diag(block_diag_operator):
 @linear_operator_algebra.RegisterInverse(
     linear_operator_kronecker.LinearOperatorKronecker)
 def _inverse_kronecker(kronecker_operator):
-    # Inverse decomposition of a Kronecker product is the Kronecker product
-    # of inverse decompositions.
+  # Inverse decomposition of a Kronecker product is the Kronecker product
+  # of inverse decompositions.
   return linear_operator_kronecker.LinearOperatorKronecker(
       operators=[
           operator.inverse() for operator in kronecker_operator.operators],
-- 
GitLab


From 5269e3db18693866373a8c774936bbb7515fa027 Mon Sep 17 00:00:00 2001
From: Katherine Wu 
Date: Thu, 10 Jan 2019 17:04:19 -0800
Subject: [PATCH 0528/2345] Move Keras saving and loading functions into
 keras/saving/, and resolve circular dependencies with the SavedModel code.

* saving.py has been split by format (hdf5 and model config)
* saving.py will be deleted after dependencies to keras.engine.saving are all fixed.

PiperOrigin-RevId: 228804552
---
 tensorflow/contrib/saved_model/BUILD          |  29 -
 .../python/saved_model/keras_saved_model.py   | 397 +-------
 .../saved_model/keras_saved_model_test.py     | 538 ----------
 tensorflow/python/keras/BUILD                 |  64 +-
 tensorflow/python/keras/__init__.py           |   2 -
 tensorflow/python/keras/engine/network.py     |   8 +-
 tensorflow/python/keras/engine/saving.py      | 945 +-----------------
 tensorflow/python/keras/engine/training.py    |   3 +-
 .../python/keras/engine/training_utils.py     |  56 --
 .../keras/engine/training_utils_test.py       | 179 ----
 .../keras/layers/cudnn_recurrent_test.py      |   2 +-
 tensorflow/python/keras/models.py             |  13 +-
 tensorflow/python/keras/saving/BUILD          |  72 --
 tensorflow/python/keras/saving/__init__.py    |  16 +-
 tensorflow/python/keras/saving/hdf5_format.py | 895 +++++++++++++++++
 .../hdf5_format_test.py}                      |  20 +-
 .../python/keras/saving/model_config.py       |  96 ++
 tensorflow/python/keras/saving/saved_model.py |  12 +-
 .../python/keras/saving/saving_utils.py       |  60 +-
 .../python/keras/saving/saving_utils_test.py  | 209 ++++
 tensorflow/python/saved_model/BUILD           |   2 +-
 21 files changed, 1369 insertions(+), 2249 deletions(-)
 delete mode 100644 tensorflow/contrib/saved_model/python/saved_model/keras_saved_model_test.py
 delete mode 100644 tensorflow/python/keras/saving/BUILD
 create mode 100644 tensorflow/python/keras/saving/hdf5_format.py
 rename tensorflow/python/keras/{engine/saving_test.py => saving/hdf5_format_test.py} (98%)
 create mode 100644 tensorflow/python/keras/saving/model_config.py
 create mode 100644 tensorflow/python/keras/saving/saving_utils_test.py

diff --git a/tensorflow/contrib/saved_model/BUILD b/tensorflow/contrib/saved_model/BUILD
index 269443b2c6..f0242a3b40 100644
--- a/tensorflow/contrib/saved_model/BUILD
+++ b/tensorflow/contrib/saved_model/BUILD
@@ -84,35 +84,6 @@ py_library(
     srcs_version = "PY2AND3",
     visibility = ["//visibility:public"],
     deps = [
-        "//tensorflow/python:array_ops",
-        "//tensorflow/python:framework_ops",
-        "//tensorflow/python:lib",
-        "//tensorflow/python:metrics",
-        "//tensorflow/python:platform",
-        "//tensorflow/python:saver",
-        "//tensorflow/python:util",
-        "//tensorflow/python/estimator:estimator_py",
-        "//tensorflow/python/keras:engine",
-        "//tensorflow/python/saved_model",
-    ],
-)
-
-py_test(
-    name = "keras_saved_model_test",
-    size = "medium",
-    srcs = ["python/saved_model/keras_saved_model_test.py"],
-    srcs_version = "PY2AND3",
-    tags = [
-        "no_oss",  # TODO(b/119349471): Re-enable
-        "no_windows",
-    ],
-    deps = [
-        ":keras_saved_model",
-        "//tensorflow/python:client_testlib",
-        "//tensorflow/python:training",
-        "//tensorflow/python/estimator:estimator_py",
         "//tensorflow/python/keras",
-        "//third_party/py/numpy",
-        "@absl_py//absl/testing:parameterized",
     ],
 )
diff --git a/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model.py b/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model.py
index 2a4b6eae36..0392ed9eee 100644
--- a/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model.py
+++ b/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model.py
@@ -18,398 +18,9 @@ from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
 
-import os
-import six
+from tensorflow.python.keras import saving
 
-from tensorflow.python.client import session
-from tensorflow.python.framework import ops
-from tensorflow.python.keras import backend as K
-from tensorflow.python.keras import models as models_lib
-from tensorflow.python.keras import optimizers
-from tensorflow.python.keras.engine import sequential
-from tensorflow.python.keras.engine import training_utils
-from tensorflow.python.keras.metrics import Metric
-from tensorflow.python.keras.models import model_from_json
-from tensorflow.python.lib.io import file_io
-from tensorflow.python.ops import variables
-from tensorflow.python.platform import tf_logging as logging
-from tensorflow.python.saved_model import builder as saved_model_builder
-from tensorflow.python.saved_model import constants
-from tensorflow.python.saved_model import save as save_lib
-from tensorflow.python.saved_model import utils_impl as saved_model_utils
-from tensorflow.python.training import saver as saver_lib
-from tensorflow.python.training.checkpointable import util as checkpointable_utils
-from tensorflow.python.util import compat
-from tensorflow.python.util import nest
-from tensorflow_estimator.python.estimator import keras as estimator_keras_util
-from tensorflow_estimator.python.estimator import model_fn as model_fn_lib
-from tensorflow_estimator.python.estimator.export import export as export_helpers
 
-
-def save_keras_model(
-    model, saved_model_path, custom_objects=None, as_text=None,
-    input_signature=None, serving_only=False):
-  """Saves a `tf.keras.Model` into Tensorflow SavedModel format.
-
-  `save_model` generates new files/folders under the `saved_model_path` folder:
-  1) a checkpoint containing the model weights.
-  2) a saved_model.pb file containing the model's MetaGraphs. The prediction
-     graph is always exported. The evaluaton and training graphs are exported
-     if the following conditions are met:
-     - Evaluation: model loss is defined.
-     - Training: model is compiled with an optimizer defined under `tf.train`.
-       This is because `tf.keras.optimizers.Optimizer` instances cannot be
-       saved to checkpoints.
-  3) Model's json configuration, if model.get_config() has been implemented.
-     This file can be used to reload the model using
-     tf.keras.models.model_from_json(). Note that if any custom objects were
-     used, they should be passed to the `custom_object` argument when loading
-     the model.
-
-  Model limitations:
-  - Sequential and functional models can always be saved.
-  - Subclassed models can only be saved when `serving_only=True`. This is due to
-    the current implementation copying the model in order to export the training
-    and evaluation graphs. Because the topology of subclassed models cannot be
-    determined, the subclassed models cannot be cloned. Subclassed models will
-    be entirely exportable in the future.
-
-  Note that each mode is exported in separate graphs, so different modes do not
-  share variables. To use the train graph with evaluation or prediction graphs,
-  create a new checkpoint if variable values have been updated.
-
-  Example:
-
-  ```python
-  import tensorflow as tf
-
-  # Create a tf.keras model.
-  model = tf.keras.Sequential()
-  model.add(tf.keras.layers.Dense(1, input_shape=[10]))
-  model.summary()
-
-  # Save the tf.keras model in the SavedModel format.
-  saved_to_path = tf.contrib.saved_model.save_keras_model(
-        model, '/tmp/my_simple_tf_keras_saved_model')
-
-  # Load the saved keras model back.
-  model_prime = tf.contrib.saved_model.load_keras_model(saved_to_path)
-  model_prime.summary()
-  ```
-
-  Args:
-    model: A `tf.keras.Model` to be saved. If the model is subclassed, the flag
-      `serving_only` must be set to True.
-    saved_model_path: a string specifying the path to the SavedModel directory.
-      The SavedModel will be saved to a timestamped folder created within this
-      directory.
-    custom_objects: Optional dictionary mapping string names to custom classes
-      or functions (e.g. custom loss functions).
-    as_text: whether to write the `SavedModel` proto in text format. Currently
-      unavailable in serving-only mode.
-    input_signature: A possibly nested sequence of `tf.TensorSpec` objects, used
-      to specify the expected model inputs. `input_signature`'s nested structure
-      should match the expected nested structure of the inputs to the model. If
-      this is not set, this function will attempt to infer the input shapes and
-      dtypes from the model. Note that if the model is subclassed, the tensor
-      inputs to the call function should be nested in the first argument (this
-      is a general requirement for using subclassed models with Keras functions
-      .fit(), .predict(), etc.).
-    serving_only: Export only the outputs produced from calling the model in
-      predict mode. The losses, optimizer, and other training configurations are
-      not saved. If the SavedModel will only be used for serving (rather than
-      retraining), or if the model is subclassed, this can be set to True.
-
-  Returns:
-    String path to the SavedModel folder, a subdirectory of `saved_model_path`.
-
-  Raises:
-    NotImplementedError: If the model is a subclassed model, and serving_only is
-      False.
-    ValueError: If the input signature cannot be inferred from the model.
-  """
-  export_dir = export_helpers.get_timestamped_export_dir(saved_model_path)
-
-  if serving_only:
-    save_lib.save(
-        model, export_dir,
-        signatures=training_utils.trace_model_call(model, input_signature))
-  else:
-    _save_v1_format(model, export_dir, custom_objects, as_text, input_signature)
-
-  try:
-    _export_model_json(model, export_dir)
-  except NotImplementedError:
-    logging.warning('Skipped saving model JSON, subclassed model does not have '
-                    'get_config() defined.')
-
-  return export_dir
-
-
-def _export_model_json(model, saved_model_path):
-  """Saves model configuration as a json string under assets folder."""
-  model_json = model.to_json()
-  model_json_filepath = os.path.join(
-      saved_model_utils.get_or_create_assets_dir(saved_model_path),
-      compat.as_text(constants.SAVED_MODEL_FILENAME_JSON))
-  file_io.write_string_to_file(model_json_filepath, model_json)
-
-
-def _export_model_variables(model, saved_model_path):
-  """Saves model weights in checkpoint format under variables folder."""
-  saved_model_utils.get_or_create_variables_dir(saved_model_path)
-  checkpoint_prefix = saved_model_utils.get_variables_path(saved_model_path)
-  model.save_weights(checkpoint_prefix, save_format='tf', overwrite=True)
-  return checkpoint_prefix
-
-
-def _save_v1_format(model, path, custom_objects, as_text, input_signature):
-  """Exports model to v1 SavedModel format."""
-  if not model._is_graph_network:
-    if isinstance(model, sequential.Sequential):
-      # If input shape is not directly set in the model, the exported model
-      # will infer the expected shapes of the input from the model.
-      if not model.built and input_signature is None:
-        raise ValueError(
-            'Sequential model\'s input shape is unknown. Please build the '
-            'model, or use the input_signature argument to specify the '
-            'model inputs.')
-    else:
-      raise NotImplementedError(
-          'Subclassed models can only be exported for serving. Please set '
-          'argument serving_only=True.')
-
-  builder = saved_model_builder._SavedModelBuilder(path)
-
-  # Manually save variables to export them in an object-based checkpoint. This
-  # skips the `builder.add_meta_graph_and_variables()` step, which saves a
-  # named-based checkpoint.
-  # TODO(b/113134168): Add fn to Builder to save with object-based saver.
-  # TODO(b/113178242): This should only export the model json structure. Only
-  # one save is needed once the weights can be copied from the model to clone.
-  checkpoint_path = _export_model_variables(model, path)
-
-  # Export each mode. Use ModeKeys enums defined for `Estimator` to ensure that
-  # Keras models and `Estimator`s are exported with the same format.
-  # Every time a mode is exported, the code checks to see if new variables have
-  # been created (e.g. optimizer slot variables). If that is the case, the
-  # checkpoint is re-saved to include the new variables.
-  export_args = {'builder': builder,
-                 'model': model,
-                 'custom_objects': custom_objects,
-                 'checkpoint_path': checkpoint_path,
-                 'input_signature': input_signature}
-
-  has_saved_vars = False
-  if model.optimizer:
-    # TODO(kathywu): Verify this works with v2 optimizer.
-    if isinstance(model.optimizer, optimizers.TFOptimizer):
-      _export_mode(model_fn_lib.ModeKeys.TRAIN, has_saved_vars, **export_args)
-      has_saved_vars = True
-      _export_mode(model_fn_lib.ModeKeys.EVAL, has_saved_vars, **export_args)
-    else:
-      logging.warning(
-          'Model was compiled with an optimizer, but the optimizer is not from '
-          '`tf.train` (e.g. `tf.train.AdagradOptimizer`). Only the serving '
-          'graph was exported. The train and evaluate graphs were not added to '
-          'the SavedModel.')
-  _export_mode(model_fn_lib.ModeKeys.PREDICT, has_saved_vars, **export_args)
-
-  builder.save(as_text)
-
-
-def _get_var_list(model):
-  """Returns list of all checkpointed saveable objects in the model."""
-  return checkpointable_utils.named_saveables(model)
-
-
-def create_placeholder(spec):
-  return K.placeholder(shape=spec.shape, dtype=spec.dtype, name=spec.name)
-
-
-def _export_mode(
-    mode, has_saved_vars, builder, model, custom_objects, checkpoint_path,
-    input_signature):
-  """Exports a model, and optionally saves new vars from the clone model.
-
-  Args:
-    mode: A `tf.estimator.ModeKeys` string.
-    has_saved_vars: A `boolean` indicating whether the SavedModel has already
-      exported variables.
-    builder: A `SavedModelBuilder` object.
-    model: A `tf.keras.Model` object.
-    custom_objects: A dictionary mapping string names to custom classes
-      or functions.
-    checkpoint_path: String path to checkpoint.
-    input_signature: Nested TensorSpec containing the expected inputs. Can be
-      `None`, in which case the signature will be inferred from the model.
-
-  Raises:
-    ValueError: If the train/eval mode is being exported, but the model does
-      not have an optimizer.
-  """
-  compile_clone = (mode != model_fn_lib.ModeKeys.PREDICT)
-  if compile_clone and not model.optimizer:
-    raise ValueError(
-        'Model does not have an optimizer. Cannot export mode %s' % mode)
-
-  model_graph = ops.get_default_graph()
-  with ops.Graph().as_default() as g:
-
-    K.set_learning_phase(mode == model_fn_lib.ModeKeys.TRAIN)
-
-    if input_signature is None:
-      input_tensors = None
-    else:
-      input_tensors = nest.map_structure(create_placeholder, input_signature)
-
-    # Clone the model into blank graph. This will create placeholders for inputs
-    # and targets.
-    clone = models_lib.clone_and_build_model(
-        model, input_tensors=input_tensors, custom_objects=custom_objects,
-        compile_clone=compile_clone)
-
-    # Make sure that iterations variable is added to the global step collection,
-    # to ensure that, when the SavedModel graph is loaded, the iterations
-    # variable is returned by `tf.train.get_global_step()`. This is required for
-    # compatibility with the SavedModelEstimator.
-    if compile_clone:
-      g.add_to_collection(ops.GraphKeys.GLOBAL_STEP, clone.optimizer.iterations)
-
-    # Extract update and train ops from train/test/predict functions.
-    train_op = None
-    if mode == model_fn_lib.ModeKeys.TRAIN:
-      clone._make_train_function()
-      train_op = clone.train_function.updates_op
-    elif mode == model_fn_lib.ModeKeys.EVAL:
-      clone._make_test_function()
-    else:
-      clone._make_predict_function()
-    g.get_collection_ref(ops.GraphKeys.UPDATE_OPS).extend(clone.state_updates)
-
-    clone_var_list = checkpointable_utils.named_saveables(clone)
-
-    with session.Session().as_default():
-      if has_saved_vars:
-        # Confirm all variables in the clone have an entry in the checkpoint.
-        status = clone.load_weights(checkpoint_path)
-        status.assert_existing_objects_matched()
-      else:
-        # Confirm that variables between the clone and model match up exactly,
-        # not counting optimizer objects. Optimizer objects are ignored because
-        # if the model has not trained, the slot variables will not have been
-        # created yet.
-        # TODO(b/113179535): Replace with checkpointable equivalence.
-        _assert_same_non_optimizer_objects(model, model_graph, clone, g)
-
-        # TODO(b/113178242): Use value transfer for checkpointable objects.
-        clone.load_weights(checkpoint_path)
-
-        # Add graph and variables to SavedModel.
-        # TODO(b/113134168): Switch to add_meta_graph_and_variables.
-        clone.save_weights(checkpoint_path, save_format='tf', overwrite=True)
-        builder._has_saved_variables = True
-
-    # Add graph to the SavedModel builder.
-    builder.add_meta_graph(
-        model_fn_lib.EXPORT_TAG_MAP[mode],
-        signature_def_map=_create_signature_def_map(clone, mode),
-        saver=saver_lib.Saver(clone_var_list),
-        init_op=variables.local_variables_initializer(),
-        train_op=train_op)
-    return None
-
-
-def _create_signature_def_map(model, mode):
-  """Creates a SignatureDef map from a Keras model."""
-  inputs_dict = {name: x for name, x in zip(model.input_names, model.inputs)}
-  if model.optimizer:
-    targets_dict = {x.name.split(':')[0]: x
-                    for x in model.targets if x is not None}
-    inputs_dict.update(targets_dict)
-  outputs_dict = {name: x
-                  for name, x in zip(model.output_names, model.outputs)}
-  metrics = estimator_keras_util._convert_keras_metrics_to_estimator(model)
-
-  # Add metric variables to the `LOCAL_VARIABLES` collection. Metric variables
-  # are by default not added to any collections. We are doing this here, so
-  # that metric variables get initialized.
-  local_vars = set(ops.get_collection(ops.GraphKeys.LOCAL_VARIABLES))
-  vars_to_add = set()
-  if metrics is not None:
-    for key, value in six.iteritems(metrics):
-      if isinstance(value, Metric):
-        vars_to_add.update(value.variables)
-        # Convert Metric instances to (value_tensor, update_op) tuple.
-        metrics[key] = (value.result(), value.updates[0])
-  # Remove variables that are in the local variables collection already.
-  vars_to_add = vars_to_add.difference(local_vars)
-  for v in vars_to_add:
-    ops.add_to_collection(ops.GraphKeys.LOCAL_VARIABLES, v)
-
-  export_outputs = model_fn_lib.export_outputs_for_mode(
-      mode,
-      predictions=outputs_dict,
-      loss=model.total_loss if model.optimizer else None,
-      metrics=metrics)
-  return export_helpers.build_all_signature_defs(
-      inputs_dict,
-      export_outputs=export_outputs,
-      serving_only=(mode == model_fn_lib.ModeKeys.PREDICT))
-
-
-def _assert_same_non_optimizer_objects(model, model_graph, clone, clone_graph):  # pylint: disable=unused-argument
-  """Asserts model and clone contain the same checkpointable objects."""
-
-  # TODO(fchollet, kathywu): make sure this works in eager mode.
-  return True
-
-
-def load_keras_model(saved_model_path):
-  """Loads a keras.Model from SavedModel.
-
-  load_model reinstantiates model state by:
-  1) loading model topology from json (this will eventually come
-     from metagraph).
-  2) loading model weights from checkpoint.
-
-  Example:
-
-  ```python
-  import tensorflow as tf
-
-  # Create a tf.keras model.
-  model = tf.keras.Sequential()
-  model.add(tf.keras.layers.Dense(1, input_shape=[10]))
-  model.summary()
-
-  # Save the tf.keras model in the SavedModel format.
-  saved_to_path = tf.contrib.saved_model.save_keras_model(
-        model, '/tmp/my_simple_tf_keras_saved_model')
-
-  # Load the saved keras model back.
-  model_prime = tf.contrib.saved_model.load_keras_model(saved_to_path)
-  model_prime.summary()
-  ```
-
-  Args:
-    saved_model_path: a string specifying the path to an existing SavedModel.
-
-  Returns:
-    a keras.Model instance.
-  """
-  # restore model topology from json string
-  model_json_filepath = os.path.join(
-      compat.as_bytes(saved_model_path),
-      compat.as_bytes(constants.ASSETS_DIRECTORY),
-      compat.as_bytes(constants.SAVED_MODEL_FILENAME_JSON))
-  model_json = file_io.read_file_to_string(model_json_filepath)
-  model = model_from_json(model_json)
-
-  # restore model weights
-  checkpoint_prefix = os.path.join(
-      compat.as_text(saved_model_path),
-      compat.as_text(constants.VARIABLES_DIRECTORY),
-      compat.as_text(constants.VARIABLES_FILENAME))
-  model.load_weights(checkpoint_prefix)
-  return model
+# TODO(kathywu): Remove all contrib callers, switch to tf.keras.
+save_keras_model = saving.export
+load_keras_model = saving.load_from_saved_model
diff --git a/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model_test.py b/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model_test.py
deleted file mode 100644
index fbf8138493..0000000000
--- a/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model_test.py
+++ /dev/null
@@ -1,538 +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.
-# ==============================================================================
-# pylint: disable=protected-access
-"""Tests for saving/loading function for keras Model."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-import shutil
-
-from absl.testing import parameterized
-import numpy as np
-
-from tensorflow.contrib.saved_model.python.saved_model import keras_saved_model
-from tensorflow.python import keras
-from tensorflow.python.client import session
-from tensorflow.python.eager import context
-from tensorflow.python.estimator import model_fn as model_fn_lib
-from tensorflow.python.framework import dtypes
-from tensorflow.python.framework import ops
-from tensorflow.python.framework import tensor_spec
-from tensorflow.python.framework import test_util
-from tensorflow.python.keras.engine import training
-from tensorflow.python.keras.utils import tf_utils
-from tensorflow.python.ops import array_ops
-from tensorflow.python.platform import test
-from tensorflow.python.saved_model import loader_impl
-from tensorflow.python.saved_model import signature_constants
-from tensorflow.python.training import training as training_module
-
-
-class TestModelSavingandLoading(test.TestCase):
-
-  def _save_model_dir(self, dirname='saved_model'):
-    temp_dir = self.get_temp_dir()
-    self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
-    return os.path.join(temp_dir, dirname)
-
-  def test_saving_sequential_model(self):
-    with self.cached_session():
-      model = keras.models.Sequential()
-      model.add(keras.layers.Dense(2, input_shape=(3,)))
-      model.add(keras.layers.RepeatVector(3))
-      model.add(keras.layers.TimeDistributed(keras.layers.Dense(3)))
-      model.compile(
-          loss=keras.losses.MSE,
-          optimizer=keras.optimizers.RMSprop(lr=0.0001),
-          metrics=[keras.metrics.categorical_accuracy],
-          sample_weight_mode='temporal')
-      x = np.random.random((1, 3))
-      y = np.random.random((1, 3, 3))
-      model.train_on_batch(x, y)
-
-      ref_y = model.predict(x)
-
-      temp_saved_model = self._save_model_dir()
-      output_path = keras_saved_model.save_keras_model(model, temp_saved_model)
-
-      loaded_model = keras_saved_model.load_keras_model(output_path)
-      y = loaded_model.predict(x)
-      self.assertAllClose(ref_y, y, atol=1e-05)
-
-  @test_util.run_in_graph_and_eager_modes
-  def test_saving_sequential_model_without_compile(self):
-    with self.cached_session():
-      model = keras.models.Sequential()
-      model.add(keras.layers.Dense(2, input_shape=(3,)))
-      model.add(keras.layers.RepeatVector(3))
-      model.add(keras.layers.TimeDistributed(keras.layers.Dense(3)))
-
-      x = np.random.random((1, 3))
-      ref_y = model.predict(x)
-
-      temp_saved_model = self._save_model_dir()
-      output_path = keras_saved_model.save_keras_model(model, temp_saved_model)
-      loaded_model = keras_saved_model.load_keras_model(output_path)
-
-      y = loaded_model.predict(x)
-      self.assertAllClose(ref_y, y, atol=1e-05)
-
-  def test_saving_functional_model(self):
-    with self.cached_session():
-      inputs = keras.layers.Input(shape=(3,))
-      x = keras.layers.Dense(2)(inputs)
-      output = keras.layers.Dense(3)(x)
-
-      model = keras.models.Model(inputs, output)
-      model.compile(
-          loss=keras.losses.MSE,
-          optimizer=keras.optimizers.RMSprop(lr=0.0001),
-          metrics=[keras.metrics.categorical_accuracy])
-      x = np.random.random((1, 3))
-      y = np.random.random((1, 3))
-      model.train_on_batch(x, y)
-
-      ref_y = model.predict(x)
-
-      temp_saved_model = self._save_model_dir()
-      output_path = keras_saved_model.save_keras_model(model, temp_saved_model)
-      loaded_model = keras_saved_model.load_keras_model(output_path)
-
-      y = loaded_model.predict(x)
-      self.assertAllClose(ref_y, y, atol=1e-05)
-
-  @test_util.run_in_graph_and_eager_modes
-  def test_saving_functional_model_without_compile(self):
-    with self.cached_session():
-      inputs = keras.layers.Input(shape=(3,))
-      x = keras.layers.Dense(2)(inputs)
-      output = keras.layers.Dense(3)(x)
-
-      model = keras.models.Model(inputs, output)
-
-      x = np.random.random((1, 3))
-      y = np.random.random((1, 3))
-
-      ref_y = model.predict(x)
-
-      temp_saved_model = self._save_model_dir()
-      output_path = keras_saved_model.save_keras_model(model, temp_saved_model)
-      loaded_model = keras_saved_model.load_keras_model(output_path)
-
-      y = loaded_model.predict(x)
-      self.assertAllClose(ref_y, y, atol=1e-05)
-
-  @test_util.run_in_graph_and_eager_modes
-  def test_saving_with_tf_optimizer(self):
-    with self.cached_session():
-      model = keras.models.Sequential()
-      model.add(keras.layers.Dense(2, input_shape=(3,)))
-      model.add(keras.layers.Dense(3))
-      model.compile(
-          loss='mse',
-          optimizer=training_module.RMSPropOptimizer(0.1),
-          metrics=['acc'])
-
-      x = np.random.random((1, 3))
-      y = np.random.random((1, 3))
-      model.train_on_batch(x, y)
-      ref_y = model.predict(x)
-
-      temp_saved_model = self._save_model_dir()
-      output_path = keras_saved_model.save_keras_model(model, temp_saved_model)
-      loaded_model = keras_saved_model.load_keras_model(output_path)
-      loaded_model.compile(
-          loss='mse',
-          optimizer=training_module.RMSPropOptimizer(0.1),
-          metrics=['acc'])
-      y = loaded_model.predict(x)
-      self.assertAllClose(ref_y, y, atol=1e-05)
-
-      # test that new updates are the same with both models
-      x = np.random.random((1, 3))
-      y = np.random.random((1, 3))
-
-      ref_loss = model.train_on_batch(x, y)
-      loss = loaded_model.train_on_batch(x, y)
-      self.assertAllClose(ref_loss, loss, atol=1e-05)
-
-      ref_y = model.predict(x)
-      y = loaded_model.predict(x)
-      self.assertAllClose(ref_y, y, atol=1e-05)
-
-      # test saving/loading again
-      temp_saved_model2 = self._save_model_dir('saved_model_2')
-      output_path2 = keras_saved_model.save_keras_model(
-          loaded_model, temp_saved_model2)
-      loaded_model = keras_saved_model.load_keras_model(output_path2)
-      y = loaded_model.predict(x)
-      self.assertAllClose(ref_y, y, atol=1e-05)
-
-  def test_saving_subclassed_model_raise_error(self):
-    # For now, saving subclassed model should raise an error. It should be
-    # avoided later with loading from SavedModel.pb.
-
-    class SubclassedModel(training.Model):
-
-      def __init__(self):
-        super(SubclassedModel, self).__init__()
-        self.layer1 = keras.layers.Dense(3)
-        self.layer2 = keras.layers.Dense(1)
-
-      def call(self, inp):
-        return self.layer2(self.layer1(inp))
-
-    model = SubclassedModel()
-
-    temp_saved_model = self._save_model_dir()
-    with self.assertRaises(NotImplementedError):
-      keras_saved_model.save_keras_model(model, temp_saved_model)
-
-
-class LayerWithLearningPhase(keras.engine.base_layer.Layer):
-
-  def call(self, x):
-    phase = keras.backend.learning_phase()
-    output = tf_utils.smart_cond(
-        phase, lambda: x * 0, lambda: array_ops.identity(x))
-    if not context.executing_eagerly():
-      output._uses_learning_phase = True  # pylint: disable=protected-access
-    return output
-
-  def compute_output_shape(self, input_shape):
-    return input_shape
-
-
-def functional_model(uses_learning_phase=True):
-  inputs = keras.layers.Input(shape=(3,))
-  x = keras.layers.Dense(2)(inputs)
-  x = keras.layers.Dense(3)(x)
-  if uses_learning_phase:
-    x = LayerWithLearningPhase()(x)
-  return keras.models.Model(inputs, x)
-
-
-def sequential_model(uses_learning_phase=True):
-  model = keras.models.Sequential()
-  model.add(keras.layers.Dense(2, input_shape=(3,)))
-  model.add(keras.layers.Dense(3))
-  if uses_learning_phase:
-    model.add(LayerWithLearningPhase())
-  return model
-
-
-def sequential_model_without_input_shape(uses_learning_phase=True):
-  model = keras.models.Sequential()
-  model.add(keras.layers.Dense(2))
-  model.add(keras.layers.Dense(3))
-  if uses_learning_phase:
-    model.add(LayerWithLearningPhase())
-  return model
-
-
-class Subclassed(keras.models.Model):
-
-  def __init__(self):
-    super(Subclassed, self).__init__()
-    self.dense1 = keras.layers.Dense(2)
-    self.dense2 = keras.layers.Dense(3)
-
-  def call(self, inputs):
-    x = self.dense1(inputs)
-    x = self.dense2(x)
-    return x
-
-
-def subclassed_model():
-  return Subclassed()
-
-
-def load_model(sess, path, mode):
-  tags = model_fn_lib.EXPORT_TAG_MAP[mode]
-  if mode == model_fn_lib.ModeKeys.PREDICT:
-    sig_def_key = signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY
-  else:
-    sig_def_key = mode
-
-  meta_graph_def = loader_impl.load(sess, tags, path)
-  inputs = {
-      k: sess.graph.get_tensor_by_name(v.name)
-      for k, v in meta_graph_def.signature_def[sig_def_key].inputs.items()}
-  outputs = {
-      k: sess.graph.get_tensor_by_name(v.name)
-      for k, v in meta_graph_def.signature_def[sig_def_key].outputs.items()}
-  return inputs, outputs, meta_graph_def
-
-
-@test_util.run_all_in_graph_and_eager_modes
-class TestModelSavedModelExport(test.TestCase, parameterized.TestCase):
-
-  def _save_model_dir(self, dirname='saved_model'):
-    temp_dir = self.get_temp_dir()
-    self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
-    return os.path.join(temp_dir, dirname)
-
-  @parameterized.parameters(
-      {
-          'model_builder': functional_model,
-          'uses_learning_phase': True,
-          'optimizer': training_module.AdadeltaOptimizer(),
-          'train_before_export': True},
-      {
-          'model_builder': functional_model,
-          'uses_learning_phase': True,
-          'optimizer': training_module.AdadeltaOptimizer(),
-          'train_before_export': False},
-      {
-          'model_builder': functional_model,
-          'uses_learning_phase': False,
-          'optimizer': None,
-          'train_before_export': False},
-      {
-          'model_builder': sequential_model,
-          'uses_learning_phase': True,
-          'optimizer': training_module.AdadeltaOptimizer(),
-          'train_before_export': True},
-      {
-          'model_builder': sequential_model,
-          'uses_learning_phase': True,
-          'optimizer': training_module.AdadeltaOptimizer(),
-          'train_before_export': False},
-      {
-          'model_builder': sequential_model,
-          'uses_learning_phase': False,
-          'optimizer': None,
-          'train_before_export': False},
-      {
-          'model_builder': sequential_model_without_input_shape,
-          'uses_learning_phase': True,
-          'optimizer': training_module.AdadeltaOptimizer(),
-          'train_before_export': False})
-  def testSaveAndLoadSavedModelExport(
-      self, model_builder, uses_learning_phase, optimizer, train_before_export):
-    saved_model_path = self._save_model_dir()
-    with self.session(graph=ops.Graph()):
-      np.random.seed(130)
-      input_arr = np.random.random((1, 3))
-      target_arr = np.random.random((1, 3))
-
-      model = model_builder(uses_learning_phase)
-      if optimizer is not None:
-        model.compile(
-            loss='mse',
-            optimizer=optimizer,
-            metrics=['mae'])
-        if train_before_export:
-          model.train_on_batch(input_arr, target_arr)
-
-        ref_loss, ref_mae = model.evaluate(input_arr, target_arr)
-
-      ref_predict = model.predict(input_arr)
-
-      # Export SavedModel
-      output_path = keras_saved_model.save_keras_model(model, saved_model_path)
-
-    input_name = model.input_names[0]
-    output_name = model.output_names[0]
-    target_name = output_name + '_target'
-
-    # Load predict graph, and test predictions
-    with session.Session(graph=ops.Graph()) as sess:
-      inputs, outputs, _ = load_model(sess, output_path,
-                                      model_fn_lib.ModeKeys.PREDICT)
-
-      predictions = sess.run(outputs[output_name],
-                             {inputs[input_name]: input_arr})
-      self.assertAllClose(ref_predict, predictions, atol=1e-05)
-
-    if optimizer:
-      # Load eval graph, and test predictions, loss and metric values
-      with session.Session(graph=ops.Graph()) as sess:
-        inputs, outputs, _ = load_model(sess, output_path,
-                                        model_fn_lib.ModeKeys.EVAL)
-
-        # First obtain the loss and predictions, and run the metric update op by
-        # feeding in the inputs and targets.
-        loss, predictions, _ = sess.run(
-            (outputs['loss'], outputs['predictions/' + output_name],
-             outputs['metrics/mean_absolute_error/update_op']), {
-                 inputs[input_name]: input_arr,
-                 inputs[target_name]: target_arr
-             })
-
-        # The metric value should be run after the update op, to ensure that it
-        # reflects the correct value.
-        metric_value = sess.run(outputs['metrics/mean_absolute_error/value'])
-
-        self.assertEqual(int(train_before_export),
-                         sess.run(training_module.get_global_step()))
-        self.assertAllClose(ref_loss, loss, atol=1e-05)
-        self.assertAllClose(ref_mae, metric_value, atol=1e-05)
-        self.assertAllClose(ref_predict, predictions, atol=1e-05)
-
-      # Load train graph, and check for the train op, and prediction values
-      with session.Session(graph=ops.Graph()) as sess:
-        inputs, outputs, meta_graph_def = load_model(
-            sess, output_path, model_fn_lib.ModeKeys.TRAIN)
-        self.assertEqual(int(train_before_export),
-                         sess.run(training_module.get_global_step()))
-        self.assertIn('loss', outputs)
-        self.assertIn('metrics/mean_absolute_error/update_op', outputs)
-        self.assertIn('metrics/mean_absolute_error/value', outputs)
-        self.assertIn('predictions/' + output_name, outputs)
-
-        # Train for a step
-        train_op = loader_impl.get_train_op(meta_graph_def)
-        train_outputs, _ = sess.run(
-            [outputs, train_op], {inputs[input_name]: input_arr,
-                                  inputs[target_name]: target_arr})
-        self.assertEqual(int(train_before_export) + 1,
-                         sess.run(training_module.get_global_step()))
-
-        if uses_learning_phase:
-          self.assertAllClose(
-              [[0, 0, 0]], train_outputs['predictions/' + output_name],
-              atol=1e-05)
-        else:
-          self.assertNotAllClose(
-              [[0, 0, 0]], train_outputs['predictions/' + output_name],
-              atol=1e-05)
-
-  def testSaveAndLoadSavedModelWithCustomObject(self):
-    saved_model_path = self._save_model_dir()
-    with session.Session(graph=ops.Graph()) as sess:
-      def relu6(x):
-        return keras.backend.relu(x, max_value=6)
-      inputs = keras.layers.Input(shape=(1,))
-      outputs = keras.layers.Activation(relu6)(inputs)
-      model = keras.models.Model(inputs, outputs)
-      output_path = keras_saved_model.save_keras_model(
-          model, saved_model_path, custom_objects={'relu6': relu6})
-    with session.Session(graph=ops.Graph()) as sess:
-      inputs, outputs, _ = load_model(sess, output_path,
-                                      model_fn_lib.ModeKeys.PREDICT)
-      input_name = model.input_names[0]
-      output_name = model.output_names[0]
-      predictions = sess.run(
-          outputs[output_name], {inputs[input_name]: [[7], [-3], [4]]})
-      self.assertAllEqual([[6], [0], [4]], predictions)
-
-  def testAssertModelCloneSameObjectsIgnoreOptimizer(self):
-    input_arr = np.random.random((1, 3))
-    target_arr = np.random.random((1, 3))
-
-    model_graph = ops.Graph()
-    clone_graph = ops.Graph()
-
-    # Create two models with the same layers but different optimizers.
-    with session.Session(graph=model_graph):
-      inputs = keras.layers.Input(shape=(3,))
-      x = keras.layers.Dense(2)(inputs)
-      x = keras.layers.Dense(3)(x)
-      model = keras.models.Model(inputs, x)
-
-      model.compile(loss='mse', optimizer=training_module.AdadeltaOptimizer())
-      model.train_on_batch(input_arr, target_arr)
-
-    with session.Session(graph=clone_graph):
-      inputs = keras.layers.Input(shape=(3,))
-      x = keras.layers.Dense(2)(inputs)
-      x = keras.layers.Dense(3)(x)
-      clone = keras.models.Model(inputs, x)
-      clone.compile(loss='mse', optimizer=keras.optimizers.RMSprop(lr=0.0001))
-      clone.train_on_batch(input_arr, target_arr)
-
-    keras_saved_model._assert_same_non_optimizer_objects(
-        model, model_graph, clone, clone_graph)
-
-  def testAssertModelCloneSameObjectsThrowError(self):
-    input_arr = np.random.random((1, 3))
-    target_arr = np.random.random((1, 3))
-
-    model_graph = ops.Graph()
-    clone_graph = ops.Graph()
-
-    # Create two models with the same layers but different optimizers.
-    with session.Session(graph=model_graph):
-      inputs = keras.layers.Input(shape=(3,))
-      x = keras.layers.Dense(2)(inputs)
-      x = keras.layers.Dense(3)(x)
-      model = keras.models.Model(inputs, x)
-
-      model.compile(loss='mse', optimizer=training_module.AdadeltaOptimizer())
-      model.train_on_batch(input_arr, target_arr)
-
-    with session.Session(graph=clone_graph):
-      inputs = keras.layers.Input(shape=(3,))
-      x = keras.layers.Dense(2)(inputs)
-      x = keras.layers.Dense(4)(x)
-      x = keras.layers.Dense(3)(x)
-      clone = keras.models.Model(inputs, x)
-      clone.compile(loss='mse', optimizer=keras.optimizers.RMSprop(lr=0.0001))
-      clone.train_on_batch(input_arr, target_arr)
-
-  def testSaveSequentialModelWithoutInputShapes(self):
-    model = sequential_model_without_input_shape(True)
-    # A Sequential model that hasn't been built should raise an error.
-    with self.assertRaisesRegexp(ValueError, 'Please build the model'):
-      keras_saved_model.save_keras_model(model, '')
-
-    saved_model_path = self._save_model_dir()
-    output_path = keras_saved_model.save_keras_model(
-        model, saved_model_path,
-        input_signature=tensor_spec.TensorSpec(shape=(10, 11, 12, 13, 14),
-                                               dtype=dtypes.float32,
-                                               name='spec_input'))
-
-    with session.Session(graph=ops.Graph()) as sess:
-      inputs, outputs, _ = load_model(sess, output_path,
-                                      model_fn_lib.ModeKeys.PREDICT)
-      self.assertEqual(5, inputs[next(iter(inputs.keys()))].shape.ndims)
-      self.assertEqual(5, outputs[next(iter(outputs.keys()))].shape.ndims)
-      self.assertEqual(3, outputs[next(iter(outputs.keys()))].shape[-1])
-
-  @test_util.run_v2_only
-  @parameterized.parameters(
-      {
-          'model_builder': sequential_model_without_input_shape,
-          'input_signature': [tensor_spec.TensorSpec(shape=[None, 3],
-                                                     dtype=dtypes.float32)]},
-      {
-          'model_builder': subclassed_model,
-          'input_signature': [tensor_spec.TensorSpec(shape=[None, 3],
-                                                     dtype=dtypes.float32)]})
-  def testServingOnly(self, model_builder, input_signature):
-    saved_model_path = self._save_model_dir()
-    input_arr = np.random.random((5, 3)).astype(np.float32)
-    model = model_builder()
-    ref_predict = model.predict(input_arr)
-
-    output_path = keras_saved_model.save_keras_model(
-        model, saved_model_path, serving_only=True,
-        input_signature=input_signature)
-
-    # Load predict graph, and test predictions
-    with session.Session(graph=ops.Graph()) as sess:
-      inputs, outputs, _ = load_model(sess, output_path,
-                                      model_fn_lib.ModeKeys.PREDICT)
-      predictions = sess.run(outputs[next(iter(outputs.keys()))],
-                             {inputs[next(iter(inputs.keys()))]: input_arr})
-      self.assertAllClose(ref_predict, predictions, atol=1e-05)
-
-
-if __name__ == '__main__':
-  test.main()
diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD
index 536acca298..5f44a1b4e8 100755
--- a/tensorflow/python/keras/BUILD
+++ b/tensorflow/python/keras/BUILD
@@ -61,13 +61,11 @@ py_library(
         ":engine",
         ":layers",
         ":pil_for_keras",
-        "@keras_applications_archive//:keras_applications",
+        ":saving",
         "//tensorflow/python:training",
         "//tensorflow/python/keras/optimizer_v2",
-        # TODO(kathywu): move saving into engine after resolving circular
-        # dependencies between Keras and SavedModel
-        "//tensorflow/python/keras/saving",
         "//tensorflow/python/saved_model",
+        "@keras_applications_archive//:keras_applications",
     ],
 )
 
@@ -152,6 +150,7 @@ py_library(
         ":losses",
         ":optimizers",
         ":regularizers",
+        ":saving",
         "//tensorflow/python/data",
         "//tensorflow/python/distribute:reduce_util",
         "//tensorflow/python/training/checkpointable:data_structures",
@@ -160,6 +159,28 @@ py_library(
     ],
 )
 
+py_library(
+    name = "saving",
+    srcs = [
+        "saving/__init__.py",
+        "saving/hdf5_format.py",
+        "saving/model_config.py",
+        "saving/saved_model.py",
+        "saving/saving_utils.py",
+    ],
+    srcs_version = "PY2AND3",
+    deps = [
+        ":backend",
+        ":engine_utils",
+        ":optimizers",
+        "//tensorflow/python:lib",
+        "//tensorflow/python:mode_keys",
+        "//tensorflow/python:saver",
+        "//tensorflow/python/saved_model",
+        "//tensorflow/python/saved_model/model_utils",
+    ],
+)
+
 py_library(
     name = "activations",
     srcs = [
@@ -248,6 +269,7 @@ py_library(
 py_library(
     name = "engine_utils",
     srcs = [
+        "utils/conv_utils.py",
         "utils/data_utils.py",
         "utils/io_utils.py",
         "utils/losses_utils.py",
@@ -276,7 +298,6 @@ py_library(
         "layers/recurrent.py",
         "layers/serialization.py",
         "layers/wrappers.py",
-        "utils/conv_utils.py",
         "utils/generic_utils.py",
         "utils/layer_utils.py",
         "utils/tf_utils.py",
@@ -1001,9 +1022,9 @@ tf_py_test(
 )
 
 tf_py_test(
-    name = "saving_test",
+    name = "hdf5_format_test",
     size = "medium",
-    srcs = ["engine/saving_test.py"],
+    srcs = ["saving/hdf5_format_test.py"],
     additional_deps = [
         ":keras",
         "@absl_py//absl/testing:parameterized",
@@ -1064,3 +1085,32 @@ tf_py_test(
     ],
     tags = ["notsan"],
 )
+
+tf_py_test(
+    name = "saved_model_test",
+    size = "medium",
+    srcs = ["saving/saved_model_test.py"],
+    additional_deps = [
+        ":keras",
+        "@absl_py//absl/testing:parameterized",
+        "//third_party/py/numpy",
+        "//tensorflow/python:client_testlib",
+    ],
+    tags = [
+        "no_oss",  # TODO(b/119349471): Re-enable
+        "no_windows",
+    ],
+)
+
+tf_py_test(
+    name = "saving_utils_test",
+    size = "medium",
+    srcs = ["saving/saving_utils_test.py"],
+    additional_deps = [
+        ":keras",
+        "@absl_py//absl/testing:parameterized",
+        "//third_party/py/numpy",
+        "//tensorflow/python:client_testlib",
+    ],
+    tags = ["notsan"],
+)
diff --git a/tensorflow/python/keras/__init__.py b/tensorflow/python/keras/__init__.py
index e59744f64d..f024b9b59a 100644
--- a/tensorflow/python/keras/__init__.py
+++ b/tensorflow/python/keras/__init__.py
@@ -42,8 +42,6 @@ from tensorflow.python.keras import wrappers
 from tensorflow.python.keras.layers import Input
 from tensorflow.python.keras.models import Model
 from tensorflow.python.keras.models import Sequential
-from tensorflow.python.keras.saving.saved_model import export
-from tensorflow.python.keras.saving.saved_model import load_from_saved_model
 
 from tensorflow.python.util.tf_export import keras_export
 
diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py
index 6c3691a30c..991950993f 100644
--- a/tensorflow/python/keras/engine/network.py
+++ b/tensorflow/python/keras/engine/network.py
@@ -37,8 +37,8 @@ from tensorflow.python.framework import tensor_shape
 from tensorflow.python.keras import backend
 from tensorflow.python.keras.engine import base_layer
 from tensorflow.python.keras.engine import base_layer_utils
-from tensorflow.python.keras.engine import saving
 from tensorflow.python.keras.engine import training_utils
+from tensorflow.python.keras.saving import hdf5_format
 from tensorflow.python.keras.utils import generic_utils
 from tensorflow.python.keras.utils import layer_utils
 from tensorflow.python.keras.utils import tf_utils
@@ -1366,7 +1366,7 @@ class Network(base_layer.Layer):
         return
     if save_format == 'h5':
       with h5py.File(filepath, 'w') as f:
-        saving.save_weights_to_hdf5_group(f, self.layers)
+        hdf5_format.save_weights_to_hdf5_group(f, self.layers)
     else:
       if context.executing_eagerly():
         session = None
@@ -1466,9 +1466,9 @@ class Network(base_layer.Layer):
       if 'layer_names' not in f.attrs and 'model_weights' in f:
         f = f['model_weights']
       if by_name:
-        saving.load_weights_from_hdf5_group_by_name(f, self.layers)
+        hdf5_format.load_weights_from_hdf5_group_by_name(f, self.layers)
       else:
-        saving.load_weights_from_hdf5_group(f, self.layers)
+        hdf5_format.load_weights_from_hdf5_group(f, self.layers)
 
   def _updated_config(self):
     """Util shared between different serialization methods.
diff --git a/tensorflow/python/keras/engine/saving.py b/tensorflow/python/keras/engine/saving.py
index 0604a389a9..b4da86d984 100644
--- a/tensorflow/python/keras/engine/saving.py
+++ b/tensorflow/python/keras/engine/saving.py
@@ -14,950 +14,11 @@
 # ==============================================================================
 # pylint: disable=protected-access
 """Model saving utilities.
+
+Everything has been moved to keras/saving/. This file will be deleted soon.
 """
 from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
 
-import json
-import os
-
-import numpy as np
-from six.moves import zip  # pylint: disable=redefined-builtin
-
-from tensorflow.python.framework import ops
-from tensorflow.python.keras import backend as K
-from tensorflow.python.keras import optimizers
-from tensorflow.python.keras.utils import conv_utils
-from tensorflow.python.keras.utils.io_utils import ask_to_proceed_with_overwrite
-from tensorflow.python.platform import tf_logging as logging
-from tensorflow.python.util import serialization
-from tensorflow.python.util.tf_export import keras_export
-
-# pylint: disable=g-import-not-at-top
-try:
-  import h5py
-  HDF5_OBJECT_HEADER_LIMIT = 64512
-except ImportError:
-  h5py = None
-
-try:
-  import yaml
-except ImportError:
-  yaml = None
-# pylint: enable=g-import-not-at-top
-
-
-@keras_export('keras.models.save_model')
-def save_model(model, filepath, overwrite=True, include_optimizer=True):
-  """Saves a model to a HDF5 file.
-
-  The saved model contains:
-      - the model's configuration (topology)
-      - the model's weights
-      - the model's optimizer's state (if any)
-
-  Thus the saved model can be reinstantiated in
-  the exact same state, without any of the code
-  used for model definition or training.
-
-  Arguments:
-      model: Keras model instance to be saved.
-      filepath: One of the following:
-          - String, path where to save the model
-          - `h5py.File` object where to save the model
-      overwrite: Whether we should overwrite any existing
-          model at the target location, or instead
-          ask the user with a manual prompt.
-      include_optimizer: If True, save optimizer's state together.
-
-  Raises:
-      ImportError: if h5py is not available.
-  """
-
-  if h5py is None:
-    raise ImportError('`save_model` requires h5py.')
-
-  from tensorflow.python.keras import __version__ as keras_version  # pylint: disable=g-import-not-at-top
-
-  # TODO(psv) Add warning when we save models that contain non-serializable
-  # entities like metrics added using `add_metric` and losses added using
-  # `add_loss.`
-
-  if not isinstance(filepath, h5py.File):
-    # If file exists and should not be overwritten.
-    if not overwrite and os.path.isfile(filepath):
-      proceed = ask_to_proceed_with_overwrite(filepath)
-      if not proceed:
-        return
-
-    f = h5py.File(filepath, mode='w')
-    opened_new_file = True
-  else:
-    f = filepath
-    opened_new_file = False
-
-  try:
-    f.attrs['keras_version'] = str(keras_version).encode('utf8')
-    f.attrs['backend'] = K.backend().encode('utf8')
-    f.attrs['model_config'] = json.dumps(
-        {
-            'class_name': model.__class__.__name__,
-            'config': model.get_config()
-        },
-        default=serialization.get_json_type).encode('utf8')
-
-    model_weights_group = f.create_group('model_weights')
-    model_layers = model.layers
-    save_weights_to_hdf5_group(model_weights_group, model_layers)
-
-    if include_optimizer and model.optimizer:
-      if isinstance(model.optimizer, optimizers.TFOptimizer):
-        logging.warning(
-            'TensorFlow optimizers do not '
-            'make it possible to access '
-            'optimizer attributes or optimizer state '
-            'after instantiation. '
-            'As a result, we cannot save the optimizer '
-            'as part of the model save file.'
-            'You will have to compile your model again after loading it. '
-            'Prefer using a Keras optimizer instead '
-            '(see keras.io/optimizers).')
-      else:
-        f.attrs['training_config'] = json.dumps(
-            {
-                'optimizer_config': {
-                    'class_name': model.optimizer.__class__.__name__,
-                    'config': model.optimizer.get_config()
-                },
-                'loss': model.loss,
-                'metrics': model._compile_metrics,
-                'weighted_metrics': model._compile_weighted_metrics,
-                'sample_weight_mode': model.sample_weight_mode,
-                'loss_weights': model.loss_weights,
-            },
-            default=serialization.get_json_type).encode('utf8')
-
-        # Save optimizer weights.
-        symbolic_weights = getattr(model.optimizer, 'weights')
-        if symbolic_weights:
-          optimizer_weights_group = f.create_group('optimizer_weights')
-          weight_values = K.batch_get_value(symbolic_weights)
-          weight_names = []
-          for w, val in zip(symbolic_weights, weight_values):
-            name = str(w.name)
-            weight_names.append(name.encode('utf8'))
-          optimizer_weights_group.attrs['weight_names'] = weight_names
-          for name, val in zip(weight_names, weight_values):
-            param_dset = optimizer_weights_group.create_dataset(
-                name, val.shape, dtype=val.dtype)
-            if not val.shape:
-              # scalar
-              param_dset[()] = val
-            else:
-              param_dset[:] = val
-    f.flush()
-  finally:
-    if opened_new_file:
-      f.close()
-
-
-@keras_export('keras.models.load_model')
-def load_model(filepath, custom_objects=None, compile=True):  # pylint: disable=redefined-builtin
-  """Loads a model saved via `save_model`.
-
-  Arguments:
-      filepath: One of the following:
-          - String, path to the saved model
-          - `h5py.File` object from which to load the model
-      custom_objects: Optional dictionary mapping names
-          (strings) to custom classes or functions to be
-          considered during deserialization.
-      compile: Boolean, whether to compile the model
-          after loading.
-
-  Returns:
-      A Keras model instance. If an optimizer was found
-      as part of the saved model, the model is already
-      compiled. Otherwise, the model is uncompiled and
-      a warning will be displayed. When `compile` is set
-      to False, the compilation is omitted without any
-      warning.
-
-  Raises:
-      ImportError: if h5py is not available.
-      ValueError: In case of an invalid savefile.
-  """
-  if h5py is None:
-    raise ImportError('`load_model` requires h5py.')
-
-  if not custom_objects:
-    custom_objects = {}
-
-  def convert_custom_objects(obj):
-    """Handles custom object lookup.
-
-    Arguments:
-        obj: object, dict, or list.
-
-    Returns:
-        The same structure, where occurrences
-            of a custom object name have been replaced
-            with the custom object.
-    """
-    if isinstance(obj, list):
-      deserialized = []
-      for value in obj:
-        deserialized.append(convert_custom_objects(value))
-      return deserialized
-    if isinstance(obj, dict):
-      deserialized = {}
-      for key, value in obj.items():
-        deserialized[key] = convert_custom_objects(value)
-      return deserialized
-    if obj in custom_objects:
-      return custom_objects[obj]
-    return obj
-
-  opened_new_file = not isinstance(filepath, h5py.File)
-  if opened_new_file:
-    f = h5py.File(filepath, mode='r')
-  else:
-    f = filepath
-
-  model = None
-  try:
-    # instantiate model
-    model_config = f.attrs.get('model_config')
-    if model_config is None:
-      raise ValueError('No model found in config file.')
-    model_config = json.loads(model_config.decode('utf-8'))
-    model = model_from_config(model_config, custom_objects=custom_objects)
-
-    # set weights
-    load_weights_from_hdf5_group(f['model_weights'], model.layers)
-
-    if compile:
-      # instantiate optimizer
-      training_config = f.attrs.get('training_config')
-      if training_config is None:
-        logging.warning('No training configuration found in save file: '
-                        'the model was *not* compiled. Compile it manually.')
-        return model
-      training_config = json.loads(training_config.decode('utf-8'))
-      optimizer_config = training_config['optimizer_config']
-      optimizer = optimizers.deserialize(
-          optimizer_config, custom_objects=custom_objects)
-
-      # Recover loss functions and metrics.
-      loss = convert_custom_objects(training_config['loss'])
-      metrics = convert_custom_objects(training_config['metrics'])
-      weighted_metrics = convert_custom_objects(
-          training_config.get('weighted_metrics', None))
-      sample_weight_mode = training_config['sample_weight_mode']
-      loss_weights = training_config['loss_weights']
-
-      # Compile model.
-      model.compile(
-          optimizer=optimizer,
-          loss=loss,
-          metrics=metrics,
-          weighted_metrics=weighted_metrics,
-          loss_weights=loss_weights,
-          sample_weight_mode=sample_weight_mode)
-
-      # Set optimizer weights.
-      if 'optimizer_weights' in f:
-        # Build train function (to get weight updates).
-        # Models that aren't graph networks must wait until they are called
-        # with data to _make_train_function() and so can't load optimizer
-        # weights.
-        if model._is_graph_network:  # pylint: disable=protected-access
-          model._make_train_function()
-          optimizer_weights_group = f['optimizer_weights']
-          optimizer_weight_names = [
-              n.decode('utf8')
-              for n in optimizer_weights_group.attrs['weight_names']
-          ]
-          optimizer_weight_values = [
-              optimizer_weights_group[n] for n in optimizer_weight_names
-          ]
-          try:
-            model.optimizer.set_weights(optimizer_weight_values)
-          except ValueError:
-            logging.warning('Error in loading the saved optimizer '
-                            'state. As a result, your model is '
-                            'starting with a freshly initialized '
-                            'optimizer.')
-        else:
-          logging.warning('Sequential models without an `input_shape` '
-                          'passed to the first layer cannot reload their '
-                          'optimizer state. As a result, your model is'
-                          'starting with a freshly initialized optimizer.')
-
-  finally:
-    if opened_new_file:
-      f.close()
-  return model
-
-
-@keras_export('keras.models.model_from_config')
-def model_from_config(config, custom_objects=None):
-  """Instantiates a Keras model from its config.
-
-  Arguments:
-      config: Configuration dictionary.
-      custom_objects: Optional dictionary mapping names
-          (strings) to custom classes or functions to be
-          considered during deserialization.
-
-  Returns:
-      A Keras model instance (uncompiled).
-
-  Raises:
-      TypeError: if `config` is not a dictionary.
-  """
-  if isinstance(config, list):
-    raise TypeError('`model_from_config` expects a dictionary, not a list. '
-                    'Maybe you meant to use '
-                    '`Sequential.from_config(config)`?')
-  from tensorflow.python.keras.layers import deserialize  # pylint: disable=g-import-not-at-top
-  return deserialize(config, custom_objects=custom_objects)
-
-
-@keras_export('keras.models.model_from_yaml')
-def model_from_yaml(yaml_string, custom_objects=None):
-  """Parses a yaml model configuration file and returns a model instance.
-
-  Arguments:
-      yaml_string: YAML string encoding a model configuration.
-      custom_objects: Optional dictionary mapping names
-          (strings) to custom classes or functions to be
-          considered during deserialization.
-
-  Returns:
-      A Keras model instance (uncompiled).
-
-  Raises:
-      ImportError: if yaml module is not found.
-  """
-  if yaml is None:
-    raise ImportError('Requires yaml module installed (`pip install pyyaml`).')
-  config = yaml.load(yaml_string)
-  from tensorflow.python.keras.layers import deserialize  # pylint: disable=g-import-not-at-top
-  return deserialize(config, custom_objects=custom_objects)
-
-
-@keras_export('keras.models.model_from_json')
-def model_from_json(json_string, custom_objects=None):
-  """Parses a JSON model configuration file and returns a model instance.
-
-  Arguments:
-      json_string: JSON string encoding a model configuration.
-      custom_objects: Optional dictionary mapping names
-          (strings) to custom classes or functions to be
-          considered during deserialization.
-
-  Returns:
-      A Keras model instance (uncompiled).
-  """
-  config = json.loads(json_string)
-  from tensorflow.python.keras.layers import deserialize  # pylint: disable=g-import-not-at-top
-  return deserialize(config, custom_objects=custom_objects)
-
-
-def preprocess_weights_for_loading(layer,
-                                   weights,
-                                   original_keras_version=None,
-                                   original_backend=None):
-  """Preprocess layer weights between different Keras formats.
-
-  Converts layers weights from Keras 1 format to Keras 2 and also weights of
-  CuDNN layers in Keras 2.
-
-  Arguments:
-      layer: Layer instance.
-      weights: List of weights values (Numpy arrays).
-      original_keras_version: Keras version for the weights, as a string.
-      original_backend: Keras backend the weights were trained with,
-          as a string.
-
-  Returns:
-      A list of weights values (Numpy arrays).
-  """
-  def convert_nested_bidirectional(weights):
-    """Converts layers nested in `Bidirectional` wrapper.
-
-    This function uses `preprocess_weights_for_loading()` for converting
-    layers.
-
-    Arguments:
-        weights: List of weights values (Numpy arrays).
-
-    Returns:
-        A list of weights values (Numpy arrays).
-    """
-    num_weights_per_layer = len(weights) // 2
-    forward_weights = preprocess_weights_for_loading(
-        layer.forward_layer, weights[:num_weights_per_layer],
-        original_keras_version, original_backend)
-    backward_weights = preprocess_weights_for_loading(
-        layer.backward_layer, weights[num_weights_per_layer:],
-        original_keras_version, original_backend)
-    return forward_weights + backward_weights
-
-  def convert_nested_time_distributed(weights):
-    """Converts layers nested in `TimeDistributed` wrapper.
-
-    This function uses `preprocess_weights_for_loading()` for converting nested
-    layers.
-
-    Arguments:
-        weights: List of weights values (Numpy arrays).
-
-    Returns:
-        A list of weights values (Numpy arrays).
-    """
-    return preprocess_weights_for_loading(
-        layer.layer, weights, original_keras_version, original_backend)
-
-  def convert_nested_model(weights):
-    """Converts layers nested in `Model` or `Sequential`.
-
-    This function uses `preprocess_weights_for_loading()` for converting nested
-    layers.
-
-    Arguments:
-        weights: List of weights values (Numpy arrays).
-
-    Returns:
-        A list of weights values (Numpy arrays).
-    """
-    new_weights = []
-    # trainable weights
-    for sublayer in layer.layers:
-      num_weights = len(sublayer.trainable_weights)
-      if num_weights > 0:
-        new_weights.extend(preprocess_weights_for_loading(
-            layer=sublayer,
-            weights=weights[:num_weights],
-            original_keras_version=original_keras_version,
-            original_backend=original_backend))
-        weights = weights[num_weights:]
-
-    # non-trainable weights
-    for sublayer in layer.layers:
-      num_weights = len([l for l in sublayer.weights
-                         if l not in sublayer.trainable_weights])
-      if num_weights > 0:
-        new_weights.extend(preprocess_weights_for_loading(
-            layer=sublayer,
-            weights=weights[:num_weights],
-            original_keras_version=original_keras_version,
-            original_backend=original_backend))
-        weights = weights[num_weights:]
-    return new_weights
-
-  # Convert layers nested in Bidirectional/Model/Sequential.
-  # Both transformation should be ran for both Keras 1->2 conversion
-  # and for conversion of CuDNN layers.
-  if layer.__class__.__name__ == 'Bidirectional':
-    weights = convert_nested_bidirectional(weights)
-  if layer.__class__.__name__ == 'TimeDistributed':
-    weights = convert_nested_time_distributed(weights)
-  elif layer.__class__.__name__ in ['Model', 'Sequential']:
-    weights = convert_nested_model(weights)
-
-  if original_keras_version == '1':
-    if layer.__class__.__name__ == 'TimeDistributed':
-      weights = preprocess_weights_for_loading(
-          layer.layer, weights, original_keras_version, original_backend)
-
-    if layer.__class__.__name__ == 'Conv1D':
-      shape = weights[0].shape
-      # Handle Keras 1.1 format
-      if shape[:2] != (layer.kernel_size[0], 1) or shape[3] != layer.filters:
-        # Legacy shape:
-        # (filters, input_dim, filter_length, 1)
-        assert shape[0] == layer.filters and shape[2:] == (layer.kernel_size[0],
-                                                           1)
-        weights[0] = np.transpose(weights[0], (2, 3, 1, 0))
-      weights[0] = weights[0][:, 0, :, :]
-
-    if layer.__class__.__name__ == 'Conv2D':
-      if layer.data_format == 'channels_first':
-        # old: (filters, stack_size, kernel_rows, kernel_cols)
-        # new: (kernel_rows, kernel_cols, stack_size, filters)
-        weights[0] = np.transpose(weights[0], (2, 3, 1, 0))
-
-    if layer.__class__.__name__ == 'Conv2DTranspose':
-      if layer.data_format == 'channels_last':
-        # old: (kernel_rows, kernel_cols, stack_size, filters)
-        # new: (kernel_rows, kernel_cols, filters, stack_size)
-        weights[0] = np.transpose(weights[0], (0, 1, 3, 2))
-      if layer.data_format == 'channels_first':
-        # old: (filters, stack_size, kernel_rows, kernel_cols)
-        # new: (kernel_rows, kernel_cols, filters, stack_size)
-        weights[0] = np.transpose(weights[0], (2, 3, 0, 1))
-
-    if layer.__class__.__name__ == 'Conv3D':
-      if layer.data_format == 'channels_first':
-        # old: (filters, stack_size, ...)
-        # new: (..., stack_size, filters)
-        weights[0] = np.transpose(weights[0], (2, 3, 4, 1, 0))
-
-    if layer.__class__.__name__ == 'GRU':
-      if len(weights) == 9:
-        kernel = np.concatenate([weights[0], weights[3], weights[6]], axis=-1)
-        recurrent_kernel = np.concatenate(
-            [weights[1], weights[4], weights[7]], axis=-1)
-        bias = np.concatenate([weights[2], weights[5], weights[8]], axis=-1)
-        weights = [kernel, recurrent_kernel, bias]
-
-    if layer.__class__.__name__ == 'LSTM':
-      if len(weights) == 12:
-        # old: i, c, f, o
-        # new: i, f, c, o
-        kernel = np.concatenate(
-            [weights[0], weights[6], weights[3], weights[9]], axis=-1)
-        recurrent_kernel = np.concatenate(
-            [weights[1], weights[7], weights[4], weights[10]], axis=-1)
-        bias = np.concatenate(
-            [weights[2], weights[8], weights[5], weights[11]], axis=-1)
-        weights = [kernel, recurrent_kernel, bias]
-
-    if layer.__class__.__name__ == 'ConvLSTM2D':
-      if len(weights) == 12:
-        kernel = np.concatenate(
-            [weights[0], weights[6], weights[3], weights[9]], axis=-1)
-        recurrent_kernel = np.concatenate(
-            [weights[1], weights[7], weights[4], weights[10]], axis=-1)
-        bias = np.concatenate(
-            [weights[2], weights[8], weights[5], weights[11]], axis=-1)
-        if layer.data_format == 'channels_first':
-          # old: (filters, stack_size, kernel_rows, kernel_cols)
-          # new: (kernel_rows, kernel_cols, stack_size, filters)
-          kernel = np.transpose(kernel, (2, 3, 1, 0))
-          recurrent_kernel = np.transpose(recurrent_kernel, (2, 3, 1, 0))
-        weights = [kernel, recurrent_kernel, bias]
-
-  conv_layers = ['Conv1D', 'Conv2D', 'Conv3D', 'Conv2DTranspose', 'ConvLSTM2D']
-  if layer.__class__.__name__ in conv_layers:
-    if original_backend == 'theano':
-      weights[0] = conv_utils.convert_kernel(weights[0])
-      if layer.__class__.__name__ == 'ConvLSTM2D':
-        weights[1] = conv_utils.convert_kernel(weights[1])
-    if K.int_shape(layer.weights[0]) != weights[0].shape:
-      weights[0] = np.transpose(weights[0], (3, 2, 0, 1))
-      if layer.__class__.__name__ == 'ConvLSTM2D':
-        weights[1] = np.transpose(weights[1], (3, 2, 0, 1))
-
-  # convert CuDNN layers
-  return _convert_rnn_weights(layer, weights)
-
-
-def _convert_rnn_weights(layer, weights):
-  """Converts weights for RNN layers between native and CuDNN format.
-
-  Input kernels for each gate are transposed and converted between Fortran
-  and C layout, recurrent kernels are transposed. For LSTM biases are summed/
-  split in half, for GRU biases are reshaped.
-
-  Weights can be converted in both directions between `LSTM` and`CuDNNSLTM`
-  and between `CuDNNGRU` and `GRU(reset_after=True)`. Default `GRU` is not
-  compatible with `CuDNNGRU`.
-
-  For missing biases in `LSTM`/`GRU` (`use_bias=False`) no conversion is made.
-
-  Arguments:
-      layer: Target layer instance.
-      weights: List of source weights values (input kernels, recurrent
-          kernels, [biases]) (Numpy arrays).
-
-  Returns:
-      A list of converted weights values (Numpy arrays).
-
-  Raises:
-      ValueError: for incompatible GRU layer/weights or incompatible biases
-  """
-
-  def transform_kernels(kernels, func, n_gates):
-    """Transforms kernel for each gate separately using given function.
-
-    Arguments:
-        kernels: Stacked array of kernels for individual gates.
-        func: Function applied to kernel of each gate.
-        n_gates: Number of gates (4 for LSTM, 3 for GRU).
-
-    Returns:
-        Stacked array of transformed kernels.
-    """
-    return np.hstack([func(k) for k in np.hsplit(kernels, n_gates)])
-
-  def transpose_input(from_cudnn):
-    """Makes a function that transforms input kernels from/to CuDNN format.
-
-    It keeps the shape, but changes between the layout (Fortran/C). Eg.:
-
-    ```
-    Keras                 CuDNN
-    [[0, 1, 2],  <--->  [[0, 2, 4],
-     [3, 4, 5]]          [1, 3, 5]]
-    ```
-
-    It can be passed to `transform_kernels()`.
-
-    Arguments:
-        from_cudnn: `True` if source weights are in CuDNN format, `False`
-            if they're in plain Keras format.
-
-    Returns:
-        Function that converts input kernel to the other format.
-    """
-    order = 'F' if from_cudnn else 'C'
-
-    def transform(kernel):
-      return kernel.T.reshape(kernel.shape, order=order)
-
-    return transform
-
-  target_class = layer.__class__.__name__
-
-  # convert the weights between CuDNNLSTM and LSTM
-  if target_class in ['LSTM', 'CuDNNLSTM'] and len(weights) == 3:
-    # determine if we're loading a CuDNNLSTM layer
-    # from the number of bias weights:
-    # CuDNNLSTM has (units * 8) weights; while LSTM has (units * 4)
-    # if there's no bias weight in the file, skip this conversion
-    units = weights[1].shape[0]
-    bias_shape = weights[2].shape
-    n_gates = 4
-
-    if bias_shape == (2 * units * n_gates,):
-      source = 'CuDNNLSTM'
-    elif bias_shape == (units * n_gates,):
-      source = 'LSTM'
-    else:
-      raise ValueError('Invalid bias shape: ' + str(bias_shape))
-
-    def convert_lstm_weights(weights, from_cudnn=True):
-      """Converts the weights between CuDNNLSTM and LSTM.
-
-      Arguments:
-        weights: Original weights.
-        from_cudnn: Indicates whether original weights are from CuDNN layer.
-
-      Returns:
-        Updated weights compatible with LSTM.
-      """
-
-      # Transpose (and reshape) input and recurrent kernels
-      kernels = transform_kernels(weights[0], transpose_input(from_cudnn),
-                                  n_gates)
-      recurrent_kernels = transform_kernels(weights[1], lambda k: k.T, n_gates)
-      if from_cudnn:
-        # merge input and recurrent biases into a single set
-        biases = np.sum(np.split(weights[2], 2, axis=0), axis=0)
-      else:
-        # Split single set of biases evenly to two sets. The way of
-        # splitting doesn't matter as long as the two sets sum is kept.
-        biases = np.tile(0.5 * weights[2], 2)
-      return [kernels, recurrent_kernels, biases]
-
-    if source != target_class:
-      weights = convert_lstm_weights(weights, from_cudnn=source == 'CuDNNLSTM')
-
-  # convert the weights between CuDNNGRU and GRU(reset_after=True)
-  if target_class in ['GRU', 'CuDNNGRU'] and len(weights) == 3:
-    # We can determine the source of the weights from the shape of the bias.
-    # If there is no bias we skip the conversion since
-    # CuDNNGRU always has biases.
-
-    units = weights[1].shape[0]
-    bias_shape = weights[2].shape
-    n_gates = 3
-
-    def convert_gru_weights(weights, from_cudnn=True):
-      """Converts the weights between CuDNNGRU and GRU.
-
-      Arguments:
-        weights: Original weights.
-        from_cudnn: Indicates whether original weights are from CuDNN layer.
-
-      Returns:
-        Updated weights compatible with GRU.
-      """
-
-      kernels = transform_kernels(weights[0], transpose_input(from_cudnn),
-                                  n_gates)
-      recurrent_kernels = transform_kernels(weights[1], lambda k: k.T, n_gates)
-      biases = np.array(weights[2]).reshape((2, -1) if from_cudnn else -1)
-      return [kernels, recurrent_kernels, biases]
-
-    if bias_shape == (2 * units * n_gates,):
-      source = 'CuDNNGRU'
-    elif bias_shape == (2, units * n_gates):
-      source = 'GRU(reset_after=True)'
-    elif bias_shape == (units * n_gates,):
-      source = 'GRU(reset_after=False)'
-    else:
-      raise ValueError('Invalid bias shape: ' + str(bias_shape))
-
-    if target_class == 'CuDNNGRU':
-      target = 'CuDNNGRU'
-    elif layer.reset_after:
-      target = 'GRU(reset_after=True)'
-    else:
-      target = 'GRU(reset_after=False)'
-
-    # only convert between different types
-    if source != target:
-      types = (source, target)
-      if 'GRU(reset_after=False)' in types:
-        raise ValueError('%s is not compatible with %s' % types)
-      if source == 'CuDNNGRU':
-        weights = convert_gru_weights(weights, from_cudnn=True)
-      elif source == 'GRU(reset_after=True)':
-        weights = convert_gru_weights(weights, from_cudnn=False)
-
-  return weights
-
-
-def save_weights_to_hdf5_group(f, layers):
-  """Saves the weights of a list of layers to a HDF5 group.
-
-  Arguments:
-      f: HDF5 group.
-      layers: List of layer instances.
-  """
-  from tensorflow.python.keras import __version__ as keras_version  # pylint: disable=g-import-not-at-top
-
-  save_attributes_to_hdf5_group(
-      f, 'layer_names', [layer.name.encode('utf8') for layer in layers])
-  f.attrs['backend'] = K.backend().encode('utf8')
-  f.attrs['keras_version'] = str(keras_version).encode('utf8')
-
-  # On TPUs, modifying the graph between session.runs() triggers some expensive
-  # recompilation overhead. To avoid this, we build up the full set of tensors
-  # to save before fetching weights, thus only modifying the graph once.
-  layer_weights_dict = {}
-  for layer in layers:
-    layer_weights_dict[layer.name] = [ops.convert_to_tensor(w)
-                                      for w in layer.weights]
-
-  for layer in layers:
-    g = f.create_group(layer.name)
-    symbolic_weights = layer_weights_dict[layer.name]
-    weight_values = K.batch_get_value(symbolic_weights)
-    weight_names = []
-    for i, (w, val) in enumerate(zip(symbolic_weights, weight_values)):
-      if hasattr(w, 'name') and w.name:
-        name = str(w.name)
-      else:
-        name = 'param_' + str(i)
-      weight_names.append(name.encode('utf8'))
-    save_attributes_to_hdf5_group(g, 'weight_names', weight_names)
-    for name, val in zip(weight_names, weight_values):
-      param_dset = g.create_dataset(name, val.shape, dtype=val.dtype)
-      if not val.shape:
-        # scalar
-        param_dset[()] = val
-      else:
-        param_dset[:] = val
-
-
-def load_weights_from_hdf5_group(f, layers):
-  """Implements topological (order-based) weight loading.
-
-  Arguments:
-      f: A pointer to a HDF5 group.
-      layers: a list of target layers.
-
-  Raises:
-      ValueError: in case of mismatch between provided layers
-          and weights file.
-  """
-  if 'keras_version' in f.attrs:
-    original_keras_version = f.attrs['keras_version'].decode('utf8')
-  else:
-    original_keras_version = '1'
-  if 'backend' in f.attrs:
-    original_backend = f.attrs['backend'].decode('utf8')
-  else:
-    original_backend = None
-
-  filtered_layers = []
-  for layer in layers:
-    weights = layer.weights
-    if weights:
-      filtered_layers.append(layer)
-
-  layer_names = load_attributes_from_hdf5_group(f, 'layer_names')
-  filtered_layer_names = []
-  for name in layer_names:
-    g = f[name]
-    weight_names = load_attributes_from_hdf5_group(g, 'weight_names')
-    if weight_names:
-      filtered_layer_names.append(name)
-  layer_names = filtered_layer_names
-  if len(layer_names) != len(filtered_layers):
-    raise ValueError('You are trying to load a weight file '
-                     'containing ' + str(len(layer_names)) +
-                     ' layers into a model with ' + str(len(filtered_layers)) +
-                     ' layers.')
-
-  # We batch weight value assignments in a single backend call
-  # which provides a speedup in TensorFlow.
-  weight_value_tuples = []
-  for k, name in enumerate(layer_names):
-    g = f[name]
-    weight_names = load_attributes_from_hdf5_group(g, 'weight_names')
-    weight_values = [np.asarray(g[weight_name]) for weight_name in weight_names]
-    layer = filtered_layers[k]
-    symbolic_weights = layer.weights
-    weight_values = preprocess_weights_for_loading(
-        layer, weight_values, original_keras_version, original_backend)
-    if len(weight_values) != len(symbolic_weights):
-      raise ValueError('Layer #' + str(k) + ' (named "' + layer.name +
-                       '" in the current model) was found to '
-                       'correspond to layer ' + name + ' in the save file. '
-                       'However the new layer ' + layer.name + ' expects ' +
-                       str(len(symbolic_weights)) +
-                       ' weights, but the saved weights have ' +
-                       str(len(weight_values)) + ' elements.')
-    weight_value_tuples += zip(symbolic_weights, weight_values)
-  K.batch_set_value(weight_value_tuples)
-
-
-def load_weights_from_hdf5_group_by_name(f, layers):
-  """Implements name-based weight loading.
-
-  (instead of topological weight loading).
-
-  Layers that have no matching name are skipped.
-
-  Arguments:
-      f: A pointer to a HDF5 group.
-      layers: a list of target layers.
-
-  Raises:
-      ValueError: in case of mismatch between provided layers
-          and weights file.
-  """
-  if 'keras_version' in f.attrs:
-    original_keras_version = f.attrs['keras_version'].decode('utf8')
-  else:
-    original_keras_version = '1'
-  if 'backend' in f.attrs:
-    original_backend = f.attrs['backend'].decode('utf8')
-  else:
-    original_backend = None
-
-  # New file format.
-  layer_names = load_attributes_from_hdf5_group(f, 'layer_names')
-
-  # Reverse index of layer name to list of layers with name.
-  index = {}
-  for layer in layers:
-    if layer.name:
-      index.setdefault(layer.name, []).append(layer)
-
-  # We batch weight value assignments in a single backend call
-  # which provides a speedup in TensorFlow.
-  weight_value_tuples = []
-  for k, name in enumerate(layer_names):
-    g = f[name]
-    weight_names = load_attributes_from_hdf5_group(g, 'weight_names')
-    weight_values = [np.asarray(g[weight_name]) for weight_name in weight_names]
-
-    for layer in index.get(name, []):
-      symbolic_weights = layer.weights
-      weight_values = preprocess_weights_for_loading(
-          layer, weight_values, original_keras_version, original_backend)
-      if len(weight_values) != len(symbolic_weights):
-        raise ValueError('Layer #' + str(k) + ' (named "' + layer.name +
-                         '") expects ' + str(len(symbolic_weights)) +
-                         ' weight(s), but the saved weights' + ' have ' +
-                         str(len(weight_values)) + ' element(s).')
-      # Set values.
-      for i in range(len(weight_values)):
-        if K.int_shape(symbolic_weights[i]) != weight_values[i].shape:
-          raise ValueError('Layer #' + str(k) +' (named "' + layer.name +
-                           '"), weight ' + str(symbolic_weights[i]) +
-                           ' has shape {}'.format(K.int_shape(
-                               symbolic_weights[i])) +
-                           ', but the saved weight has shape ' +
-                           str(weight_values[i].shape) + '.')
-
-        else:
-          weight_value_tuples.append((symbolic_weights[i], weight_values[i]))
-  K.batch_set_value(weight_value_tuples)
-
-
-def save_attributes_to_hdf5_group(group, name, data):
-  """Saves attributes (data) of the specified name into the HDF5 group.
-
-  This method deals with an inherent problem of HDF5 file which is not
-  able to store data larger than HDF5_OBJECT_HEADER_LIMIT bytes.
-
-  Arguments:
-      group: A pointer to a HDF5 group.
-      name: A name of the attributes to save.
-      data: Attributes data to store.
-
-  Raises:
-    RuntimeError: If any single attribute is too large to be saved.
-  """
-  # Check that no item in `data` is larger than `HDF5_OBJECT_HEADER_LIMIT`
-  # because in that case even chunking the array would not make the saving
-  # possible.
-  bad_attributes = [x for x in data if len(x) > HDF5_OBJECT_HEADER_LIMIT]
-
-  # Expecting this to never be true.
-  if bad_attributes:
-    raise RuntimeError('The following attributes cannot be saved to HDF5 '
-                       'file because they are larger than %d bytes: %s' %
-                       (HDF5_OBJECT_HEADER_LIMIT,
-                        ', '.join([x for x in bad_attributes])))
-
-  data_npy = np.asarray(data)
-
-  num_chunks = 1
-  chunked_data = np.array_split(data_npy, num_chunks)
-
-  # This will never loop forever thanks to the test above.
-  while any(x.nbytes > HDF5_OBJECT_HEADER_LIMIT for x in chunked_data):
-    num_chunks += 1
-    chunked_data = np.array_split(data_npy, num_chunks)
-
-  if num_chunks > 1:
-    for chunk_id, chunk_data in enumerate(chunked_data):
-      group.attrs['%s%d' % (name, chunk_id)] = chunk_data
-  else:
-    group.attrs[name] = data
-
-
-def load_attributes_from_hdf5_group(group, name):
-  """Loads attributes of the specified name from the HDF5 group.
-
-  This method deals with an inherent problem
-  of HDF5 file which is not able to store
-  data larger than HDF5_OBJECT_HEADER_LIMIT bytes.
-
-  Arguments:
-      group: A pointer to a HDF5 group.
-      name: A name of the attributes to load.
-
-  Returns:
-      data: Attributes data.
-  """
-  if name in group.attrs:
-    data = [n.decode('utf8') for n in group.attrs[name]]
-  else:
-    data = []
-    chunk_id = 0
-    while '%s%d' % (name, chunk_id) in group.attrs:
-      data.extend(
-          [n.decode('utf8') for n in group.attrs['%s%d' % (name, chunk_id)]])
-      chunk_id += 1
-  return data
+from tensorflow.python.keras.saving import *  # pylint: disable=wildcard-import
diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py
index 0b1743af38..5614c93842 100644
--- a/tensorflow/python/keras/engine/training.py
+++ b/tensorflow/python/keras/engine/training.py
@@ -40,6 +40,7 @@ from tensorflow.python.keras.engine import training_generator
 from tensorflow.python.keras.engine import training_utils
 from tensorflow.python.keras.engine.network import Network
 from tensorflow.python.keras.optimizer_v2 import optimizer_v2
+from tensorflow.python.keras.saving import saving_utils
 from tensorflow.python.keras.utils import data_utils
 from tensorflow.python.keras.utils.generic_utils import slice_arrays
 from tensorflow.python.keras.utils.losses_utils import squeeze_or_expand_dimensions
@@ -1702,7 +1703,7 @@ class Model(Network):
 
   @property
   def _default_save_signature(self):
-    return training_utils.trace_model_call(self)
+    return saving_utils.trace_model_call(self)
 
   def _set_sample_weight_attributes(self, sample_weight_mode,
                                     skip_target_weighing_indices):
diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py
index 949f1e400d..a4ee6c1ba5 100644
--- a/tensorflow/python/keras/engine/training_utils.py
+++ b/tensorflow/python/keras/engine/training_utils.py
@@ -29,12 +29,10 @@ 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.eager import context
-from tensorflow.python.eager import def_function
 from tensorflow.python.framework import constant_op
 from tensorflow.python.framework import errors
 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.keras import backend as K
 from tensorflow.python.keras import callbacks as cbks
@@ -1387,60 +1385,6 @@ def generic_output_names(outputs_list):
   return ['output_%d' % (i + 1) for i in range(len(outputs_list))]
 
 
-def trace_model_call(model, input_signature=None):
-  """Trace the model call to create a tf.function for exporting a Keras model.
-
-  Args:
-    model: A Keras model.
-    input_signature: optional, a list of tf.TensorSpec objects specifying the
-      inputs to the model.
-
-  Returns:
-    A tf.function wrapping the model's call function with input signatures set.
-
-  Raises:
-    ValueError: if input signature cannot be inferred from the model.
-  """
-  if input_signature is None:
-    if isinstance(model.call, def_function.PolymorphicFunction):
-      input_signature = model.call.input_signature
-
-  if input_signature is None:
-    try:
-      inputs = model.inputs
-      input_names = model.input_names
-    except AttributeError:
-      raise ValueError(
-          'Model {} cannot be saved because the input shapes have not been '
-          'set. Usually, input shapes are automatically determined from calling'
-          ' .fit() or .predict(). To manually set the shapes, call '
-          'model._set_inputs(inputs).'.format(model))
-    input_specs = []
-    for input_tensor, input_name in zip(inputs, input_names):
-      input_specs.append(tensor_spec.TensorSpec(
-          shape=input_tensor.shape, dtype=input_tensor.dtype,
-          name=input_name))
-    # The input signature of the call function is a list with one element, since
-    # all tensor inputs must be passed in as the first argument.
-    input_signature = [input_specs] if len(input_specs) > 1 else input_specs
-
-  # TODO(mdan): Should the model's call be autographed by default?
-  @def_function.function(input_signature=input_signature, autograph=False)
-  def _wrapped_model(*args):
-    """A concrete tf.function that wraps the model's call function."""
-    # When given a single input, Keras models will call the model on the tensor
-    # rather than a list consisting of the single tensor.
-    inputs = args[0] if len(input_signature) == 1 else list(args)
-    outputs_list = nest.flatten(model(inputs=inputs))
-    try:
-      output_names = model.output_names
-    except AttributeError:
-      output_names = generic_output_names(outputs_list)
-    return {name: output for name, output in zip(output_names, outputs_list)}
-
-  return _wrapped_model
-
-
 def set_run_eagerly_for_dict_structure(model, x):
   """Set model.run_eagerly to true if x is dict structure.
 
diff --git a/tensorflow/python/keras/engine/training_utils_test.py b/tensorflow/python/keras/engine/training_utils_test.py
index d0b2567b55..d8d8c58610 100644
--- a/tensorflow/python/keras/engine/training_utils_test.py
+++ b/tensorflow/python/keras/engine/training_utils_test.py
@@ -18,33 +18,18 @@ from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
 
-import os
-
 from absl.testing import parameterized
 import numpy as np
 
 
-from tensorflow.python import keras
-from tensorflow.python.client import session as session_lib
 from tensorflow.python.data.ops import dataset_ops
 from tensorflow.python.data.ops import readers
 from tensorflow.python.eager import context
-from tensorflow.python.eager import def_function
-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.keras import backend as K
 from tensorflow.python.keras import keras_parameterized
-from tensorflow.python.keras import testing_utils
 from tensorflow.python.keras.engine import training_utils
 from tensorflow.python.keras.utils import tf_utils
-from tensorflow.python.ops import array_ops
 from tensorflow.python.platform import test
-from tensorflow.python.saved_model import loader
-from tensorflow.python.saved_model import save as save_lib
-from tensorflow.python.saved_model import signature_constants
-from tensorflow.python.saved_model import tag_constants
 
 
 class ModelInputsTest(test.TestCase):
@@ -105,170 +90,6 @@ class ModelInputsTest(test.TestCase):
       self.assertTrue(tf_utils.is_symbolic_tensor(vals['b']))
 
 
-class TraceModelCallTest(keras_parameterized.TestCase):
-
-  def _assert_all_close(self, expected, actual):
-    if not context.executing_eagerly():
-      with self.cached_session() as sess:
-        K._initialize_variables(sess)
-        self.assertAllClose(expected, actual)
-    else:
-      self.assertAllClose(expected, actual)
-
-  @keras_parameterized.run_with_all_model_types
-  @keras_parameterized.run_all_keras_modes
-  def test_trace_model_outputs(self):
-    input_dim = 5 if testing_utils.get_model_type() == 'functional' else None
-    model = testing_utils.get_small_mlp(10, 3, input_dim)
-    inputs = array_ops.ones((8, 5))
-
-    if input_dim is None:
-      with self.assertRaisesRegexp(ValueError,
-                                   'input shapes have not been set'):
-        training_utils.trace_model_call(model)
-      model._set_inputs(inputs)
-
-    fn = training_utils.trace_model_call(model)
-    signature_outputs = fn(inputs)
-    expected_outputs = {model.output_names[0]: model(inputs)}
-
-    self._assert_all_close(expected_outputs, signature_outputs)
-
-  @keras_parameterized.run_with_all_model_types
-  @keras_parameterized.run_all_keras_modes
-  def test_trace_model_outputs_after_fitting(self):
-    input_dim = 5 if testing_utils.get_model_type() == 'functional' else None
-    model = testing_utils.get_small_mlp(10, 3, input_dim)
-    model.compile(optimizer='sgd', loss='mse')
-    model.fit(x=np.random.random((8, 5)),
-              y=np.random.random((8, 3)), epochs=2)
-
-    inputs = array_ops.ones((8, 5))
-
-    fn = training_utils.trace_model_call(model)
-    signature_outputs = fn(inputs)
-    expected_outputs = {model.output_names[0]: model(inputs)}
-
-    self._assert_all_close(expected_outputs, signature_outputs)
-
-  @keras_parameterized.run_with_all_model_types(exclude_models='sequential')
-  @keras_parameterized.run_all_keras_modes
-  def test_trace_multi_io_model_outputs(self):
-    input_dim = 5
-    num_classes = 3
-    num_classes_b = 4
-    input_a = keras.layers.Input(shape=(input_dim,), name='input_a')
-    input_b = keras.layers.Input(shape=(input_dim,), name='input_b')
-
-    dense = keras.layers.Dense(num_classes, name='dense')
-    dense2 = keras.layers.Dense(num_classes_b, name='dense2')
-    dropout = keras.layers.Dropout(0.5, name='dropout')
-    branch_a = [input_a, dense]
-    branch_b = [input_b, dense, dense2, dropout]
-
-    model = testing_utils.get_multi_io_model(branch_a, branch_b)
-
-    input_a_np = np.random.random((10, input_dim)).astype(np.float32)
-    input_b_np = np.random.random((10, input_dim)).astype(np.float32)
-
-    if testing_utils.get_model_type() == 'subclass':
-      with self.assertRaisesRegexp(ValueError,
-                                   'input shapes have not been set'):
-        training_utils.trace_model_call(model)
-
-    model.compile(optimizer='sgd', loss='mse')
-    model.fit(x=[np.random.random((8, input_dim)).astype(np.float32),
-                 np.random.random((8, input_dim)).astype(np.float32)],
-              y=[np.random.random((8, num_classes)).astype(np.float32),
-                 np.random.random((8, num_classes_b)).astype(np.float32)],
-              epochs=2)
-
-    fn = training_utils.trace_model_call(model)
-    signature_outputs = fn([input_a_np, input_b_np])
-    outputs = model([input_a_np, input_b_np])
-    expected_outputs = {model.output_names[0]: outputs[0],
-                        model.output_names[1]: outputs[1]}
-
-    self._assert_all_close(expected_outputs, signature_outputs)
-
-  @keras_parameterized.run_all_keras_modes
-  def test_specify_input_signature(self):
-    model = testing_utils.get_small_sequential_mlp(10, 3, None)
-    inputs = array_ops.ones((8, 5))
-
-    with self.assertRaisesRegexp(ValueError, 'input shapes have not been set'):
-      training_utils.trace_model_call(model)
-
-    fn = training_utils.trace_model_call(
-        model, [tensor_spec.TensorSpec(shape=[None, 5], dtype=dtypes.float32)])
-    signature_outputs = fn(inputs)
-    expected_outputs = {model.output_names[0]: model(inputs)}
-    self._assert_all_close(expected_outputs, signature_outputs)
-
-  @keras_parameterized.run_all_keras_modes
-  def test_subclassed_model_with_input_signature(self):
-
-    class Model(keras.Model):
-
-      def __init__(self):
-        super(Model, self).__init__()
-        self.dense = keras.layers.Dense(3, name='dense')
-
-      @def_function.function(
-          input_signature=[[tensor_spec.TensorSpec([None, 5], dtypes.float32),
-                            tensor_spec.TensorSpec([None], dtypes.float32)]],)
-      def call(self, inputs, *args):
-        x, y = inputs
-        return self.dense(x) + y
-
-    model = Model()
-    fn = training_utils.trace_model_call(model)
-    x = array_ops.ones((8, 5), dtype=dtypes.float32)
-    y = array_ops.ones((3,), dtype=dtypes.float32)
-    expected_outputs = {'output_1': model([x, y])}
-    signature_outputs = fn([x, y])
-    self._assert_all_close(expected_outputs, signature_outputs)
-
-
-def _import_and_infer(save_dir, inputs):
-  """Import a SavedModel into a TF 1.x-style graph and run `signature_key`."""
-  graph = ops.Graph()
-  with graph.as_default(), session_lib.Session() as session:
-    model = loader.load(session, [tag_constants.SERVING], save_dir)
-    signature = model.signature_def[
-        signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY]
-    assert set(inputs.keys()) == set(signature.inputs.keys())
-    feed_dict = {}
-    for arg_name in inputs.keys():
-      feed_dict[graph.get_tensor_by_name(signature.inputs[arg_name].name)] = (
-          inputs[arg_name])
-    output_dict = {}
-    for output_name, output_tensor_info in signature.outputs.items():
-      output_dict[output_name] = graph.get_tensor_by_name(
-          output_tensor_info.name)
-    return session.run(output_dict, feed_dict=feed_dict)
-
-
-class ModelSaveTest(keras_parameterized.TestCase):
-
-  @keras_parameterized.run_with_all_model_types
-  @keras_parameterized.run_all_keras_modes(always_skip_v1=True)
-  def test_model_save(self):
-    input_dim = 5
-    model = testing_utils.get_small_mlp(10, 3, input_dim)
-    inputs = array_ops.ones((8, 5))
-
-    if testing_utils.get_model_type() == 'subclass':
-      model._set_inputs(inputs)
-
-    save_dir = os.path.join(self.get_temp_dir(), 'saved_model')
-    save_lib.save(model, save_dir)
-
-    self.assertAllClose(
-        {model.output_names[0]: model.predict_on_batch(inputs)},
-        _import_and_infer(save_dir, {model.input_names[0]: np.ones((8, 5))}))
-
-
 class DatasetUtilsTest(test.TestCase, parameterized.TestCase):
 
   @parameterized.named_parameters(
diff --git a/tensorflow/python/keras/layers/cudnn_recurrent_test.py b/tensorflow/python/keras/layers/cudnn_recurrent_test.py
index 36f2d2fa38..1e305baefd 100644
--- a/tensorflow/python/keras/layers/cudnn_recurrent_test.py
+++ b/tensorflow/python/keras/layers/cudnn_recurrent_test.py
@@ -467,7 +467,7 @@ class CuDNNV1OnlyTest(keras_parameterized.TestCase):
 
     def assert_not_compatible(src, dest, message):
       with self.assertRaises(ValueError) as ex:
-        keras.engine.saving.preprocess_weights_for_loading(
+        keras.saving.preprocess_weights_for_loading(
             dest,
             get_layer_weights(src))
       self.assertIn(message, str(ex.exception))
diff --git a/tensorflow/python/keras/models.py b/tensorflow/python/keras/models.py
index 851de6e71f..9bc5aa2be5 100644
--- a/tensorflow/python/keras/models.py
+++ b/tensorflow/python/keras/models.py
@@ -22,13 +22,14 @@ from __future__ import print_function
 from tensorflow.python.keras import backend as K
 from tensorflow.python.keras import metrics as metrics_module
 from tensorflow.python.keras import optimizers
-from tensorflow.python.keras.engine import saving
 from tensorflow.python.keras.engine import sequential
 from tensorflow.python.keras.engine import training
 from tensorflow.python.keras.engine.base_layer import Layer
 from tensorflow.python.keras.engine.input_layer import Input
 from tensorflow.python.keras.engine.input_layer import InputLayer
 from tensorflow.python.keras.engine.network import Network
+from tensorflow.python.keras.saving import hdf5_format
+from tensorflow.python.keras.saving import model_config
 from tensorflow.python.keras.utils import generic_utils
 from tensorflow.python.keras.utils.generic_utils import CustomObjectScope
 from tensorflow.python.util import nest
@@ -38,11 +39,11 @@ from tensorflow.python.util.tf_export import keras_export
 # API entries importable from `keras.models`:
 Model = training.Model  # pylint: disable=invalid-name
 Sequential = sequential.Sequential  # pylint: disable=invalid-name
-save_model = saving.save_model
-load_model = saving.load_model
-model_from_config = saving.model_from_config
-model_from_yaml = saving.model_from_yaml
-model_from_json = saving.model_from_json
+save_model = hdf5_format.save_model
+load_model = hdf5_format.load_model
+model_from_config = model_config.model_from_config
+model_from_yaml = model_config.model_from_yaml
+model_from_json = model_config.model_from_json
 
 
 def _clone_layer(layer):
diff --git a/tensorflow/python/keras/saving/BUILD b/tensorflow/python/keras/saving/BUILD
deleted file mode 100644
index a78d58e876..0000000000
--- a/tensorflow/python/keras/saving/BUILD
+++ /dev/null
@@ -1,72 +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.
-# ==============================================================================
-
-# Description:
-#   Keras saving and loading libraries.
-load("//tensorflow:tensorflow.bzl", "py_test")
-
-package(default_visibility = ["//tensorflow:__subpackages__"])
-
-licenses(["notice"])  # Apache 2.0
-
-exports_files(["LICENSE"])
-
-py_library(
-    name = "saving",
-    srcs = ["__init__.py"],
-    deps = [":saved_model"],
-)
-
-py_library(
-    name = "saved_model",
-    srcs = [
-        "saved_model.py",
-        "saving_utils.py",
-    ],
-    srcs_version = "PY2AND3",
-    visibility = ["//visibility:public"],
-    deps = [
-        "//tensorflow/python:array_ops",
-        "//tensorflow/python:framework_ops",
-        "//tensorflow/python:lib",
-        "//tensorflow/python:metrics",
-        "//tensorflow/python:mode_keys",
-        "//tensorflow/python:platform",
-        "//tensorflow/python:saver",
-        "//tensorflow/python:util",
-        "//tensorflow/python/keras:engine",
-        "//tensorflow/python/saved_model",
-        "//tensorflow/python/saved_model/model_utils",
-    ],
-)
-
-py_test(
-    name = "saved_model_test",
-    size = "medium",
-    srcs = ["saved_model_test.py"],
-    srcs_version = "PY2AND3",
-    tags = [
-        "no_oss",  # TODO(b/119349471): Re-enable
-        "no_windows",
-    ],
-    deps = [
-        ":saved_model",
-        "//tensorflow/python:client_testlib",
-        "//tensorflow/python:mode_keys",
-        "//tensorflow/python/keras",
-        "//third_party/py/numpy",
-        "@absl_py//absl/testing:parameterized",
-    ],
-)
diff --git a/tensorflow/python/keras/saving/__init__.py b/tensorflow/python/keras/saving/__init__.py
index 8ff9f3b74e..bb4db68124 100644
--- a/tensorflow/python/keras/saving/__init__.py
+++ b/tensorflow/python/keras/saving/__init__.py
@@ -12,10 +12,24 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 # ==============================================================================
-"""Utils for saving a Keras Model or Estimator to the SavedModel format."""
+"""Utils for saving and loading Keras Models."""
 from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
 
+from tensorflow.python.keras.saving.hdf5_format import load_attributes_from_hdf5_group
+from tensorflow.python.keras.saving.hdf5_format import load_model
+from tensorflow.python.keras.saving.hdf5_format import load_weights_from_hdf5_group
+from tensorflow.python.keras.saving.hdf5_format import load_weights_from_hdf5_group_by_name
+from tensorflow.python.keras.saving.hdf5_format import preprocess_weights_for_loading
+from tensorflow.python.keras.saving.hdf5_format import save_attributes_to_hdf5_group
+from tensorflow.python.keras.saving.hdf5_format import save_model
+from tensorflow.python.keras.saving.hdf5_format import save_weights_to_hdf5_group
+from tensorflow.python.keras.saving.model_config import model_from_config
+from tensorflow.python.keras.saving.model_config import model_from_json
+from tensorflow.python.keras.saving.model_config import model_from_yaml
 from tensorflow.python.keras.saving.saved_model import export
 from tensorflow.python.keras.saving.saved_model import load_from_saved_model
+from tensorflow.python.keras.saving.saving_utils import trace_model_call
+
+
diff --git a/tensorflow/python/keras/saving/hdf5_format.py b/tensorflow/python/keras/saving/hdf5_format.py
new file mode 100644
index 0000000000..cb925c30a6
--- /dev/null
+++ b/tensorflow/python/keras/saving/hdf5_format.py
@@ -0,0 +1,895 @@
+# 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.
+# ==============================================================================
+# pylint: disable=protected-access
+"""Functions for saving and loading a Keras Model from HDF5 format.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import json
+import os
+
+import numpy as np
+from six.moves import zip  # pylint: disable=redefined-builtin
+
+from tensorflow.python.framework import ops
+from tensorflow.python.keras import backend as K
+from tensorflow.python.keras import optimizers
+from tensorflow.python.keras.saving import model_config as model_config_lib
+from tensorflow.python.keras.utils import conv_utils
+from tensorflow.python.keras.utils.io_utils import ask_to_proceed_with_overwrite
+from tensorflow.python.platform import tf_logging as logging
+from tensorflow.python.util import serialization
+from tensorflow.python.util.tf_export import keras_export
+
+# pylint: disable=g-import-not-at-top
+try:
+  import h5py
+  HDF5_OBJECT_HEADER_LIMIT = 64512
+except ImportError:
+  h5py = None
+# pylint: enable=g-import-not-at-top
+
+
+@keras_export('keras.models.save_model')
+def save_model(model, filepath, overwrite=True, include_optimizer=True):
+  """Saves a model to a HDF5 file.
+
+  The saved model contains:
+      - the model's configuration (topology)
+      - the model's weights
+      - the model's optimizer's state (if any)
+
+  Thus the saved model can be reinstantiated in
+  the exact same state, without any of the code
+  used for model definition or training.
+
+  Arguments:
+      model: Keras model instance to be saved.
+      filepath: One of the following:
+          - String, path where to save the model
+          - `h5py.File` object where to save the model
+      overwrite: Whether we should overwrite any existing
+          model at the target location, or instead
+          ask the user with a manual prompt.
+      include_optimizer: If True, save optimizer's state together.
+
+  Raises:
+      ImportError: if h5py is not available.
+  """
+
+  if h5py is None:
+    raise ImportError('`save_model` requires h5py.')
+
+  from tensorflow.python.keras import __version__ as keras_version  # pylint: disable=g-import-not-at-top
+
+  # TODO(psv) Add warning when we save models that contain non-serializable
+  # entities like metrics added using `add_metric` and losses added using
+  # `add_loss.`
+
+  if not isinstance(filepath, h5py.File):
+    # If file exists and should not be overwritten.
+    if not overwrite and os.path.isfile(filepath):
+      proceed = ask_to_proceed_with_overwrite(filepath)
+      if not proceed:
+        return
+
+    f = h5py.File(filepath, mode='w')
+    opened_new_file = True
+  else:
+    f = filepath
+    opened_new_file = False
+
+  try:
+    f.attrs['keras_version'] = str(keras_version).encode('utf8')
+    f.attrs['backend'] = K.backend().encode('utf8')
+    f.attrs['model_config'] = json.dumps(
+        {
+            'class_name': model.__class__.__name__,
+            'config': model.get_config()
+        },
+        default=serialization.get_json_type).encode('utf8')
+
+    model_weights_group = f.create_group('model_weights')
+    model_layers = model.layers
+    save_weights_to_hdf5_group(model_weights_group, model_layers)
+
+    if include_optimizer and model.optimizer:
+      if isinstance(model.optimizer, optimizers.TFOptimizer):
+        logging.warning(
+            'TensorFlow optimizers do not '
+            'make it possible to access '
+            'optimizer attributes or optimizer state '
+            'after instantiation. '
+            'As a result, we cannot save the optimizer '
+            'as part of the model save file.'
+            'You will have to compile your model again after loading it. '
+            'Prefer using a Keras optimizer instead '
+            '(see keras.io/optimizers).')
+      else:
+        f.attrs['training_config'] = json.dumps(
+            {
+                'optimizer_config': {
+                    'class_name': model.optimizer.__class__.__name__,
+                    'config': model.optimizer.get_config()
+                },
+                'loss': model.loss,
+                'metrics': model._compile_metrics,
+                'weighted_metrics': model._compile_weighted_metrics,
+                'sample_weight_mode': model.sample_weight_mode,
+                'loss_weights': model.loss_weights,
+            },
+            default=serialization.get_json_type).encode('utf8')
+
+        # Save optimizer weights.
+        symbolic_weights = getattr(model.optimizer, 'weights')
+        if symbolic_weights:
+          optimizer_weights_group = f.create_group('optimizer_weights')
+          weight_values = K.batch_get_value(symbolic_weights)
+          weight_names = []
+          for w, val in zip(symbolic_weights, weight_values):
+            name = str(w.name)
+            weight_names.append(name.encode('utf8'))
+          optimizer_weights_group.attrs['weight_names'] = weight_names
+          for name, val in zip(weight_names, weight_values):
+            param_dset = optimizer_weights_group.create_dataset(
+                name, val.shape, dtype=val.dtype)
+            if not val.shape:
+              # scalar
+              param_dset[()] = val
+            else:
+              param_dset[:] = val
+    f.flush()
+  finally:
+    if opened_new_file:
+      f.close()
+
+
+@keras_export('keras.models.load_model')
+def load_model(filepath, custom_objects=None, compile=True):  # pylint: disable=redefined-builtin
+  """Loads a model saved via `save_model`.
+
+  Arguments:
+      filepath: One of the following:
+          - String, path to the saved model
+          - `h5py.File` object from which to load the model
+      custom_objects: Optional dictionary mapping names
+          (strings) to custom classes or functions to be
+          considered during deserialization.
+      compile: Boolean, whether to compile the model
+          after loading.
+
+  Returns:
+      A Keras model instance. If an optimizer was found
+      as part of the saved model, the model is already
+      compiled. Otherwise, the model is uncompiled and
+      a warning will be displayed. When `compile` is set
+      to False, the compilation is omitted without any
+      warning.
+
+  Raises:
+      ImportError: if h5py is not available.
+      ValueError: In case of an invalid savefile.
+  """
+  if h5py is None:
+    raise ImportError('`load_model` requires h5py.')
+
+  if not custom_objects:
+    custom_objects = {}
+
+  def convert_custom_objects(obj):
+    """Handles custom object lookup.
+
+    Arguments:
+        obj: object, dict, or list.
+
+    Returns:
+        The same structure, where occurrences
+            of a custom object name have been replaced
+            with the custom object.
+    """
+    if isinstance(obj, list):
+      deserialized = []
+      for value in obj:
+        deserialized.append(convert_custom_objects(value))
+      return deserialized
+    if isinstance(obj, dict):
+      deserialized = {}
+      for key, value in obj.items():
+        deserialized[key] = convert_custom_objects(value)
+      return deserialized
+    if obj in custom_objects:
+      return custom_objects[obj]
+    return obj
+
+  opened_new_file = not isinstance(filepath, h5py.File)
+  if opened_new_file:
+    f = h5py.File(filepath, mode='r')
+  else:
+    f = filepath
+
+  model = None
+  try:
+    # instantiate model
+    model_config = f.attrs.get('model_config')
+    if model_config is None:
+      raise ValueError('No model found in config file.')
+    model_config = json.loads(model_config.decode('utf-8'))
+    model = model_config_lib.model_from_config(model_config,
+                                               custom_objects=custom_objects)
+
+    # set weights
+    load_weights_from_hdf5_group(f['model_weights'], model.layers)
+
+    if compile:
+      # instantiate optimizer
+      training_config = f.attrs.get('training_config')
+      if training_config is None:
+        logging.warning('No training configuration found in save file: '
+                        'the model was *not* compiled. Compile it manually.')
+        return model
+      training_config = json.loads(training_config.decode('utf-8'))
+      optimizer_config = training_config['optimizer_config']
+      optimizer = optimizers.deserialize(
+          optimizer_config, custom_objects=custom_objects)
+
+      # Recover loss functions and metrics.
+      loss = convert_custom_objects(training_config['loss'])
+      metrics = convert_custom_objects(training_config['metrics'])
+      weighted_metrics = convert_custom_objects(
+          training_config.get('weighted_metrics', None))
+      sample_weight_mode = training_config['sample_weight_mode']
+      loss_weights = training_config['loss_weights']
+
+      # Compile model.
+      model.compile(
+          optimizer=optimizer,
+          loss=loss,
+          metrics=metrics,
+          weighted_metrics=weighted_metrics,
+          loss_weights=loss_weights,
+          sample_weight_mode=sample_weight_mode)
+
+      # Set optimizer weights.
+      if 'optimizer_weights' in f:
+        # Build train function (to get weight updates).
+        # Models that aren't graph networks must wait until they are called
+        # with data to _make_train_function() and so can't load optimizer
+        # weights.
+        if model._is_graph_network:  # pylint: disable=protected-access
+          model._make_train_function()
+          optimizer_weights_group = f['optimizer_weights']
+          optimizer_weight_names = [
+              n.decode('utf8')
+              for n in optimizer_weights_group.attrs['weight_names']
+          ]
+          optimizer_weight_values = [
+              optimizer_weights_group[n] for n in optimizer_weight_names
+          ]
+          try:
+            model.optimizer.set_weights(optimizer_weight_values)
+          except ValueError:
+            logging.warning('Error in loading the saved optimizer '
+                            'state. As a result, your model is '
+                            'starting with a freshly initialized '
+                            'optimizer.')
+        else:
+          logging.warning('Sequential models without an `input_shape` '
+                          'passed to the first layer cannot reload their '
+                          'optimizer state. As a result, your model is'
+                          'starting with a freshly initialized optimizer.')
+
+  finally:
+    if opened_new_file:
+      f.close()
+  return model
+
+
+def preprocess_weights_for_loading(layer,
+                                   weights,
+                                   original_keras_version=None,
+                                   original_backend=None):
+  """Preprocess layer weights between different Keras formats.
+
+  Converts layers weights from Keras 1 format to Keras 2 and also weights of
+  CuDNN layers in Keras 2.
+
+  Arguments:
+      layer: Layer instance.
+      weights: List of weights values (Numpy arrays).
+      original_keras_version: Keras version for the weights, as a string.
+      original_backend: Keras backend the weights were trained with,
+          as a string.
+
+  Returns:
+      A list of weights values (Numpy arrays).
+  """
+  def convert_nested_bidirectional(weights):
+    """Converts layers nested in `Bidirectional` wrapper.
+
+    This function uses `preprocess_weights_for_loading()` for converting
+    layers.
+
+    Arguments:
+        weights: List of weights values (Numpy arrays).
+
+    Returns:
+        A list of weights values (Numpy arrays).
+    """
+    num_weights_per_layer = len(weights) // 2
+    forward_weights = preprocess_weights_for_loading(
+        layer.forward_layer, weights[:num_weights_per_layer],
+        original_keras_version, original_backend)
+    backward_weights = preprocess_weights_for_loading(
+        layer.backward_layer, weights[num_weights_per_layer:],
+        original_keras_version, original_backend)
+    return forward_weights + backward_weights
+
+  def convert_nested_time_distributed(weights):
+    """Converts layers nested in `TimeDistributed` wrapper.
+
+    This function uses `preprocess_weights_for_loading()` for converting nested
+    layers.
+
+    Arguments:
+        weights: List of weights values (Numpy arrays).
+
+    Returns:
+        A list of weights values (Numpy arrays).
+    """
+    return preprocess_weights_for_loading(
+        layer.layer, weights, original_keras_version, original_backend)
+
+  def convert_nested_model(weights):
+    """Converts layers nested in `Model` or `Sequential`.
+
+    This function uses `preprocess_weights_for_loading()` for converting nested
+    layers.
+
+    Arguments:
+        weights: List of weights values (Numpy arrays).
+
+    Returns:
+        A list of weights values (Numpy arrays).
+    """
+    new_weights = []
+    # trainable weights
+    for sublayer in layer.layers:
+      num_weights = len(sublayer.trainable_weights)
+      if num_weights > 0:
+        new_weights.extend(preprocess_weights_for_loading(
+            layer=sublayer,
+            weights=weights[:num_weights],
+            original_keras_version=original_keras_version,
+            original_backend=original_backend))
+        weights = weights[num_weights:]
+
+    # non-trainable weights
+    for sublayer in layer.layers:
+      num_weights = len([l for l in sublayer.weights
+                         if l not in sublayer.trainable_weights])
+      if num_weights > 0:
+        new_weights.extend(preprocess_weights_for_loading(
+            layer=sublayer,
+            weights=weights[:num_weights],
+            original_keras_version=original_keras_version,
+            original_backend=original_backend))
+        weights = weights[num_weights:]
+    return new_weights
+
+  # Convert layers nested in Bidirectional/Model/Sequential.
+  # Both transformation should be ran for both Keras 1->2 conversion
+  # and for conversion of CuDNN layers.
+  if layer.__class__.__name__ == 'Bidirectional':
+    weights = convert_nested_bidirectional(weights)
+  if layer.__class__.__name__ == 'TimeDistributed':
+    weights = convert_nested_time_distributed(weights)
+  elif layer.__class__.__name__ in ['Model', 'Sequential']:
+    weights = convert_nested_model(weights)
+
+  if original_keras_version == '1':
+    if layer.__class__.__name__ == 'TimeDistributed':
+      weights = preprocess_weights_for_loading(
+          layer.layer, weights, original_keras_version, original_backend)
+
+    if layer.__class__.__name__ == 'Conv1D':
+      shape = weights[0].shape
+      # Handle Keras 1.1 format
+      if shape[:2] != (layer.kernel_size[0], 1) or shape[3] != layer.filters:
+        # Legacy shape:
+        # (filters, input_dim, filter_length, 1)
+        assert shape[0] == layer.filters and shape[2:] == (layer.kernel_size[0],
+                                                           1)
+        weights[0] = np.transpose(weights[0], (2, 3, 1, 0))
+      weights[0] = weights[0][:, 0, :, :]
+
+    if layer.__class__.__name__ == 'Conv2D':
+      if layer.data_format == 'channels_first':
+        # old: (filters, stack_size, kernel_rows, kernel_cols)
+        # new: (kernel_rows, kernel_cols, stack_size, filters)
+        weights[0] = np.transpose(weights[0], (2, 3, 1, 0))
+
+    if layer.__class__.__name__ == 'Conv2DTranspose':
+      if layer.data_format == 'channels_last':
+        # old: (kernel_rows, kernel_cols, stack_size, filters)
+        # new: (kernel_rows, kernel_cols, filters, stack_size)
+        weights[0] = np.transpose(weights[0], (0, 1, 3, 2))
+      if layer.data_format == 'channels_first':
+        # old: (filters, stack_size, kernel_rows, kernel_cols)
+        # new: (kernel_rows, kernel_cols, filters, stack_size)
+        weights[0] = np.transpose(weights[0], (2, 3, 0, 1))
+
+    if layer.__class__.__name__ == 'Conv3D':
+      if layer.data_format == 'channels_first':
+        # old: (filters, stack_size, ...)
+        # new: (..., stack_size, filters)
+        weights[0] = np.transpose(weights[0], (2, 3, 4, 1, 0))
+
+    if layer.__class__.__name__ == 'GRU':
+      if len(weights) == 9:
+        kernel = np.concatenate([weights[0], weights[3], weights[6]], axis=-1)
+        recurrent_kernel = np.concatenate(
+            [weights[1], weights[4], weights[7]], axis=-1)
+        bias = np.concatenate([weights[2], weights[5], weights[8]], axis=-1)
+        weights = [kernel, recurrent_kernel, bias]
+
+    if layer.__class__.__name__ == 'LSTM':
+      if len(weights) == 12:
+        # old: i, c, f, o
+        # new: i, f, c, o
+        kernel = np.concatenate(
+            [weights[0], weights[6], weights[3], weights[9]], axis=-1)
+        recurrent_kernel = np.concatenate(
+            [weights[1], weights[7], weights[4], weights[10]], axis=-1)
+        bias = np.concatenate(
+            [weights[2], weights[8], weights[5], weights[11]], axis=-1)
+        weights = [kernel, recurrent_kernel, bias]
+
+    if layer.__class__.__name__ == 'ConvLSTM2D':
+      if len(weights) == 12:
+        kernel = np.concatenate(
+            [weights[0], weights[6], weights[3], weights[9]], axis=-1)
+        recurrent_kernel = np.concatenate(
+            [weights[1], weights[7], weights[4], weights[10]], axis=-1)
+        bias = np.concatenate(
+            [weights[2], weights[8], weights[5], weights[11]], axis=-1)
+        if layer.data_format == 'channels_first':
+          # old: (filters, stack_size, kernel_rows, kernel_cols)
+          # new: (kernel_rows, kernel_cols, stack_size, filters)
+          kernel = np.transpose(kernel, (2, 3, 1, 0))
+          recurrent_kernel = np.transpose(recurrent_kernel, (2, 3, 1, 0))
+        weights = [kernel, recurrent_kernel, bias]
+
+  conv_layers = ['Conv1D', 'Conv2D', 'Conv3D', 'Conv2DTranspose', 'ConvLSTM2D']
+  if layer.__class__.__name__ in conv_layers:
+    if original_backend == 'theano':
+      weights[0] = conv_utils.convert_kernel(weights[0])
+      if layer.__class__.__name__ == 'ConvLSTM2D':
+        weights[1] = conv_utils.convert_kernel(weights[1])
+    if K.int_shape(layer.weights[0]) != weights[0].shape:
+      weights[0] = np.transpose(weights[0], (3, 2, 0, 1))
+      if layer.__class__.__name__ == 'ConvLSTM2D':
+        weights[1] = np.transpose(weights[1], (3, 2, 0, 1))
+
+  # convert CuDNN layers
+  return _convert_rnn_weights(layer, weights)
+
+
+def _convert_rnn_weights(layer, weights):
+  """Converts weights for RNN layers between native and CuDNN format.
+
+  Input kernels for each gate are transposed and converted between Fortran
+  and C layout, recurrent kernels are transposed. For LSTM biases are summed/
+  split in half, for GRU biases are reshaped.
+
+  Weights can be converted in both directions between `LSTM` and`CuDNNSLTM`
+  and between `CuDNNGRU` and `GRU(reset_after=True)`. Default `GRU` is not
+  compatible with `CuDNNGRU`.
+
+  For missing biases in `LSTM`/`GRU` (`use_bias=False`) no conversion is made.
+
+  Arguments:
+      layer: Target layer instance.
+      weights: List of source weights values (input kernels, recurrent
+          kernels, [biases]) (Numpy arrays).
+
+  Returns:
+      A list of converted weights values (Numpy arrays).
+
+  Raises:
+      ValueError: for incompatible GRU layer/weights or incompatible biases
+  """
+
+  def transform_kernels(kernels, func, n_gates):
+    """Transforms kernel for each gate separately using given function.
+
+    Arguments:
+        kernels: Stacked array of kernels for individual gates.
+        func: Function applied to kernel of each gate.
+        n_gates: Number of gates (4 for LSTM, 3 for GRU).
+
+    Returns:
+        Stacked array of transformed kernels.
+    """
+    return np.hstack([func(k) for k in np.hsplit(kernels, n_gates)])
+
+  def transpose_input(from_cudnn):
+    """Makes a function that transforms input kernels from/to CuDNN format.
+
+    It keeps the shape, but changes between the layout (Fortran/C). Eg.:
+
+    ```
+    Keras                 CuDNN
+    [[0, 1, 2],  <--->  [[0, 2, 4],
+     [3, 4, 5]]          [1, 3, 5]]
+    ```
+
+    It can be passed to `transform_kernels()`.
+
+    Arguments:
+        from_cudnn: `True` if source weights are in CuDNN format, `False`
+            if they're in plain Keras format.
+
+    Returns:
+        Function that converts input kernel to the other format.
+    """
+    order = 'F' if from_cudnn else 'C'
+
+    def transform(kernel):
+      return kernel.T.reshape(kernel.shape, order=order)
+
+    return transform
+
+  target_class = layer.__class__.__name__
+
+  # convert the weights between CuDNNLSTM and LSTM
+  if target_class in ['LSTM', 'CuDNNLSTM'] and len(weights) == 3:
+    # determine if we're loading a CuDNNLSTM layer
+    # from the number of bias weights:
+    # CuDNNLSTM has (units * 8) weights; while LSTM has (units * 4)
+    # if there's no bias weight in the file, skip this conversion
+    units = weights[1].shape[0]
+    bias_shape = weights[2].shape
+    n_gates = 4
+
+    if bias_shape == (2 * units * n_gates,):
+      source = 'CuDNNLSTM'
+    elif bias_shape == (units * n_gates,):
+      source = 'LSTM'
+    else:
+      raise ValueError('Invalid bias shape: ' + str(bias_shape))
+
+    def convert_lstm_weights(weights, from_cudnn=True):
+      """Converts the weights between CuDNNLSTM and LSTM.
+
+      Arguments:
+        weights: Original weights.
+        from_cudnn: Indicates whether original weights are from CuDNN layer.
+
+      Returns:
+        Updated weights compatible with LSTM.
+      """
+
+      # Transpose (and reshape) input and recurrent kernels
+      kernels = transform_kernels(weights[0], transpose_input(from_cudnn),
+                                  n_gates)
+      recurrent_kernels = transform_kernels(weights[1], lambda k: k.T, n_gates)
+      if from_cudnn:
+        # merge input and recurrent biases into a single set
+        biases = np.sum(np.split(weights[2], 2, axis=0), axis=0)
+      else:
+        # Split single set of biases evenly to two sets. The way of
+        # splitting doesn't matter as long as the two sets sum is kept.
+        biases = np.tile(0.5 * weights[2], 2)
+      return [kernels, recurrent_kernels, biases]
+
+    if source != target_class:
+      weights = convert_lstm_weights(weights, from_cudnn=source == 'CuDNNLSTM')
+
+  # convert the weights between CuDNNGRU and GRU(reset_after=True)
+  if target_class in ['GRU', 'CuDNNGRU'] and len(weights) == 3:
+    # We can determine the source of the weights from the shape of the bias.
+    # If there is no bias we skip the conversion since
+    # CuDNNGRU always has biases.
+
+    units = weights[1].shape[0]
+    bias_shape = weights[2].shape
+    n_gates = 3
+
+    def convert_gru_weights(weights, from_cudnn=True):
+      """Converts the weights between CuDNNGRU and GRU.
+
+      Arguments:
+        weights: Original weights.
+        from_cudnn: Indicates whether original weights are from CuDNN layer.
+
+      Returns:
+        Updated weights compatible with GRU.
+      """
+
+      kernels = transform_kernels(weights[0], transpose_input(from_cudnn),
+                                  n_gates)
+      recurrent_kernels = transform_kernels(weights[1], lambda k: k.T, n_gates)
+      biases = np.array(weights[2]).reshape((2, -1) if from_cudnn else -1)
+      return [kernels, recurrent_kernels, biases]
+
+    if bias_shape == (2 * units * n_gates,):
+      source = 'CuDNNGRU'
+    elif bias_shape == (2, units * n_gates):
+      source = 'GRU(reset_after=True)'
+    elif bias_shape == (units * n_gates,):
+      source = 'GRU(reset_after=False)'
+    else:
+      raise ValueError('Invalid bias shape: ' + str(bias_shape))
+
+    if target_class == 'CuDNNGRU':
+      target = 'CuDNNGRU'
+    elif layer.reset_after:
+      target = 'GRU(reset_after=True)'
+    else:
+      target = 'GRU(reset_after=False)'
+
+    # only convert between different types
+    if source != target:
+      types = (source, target)
+      if 'GRU(reset_after=False)' in types:
+        raise ValueError('%s is not compatible with %s' % types)
+      if source == 'CuDNNGRU':
+        weights = convert_gru_weights(weights, from_cudnn=True)
+      elif source == 'GRU(reset_after=True)':
+        weights = convert_gru_weights(weights, from_cudnn=False)
+
+  return weights
+
+
+def save_weights_to_hdf5_group(f, layers):
+  """Saves the weights of a list of layers to a HDF5 group.
+
+  Arguments:
+      f: HDF5 group.
+      layers: List of layer instances.
+  """
+  from tensorflow.python.keras import __version__ as keras_version  # pylint: disable=g-import-not-at-top
+
+  save_attributes_to_hdf5_group(
+      f, 'layer_names', [layer.name.encode('utf8') for layer in layers])
+  f.attrs['backend'] = K.backend().encode('utf8')
+  f.attrs['keras_version'] = str(keras_version).encode('utf8')
+
+  # On TPUs, modifying the graph between session.runs() triggers some expensive
+  # recompilation overhead. To avoid this, we build up the full set of tensors
+  # to save before fetching weights, thus only modifying the graph once.
+  layer_weights_dict = {}
+  for layer in layers:
+    layer_weights_dict[layer.name] = [ops.convert_to_tensor(w)
+                                      for w in layer.weights]
+
+  for layer in layers:
+    g = f.create_group(layer.name)
+    symbolic_weights = layer_weights_dict[layer.name]
+    weight_values = K.batch_get_value(symbolic_weights)
+    weight_names = []
+    for i, (w, val) in enumerate(zip(symbolic_weights, weight_values)):
+      if hasattr(w, 'name') and w.name:
+        name = str(w.name)
+      else:
+        name = 'param_' + str(i)
+      weight_names.append(name.encode('utf8'))
+    save_attributes_to_hdf5_group(g, 'weight_names', weight_names)
+    for name, val in zip(weight_names, weight_values):
+      param_dset = g.create_dataset(name, val.shape, dtype=val.dtype)
+      if not val.shape:
+        # scalar
+        param_dset[()] = val
+      else:
+        param_dset[:] = val
+
+
+def load_weights_from_hdf5_group(f, layers):
+  """Implements topological (order-based) weight loading.
+
+  Arguments:
+      f: A pointer to a HDF5 group.
+      layers: a list of target layers.
+
+  Raises:
+      ValueError: in case of mismatch between provided layers
+          and weights file.
+  """
+  if 'keras_version' in f.attrs:
+    original_keras_version = f.attrs['keras_version'].decode('utf8')
+  else:
+    original_keras_version = '1'
+  if 'backend' in f.attrs:
+    original_backend = f.attrs['backend'].decode('utf8')
+  else:
+    original_backend = None
+
+  filtered_layers = []
+  for layer in layers:
+    weights = layer.weights
+    if weights:
+      filtered_layers.append(layer)
+
+  layer_names = load_attributes_from_hdf5_group(f, 'layer_names')
+  filtered_layer_names = []
+  for name in layer_names:
+    g = f[name]
+    weight_names = load_attributes_from_hdf5_group(g, 'weight_names')
+    if weight_names:
+      filtered_layer_names.append(name)
+  layer_names = filtered_layer_names
+  if len(layer_names) != len(filtered_layers):
+    raise ValueError('You are trying to load a weight file '
+                     'containing ' + str(len(layer_names)) +
+                     ' layers into a model with ' + str(len(filtered_layers)) +
+                     ' layers.')
+
+  # We batch weight value assignments in a single backend call
+  # which provides a speedup in TensorFlow.
+  weight_value_tuples = []
+  for k, name in enumerate(layer_names):
+    g = f[name]
+    weight_names = load_attributes_from_hdf5_group(g, 'weight_names')
+    weight_values = [np.asarray(g[weight_name]) for weight_name in weight_names]
+    layer = filtered_layers[k]
+    symbolic_weights = layer.weights
+    weight_values = preprocess_weights_for_loading(
+        layer, weight_values, original_keras_version, original_backend)
+    if len(weight_values) != len(symbolic_weights):
+      raise ValueError('Layer #' + str(k) + ' (named "' + layer.name +
+                       '" in the current model) was found to '
+                       'correspond to layer ' + name + ' in the save file. '
+                       'However the new layer ' + layer.name + ' expects ' +
+                       str(len(symbolic_weights)) +
+                       ' weights, but the saved weights have ' +
+                       str(len(weight_values)) + ' elements.')
+    weight_value_tuples += zip(symbolic_weights, weight_values)
+  K.batch_set_value(weight_value_tuples)
+
+
+def load_weights_from_hdf5_group_by_name(f, layers):
+  """Implements name-based weight loading.
+
+  (instead of topological weight loading).
+
+  Layers that have no matching name are skipped.
+
+  Arguments:
+      f: A pointer to a HDF5 group.
+      layers: a list of target layers.
+
+  Raises:
+      ValueError: in case of mismatch between provided layers
+          and weights file.
+  """
+  if 'keras_version' in f.attrs:
+    original_keras_version = f.attrs['keras_version'].decode('utf8')
+  else:
+    original_keras_version = '1'
+  if 'backend' in f.attrs:
+    original_backend = f.attrs['backend'].decode('utf8')
+  else:
+    original_backend = None
+
+  # New file format.
+  layer_names = load_attributes_from_hdf5_group(f, 'layer_names')
+
+  # Reverse index of layer name to list of layers with name.
+  index = {}
+  for layer in layers:
+    if layer.name:
+      index.setdefault(layer.name, []).append(layer)
+
+  # We batch weight value assignments in a single backend call
+  # which provides a speedup in TensorFlow.
+  weight_value_tuples = []
+  for k, name in enumerate(layer_names):
+    g = f[name]
+    weight_names = load_attributes_from_hdf5_group(g, 'weight_names')
+    weight_values = [np.asarray(g[weight_name]) for weight_name in weight_names]
+
+    for layer in index.get(name, []):
+      symbolic_weights = layer.weights
+      weight_values = preprocess_weights_for_loading(
+          layer, weight_values, original_keras_version, original_backend)
+      if len(weight_values) != len(symbolic_weights):
+        raise ValueError('Layer #' + str(k) + ' (named "' + layer.name +
+                         '") expects ' + str(len(symbolic_weights)) +
+                         ' weight(s), but the saved weights' + ' have ' +
+                         str(len(weight_values)) + ' element(s).')
+      # Set values.
+      for i in range(len(weight_values)):
+        if K.int_shape(symbolic_weights[i]) != weight_values[i].shape:
+          raise ValueError('Layer #' + str(k) +' (named "' + layer.name +
+                           '"), weight ' + str(symbolic_weights[i]) +
+                           ' has shape {}'.format(K.int_shape(
+                               symbolic_weights[i])) +
+                           ', but the saved weight has shape ' +
+                           str(weight_values[i].shape) + '.')
+
+        else:
+          weight_value_tuples.append((symbolic_weights[i], weight_values[i]))
+  K.batch_set_value(weight_value_tuples)
+
+
+def save_attributes_to_hdf5_group(group, name, data):
+  """Saves attributes (data) of the specified name into the HDF5 group.
+
+  This method deals with an inherent problem of HDF5 file which is not
+  able to store data larger than HDF5_OBJECT_HEADER_LIMIT bytes.
+
+  Arguments:
+      group: A pointer to a HDF5 group.
+      name: A name of the attributes to save.
+      data: Attributes data to store.
+
+  Raises:
+    RuntimeError: If any single attribute is too large to be saved.
+  """
+  # Check that no item in `data` is larger than `HDF5_OBJECT_HEADER_LIMIT`
+  # because in that case even chunking the array would not make the saving
+  # possible.
+  bad_attributes = [x for x in data if len(x) > HDF5_OBJECT_HEADER_LIMIT]
+
+  # Expecting this to never be true.
+  if bad_attributes:
+    raise RuntimeError('The following attributes cannot be saved to HDF5 '
+                       'file because they are larger than %d bytes: %s' %
+                       (HDF5_OBJECT_HEADER_LIMIT,
+                        ', '.join([x for x in bad_attributes])))
+
+  data_npy = np.asarray(data)
+
+  num_chunks = 1
+  chunked_data = np.array_split(data_npy, num_chunks)
+
+  # This will never loop forever thanks to the test above.
+  while any(x.nbytes > HDF5_OBJECT_HEADER_LIMIT for x in chunked_data):
+    num_chunks += 1
+    chunked_data = np.array_split(data_npy, num_chunks)
+
+  if num_chunks > 1:
+    for chunk_id, chunk_data in enumerate(chunked_data):
+      group.attrs['%s%d' % (name, chunk_id)] = chunk_data
+  else:
+    group.attrs[name] = data
+
+
+def load_attributes_from_hdf5_group(group, name):
+  """Loads attributes of the specified name from the HDF5 group.
+
+  This method deals with an inherent problem
+  of HDF5 file which is not able to store
+  data larger than HDF5_OBJECT_HEADER_LIMIT bytes.
+
+  Arguments:
+      group: A pointer to a HDF5 group.
+      name: A name of the attributes to load.
+
+  Returns:
+      data: Attributes data.
+  """
+  if name in group.attrs:
+    data = [n.decode('utf8') for n in group.attrs[name]]
+  else:
+    data = []
+    chunk_id = 0
+    while '%s%d' % (name, chunk_id) in group.attrs:
+      data.extend(
+          [n.decode('utf8') for n in group.attrs['%s%d' % (name, chunk_id)]])
+      chunk_id += 1
+  return data
diff --git a/tensorflow/python/keras/engine/saving_test.py b/tensorflow/python/keras/saving/hdf5_format_test.py
similarity index 98%
rename from tensorflow/python/keras/engine/saving_test.py
rename to tensorflow/python/keras/saving/hdf5_format_test.py
index 92fac6f242..3b496e70c1 100644
--- a/tensorflow/python/keras/engine/saving_test.py
+++ b/tensorflow/python/keras/saving/hdf5_format_test.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #,============================================================================
-"""Tests for model saving."""
+"""Tests for model saving in the HDF5 format."""
 
 from __future__ import absolute_import
 from __future__ import division
@@ -31,8 +31,8 @@ from tensorflow.python.framework import dtypes
 from tensorflow.python.framework import ops
 from tensorflow.python.framework import test_util
 from tensorflow.python.keras import optimizers
-from tensorflow.python.keras.engine import saving
 from tensorflow.python.keras.engine import training
+from tensorflow.python.keras.saving import hdf5_format
 from tensorflow.python.lib.io import file_io
 from tensorflow.python.ops import array_ops
 from tensorflow.python.ops import random_ops
@@ -174,17 +174,17 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase):
     ]
     for layer, weights, input_shape in cases:
       layer.build(input_shape)
-      _ = keras.engine.saving.preprocess_weights_for_loading(
+      _ = hdf5_format.preprocess_weights_for_loading(
           layer, weights, original_keras_version='1')
 
     model = keras.models.Sequential([keras.layers.Dense(2, input_dim=2)])
-    _ = keras.engine.saving.preprocess_weights_for_loading(
+    _ = hdf5_format.preprocess_weights_for_loading(
         model, model.weights, original_keras_version='1')
 
     x = keras.Input((2,))
     y = keras.layers.Dense(2)(x)
     model = keras.models.Model(x, y)
-    _ = keras.engine.saving.preprocess_weights_for_loading(
+    _ = hdf5_format.preprocess_weights_for_loading(
         model, model.weights, original_keras_version='1')
 
   @parameterized.named_parameters(
@@ -215,7 +215,7 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase):
       layer = layer_class(**layer_args)
       layer.build(input_shape=layer_args.get('input_shape'))
       weights1 = layer.get_weights()
-      weights2 = keras.engine.saving.preprocess_weights_for_loading(
+      weights2 = hdf5_format.preprocess_weights_for_loading(
           layer, weights1)
       _ = [
           self.assertAllClose(x, y, rtol=1e-05)
@@ -274,7 +274,7 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase):
                         metrics=[keras.metrics.categorical_accuracy])
 
       f_ref_model = h5py.File(h5_path, 'w')
-      saving.save_weights_to_hdf5_group(f_ref_model, ref_model.layers)
+      hdf5_format.save_weights_to_hdf5_group(f_ref_model, ref_model.layers)
 
       f_model = h5py.File(h5_path, 'r')
       model = keras.models.Sequential()
@@ -288,7 +288,7 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase):
                                  r'Layer #0 \(named \"d1\"\) expects 1 '
                                  r'weight\(s\), but the saved weights have 2 '
                                  r'element\(s\)\.'):
-      saving.load_weights_from_hdf5_group_by_name(f_model, model.layers)
+      hdf5_format.load_weights_from_hdf5_group_by_name(f_model, model.layers)
 
   @test_util.run_deprecated_v1
   def test_sequential_weight_loading_group_name_with_incorrect_shape(self):
@@ -312,7 +312,7 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase):
                         metrics=[keras.metrics.categorical_accuracy])
 
       f_ref_model = h5py.File(h5_path, 'w')
-      saving.save_weights_to_hdf5_group(f_ref_model, ref_model.layers)
+      hdf5_format.save_weights_to_hdf5_group(f_ref_model, ref_model.layers)
 
       f_model = h5py.File(h5_path, 'r')
       model = keras.models.Sequential()
@@ -328,7 +328,7 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase):
                                    r'shape=\(3, 10\) dtype=float32> has '
                                    r'shape \(3, 10\), but the saved weight has '
                                    r'shape \(3, 5\)\.'):
-        saving.load_weights_from_hdf5_group_by_name(f_model, model.layers)
+        hdf5_format.load_weights_from_hdf5_group_by_name(f_model, model.layers)
 
 
 class TestWholeModelSaving(test.TestCase):
diff --git a/tensorflow/python/keras/saving/model_config.py b/tensorflow/python/keras/saving/model_config.py
new file mode 100644
index 0000000000..7f59ecd7df
--- /dev/null
+++ b/tensorflow/python/keras/saving/model_config.py
@@ -0,0 +1,96 @@
+# 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.
+# ==============================================================================
+# pylint: disable=protected-access
+"""Functions that save the model's config into different formats.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import json
+
+from tensorflow.python.util.tf_export import keras_export
+
+# pylint: disable=g-import-not-at-top
+try:
+  import yaml
+except ImportError:
+  yaml = None
+# pylint: enable=g-import-not-at-top
+
+
+@keras_export('keras.models.model_from_config')
+def model_from_config(config, custom_objects=None):
+  """Instantiates a Keras model from its config.
+
+  Arguments:
+      config: Configuration dictionary.
+      custom_objects: Optional dictionary mapping names
+          (strings) to custom classes or functions to be
+          considered during deserialization.
+
+  Returns:
+      A Keras model instance (uncompiled).
+
+  Raises:
+      TypeError: if `config` is not a dictionary.
+  """
+  if isinstance(config, list):
+    raise TypeError('`model_from_config` expects a dictionary, not a list. '
+                    'Maybe you meant to use '
+                    '`Sequential.from_config(config)`?')
+  from tensorflow.python.keras.layers import deserialize  # pylint: disable=g-import-not-at-top
+  return deserialize(config, custom_objects=custom_objects)
+
+
+@keras_export('keras.models.model_from_yaml')
+def model_from_yaml(yaml_string, custom_objects=None):
+  """Parses a yaml model configuration file and returns a model instance.
+
+  Arguments:
+      yaml_string: YAML string encoding a model configuration.
+      custom_objects: Optional dictionary mapping names
+          (strings) to custom classes or functions to be
+          considered during deserialization.
+
+  Returns:
+      A Keras model instance (uncompiled).
+
+  Raises:
+      ImportError: if yaml module is not found.
+  """
+  if yaml is None:
+    raise ImportError('Requires yaml module installed (`pip install pyyaml`).')
+  config = yaml.load(yaml_string)
+  from tensorflow.python.keras.layers import deserialize  # pylint: disable=g-import-not-at-top
+  return deserialize(config, custom_objects=custom_objects)
+
+
+@keras_export('keras.models.model_from_json')
+def model_from_json(json_string, custom_objects=None):
+  """Parses a JSON model configuration file and returns a model instance.
+
+  Arguments:
+      json_string: JSON string encoding a model configuration.
+      custom_objects: Optional dictionary mapping names
+          (strings) to custom classes or functions to be
+          considered during deserialization.
+
+  Returns:
+      A Keras model instance (uncompiled).
+  """
+  config = json.loads(json_string)
+  from tensorflow.python.keras.layers import deserialize  # pylint: disable=g-import-not-at-top
+  return deserialize(config, custom_objects=custom_objects)
diff --git a/tensorflow/python/keras/saving/saved_model.py b/tensorflow/python/keras/saving/saved_model.py
index 5de6d2657f..06e02acf32 100644
--- a/tensorflow/python/keras/saving/saved_model.py
+++ b/tensorflow/python/keras/saving/saved_model.py
@@ -24,12 +24,8 @@ import six
 from tensorflow.python.client import session
 from tensorflow.python.framework import ops
 from tensorflow.python.keras import backend as K
-from tensorflow.python.keras import models as models_lib
 from tensorflow.python.keras import optimizers
-from tensorflow.python.keras.engine import sequential
-from tensorflow.python.keras.engine import training_utils
-from tensorflow.python.keras.metrics import Metric
-from tensorflow.python.keras.models import model_from_json
+from tensorflow.python.keras.saving import model_from_json
 from tensorflow.python.keras.saving import saving_utils
 from tensorflow.python.lib.io import file_io
 from tensorflow.python.ops import variables
@@ -135,7 +131,7 @@ def export(
   if serving_only:
     save_lib.save(
         model, export_dir,
-        signatures=training_utils.trace_model_call(model, input_signature))
+        signatures=saving_utils.trace_model_call(model, input_signature))
   else:
     _save_v1_format(model, export_dir, custom_objects, as_text, input_signature)
 
@@ -167,6 +163,8 @@ def _export_model_variables(model, saved_model_path):
 
 def _save_v1_format(model, path, custom_objects, as_text, input_signature):
   """Exports model to v1 SavedModel format."""
+  from tensorflow.python.keras.engine import sequential  # pylint: disable=g-import-not-at-top
+
   if not model._is_graph_network:
     if isinstance(model, sequential.Sequential):
       # If input shape is not directly set in the model, the exported model
@@ -250,6 +248,7 @@ def _export_mode(
     ValueError: If the train/eval mode is being exported, but the model does
       not have an optimizer.
   """
+  from tensorflow.python.keras import models as models_lib  # pylint: disable=g-import-not-at-top
   compile_clone = (mode != mode_keys.ModeKeys.PREDICT)
   if compile_clone and not model.optimizer:
     raise ValueError(
@@ -339,6 +338,7 @@ def _create_signature_def_map(model, mode):
   local_vars = set(ops.get_collection(ops.GraphKeys.LOCAL_VARIABLES))
   vars_to_add = set()
   if metrics is not None:
+    from tensorflow.python.keras.metrics import Metric  # pylint: disable=g-import-not-at-top
     for key, value in six.iteritems(metrics):
       if isinstance(value, Metric):
         vars_to_add.update(value.variables)
diff --git a/tensorflow/python/keras/saving/saving_utils.py b/tensorflow/python/keras/saving/saving_utils.py
index ddd98aa581..0b643453c9 100644
--- a/tensorflow/python/keras/saving/saving_utils.py
+++ b/tensorflow/python/keras/saving/saving_utils.py
@@ -18,7 +18,9 @@ from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
 
-from tensorflow.python.keras import metrics
+from tensorflow.python.eager import def_function
+from tensorflow.python.framework import tensor_spec
+from tensorflow.python.util import nest
 
 
 def extract_model_metrics(model):
@@ -33,6 +35,7 @@ def extract_model_metrics(model):
     Dictionary mapping metric names to tuples of (value, update) ops. May return
     `None` if the model does not contain any metrics.
   """
+  from tensorflow.python.keras import metrics  # pylint: disable=g-import-not-at-top
   if not getattr(model, '_compile_metrics', None):
     return None
 
@@ -43,3 +46,58 @@ def extract_model_metrics(model):
     m(model._compile_metrics_tensors[metric_name])
     eval_metric_ops[metric_name] = m
   return eval_metric_ops
+
+
+def trace_model_call(model, input_signature=None):
+  """Trace the model call to create a tf.function for exporting a Keras model.
+
+  Args:
+    model: A Keras model.
+    input_signature: optional, a list of tf.TensorSpec objects specifying the
+      inputs to the model.
+
+  Returns:
+    A tf.function wrapping the model's call function with input signatures set.
+
+  Raises:
+    ValueError: if input signature cannot be inferred from the model.
+  """
+  if input_signature is None:
+    if isinstance(model.call, def_function.PolymorphicFunction):
+      input_signature = model.call.input_signature
+
+  if input_signature is None:
+    try:
+      inputs = model.inputs
+      input_names = model.input_names
+    except AttributeError:
+      raise ValueError(
+          'Model {} cannot be saved because the input shapes have not been '
+          'set. Usually, input shapes are automatically determined from calling'
+          ' .fit() or .predict(). To manually set the shapes, call '
+          'model._set_inputs(inputs).'.format(model))
+    input_specs = []
+    for input_tensor, input_name in zip(inputs, input_names):
+      input_specs.append(tensor_spec.TensorSpec(
+          shape=input_tensor.shape, dtype=input_tensor.dtype,
+          name=input_name))
+    # The input signature of the call function is a list with one element, since
+    # all tensor inputs must be passed in as the first argument.
+    input_signature = [input_specs] if len(input_specs) > 1 else input_specs
+
+  # TODO(mdan): Should the model's call be autographed by default?
+  @def_function.function(input_signature=input_signature, autograph=False)
+  def _wrapped_model(*args):
+    """A concrete tf.function that wraps the model's call function."""
+    # When given a single input, Keras models will call the model on the tensor
+    # rather than a list consisting of the single tensor.
+    inputs = args[0] if len(input_signature) == 1 else list(args)
+    outputs_list = nest.flatten(model(inputs=inputs))
+    try:
+      output_names = model.output_names
+    except AttributeError:
+      from tensorflow.python.keras.engine import training_utils  # pylint: disable=g-import-not-at-top
+      output_names = training_utils.generic_output_names(outputs_list)
+    return {name: output for name, output in zip(output_names, outputs_list)}
+
+  return _wrapped_model
diff --git a/tensorflow/python/keras/saving/saving_utils_test.py b/tensorflow/python/keras/saving/saving_utils_test.py
new file mode 100644
index 0000000000..ae267e283c
--- /dev/null
+++ b/tensorflow/python/keras/saving/saving_utils_test.py
@@ -0,0 +1,209 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests for saving utility functions."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+
+import numpy as np
+
+
+from tensorflow.python import keras
+from tensorflow.python.client import session as session_lib
+from tensorflow.python.eager import context
+from tensorflow.python.eager import def_function
+from tensorflow.python.framework import dtypes
+from tensorflow.python.framework import ops
+from tensorflow.python.framework import tensor_spec
+from tensorflow.python.keras import backend as K
+from tensorflow.python.keras import keras_parameterized
+from tensorflow.python.keras import testing_utils
+from tensorflow.python.keras.saving import saving_utils
+from tensorflow.python.ops import array_ops
+from tensorflow.python.platform import test
+from tensorflow.python.saved_model import loader
+from tensorflow.python.saved_model import save as save_lib
+from tensorflow.python.saved_model import signature_constants
+from tensorflow.python.saved_model import tag_constants
+
+
+class TraceModelCallTest(keras_parameterized.TestCase):
+
+  def _assert_all_close(self, expected, actual):
+    if not context.executing_eagerly():
+      with self.cached_session() as sess:
+        K._initialize_variables(sess)
+        self.assertAllClose(expected, actual)
+    else:
+      self.assertAllClose(expected, actual)
+
+  @keras_parameterized.run_with_all_model_types
+  @keras_parameterized.run_all_keras_modes
+  def test_trace_model_outputs(self):
+    input_dim = 5 if testing_utils.get_model_type() == 'functional' else None
+    model = testing_utils.get_small_mlp(10, 3, input_dim)
+    inputs = array_ops.ones((8, 5))
+
+    if input_dim is None:
+      with self.assertRaisesRegexp(ValueError,
+                                   'input shapes have not been set'):
+        saving_utils.trace_model_call(model)
+      model._set_inputs(inputs)
+
+    fn = saving_utils.trace_model_call(model)
+    signature_outputs = fn(inputs)
+    expected_outputs = {model.output_names[0]: model(inputs)}
+
+    self._assert_all_close(expected_outputs, signature_outputs)
+
+  @keras_parameterized.run_with_all_model_types
+  @keras_parameterized.run_all_keras_modes
+  def test_trace_model_outputs_after_fitting(self):
+    input_dim = 5 if testing_utils.get_model_type() == 'functional' else None
+    model = testing_utils.get_small_mlp(10, 3, input_dim)
+    model.compile(optimizer='sgd', loss='mse')
+    model.fit(x=np.random.random((8, 5)),
+              y=np.random.random((8, 3)), epochs=2)
+
+    inputs = array_ops.ones((8, 5))
+
+    fn = saving_utils.trace_model_call(model)
+    signature_outputs = fn(inputs)
+    expected_outputs = {model.output_names[0]: model(inputs)}
+
+    self._assert_all_close(expected_outputs, signature_outputs)
+
+  @keras_parameterized.run_with_all_model_types(exclude_models='sequential')
+  @keras_parameterized.run_all_keras_modes
+  def test_trace_multi_io_model_outputs(self):
+    input_dim = 5
+    num_classes = 3
+    num_classes_b = 4
+    input_a = keras.layers.Input(shape=(input_dim,), name='input_a')
+    input_b = keras.layers.Input(shape=(input_dim,), name='input_b')
+
+    dense = keras.layers.Dense(num_classes, name='dense')
+    dense2 = keras.layers.Dense(num_classes_b, name='dense2')
+    dropout = keras.layers.Dropout(0.5, name='dropout')
+    branch_a = [input_a, dense]
+    branch_b = [input_b, dense, dense2, dropout]
+
+    model = testing_utils.get_multi_io_model(branch_a, branch_b)
+
+    input_a_np = np.random.random((10, input_dim)).astype(np.float32)
+    input_b_np = np.random.random((10, input_dim)).astype(np.float32)
+
+    if testing_utils.get_model_type() == 'subclass':
+      with self.assertRaisesRegexp(ValueError,
+                                   'input shapes have not been set'):
+        saving_utils.trace_model_call(model)
+
+    model.compile(optimizer='sgd', loss='mse')
+    model.fit(x=[np.random.random((8, input_dim)).astype(np.float32),
+                 np.random.random((8, input_dim)).astype(np.float32)],
+              y=[np.random.random((8, num_classes)).astype(np.float32),
+                 np.random.random((8, num_classes_b)).astype(np.float32)],
+              epochs=2)
+
+    fn = saving_utils.trace_model_call(model)
+    signature_outputs = fn([input_a_np, input_b_np])
+    outputs = model([input_a_np, input_b_np])
+    expected_outputs = {model.output_names[0]: outputs[0],
+                        model.output_names[1]: outputs[1]}
+
+    self._assert_all_close(expected_outputs, signature_outputs)
+
+  @keras_parameterized.run_all_keras_modes
+  def test_specify_input_signature(self):
+    model = testing_utils.get_small_sequential_mlp(10, 3, None)
+    inputs = array_ops.ones((8, 5))
+
+    with self.assertRaisesRegexp(ValueError, 'input shapes have not been set'):
+      saving_utils.trace_model_call(model)
+
+    fn = saving_utils.trace_model_call(
+        model, [tensor_spec.TensorSpec(shape=[None, 5], dtype=dtypes.float32)])
+    signature_outputs = fn(inputs)
+    expected_outputs = {model.output_names[0]: model(inputs)}
+    self._assert_all_close(expected_outputs, signature_outputs)
+
+  @keras_parameterized.run_all_keras_modes
+  def test_subclassed_model_with_input_signature(self):
+
+    class Model(keras.Model):
+
+      def __init__(self):
+        super(Model, self).__init__()
+        self.dense = keras.layers.Dense(3, name='dense')
+
+      @def_function.function(
+          input_signature=[[tensor_spec.TensorSpec([None, 5], dtypes.float32),
+                            tensor_spec.TensorSpec([None], dtypes.float32)]],)
+      def call(self, inputs, *args):
+        x, y = inputs
+        return self.dense(x) + y
+
+    model = Model()
+    fn = saving_utils.trace_model_call(model)
+    x = array_ops.ones((8, 5), dtype=dtypes.float32)
+    y = array_ops.ones((3,), dtype=dtypes.float32)
+    expected_outputs = {'output_1': model([x, y])}
+    signature_outputs = fn([x, y])
+    self._assert_all_close(expected_outputs, signature_outputs)
+
+
+def _import_and_infer(save_dir, inputs):
+  """Import a SavedModel into a TF 1.x-style graph and run `signature_key`."""
+  graph = ops.Graph()
+  with graph.as_default(), session_lib.Session() as session:
+    model = loader.load(session, [tag_constants.SERVING], save_dir)
+    signature = model.signature_def[
+        signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY]
+    assert set(inputs.keys()) == set(signature.inputs.keys())
+    feed_dict = {}
+    for arg_name in inputs.keys():
+      feed_dict[graph.get_tensor_by_name(signature.inputs[arg_name].name)] = (
+          inputs[arg_name])
+    output_dict = {}
+    for output_name, output_tensor_info in signature.outputs.items():
+      output_dict[output_name] = graph.get_tensor_by_name(
+          output_tensor_info.name)
+    return session.run(output_dict, feed_dict=feed_dict)
+
+
+class ModelSaveTest(keras_parameterized.TestCase):
+
+  @keras_parameterized.run_with_all_model_types
+  @keras_parameterized.run_all_keras_modes(always_skip_v1=True)
+  def test_model_save(self):
+    input_dim = 5
+    model = testing_utils.get_small_mlp(10, 3, input_dim)
+    inputs = array_ops.ones((8, 5))
+
+    if testing_utils.get_model_type() == 'subclass':
+      model._set_inputs(inputs)
+
+    save_dir = os.path.join(self.get_temp_dir(), 'saved_model')
+    save_lib.save(model, save_dir)
+
+    self.assertAllClose(
+        {model.output_names[0]: model.predict_on_batch(inputs)},
+        _import_and_infer(save_dir, {model.input_names[0]: np.ones((8, 5))}))
+
+if __name__ == '__main__':
+  test.main()
diff --git a/tensorflow/python/saved_model/BUILD b/tensorflow/python/saved_model/BUILD
index 8da6ff5142..8a54888582 100644
--- a/tensorflow/python/saved_model/BUILD
+++ b/tensorflow/python/saved_model/BUILD
@@ -71,7 +71,7 @@ py_library(
         "//tensorflow/python:framework_for_generated_wrappers",
         "//tensorflow/python:lib",
         "//tensorflow/python:platform",
-        "//tensorflow/python:training",
+        "//tensorflow/python:saver",
         "//tensorflow/python:util",
         "//tensorflow/python:variables",
     ],
-- 
GitLab


From bcf94f9cf2a4cfe8d2b7a76a1fe1be553a591edd Mon Sep 17 00:00:00 2001
From: Skye Wanderman-Milne 
Date: Thu, 10 Jan 2019 17:04:59 -0800
Subject: [PATCH 0529/2345] Minor refactor of
 _WhileBodyGradFuncGraph._capture_helper.

It was getting kind of long. This also makes minor stylistic changes to the moved code.

PiperOrigin-RevId: 228804655
---
 tensorflow/python/ops/while_v2.py | 61 +++++++++++++++++++------------
 1 file changed, 37 insertions(+), 24 deletions(-)

diff --git a/tensorflow/python/ops/while_v2.py b/tensorflow/python/ops/while_v2.py
index f5a51bb1bc..cefadfb098 100644
--- a/tensorflow/python/ops/while_v2.py
+++ b/tensorflow/python/ops/while_v2.py
@@ -724,31 +724,9 @@ class _WhileBodyGradFuncGraph(util.WhileBodyFuncGraph):
     if captured_tensor is not None:
       return captured_tensor
 
+    # Resource tensors are not accumulated and handled specially.
     if tensor.dtype == dtypes.resource:
-      # Resource-type tensors are not accumulated.
-      # If a resource tensor exists in the loop body it must either be a loop
-      # input or an output of a nested While op inside the loop body which
-      # had captured the external resource.
-      if tensor in self._forward_graph.inputs:
-        index = self._forward_graph.inputs.index(tensor)
-      elif tensor.op.type == "While":
-        # Captured resources occur at the same index in the lists of inputs and
-        # outputs of a while op. So we lookup the input of `tensor.op` at the
-        # same index as the index of `tensor` in the `tensor.op.outputs`.
-        index = self._forward_graph.inputs.index(
-            tensor.op.inputs[tensor.value_index])
-      else:
-        raise ValueError(
-            "Taking gradient of a while loop which creates"
-            " a resource in its body is not supported: %s" % str(tensor))
-      # This must be a loop invariant.
-      assert self._forward_graph.inputs[index] == self._forward_graph.outputs[
-          index], "Resource tensors must be loop invariants %s." % str(
-              self._forward_graph._while.inputs[index])
-      tensor_in_outer_graph = self._forward_graph._while.inputs[index]
-      self._indirect_captures[tensor] = self.capture(
-          tensor_in_outer_graph, whitelisted=True)
-      return self._indirect_captures[tensor]
+      return self._resource_capture_helper(tensor)
 
     # Create or find an existing accumulator output for `tensor` in the forward
     # graph, and fetch from this accumulator in the gradient graph to get the
@@ -789,6 +767,41 @@ class _WhileBodyGradFuncGraph(util.WhileBodyFuncGraph):
     self.popped_tensor_lists[captured_accumulator] = new_tensor_list
     return captured_tensor
 
+  def _resource_capture_helper(self, tensor):
+    """Returns the captured resource tensor.
+
+    Resource-type tensors are not accumulated. If a resource tensor exists in
+    the loop body it must either be a loop input or an output of a nested While
+    op inside the loop body which had captured the external resource.
+
+    Args:
+      tensor: the external resource Tensor to be captured.
+
+    Returns:
+      Tensor in this graph.
+    """
+    assert tensor.dtype == dtypes.resource
+    if tensor in self._forward_graph.inputs:
+      index = self._forward_graph.inputs.index(tensor)
+    elif tensor.op.type == "While":
+      # Captured resources occur at the same index in the lists of inputs and
+      # outputs of a while op. So we lookup the input of `tensor.op` at the
+      # same index as the index of `tensor` in the `tensor.op.outputs`.
+      index = self._forward_graph.inputs.index(
+          tensor.op.inputs[tensor.value_index])
+    else:
+      raise ValueError(
+          "Taking gradient of a while loop which creates "
+          "a resource in its body is not supported: %s" % tensor)
+    # This must be a loop invariant.
+    assert self._forward_graph.inputs[index] == self._forward_graph.outputs[
+        index], ("Resource tensors must be loop invariants %s." %
+                 self._forward_graph._while.inputs[index])
+    tensor_in_outer_graph = self._forward_graph._while.inputs[index]
+    self._indirect_captures[tensor] = self.capture(
+        tensor_in_outer_graph, whitelisted=True)
+    return self._indirect_captures[tensor]
+
 
 def _check_shapes_compat(output_tensors, shape_invariants, input_tensors):
   for (t, shape, input_t) in zip(output_tensors, shape_invariants,
-- 
GitLab


From 3fc2b09b601e9d2c3e1bdd80d299c31b3adb0c0d Mon Sep 17 00:00:00 2001
From: Tim Shen 
Date: Thu, 10 Jan 2019 17:19:40 -0800
Subject: [PATCH 0530/2345] Centralize the logic that infers accumulator type
 from element type.

PiperOrigin-RevId: 228806506
---
 tensorflow/stream_executor/cuda/cuda_dnn.cc | 132 ++++++++++----------
 1 file changed, 68 insertions(+), 64 deletions(-)

diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc
index 0bd953fe3b..60648075c0 100644
--- a/tensorflow/stream_executor/cuda/cuda_dnn.cc
+++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc
@@ -2710,6 +2710,23 @@ cudnnDataType_t GetRnnComputeType(dnn::DataType data_type) {
   }
 }
 
+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:
+      return CudnnEnvVar::IsEnabled()
+                 ? dnn::DataType::kFloat
+                 : dnn::DataType::kHalf;
+    case dnn::DataType::kInt8:
+    case dnn::DataType::kInt32:
+      return dnn::DataType::kInt32;
+    default:
+      LOG(FATAL) << "Invalid DNN data type: " << static_cast(data_type);
+  }
+}
+
 // Determines whether we can safely perform a winograd non-fused convolution for
 // the given input and output shapes.  This works around b/68264959, an integer
 // overflow in cuDNNv5 and cuDNNv6.
@@ -3405,8 +3422,8 @@ bool CudnnSupport::DoConvolve(
   return IsStatusOk(
       DoConvolveImpl(stream, batch_descriptor, input_data, filter_descriptor,
                      filter_data, convolution_descriptor, output_descriptor,
-                     output_data, dnn::DataType::kFloat, algorithm_desc,
-                     scratch_memory, output_profile_result),
+                     output_data, GetConvAccumulatorType(dnn::DataType::kFloat),
+                     algorithm_desc, scratch_memory, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -3423,8 +3440,9 @@ bool CudnnSupport::DoConvolve(
   return IsStatusOk(
       DoConvolveImpl(stream, batch_descriptor, input_data, filter_descriptor,
                      filter_data, convolution_descriptor, output_descriptor,
-                     output_data, dnn::DataType::kDouble, algorithm_desc,
-                     scratch_memory, output_profile_result),
+                     output_data,
+                     GetConvAccumulatorType(dnn::DataType::kDouble),
+                     algorithm_desc, scratch_memory, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -3439,15 +3457,11 @@ bool CudnnSupport::DoConvolve(
     const dnn::AlgorithmDesc& algorithm_desc,
     DeviceMemory* scratch_memory,
     dnn::ProfileResult* output_profile_result) {
-  dnn::DataType acc_type =
-      CudnnEnvVar::IsEnabled()
-          ? dnn::DataType::kFloat
-          : dnn::DataType::kHalf;
   return IsStatusOk(
       DoConvolveImpl(stream, batch_descriptor, input_data, filter_descriptor,
                      filter_data, convolution_descriptor, output_descriptor,
-                     output_data, acc_type, algorithm_desc, scratch_memory,
-                     output_profile_result),
+                     output_data, GetConvAccumulatorType(dnn::DataType::kHalf),
+                     algorithm_desc, scratch_memory, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -3465,13 +3479,13 @@ bool CudnnSupport::DoFusedConvolve(
     const dnn::AlgorithmConfig& algorithm_config,
     dnn::ProfileResult* output_profile_result) {
   return IsStatusOk(
-      DoFusedConvolveImpl(stream, conv_input_descriptor, conv_input_data,
-                          conv_input_scale, filter_descriptor, filter_data,
-                          convolution_descriptor, side_input_data,
-                          side_input_scale, bias_descriptor, biases,
-                          activation_mode, output_descriptor, output_data,
-                          dnn::DataType::kDouble, scratch_allocator,
-                          algorithm_config, output_profile_result),
+      DoFusedConvolveImpl(
+          stream, conv_input_descriptor, conv_input_data, conv_input_scale,
+          filter_descriptor, filter_data, convolution_descriptor,
+          side_input_data, side_input_scale, bias_descriptor, biases,
+          activation_mode, output_descriptor, output_data,
+          GetConvAccumulatorType(dnn::DataType::kDouble), scratch_allocator,
+          algorithm_config, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -3489,13 +3503,13 @@ bool CudnnSupport::DoFusedConvolve(
     const dnn::AlgorithmConfig& algorithm_config,
     dnn::ProfileResult* output_profile_result) {
   return IsStatusOk(
-      DoFusedConvolveImpl(stream, conv_input_descriptor, conv_input_data,
-                          conv_input_scale, filter_descriptor, filter_data,
-                          convolution_descriptor, side_input_data,
-                          side_input_scale, bias_descriptor, biases,
-                          activation_mode, output_descriptor, output_data,
-                          dnn::DataType::kFloat, scratch_allocator,
-                          algorithm_config, output_profile_result),
+      DoFusedConvolveImpl(
+          stream, conv_input_descriptor, conv_input_data, conv_input_scale,
+          filter_descriptor, filter_data, convolution_descriptor,
+          side_input_data, side_input_scale, bias_descriptor, biases,
+          activation_mode, output_descriptor, output_data,
+          GetConvAccumulatorType(dnn::DataType::kFloat), scratch_allocator,
+          algorithm_config, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -3513,17 +3527,14 @@ bool CudnnSupport::DoFusedConvolve(
     DeviceMemory* output_data, ScratchAllocator* scratch_allocator,
     const dnn::AlgorithmConfig& algorithm_config,
     dnn::ProfileResult* output_profile_result) {
-  dnn::DataType acc_type =
-      CudnnEnvVar::IsEnabled()
-          ? dnn::DataType::kFloat
-          : dnn::DataType::kHalf;
   return IsStatusOk(
       DoFusedConvolveImpl(
           stream, conv_input_descriptor, conv_input_data, conv_input_scale,
           filter_descriptor, filter_data, convolution_descriptor,
           side_input_data, side_input_scale, bias_descriptor, biases,
-          activation_mode, output_descriptor, output_data, acc_type,
-          scratch_allocator, algorithm_config, output_profile_result),
+          activation_mode, output_descriptor, output_data,
+          GetConvAccumulatorType(dnn::DataType::kHalf), scratch_allocator,
+          algorithm_config, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -3549,13 +3560,13 @@ bool CudnnSupport::DoFusedConvolve(
     return false;
   }
   return IsStatusOk(
-      DoFusedConvolveImpl(stream, conv_input_descriptor, conv_input_data,
-                          conv_input_scale, filter_descriptor, filter_data,
-                          convolution_descriptor, side_input_data,
-                          side_input_scale, bias_descriptor, biases,
-                          activation_mode, output_descriptor, output_data,
-                          dnn::DataType::kInt32, scratch_allocator,
-                          algorithm_config, output_profile_result),
+      DoFusedConvolveImpl(
+          stream, conv_input_descriptor, conv_input_data, conv_input_scale,
+          filter_descriptor, filter_data, convolution_descriptor,
+          side_input_data, side_input_scale, bias_descriptor, biases,
+          activation_mode, output_descriptor, output_data,
+          GetConvAccumulatorType(dnn::DataType::kInt8), scratch_allocator,
+          algorithm_config, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -3790,8 +3801,8 @@ bool CudnnSupport::DoConvolveBackwardData(
       DoConvolveBackwardDataImpl(
           stream, filter_descriptor, filter_data, output_descriptor,
           backward_output_data, convolution_descriptor, input_descriptor,
-          backward_input_data, dnn::DataType::kDouble, algorithm_desc,
-          scratch_memory, output_profile_result),
+          backward_input_data, GetConvAccumulatorType(dnn::DataType::kDouble),
+          algorithm_desc, scratch_memory, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -3810,8 +3821,8 @@ bool CudnnSupport::DoConvolveBackwardData(
       DoConvolveBackwardDataImpl(
           stream, filter_descriptor, filter_data, output_descriptor,
           backward_output_data, convolution_descriptor, input_descriptor,
-          backward_input_data, dnn::DataType::kFloat, algorithm_desc,
-          scratch_memory, output_profile_result),
+          backward_input_data, GetConvAccumulatorType(dnn::DataType::kFloat),
+          algorithm_desc, scratch_memory, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -3826,16 +3837,12 @@ bool CudnnSupport::DoConvolveBackwardData(
     const dnn::AlgorithmDesc& algorithm_desc,
     DeviceMemory* scratch_memory,
     dnn::ProfileResult* output_profile_result) {
-  dnn::DataType acc_type =
-      CudnnEnvVar::IsEnabled()
-          ? dnn::DataType::kFloat
-          : dnn::DataType::kHalf;
   return IsStatusOk(
-      DoConvolveBackwardDataImpl(stream, filter_descriptor, filter_data,
-                                 output_descriptor, backward_output_data,
-                                 convolution_descriptor, input_descriptor,
-                                 backward_input_data, acc_type, algorithm_desc,
-                                 scratch_memory, output_profile_result),
+      DoConvolveBackwardDataImpl(
+          stream, filter_descriptor, filter_data, output_descriptor,
+          backward_output_data, convolution_descriptor, input_descriptor,
+          backward_input_data, GetConvAccumulatorType(dnn::DataType::kHalf),
+          algorithm_desc, scratch_memory, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -4085,8 +4092,8 @@ bool CudnnSupport::DoConvolveBackwardFilter(
       DoConvolveBackwardFilterImpl(
           stream, input_descriptor, input_data, output_descriptor,
           backward_output_data, convolution_descriptor, filter_descriptor,
-          backward_filter_data, dnn::DataType::kDouble, algorithm_desc,
-          scratch_memory, output_profile_result),
+          backward_filter_data, GetConvAccumulatorType(dnn::DataType::kDouble),
+          algorithm_desc, scratch_memory, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -4105,8 +4112,8 @@ bool CudnnSupport::DoConvolveBackwardFilter(
       DoConvolveBackwardFilterImpl(
           stream, input_descriptor, input_data, output_descriptor,
           backward_output_data, convolution_descriptor, filter_descriptor,
-          backward_filter_data, dnn::DataType::kFloat, algorithm_desc,
-          scratch_memory, output_profile_result),
+          backward_filter_data, GetConvAccumulatorType(dnn::DataType::kFloat),
+          algorithm_desc, scratch_memory, output_profile_result),
       /*report_error=*/!output_profile_result);
 }
 
@@ -4121,16 +4128,13 @@ bool CudnnSupport::DoConvolveBackwardFilter(
     const dnn::AlgorithmDesc& algorithm_desc,
     DeviceMemory* scratch_memory,
     dnn::ProfileResult* output_profile_result) {
-  dnn::DataType acc_type =
-      CudnnEnvVar::IsEnabled()
-          ? dnn::DataType::kFloat
-          : dnn::DataType::kHalf;
-  return IsStatusOk(DoConvolveBackwardFilterImpl(
-                        stream, input_descriptor, input_data, output_descriptor,
-                        backward_output_data, convolution_descriptor,
-                        filter_descriptor, backward_filter_data, acc_type,
-                        algorithm_desc, scratch_memory, output_profile_result),
-                    /*report_error=*/!output_profile_result);
+  return IsStatusOk(
+      DoConvolveBackwardFilterImpl(
+          stream, input_descriptor, input_data, output_descriptor,
+          backward_output_data, convolution_descriptor, filter_descriptor,
+          backward_filter_data, GetConvAccumulatorType(dnn::DataType::kHalf),
+          algorithm_desc, scratch_memory, output_profile_result),
+      /*report_error=*/!output_profile_result);
 }
 
 template 
-- 
GitLab


From 578bd3a2765f32112ef69bf355b4e1221b1c723b Mon Sep 17 00:00:00 2001
From: Zhenyu Tan 
Date: Thu, 10 Jan 2019 17:33:58 -0800
Subject: [PATCH 0531/2345] Move clustering ops to core.

PiperOrigin-RevId: 228808275
---
 tensorflow/contrib/factorization/BUILD        | 24 +----
 .../contrib/factorization/kernels/BUILD       | 28 ------
 .../factorization/ops/clustering_ops.cc       | 91 -------------------
 .../python/ops/clustering_ops.py              | 15 +--
 tensorflow/core/BUILD                         |  3 +
 .../api_def_KMC2ChainInitialization.pbtxt     | 30 ++++++
 ...api_def_KmeansPlusPlusInitialization.pbtxt | 44 +++++++++
 .../base_api/api_def_NearestNeighbors.pbtxt   | 43 +++++++++
 .../api_def_KMC2ChainInitialization.pbtxt     |  4 +
 ...api_def_KmeansPlusPlusInitialization.pbtxt |  4 +
 .../python_api/api_def_NearestNeighbors.pbtxt |  4 +
 tensorflow/core/kernels/BUILD                 | 27 ++++++
 .../kernels/clustering_ops.cc                 | 10 +-
 .../kernels/clustering_ops_test.cc            |  0
 tensorflow/core/ops/clustering_ops.cc         | 43 +++++++++
 tensorflow/python/BUILD                       |  8 ++
 16 files changed, 221 insertions(+), 157 deletions(-)
 delete mode 100644 tensorflow/contrib/factorization/ops/clustering_ops.cc
 create mode 100644 tensorflow/core/api_def/base_api/api_def_KMC2ChainInitialization.pbtxt
 create mode 100644 tensorflow/core/api_def/base_api/api_def_KmeansPlusPlusInitialization.pbtxt
 create mode 100644 tensorflow/core/api_def/base_api/api_def_NearestNeighbors.pbtxt
 create mode 100644 tensorflow/core/api_def/python_api/api_def_KMC2ChainInitialization.pbtxt
 create mode 100644 tensorflow/core/api_def/python_api/api_def_KmeansPlusPlusInitialization.pbtxt
 create mode 100644 tensorflow/core/api_def/python_api/api_def_NearestNeighbors.pbtxt
 rename tensorflow/{contrib/factorization => core}/kernels/clustering_ops.cc (99%)
 rename tensorflow/{contrib/factorization => core}/kernels/clustering_ops_test.cc (100%)
 create mode 100644 tensorflow/core/ops/clustering_ops.cc

diff --git a/tensorflow/contrib/factorization/BUILD b/tensorflow/contrib/factorization/BUILD
index e344d7a23b..cb86efb8da 100644
--- a/tensorflow/contrib/factorization/BUILD
+++ b/tensorflow/contrib/factorization/BUILD
@@ -28,7 +28,6 @@ tf_custom_op_py_library(
         "python/ops/wals.py",
     ],
     dso = [
-        ":python/ops/_clustering_ops.so",
         ":python/ops/_factorization_ops.so",
     ],
     kernels = [
@@ -38,12 +37,12 @@ tf_custom_op_py_library(
     srcs_version = "PY2AND3",
     deps = [
         ":factorization_ops_test_utils_py",
-        ":gen_clustering_ops",
         ":gen_factorization_ops",
         "//tensorflow/contrib/framework:framework_py",
         "//tensorflow/contrib/util:util_py",
         "//tensorflow/python:array_ops",
         "//tensorflow/python:check_ops",
+        "//tensorflow/python:clustering_ops_gen",
         "//tensorflow/python:control_flow_ops",
         "//tensorflow/python:data_flow_ops",
         "//tensorflow/python:embedding_ops",
@@ -77,17 +76,6 @@ py_library(
     ],
 )
 
-# Ops
-tf_custom_op_library(
-    name = "python/ops/_clustering_ops.so",
-    srcs = [
-        "ops/clustering_ops.cc",
-    ],
-    deps = [
-        "//tensorflow/contrib/factorization/kernels:clustering_ops",
-    ],
-)
-
 tf_custom_op_library(
     name = "python/ops/_factorization_ops.so",
     srcs = [
@@ -100,26 +88,16 @@ tf_custom_op_library(
 )
 
 tf_gen_op_libs([
-    "clustering_ops",
     "factorization_ops",
 ])
 
 cc_library(
     name = "all_ops",
     deps = [
-        ":clustering_ops_op_lib",
         ":factorization_ops_op_lib",
     ],
 )
 
-tf_gen_op_wrapper_py(
-    name = "gen_clustering_ops",
-    out = "python/ops/gen_clustering_ops.py",
-    deps = [
-        ":clustering_ops_op_lib",
-    ],
-)
-
 tf_gen_op_wrapper_py(
     name = "gen_factorization_ops",
     out = "python/ops/gen_factorization_ops.py",
diff --git a/tensorflow/contrib/factorization/kernels/BUILD b/tensorflow/contrib/factorization/kernels/BUILD
index ea8b9a17a2..23d7e088d0 100644
--- a/tensorflow/contrib/factorization/kernels/BUILD
+++ b/tensorflow/contrib/factorization/kernels/BUILD
@@ -11,7 +11,6 @@ load("//tensorflow:tensorflow.bzl", "tf_cc_test")
 cc_library(
     name = "all_kernels",
     deps = [
-        ":clustering_ops",
         ":masked_matmul_ops",
         ":wals_solver_ops",
         "@protobuf_archive//:protobuf_headers",
@@ -29,17 +28,6 @@ cc_library(
     alwayslink = 1,
 )
 
-cc_library(
-    name = "clustering_ops",
-    srcs = ["clustering_ops.cc"],
-    deps = [
-        "//tensorflow/core:framework_headers_lib",
-        "//third_party/eigen3",
-        "@protobuf_archive//:protobuf_headers",
-    ],
-    alwayslink = 1,
-)
-
 cc_library(
     name = "masked_matmul_ops",
     srcs = ["masked_matmul_ops.cc"],
@@ -51,19 +39,3 @@ cc_library(
     ],
     alwayslink = 1,
 )
-
-tf_cc_test(
-    name = "clustering_ops_test",
-    srcs = ["clustering_ops_test.cc"],
-    deps = [
-        ":clustering_ops",
-        "//tensorflow/contrib/factorization:clustering_ops_op_lib",
-        "//tensorflow/core:core_cpu",
-        "//tensorflow/core:framework",
-        "//tensorflow/core:lib",
-        "//tensorflow/core:protos_all_cc",
-        "//tensorflow/core:test",
-        "//tensorflow/core:test_main",
-        "//tensorflow/core:testlib",
-    ],
-)
diff --git a/tensorflow/contrib/factorization/ops/clustering_ops.cc b/tensorflow/contrib/factorization/ops/clustering_ops.cc
deleted file mode 100644
index 2686702c1d..0000000000
--- a/tensorflow/contrib/factorization/ops/clustering_ops.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License.  You may obtain a copy
-// of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
-// License for the specific language governing permissions and limitations under
-// the License.
-// ==============================================================================
-
-#include "tensorflow/core/framework/common_shape_fns.h"
-#include "tensorflow/core/framework/op.h"
-
-namespace tensorflow {
-
-REGISTER_OP("KmeansPlusPlusInitialization")
-    .Input("points: float32")
-    .Input("num_to_sample: int64")
-    .Input("seed: int64")
-    .Input("num_retries_per_sample: int64")
-    .Output("samples: float32")
-    .SetShapeFn(shape_inference::UnknownShape)
-    .Doc(R"(
-Selects num_to_sample rows of input using the KMeans++ criterion.
-
-Rows of points are assumed to be input points. One row is selected at random.
-Subsequent rows are sampled with probability proportional to the squared L2
-distance from the nearest row selected thus far till num_to_sample rows have
-been sampled.
-
-points: Matrix of shape (n, d). Rows are assumed to be input points.
-num_to_sample: Scalar. The number of rows to sample. This value must not be
-  larger than n.
-seed: Scalar. Seed for initializing the random number generator.
-num_retries_per_sample: Scalar. For each row that is sampled, this parameter
-  specifies the number of additional points to draw from the current
-  distribution before selecting the best. If a negative value is specified, a
-  heuristic is used to sample O(log(num_to_sample)) additional points.
-samples: Matrix of shape (num_to_sample, d). The sampled rows.
-)");
-
-REGISTER_OP("KMC2ChainInitialization")
-    .Input("distances: float32")
-    .Input("seed: int64")
-    .Output("index: int64")
-    .SetShapeFn(shape_inference::ScalarShape)
-    .Doc(R"(
-Returns the index of a data point that should be added to the seed set.
-
-Entries in distances are assumed to be squared distances of candidate points to
-the already sampled centers in the seed set. The op constructs one Markov chain
-of the k-MC^2 algorithm and returns the index of one candidate point to be added
-as an additional cluster center.
-
-distances: Vector with squared distances to the closest previously sampled
-  cluster center for each candidate point.
-seed: Scalar. Seed for initializing the random number generator.
-index: Scalar with the index of the sampled point.
-)");
-
-REGISTER_OP("NearestNeighbors")
-    .Input("points: float32")
-    .Input("centers: float32")
-    .Input("k: int64")
-    .Output("nearest_center_indices: int64")
-    .Output("nearest_center_distances: float32")
-    .SetShapeFn(shape_inference::UnknownShape)
-    .Doc(R"(
-Selects the k nearest centers for each point.
-
-Rows of points are assumed to be input points. Rows of centers are assumed to be
-the list of candidate centers. For each point, the k centers that have least L2
-distance to it are computed.
-
-points: Matrix of shape (n, d). Rows are assumed to be input points.
-centers: Matrix of shape (m, d). Rows are assumed to be centers.
-k: Scalar. Number of nearest centers to return for each point. If k is larger
-  than m, then only m centers are returned.
-nearest_center_indices: Matrix of shape (n, min(m, k)). Each row contains the
-  indices of the centers closest to the corresponding point, ordered by
-  increasing distance.
-nearest_center_distances: Matrix of shape (n, min(m, k)). Each row contains the
-  squared L2 distance to the corresponding center in nearest_center_indices.
-)");
-
-}  // namespace tensorflow
diff --git a/tensorflow/contrib/factorization/python/ops/clustering_ops.py b/tensorflow/contrib/factorization/python/ops/clustering_ops.py
index 84e80791f4..d48b89cbac 100644
--- a/tensorflow/contrib/factorization/python/ops/clustering_ops.py
+++ b/tensorflow/contrib/factorization/python/ops/clustering_ops.py
@@ -18,28 +18,23 @@ from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
 
-from tensorflow.contrib.factorization.python.ops import gen_clustering_ops
-# go/tf-wildcard-import
-# pylint: disable=wildcard-import
-from tensorflow.contrib.factorization.python.ops.gen_clustering_ops import *
-# pylint: enable=wildcard-import
-from tensorflow.contrib.util import loader
 from tensorflow.python.framework import constant_op
 from tensorflow.python.framework import dtypes
 from tensorflow.python.framework import ops
 from tensorflow.python.ops import array_ops
 from tensorflow.python.ops import check_ops
 from tensorflow.python.ops import control_flow_ops
+from tensorflow.python.ops import gen_clustering_ops
 from tensorflow.python.ops import math_ops
 from tensorflow.python.ops import nn_impl
 from tensorflow.python.ops import random_ops
 from tensorflow.python.ops import state_ops
 from tensorflow.python.ops import variable_scope
 from tensorflow.python.ops.embedding_ops import embedding_lookup
-from tensorflow.python.platform import resource_loader
-
-_clustering_ops = loader.load_op_library(
-    resource_loader.get_path_to_datafile('_clustering_ops.so'))
+# go/tf-wildcard-import
+# pylint: disable=wildcard-import
+from tensorflow.python.ops.gen_clustering_ops import *
+# pylint: enable=wildcard-import
 
 # Euclidean distance between vectors U and V is defined as \\(||U - V||_F\\)
 # which is the square root of the sum of the absolute squares of the elements
diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD
index 690fa4a19e..20f5815423 100644
--- a/tensorflow/core/BUILD
+++ b/tensorflow/core/BUILD
@@ -1074,6 +1074,7 @@ tf_gen_op_libs(
         "tensor_forest_ops",
         "candidate_sampling_ops",
         "checkpoint_ops",
+        "clustering_ops",
         "collective_ops",
         "control_flow_ops",
         "ctc_ops",
@@ -1228,6 +1229,7 @@ cc_library(
         ":tensor_forest_ops_op_lib",
         ":candidate_sampling_ops_op_lib",
         ":checkpoint_ops_op_lib",
+        ":clustering_ops_op_lib",
         ":collective_ops_op_lib",
         ":control_flow_ops_op_lib",
         ":ctc_ops_op_lib",
@@ -1382,6 +1384,7 @@ cc_library(
         "//tensorflow/core/kernels:tensor_forest_ops",
         "//tensorflow/core/kernels:candidate_sampler_ops",
         "//tensorflow/core/kernels:checkpoint_ops",
+        "//tensorflow/core/kernels:clustering_ops",
         "//tensorflow/core/kernels:collective_ops",
         "//tensorflow/core/kernels:control_flow_ops",
         "//tensorflow/core/kernels:ctc_ops",
diff --git a/tensorflow/core/api_def/base_api/api_def_KMC2ChainInitialization.pbtxt b/tensorflow/core/api_def/base_api/api_def_KMC2ChainInitialization.pbtxt
new file mode 100644
index 0000000000..c6ff4b9e2d
--- /dev/null
+++ b/tensorflow/core/api_def/base_api/api_def_KMC2ChainInitialization.pbtxt
@@ -0,0 +1,30 @@
+op {
+  graph_op_name: "KMC2ChainInitialization"
+  visibility: HIDDEN
+  in_arg {
+    name: "distances"
+    description: <
-- GitLab From d292c630911d6ba75fd0219792ef1cb47f531579 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 15 Jan 2019 13:59:24 +0530 Subject: [PATCH 0712/2345] Update options.md typo --- tensorflow/core/profiler/g3doc/options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/profiler/g3doc/options.md b/tensorflow/core/profiler/g3doc/options.md index 7f2cd3f698..19f33253fb 100644 --- a/tensorflow/core/profiler/g3doc/options.md +++ b/tensorflow/core/profiler/g3doc/options.md @@ -57,7 +57,7 @@ cpu_micros: This is the cpu times. Tensor memory are usually ref-counted. The memory is released when there is no more reference to it. It will be difficult to track the release of memory. Currently, profiler only tracks the allocation of memory. As a result, the -accumulated memory request is uaually larger than the peak memory of the overall +accumulated memory request is usually larger than the peak memory of the overall model. It's recommended to generate timeline to see the allocator memory usage over -- GitLab From dc07d3662531af7b3f049c6f3d3b010d4252df50 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 01:02:19 -0800 Subject: [PATCH 0713/2345] compat: Update forward compatibility horizon to 2019-01-15 PiperOrigin-RevId: 229327462 --- 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 c9f2397fa5..83f31bd533 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, 1, 14) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 15) @tf_export("compat.forward_compatible") -- GitLab From b329214d7bbf2e451e8631fee86ae40694869dd2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 07:00:53 -0800 Subject: [PATCH 0714/2345] Add a ragged size op and register it to the op dispatcher PiperOrigin-RevId: 229364271 --- tensorflow/python/ops/array_ops.py | 2 + tensorflow/python/ops/ragged/BUILD | 15 ++++++ .../python/ops/ragged/ragged_array_ops.py | 30 ++++++++++++ .../python/ops/ragged/ragged_dispatch.py | 7 +++ .../python/ops/ragged/ragged_dispatch_test.py | 8 ++++ .../python/ops/ragged/ragged_size_op_test.py | 48 +++++++++++++++++++ 6 files changed, 110 insertions(+) create mode 100644 tensorflow/python/ops/ragged/ragged_size_op_test.py diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index 81558db04f..014fdd25cc 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -357,12 +357,14 @@ def shape_n(input, out_type=dtypes.int32, name=None): @tf_export("size", v1=[]) +@dispatch.add_dispatch_support def size_v2(input, out_type=dtypes.int32, name=None): # pylint: disable=redefined-builtin return size(input, name, out_type) @tf_export(v1=["size"]) +@dispatch.add_dispatch_support def size(input, name=None, out_type=dtypes.int32): # pylint: disable=redefined-builtin """Returns the size of a tensor. diff --git a/tensorflow/python/ops/ragged/BUILD b/tensorflow/python/ops/ragged/BUILD index 46f7fa62a3..972b14955f 100644 --- a/tensorflow/python/ops/ragged/BUILD +++ b/tensorflow/python/ops/ragged/BUILD @@ -875,3 +875,18 @@ py_test( "@absl_py//absl/testing:parameterized", ], ) + +py_test( + name = "ragged_size_op_test", + srcs = ["ragged_size_op_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":ragged_array_ops", + ":ragged_factory_ops", + ":ragged_test_util", + "//tensorflow/python:constant_op", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:platform_test", + "@absl_py//absl/testing:parameterized", + ], +) diff --git a/tensorflow/python/ops/ragged/ragged_array_ops.py b/tensorflow/python/ops/ragged/ragged_array_ops.py index a6b2442f09..399ddbe597 100644 --- a/tensorflow/python/ops/ragged/ragged_array_ops.py +++ b/tensorflow/python/ops/ragged/ragged_array_ops.py @@ -1194,6 +1194,36 @@ def _coordinate_where(condition): axis=1) +#=============================================================================== +# RaggedTensor Size +#=============================================================================== + + +def size(input, out_type=dtypes.int32, name=None): # pylint: disable=redefined-builtin + """Returns the size of a potentially ragged tensor. + + The size of a ragged tensor is the size of its inner values. + + Args: + input: A potentially ragged `Tensor`. + out_type: The numeric output type for the operation. + name: A name for the operation (optional). + + Returns: + A Tensor of type `out_type`. + + #### Example: + ```python + >>> tf.size(tf.ragged.constant([[1, 2], [3]])) + 3 + ``` + """ + if ragged_tensor.is_ragged(input): + return array_ops.size(input.flat_values, out_type=out_type, name=name) + else: + return array_ops.size(input, out_type=out_type, name=name) + + #=============================================================================== # Internal Helper Functions #=============================================================================== diff --git a/tensorflow/python/ops/ragged/ragged_dispatch.py b/tensorflow/python/ops/ragged/ragged_dispatch.py index 970d88663a..5284750198 100644 --- a/tensorflow/python/ops/ragged/ragged_dispatch.py +++ b/tensorflow/python/ops/ragged/ragged_dispatch.py @@ -21,6 +21,7 @@ from __future__ import print_function import collections import numpy as np +from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.framework import sparse_tensor from tensorflow.python.ops import array_ops @@ -415,6 +416,10 @@ def _ragged_expand_dims_v1(input, axis=None, name=None, dim=None): # pylint: di return ragged_array_ops.expand_dims(input=input, axis=axis, name=name) +def _ragged_size_v1(input, name=None, out_type=dtypes.int32): # pylint: disable=redefined-builtin + return ragged_array_ops.size(input=input, out_type=out_type, name=name) + + # (original_op, ragged_op, ragged_args) _RAGGED_DISPATCH_OPS = [ (array_ops.batch_gather, ragged_array_ops.batch_gather, @@ -426,6 +431,8 @@ _RAGGED_DISPATCH_OPS = [ (array_ops.gather_v2, ragged_array_ops.gather, ['params', 'indices']), (array_ops.gather_nd, ragged_array_ops.gather_nd, ['params', 'indices']), (array_ops.rank, ragged_array_ops.rank, ['input']), + (array_ops.size, _ragged_size_v1, ['input']), + (array_ops.size_v2, ragged_array_ops.size, ['input']), (array_ops.stack, ragged_array_ops.stack, ['[values]']), (array_ops.tile, ragged_array_ops.tile, ['input']), (array_ops.where, ragged_array_ops.where, ['condition', 'x', 'y']), diff --git a/tensorflow/python/ops/ragged/ragged_dispatch_test.py b/tensorflow/python/ops/ragged/ragged_dispatch_test.py index 1aa612633e..04ef0d7cd6 100644 --- a/tensorflow/python/ops/ragged/ragged_dispatch_test.py +++ b/tensorflow/python/ops/ragged/ragged_dispatch_test.py @@ -687,6 +687,14 @@ class RaggedElementwiseOpsTest(ragged_test_util.RaggedTensorTestCase, op=array_ops.rank, kwargs={'input': ragged_factory_ops.constant_value([[8, 3], [5]])}, expected=2), + dict( + op=array_ops.size, + kwargs={'input': ragged_factory_ops.constant_value([[8, 3], [5]])}, + expected=3), + dict( + op=array_ops.size_v2, + kwargs={'input': ragged_factory_ops.constant_value([[8, 3], [5]])}, + expected=3), ]) def testRaggedDispatch(self, op, expected, args=(), kwargs=None): if kwargs is None: kwargs = {} diff --git a/tensorflow/python/ops/ragged/ragged_size_op_test.py b/tensorflow/python/ops/ragged/ragged_size_op_test.py new file mode 100644 index 0000000000..6ffed11b13 --- /dev/null +++ b/tensorflow/python/ops/ragged/ragged_size_op_test.py @@ -0,0 +1,48 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for ragged.size.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl.testing import parameterized + +from tensorflow.python.framework import test_util +from tensorflow.python.ops.ragged import ragged_array_ops +from tensorflow.python.ops.ragged import ragged_factory_ops +from tensorflow.python.ops.ragged import ragged_test_util +from tensorflow.python.platform import googletest + + +@test_util.run_all_in_graph_and_eager_modes +class RaggedSizeOpTest(ragged_test_util.RaggedTensorTestCase, + parameterized.TestCase): + + @parameterized.parameters([ + {'size': 1, 'test_input': 1}, + {'size': 0, 'test_input': []}, + {'size': 0, 'test_input': [], 'ragged_rank': 1}, + {'size': 3, 'test_input': [1, 1, 1]}, + {'size': 3, 'test_input': [[1, 1], [1]]}, + {'size': 5, 'test_input': [[[1, 1, 1], [1]], [[1]]]}, + {'size': 6, 'test_input': [[[1, 1], [1, 1]], [[1, 1]]], 'ragged_rank': 1}, + ]) + def testRaggedSize(self, test_input, size, ragged_rank=None): + input_rt = ragged_factory_ops.constant(test_input, ragged_rank=ragged_rank) + self.assertAllEqual(ragged_array_ops.size(input_rt), size) + +if __name__ == '__main__': + googletest.main() -- GitLab From 9825f1d7534cb6d0516813346750fe9a59df42ce Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 08:43:07 -0800 Subject: [PATCH 0715/2345] Split NeuralNetworksShim.h: - Type definitions go into NeuralNetworksTypes.h - Function definitions stay into NeuralNetworkShim.h PiperOrigin-RevId: 229377846 --- tensorflow/lite/nnapi/BUILD | 1 + tensorflow/lite/nnapi/NeuralNetworksShim.h | 330 +----------------- tensorflow/lite/nnapi/NeuralNetworksTypes.h | 352 ++++++++++++++++++++ 3 files changed, 355 insertions(+), 328 deletions(-) create mode 100644 tensorflow/lite/nnapi/NeuralNetworksTypes.h diff --git a/tensorflow/lite/nnapi/BUILD b/tensorflow/lite/nnapi/BUILD index 467a2b7a7b..bd3a8a69af 100644 --- a/tensorflow/lite/nnapi/BUILD +++ b/tensorflow/lite/nnapi/BUILD @@ -8,6 +8,7 @@ cc_library( name = "nnapi_lib", hdrs = [ "NeuralNetworksShim.h", + "NeuralNetworksTypes.h", ], linkopts = ["-ldl"], ) diff --git a/tensorflow/lite/nnapi/NeuralNetworksShim.h b/tensorflow/lite/nnapi/NeuralNetworksShim.h index c39502f4ac..2ce6e50de6 100644 --- a/tensorflow/lite/nnapi/NeuralNetworksShim.h +++ b/tensorflow/lite/nnapi/NeuralNetworksShim.h @@ -20,6 +20,8 @@ limitations under the License. #include #include +#include "tensorflow/lite/nnapi/NeuralNetworksTypes.h" + // helpers #define NNAPI_LOG(format, ...) fprintf(stderr, format "\n", __VA_ARGS__); @@ -44,8 +46,6 @@ inline void* loadLibrary(const char* name) { return handle; } -typedef int (*ASharedMemory_create_fn)(const char* name, size_t size); - // ASharedMemory_create was added in Android 8.0, so safe to use with NNAPI // which was added in 8.1. inline int ASharedMemory_create(const char* name, size_t size) { @@ -81,332 +81,6 @@ inline bool NNAPIExists() { // NN api types based on NNAPI header file // https://developer.android.com/ndk/reference/group/neural-networks -/** - * Operand types. - * - * The type of operands that can be added to a model. - * - * Although we define many types, most operators accept just a few - * types. Most used are ANEURALNETWORKS_TENSOR_FLOAT32, - * ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, and ANEURALNETWORKS_INT32. - */ -enum { - ANEURALNETWORKS_FLOAT32 = 0, - ANEURALNETWORKS_INT32 = 1, - ANEURALNETWORKS_UINT32 = 2, - ANEURALNETWORKS_TENSOR_FLOAT32 = 3, - ANEURALNETWORKS_TENSOR_INT32 = 4, - ANEURALNETWORKS_TENSOR_QUANT8_ASYMM = 5, -}; - -/** - * Operation types. - * - * The type of operations that can be added to a model. - */ -enum { - ANEURALNETWORKS_ADD = 0, - ANEURALNETWORKS_AVERAGE_POOL_2D = 1, - ANEURALNETWORKS_CONCATENATION = 2, - ANEURALNETWORKS_CONV_2D = 3, - ANEURALNETWORKS_DEPTHWISE_CONV_2D = 4, - ANEURALNETWORKS_DEPTH_TO_SPACE = 5, - ANEURALNETWORKS_DEQUANTIZE = 6, - ANEURALNETWORKS_EMBEDDING_LOOKUP = 7, - ANEURALNETWORKS_FLOOR = 8, - ANEURALNETWORKS_FULLY_CONNECTED = 9, - ANEURALNETWORKS_HASHTABLE_LOOKUP = 10, - ANEURALNETWORKS_L2_NORMALIZATION = 11, - ANEURALNETWORKS_L2_POOL_2D = 12, - ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION = 13, - ANEURALNETWORKS_LOGISTIC = 14, - ANEURALNETWORKS_LSH_PROJECTION = 15, - ANEURALNETWORKS_LSTM = 16, - ANEURALNETWORKS_MAX_POOL_2D = 17, - ANEURALNETWORKS_MUL = 18, - ANEURALNETWORKS_RELU = 19, - ANEURALNETWORKS_RELU1 = 20, - ANEURALNETWORKS_RELU6 = 21, - ANEURALNETWORKS_RESHAPE = 22, - ANEURALNETWORKS_RESIZE_BILINEAR = 23, - ANEURALNETWORKS_RNN = 24, - ANEURALNETWORKS_SOFTMAX = 25, - ANEURALNETWORKS_SPACE_TO_DEPTH = 26, - ANEURALNETWORKS_SVDF = 27, - ANEURALNETWORKS_TANH = 28, - ANEURALNETWORKS_BATCH_TO_SPACE_ND = 29, - ANEURALNETWORKS_DIV = 30, - ANEURALNETWORKS_MEAN = 31, - ANEURALNETWORKS_PAD = 32, - ANEURALNETWORKS_SPACE_TO_BATCH_ND = 33, - ANEURALNETWORKS_SQUEEZE = 34, - ANEURALNETWORKS_STRIDED_SLICE = 35, - ANEURALNETWORKS_SUB = 36, - ANEURALNETWORKS_TRANSPOSE = 37, -}; - -/** - * Fused activation function types. - * - */ -enum { - ANEURALNETWORKS_FUSED_NONE = 0, - ANEURALNETWORKS_FUSED_RELU = 1, - ANEURALNETWORKS_FUSED_RELU1 = 2, - ANEURALNETWORKS_FUSED_RELU6 = 3, -}; - -/** - * Execution preferences. - */ -enum { - ANEURALNETWORKS_PREFER_LOW_POWER = 0, - ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER = 1, - ANEURALNETWORKS_PREFER_SUSTAINED_SPEED = 2, -}; - -/** - * Result codes. - */ -enum { - ANEURALNETWORKS_NO_ERROR = 0, - ANEURALNETWORKS_OUT_OF_MEMORY = 1, - ANEURALNETWORKS_INCOMPLETE = 2, - ANEURALNETWORKS_UNEXPECTED_NULL = 3, - ANEURALNETWORKS_BAD_DATA = 4, - ANEURALNETWORKS_OP_FAILED = 5, - ANEURALNETWORKS_UNMAPPABLE = 5, - ANEURALNETWORKS_BAD_STATE = 6, -}; - -/** - * Implicit padding algorithms. - */ -enum { - ANEURALNETWORKS_PADDING_SAME = 1, - ANEURALNETWORKS_PADDING_VALID = 2, -}; - -/** - * ANeuralNetworksMemory is an opaque type that represents memory. - * - * This type is used to represent shared memory, memory mapped files, - * and similar memories. - * - * By using shared memory, a program can efficiently communicate to the - * runtime and drivers the tensors that define a model. See - * {@link ANeuralNetworksModel_setOperandValueFromMemory}. An application - * should typically create one shared memory object that contains every tensor - * needed to define a model. {@link ANeuralNetworksMemory_createFromFd} can be - * used to create shared memory from a file handle. {@link - * ANeuralNetworksMemory_createShared} can be used to directly created shared - * memory. - * - * Memory objects can also be used to specify the input and output arguments of - * an execution. See {@link ANeuralNetworksExecution_setInputFromMemory} - * and {@link ANeuralNetworksExecution_setOutputFromMemory}. - */ -typedef struct ANeuralNetworksMemory ANeuralNetworksMemory; - -/** - * ANeuralNetworksModel is an opaque type that contains a description of the - * mathematical operations that constitute the model. - * - *

The model will be built by calling

    - *
  • {@link ANeuralNetworksModel_create},
  • - *
  • {@link ANeuralNetworksModel_addOperation},
  • - *
  • {@link ANeuralNetworksModel_addOperand},
  • - *
- * - * A model is completed by calling {@link ANeuralNetworksModel_finish}. - * A model is destroyed by calling {@link ANeuralNetworksModel_free}. - * - *

It is the application's responsibility to make sure that only one thread - * modifies a model at a given time. It is however safe for more than one - * thread to use the model once {@link ANeuralNetworksModel_finish} has - * returned.

- * - *

It is also the application's responsibility to ensure that there are no - * other uses of the model after calling {@link ANeuralNetworksModel_free}. This - * includes any compilation or execution object created using the model.

- */ -typedef struct ANeuralNetworksModel ANeuralNetworksModel; - -/** - * ANeuralNetworksCompilation is an opaque type that can be used to compile - * a machine learning model. - * - *

To use:

    - *
  • Create a new compilation instance by calling the - * {@link ANeuralNetworksCompilation_create} function.
  • - *
  • Perform the compilation with {@link - * ANeuralNetworksCompilation_start}.
  • Wait for the compilation to - * complete with {@link ANeuralNetworksCompilation_wait}.
  • Use the - * compilation as many times as needed with {@link - * ANeuralNetworksExecution_create}.
  • Destroy the compilation with - * {@link ANeuralNetworksCompilation_free} once all executions using the - * compilation have completed.

- * - *

A compilation cannot be modified once {@link - * ANeuralNetworksCompilation_start} has been called on it.

- * - *

It is the application's responsibility to make sure that only one thread - * modifies a compilation at a given time. It is however safe for more than one - * thread to use {@link ANeuralNetworksCompilation_wait} at the same time. - * It is also safe for multiple threads to use a compilation object once - * {@link ANeuralNetworksCompilation_wait} has completed.

- * - *

It is also the application's responsibility to ensure that there are no - * other uses of the compilation after calling {@link - * ANeuralNetworksCompilation_free}. This includes any execution object created - * using the compilation.

- */ -typedef struct ANeuralNetworksCompilation ANeuralNetworksCompilation; - -/** - * ANeuralNetworksExecution is an opaque type that can be used to apply a - * machine learning model to a set of inputs. - * - *

To use:

    - *
  • Create a new execution instance by calling the - * {@link ANeuralNetworksExecution_create} function.
  • - *
  • Associate data to the model inputs with - * {@link ANeuralNetworksExecution_setInput} or - * {@link ANeuralNetworksExecution_setInputFromMemory}.
  • - *
  • Associate output buffers to the model outputs with - * {@link ANeuralNetworksExecution_setOutput} or - * {@link ANeuralNetworksExecution_setOutputFromMemory}.
  • - *
  • Apply the model with {@link - * ANeuralNetworksExecution_startCompute}.
  • Wait for the execution to - * complete with {@link ANeuralNetworksExecution_wait}.
  • Destroy the - * execution with - * {@link ANeuralNetworksExecution_free}.

- * - *

An execution cannot be modified once {@link - * ANeuralNetworksExecution_start} has been called on it.

- * - *

An execution can be applied to a model with - * {@link ANeuralNetworksExecution_startCompute} only once. Create new - * executions to do new evaluations of the model.

- * - *

It is the application's responsibility to make sure that only one thread - * modifies an execution at a given time. It is however safe for more than one - * thread to use {@link ANeuralNetworksExecution_wait} at the same time.

- * - *

It is also the application's responsibility to ensure that there are no - * other uses of the request after calling {@link - * ANeuralNetworksRequest_free}.

- */ -typedef struct ANeuralNetworksExecution ANeuralNetworksExecution; - -/** - * ANeuralNetworksOperandType describes the type of an operand. - * This structure is used to describe both scalars and tensors. - */ -typedef struct ANeuralNetworksOperandType { - /** The data type, e.g ANEURALNETWORKS_INT8. */ - int32_t type; - /** The number of dimensions. It should be 0 for scalars. */ - uint32_t dimensionCount; - /** The dimensions of the tensor. It should be nullptr for scalars. */ - const uint32_t* dimensions; - /** These two fields are only used for quantized tensors. - * They should be zero for scalars and non-fixed point tensors. - * The dequantized value of each entry is (value - offset) * scale. - */ - float scale; - int32_t zeroPoint; -} ANeuralNetworksOperandType; - -/** - * ANeuralNetworksEvent is an opaque type that represents an event - * that will be signaled once an execution completes. - */ -typedef struct ANeuralNetworksEvent ANeuralNetworksEvent; - -typedef int32_t ANeuralNetworksOperationType; - -// nn api function types - -typedef int (*ANeuralNetworksMemory_createFromFd_fn)( - size_t size, int protect, int fd, size_t offset, - ANeuralNetworksMemory** memory); - -typedef void (*ANeuralNetworksMemory_free_fn)(ANeuralNetworksMemory* memory); - -typedef int (*ANeuralNetworksModel_create_fn)(ANeuralNetworksModel** model); - -typedef int (*ANeuralNetworksModel_finish_fn)(ANeuralNetworksModel* model); - -typedef void (*ANeuralNetworksModel_free_fn)(ANeuralNetworksModel* model); - -typedef int (*ANeuralNetworksCompilation_create_fn)( - ANeuralNetworksModel* model, ANeuralNetworksCompilation** compilation); - -typedef void (*ANeuralNetworksCompilation_free_fn)( - ANeuralNetworksCompilation* compilation); - -typedef int (*ANeuralNetworksCompilation_setPreference_fn)( - ANeuralNetworksCompilation* compilation, int32_t preference); - -typedef int (*ANeuralNetworksCompilation_finish_fn)( - ANeuralNetworksCompilation* compilation); - -typedef int (*ANeuralNetworksModel_addOperand_fn)( - ANeuralNetworksModel* model, const ANeuralNetworksOperandType* type); - -typedef int (*ANeuralNetworksModel_setOperandValue_fn)( - ANeuralNetworksModel* model, int32_t index, const void* buffer, - size_t length); - -typedef int (*ANeuralNetworksModel_setOperandValueFromMemory_fn)( - ANeuralNetworksModel* model, int32_t index, - const ANeuralNetworksMemory* memory, size_t offset, size_t length); - -typedef int (*ANeuralNetworksModel_addOperation_fn)( - ANeuralNetworksModel* model, ANeuralNetworksOperationType type, - uint32_t inputCount, const uint32_t* inputs, uint32_t outputCount, - const uint32_t* outputs); - -typedef int (*ANeuralNetworksModel_identifyInputsAndOutputs_fn)( - ANeuralNetworksModel* model, uint32_t inputCount, const uint32_t* inputs, - uint32_t outputCount, const uint32_t* outputs); - -typedef int (*ANeuralNetworksModel_relaxComputationFloat32toFloat16_fn)( - ANeuralNetworksModel* model, bool allow); - -typedef int (*ANeuralNetworksExecution_create_fn)( - ANeuralNetworksCompilation* compilation, - ANeuralNetworksExecution** execution); - -typedef void (*ANeuralNetworksExecution_free_fn)( - ANeuralNetworksExecution* execution); - -typedef int (*ANeuralNetworksExecution_setInput_fn)( - ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, const void* buffer, size_t length); - -typedef int (*ANeuralNetworksExecution_setInputFromMemory_fn)( - ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, const ANeuralNetworksMemory* memory, - size_t offset, size_t length); - -typedef int (*ANeuralNetworksExecution_setOutput_fn)( - ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, void* buffer, size_t length); - -typedef int (*ANeuralNetworksExecution_setOutputFromMemory_fn)( - ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, const ANeuralNetworksMemory* memory, - size_t offset, size_t length); - -typedef int (*ANeuralNetworksExecution_startCompute_fn)( - ANeuralNetworksExecution* execution, ANeuralNetworksEvent** event); - -typedef int (*ANeuralNetworksEvent_wait_fn)(ANeuralNetworksEvent* event); - -typedef void (*ANeuralNetworksEvent_free_fn)(ANeuralNetworksEvent* event); - /** * Creates a shared memory object from a file descriptor. * diff --git a/tensorflow/lite/nnapi/NeuralNetworksTypes.h b/tensorflow/lite/nnapi/NeuralNetworksTypes.h new file mode 100644 index 0000000000..de8b84a823 --- /dev/null +++ b/tensorflow/lite/nnapi/NeuralNetworksTypes.h @@ -0,0 +1,352 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_LITE_NNAPI_NEURALNETWORKSTYPES_H_ +#define TENSORFLOW_LITE_NNAPI_NEURALNETWORKSTYPES_H_ + +#include +#include + +// NN api types based on NNAPI header file +// https://developer.android.com/ndk/reference/group/neural-networks + +/** + * Operand types. + * + * The type of operands that can be added to a model. + * + * Although we define many types, most operators accept just a few + * types. Most used are ANEURALNETWORKS_TENSOR_FLOAT32, + * ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, and ANEURALNETWORKS_INT32. + */ +enum { + ANEURALNETWORKS_FLOAT32 = 0, + ANEURALNETWORKS_INT32 = 1, + ANEURALNETWORKS_UINT32 = 2, + ANEURALNETWORKS_TENSOR_FLOAT32 = 3, + ANEURALNETWORKS_TENSOR_INT32 = 4, + ANEURALNETWORKS_TENSOR_QUANT8_ASYMM = 5, +}; + +/** + * Operation types. + * + * The type of operations that can be added to a model. + */ +enum { + ANEURALNETWORKS_ADD = 0, + ANEURALNETWORKS_AVERAGE_POOL_2D = 1, + ANEURALNETWORKS_CONCATENATION = 2, + ANEURALNETWORKS_CONV_2D = 3, + ANEURALNETWORKS_DEPTHWISE_CONV_2D = 4, + ANEURALNETWORKS_DEPTH_TO_SPACE = 5, + ANEURALNETWORKS_DEQUANTIZE = 6, + ANEURALNETWORKS_EMBEDDING_LOOKUP = 7, + ANEURALNETWORKS_FLOOR = 8, + ANEURALNETWORKS_FULLY_CONNECTED = 9, + ANEURALNETWORKS_HASHTABLE_LOOKUP = 10, + ANEURALNETWORKS_L2_NORMALIZATION = 11, + ANEURALNETWORKS_L2_POOL_2D = 12, + ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION = 13, + ANEURALNETWORKS_LOGISTIC = 14, + ANEURALNETWORKS_LSH_PROJECTION = 15, + ANEURALNETWORKS_LSTM = 16, + ANEURALNETWORKS_MAX_POOL_2D = 17, + ANEURALNETWORKS_MUL = 18, + ANEURALNETWORKS_RELU = 19, + ANEURALNETWORKS_RELU1 = 20, + ANEURALNETWORKS_RELU6 = 21, + ANEURALNETWORKS_RESHAPE = 22, + ANEURALNETWORKS_RESIZE_BILINEAR = 23, + ANEURALNETWORKS_RNN = 24, + ANEURALNETWORKS_SOFTMAX = 25, + ANEURALNETWORKS_SPACE_TO_DEPTH = 26, + ANEURALNETWORKS_SVDF = 27, + ANEURALNETWORKS_TANH = 28, + ANEURALNETWORKS_BATCH_TO_SPACE_ND = 29, + ANEURALNETWORKS_DIV = 30, + ANEURALNETWORKS_MEAN = 31, + ANEURALNETWORKS_PAD = 32, + ANEURALNETWORKS_SPACE_TO_BATCH_ND = 33, + ANEURALNETWORKS_SQUEEZE = 34, + ANEURALNETWORKS_STRIDED_SLICE = 35, + ANEURALNETWORKS_SUB = 36, + ANEURALNETWORKS_TRANSPOSE = 37, +}; + +/** + * Fused activation function types. + * + */ +enum { + ANEURALNETWORKS_FUSED_NONE = 0, + ANEURALNETWORKS_FUSED_RELU = 1, + ANEURALNETWORKS_FUSED_RELU1 = 2, + ANEURALNETWORKS_FUSED_RELU6 = 3, +}; + +/** + * Execution preferences. + */ +enum { + ANEURALNETWORKS_PREFER_LOW_POWER = 0, + ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER = 1, + ANEURALNETWORKS_PREFER_SUSTAINED_SPEED = 2, +}; + +/** + * Result codes. + */ +enum { + ANEURALNETWORKS_NO_ERROR = 0, + ANEURALNETWORKS_OUT_OF_MEMORY = 1, + ANEURALNETWORKS_INCOMPLETE = 2, + ANEURALNETWORKS_UNEXPECTED_NULL = 3, + ANEURALNETWORKS_BAD_DATA = 4, + ANEURALNETWORKS_OP_FAILED = 5, + ANEURALNETWORKS_UNMAPPABLE = 5, + ANEURALNETWORKS_BAD_STATE = 6, +}; + +/** + * Implicit padding algorithms. + */ +enum { + ANEURALNETWORKS_PADDING_SAME = 1, + ANEURALNETWORKS_PADDING_VALID = 2, +}; + +/** + * ANeuralNetworksMemory is an opaque type that represents memory. + * + * This type is used to represent shared memory, memory mapped files, + * and similar memories. + * + * By using shared memory, a program can efficiently communicate to the + * runtime and drivers the tensors that define a model. See + * {@link ANeuralNetworksModel_setOperandValueFromMemory}. An application + * should typically create one shared memory object that contains every tensor + * needed to define a model. {@link ANeuralNetworksMemory_createFromFd} can be + * used to create shared memory from a file handle. {@link + * ANeuralNetworksMemory_createShared} can be used to directly created shared + * memory. + * + * Memory objects can also be used to specify the input and output arguments of + * an execution. See {@link ANeuralNetworksExecution_setInputFromMemory} + * and {@link ANeuralNetworksExecution_setOutputFromMemory}. + */ +typedef struct ANeuralNetworksMemory ANeuralNetworksMemory; + +/** + * ANeuralNetworksModel is an opaque type that contains a description of the + * mathematical operations that constitute the model. + * + *

The model will be built by calling

    + *
  • {@link ANeuralNetworksModel_create},
  • + *
  • {@link ANeuralNetworksModel_addOperation},
  • + *
  • {@link ANeuralNetworksModel_addOperand},
  • + *
+ * + * A model is completed by calling {@link ANeuralNetworksModel_finish}. + * A model is destroyed by calling {@link ANeuralNetworksModel_free}. + * + *

It is the application's responsibility to make sure that only one thread + * modifies a model at a given time. It is however safe for more than one + * thread to use the model once {@link ANeuralNetworksModel_finish} has + * returned.

+ * + *

It is also the application's responsibility to ensure that there are no + * other uses of the model after calling {@link ANeuralNetworksModel_free}. This + * includes any compilation or execution object created using the model.

+ */ +typedef struct ANeuralNetworksModel ANeuralNetworksModel; + +/** + * ANeuralNetworksCompilation is an opaque type that can be used to compile + * a machine learning model. + * + *

To use:

    + *
  • Create a new compilation instance by calling the + * {@link ANeuralNetworksCompilation_create} function.
  • + *
  • Perform the compilation with {@link + * ANeuralNetworksCompilation_start}.
  • Wait for the compilation to + * complete with {@link ANeuralNetworksCompilation_wait}.
  • Use the + * compilation as many times as needed with {@link + * ANeuralNetworksExecution_create}.
  • Destroy the compilation with + * {@link ANeuralNetworksCompilation_free} once all executions using the + * compilation have completed.

+ * + *

A compilation cannot be modified once {@link + * ANeuralNetworksCompilation_start} has been called on it.

+ * + *

It is the application's responsibility to make sure that only one thread + * modifies a compilation at a given time. It is however safe for more than one + * thread to use {@link ANeuralNetworksCompilation_wait} at the same time. + * It is also safe for multiple threads to use a compilation object once + * {@link ANeuralNetworksCompilation_wait} has completed.

+ * + *

It is also the application's responsibility to ensure that there are no + * other uses of the compilation after calling {@link + * ANeuralNetworksCompilation_free}. This includes any execution object created + * using the compilation.

+ */ +typedef struct ANeuralNetworksCompilation ANeuralNetworksCompilation; + +/** + * ANeuralNetworksExecution is an opaque type that can be used to apply a + * machine learning model to a set of inputs. + * + *

To use:

    + *
  • Create a new execution instance by calling the + * {@link ANeuralNetworksExecution_create} function.
  • + *
  • Associate data to the model inputs with + * {@link ANeuralNetworksExecution_setInput} or + * {@link ANeuralNetworksExecution_setInputFromMemory}.
  • + *
  • Associate output buffers to the model outputs with + * {@link ANeuralNetworksExecution_setOutput} or + * {@link ANeuralNetworksExecution_setOutputFromMemory}.
  • + *
  • Apply the model with {@link + * ANeuralNetworksExecution_startCompute}.
  • Wait for the execution to + * complete with {@link ANeuralNetworksExecution_wait}.
  • Destroy the + * execution with + * {@link ANeuralNetworksExecution_free}.

+ * + *

An execution cannot be modified once {@link + * ANeuralNetworksExecution_start} has been called on it.

+ * + *

An execution can be applied to a model with + * {@link ANeuralNetworksExecution_startCompute} only once. Create new + * executions to do new evaluations of the model.

+ * + *

It is the application's responsibility to make sure that only one thread + * modifies an execution at a given time. It is however safe for more than one + * thread to use {@link ANeuralNetworksExecution_wait} at the same time.

+ * + *

It is also the application's responsibility to ensure that there are no + * other uses of the request after calling {@link + * ANeuralNetworksRequest_free}.

+ */ +typedef struct ANeuralNetworksExecution ANeuralNetworksExecution; + +/** + * ANeuralNetworksOperandType describes the type of an operand. + * This structure is used to describe both scalars and tensors. + */ +typedef struct ANeuralNetworksOperandType { + /** The data type, e.g ANEURALNETWORKS_INT8. */ + int32_t type; + /** The number of dimensions. It should be 0 for scalars. */ + uint32_t dimensionCount; + /** The dimensions of the tensor. It should be nullptr for scalars. */ + const uint32_t* dimensions; + /** These two fields are only used for quantized tensors. + * They should be zero for scalars and non-fixed point tensors. + * The dequantized value of each entry is (value - offset) * scale. + */ + float scale; + int32_t zeroPoint; +} ANeuralNetworksOperandType; + +/** + * ANeuralNetworksEvent is an opaque type that represents an event + * that will be signaled once an execution completes. + */ +typedef struct ANeuralNetworksEvent ANeuralNetworksEvent; + +typedef int32_t ANeuralNetworksOperationType; + +// nn api function types + +typedef int (*ANeuralNetworksMemory_createFromFd_fn)( + size_t size, int protect, int fd, size_t offset, + ANeuralNetworksMemory** memory); + +typedef void (*ANeuralNetworksMemory_free_fn)(ANeuralNetworksMemory* memory); + +typedef int (*ANeuralNetworksModel_create_fn)(ANeuralNetworksModel** model); + +typedef int (*ANeuralNetworksModel_finish_fn)(ANeuralNetworksModel* model); + +typedef void (*ANeuralNetworksModel_free_fn)(ANeuralNetworksModel* model); + +typedef int (*ANeuralNetworksCompilation_create_fn)( + ANeuralNetworksModel* model, ANeuralNetworksCompilation** compilation); + +typedef void (*ANeuralNetworksCompilation_free_fn)( + ANeuralNetworksCompilation* compilation); + +typedef int (*ANeuralNetworksCompilation_setPreference_fn)( + ANeuralNetworksCompilation* compilation, int32_t preference); + +typedef int (*ANeuralNetworksCompilation_finish_fn)( + ANeuralNetworksCompilation* compilation); + +typedef int (*ANeuralNetworksModel_addOperand_fn)( + ANeuralNetworksModel* model, const ANeuralNetworksOperandType* type); + +typedef int (*ANeuralNetworksModel_setOperandValue_fn)( + ANeuralNetworksModel* model, int32_t index, const void* buffer, + size_t length); + +typedef int (*ANeuralNetworksModel_setOperandValueFromMemory_fn)( + ANeuralNetworksModel* model, int32_t index, + const ANeuralNetworksMemory* memory, size_t offset, size_t length); + +typedef int (*ANeuralNetworksModel_addOperation_fn)( + ANeuralNetworksModel* model, ANeuralNetworksOperationType type, + uint32_t inputCount, const uint32_t* inputs, uint32_t outputCount, + const uint32_t* outputs); + +typedef int (*ANeuralNetworksModel_identifyInputsAndOutputs_fn)( + ANeuralNetworksModel* model, uint32_t inputCount, const uint32_t* inputs, + uint32_t outputCount, const uint32_t* outputs); + +typedef int (*ANeuralNetworksModel_relaxComputationFloat32toFloat16_fn)( + ANeuralNetworksModel* model, bool allow); + +typedef int (*ANeuralNetworksExecution_create_fn)( + ANeuralNetworksCompilation* compilation, + ANeuralNetworksExecution** execution); + +typedef void (*ANeuralNetworksExecution_free_fn)( + ANeuralNetworksExecution* execution); + +typedef int (*ANeuralNetworksExecution_setInput_fn)( + ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, const void* buffer, size_t length); + +typedef int (*ANeuralNetworksExecution_setInputFromMemory_fn)( + ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, const ANeuralNetworksMemory* memory, + size_t offset, size_t length); + +typedef int (*ANeuralNetworksExecution_setOutput_fn)( + ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, void* buffer, size_t length); + +typedef int (*ANeuralNetworksExecution_setOutputFromMemory_fn)( + ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, const ANeuralNetworksMemory* memory, + size_t offset, size_t length); + +typedef int (*ANeuralNetworksExecution_startCompute_fn)( + ANeuralNetworksExecution* execution, ANeuralNetworksEvent** event); + +typedef int (*ANeuralNetworksEvent_wait_fn)(ANeuralNetworksEvent* event); + +typedef void (*ANeuralNetworksEvent_free_fn)(ANeuralNetworksEvent* event); + +typedef int (*ASharedMemory_create_fn)(const char* name, size_t size); + +#endif // TENSORFLOW_LITE_NNAPI_NEURALNETWORKSTYPES_H_ -- GitLab From a6b1f39d10bf7312d7e3c43dcb7dea74b24c42e3 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 08:44:29 -0800 Subject: [PATCH 0716/2345] Replace calls to deprecated googletest macros *TEST_CASE() with *TEST_SUITE() PiperOrigin-RevId: 229378037 --- .../compiler/xla/client/lib/cholesky_test.cc | 10 +++---- .../xla/client/lib/triangular_solve_test.cc | 6 ++-- tensorflow/compiler/xla/literal_test.cc | 2 +- .../xla/service/algebraic_simplifier_test.cc | 18 ++++++------ .../cpu/cpu_instruction_fusion_test.cc | 7 +++-- .../xla/service/cpu/cpu_runtime_test.cc | 11 ++++---- .../cpu/tests/cpu_eigen_dot_operation_test.cc | 8 +++--- .../service/cpu/tests/cpu_intrinsic_test.cc | 8 +++--- .../xla/service/hlo_dataflow_analysis_test.cc | 6 ++-- .../xla/service/hlo_evaluator_test.cc | 4 +-- .../compiler/xla/service/hlo_parser_test.cc | 28 +++++++++---------- .../xla/service/hlo_rematerialization_test.cc | 4 +-- 12 files changed, 57 insertions(+), 55 deletions(-) diff --git a/tensorflow/compiler/xla/client/lib/cholesky_test.cc b/tensorflow/compiler/xla/client/lib/cholesky_test.cc index ba9580a3d3..095dd4fbf8 100644 --- a/tensorflow/compiler/xla/client/lib/cholesky_test.cc +++ b/tensorflow/compiler/xla/client/lib/cholesky_test.cc @@ -157,10 +157,10 @@ XLA_TEST_P(RandomCholeskyTest, Random) { xla::ErrorSpec(1e-4, 1e-4)); } -INSTANTIATE_TEST_CASE_P(RandomCholeskyTestInstance, RandomCholeskyTest, - ::testing::Values(CholeskyTestCase{1, 1}, - CholeskyTestCase{1, 2}, - CholeskyTestCase{10, 5}, - CholeskyTestCase{2, 20})); +INSTANTIATE_TEST_SUITE_P(RandomCholeskyTestInstance, RandomCholeskyTest, + ::testing::Values(CholeskyTestCase{1, 1}, + CholeskyTestCase{1, 2}, + CholeskyTestCase{10, 5}, + CholeskyTestCase{2, 20})); } // namespace diff --git a/tensorflow/compiler/xla/client/lib/triangular_solve_test.cc b/tensorflow/compiler/xla/client/lib/triangular_solve_test.cc index 703227c949..284a2e9d18 100644 --- a/tensorflow/compiler/xla/client/lib/triangular_solve_test.cc +++ b/tensorflow/compiler/xla/client/lib/triangular_solve_test.cc @@ -439,9 +439,9 @@ std::vector TriangularSolveTests() { return specs; } -INSTANTIATE_TEST_CASE_P(TriangularSolveParametricTestInstantiation, - TriangularSolveParametricTest, - ::testing::ValuesIn(TriangularSolveTests())); +INSTANTIATE_TEST_SUITE_P(TriangularSolveParametricTestInstantiation, + TriangularSolveParametricTest, + ::testing::ValuesIn(TriangularSolveTests())); } // namespace } // namespace xla diff --git a/tensorflow/compiler/xla/literal_test.cc b/tensorflow/compiler/xla/literal_test.cc index e67bb5c32f..b54a71ae68 100644 --- a/tensorflow/compiler/xla/literal_test.cc +++ b/tensorflow/compiler/xla/literal_test.cc @@ -641,7 +641,7 @@ template class LiteralUtilTestTemplated : public ::testing::Test {}; using TestedTypes = ::testing::Types; -TYPED_TEST_CASE(LiteralUtilTestTemplated, TestedTypes); +TYPED_TEST_SUITE(LiteralUtilTestTemplated, TestedTypes); TYPED_TEST(LiteralUtilTestTemplated, Relayout2x2) { // Make a non-integer for floating point types. diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc index d2ff0c9f21..0dea498456 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc @@ -2989,7 +2989,7 @@ class ConvInputPaddingTest : public AlgebraicSimplifierTest, public ::testing::WithParamInterface {}; -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( ConvInputPaddingTestCases, ConvInputPaddingTest, ::testing::ValuesIn(std::vector{ // Merge this edge padding into the conv. @@ -3097,7 +3097,7 @@ class ConvFilterPaddingTest : public AlgebraicSimplifierTest, public ::testing::WithParamInterface {}; -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( ConvFilterPaddingTestCases, ConvFilterPaddingTest, ::testing::ValuesIn(std::vector{ // Can only merge interior padding on the filter's spatial dimensions; @@ -4197,7 +4197,7 @@ PadReduceWindowEffectiveBroadcastCases() { return *cases; } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( PadReduceWindowEffectiveBroadcastInstantiation, PadReduceWindowEffectiveBroadcastTest, ::testing::ValuesIn(PadReduceWindowEffectiveBroadcastCases())); @@ -4248,7 +4248,7 @@ TEST_P(BatchDotStrengthReductionTest, BatchDotStrengthReduction) { EXPECT_EQ(has_no_dot, dot_should_be_transformed); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( BatchDotStrengthReductionTestInstantiation, BatchDotStrengthReductionTest, ::testing::Combine(::testing::Values(1, 2), ::testing::Values(1, 2), ::testing::Values(1, 2), ::testing::Values(F32, BF16))); @@ -4305,7 +4305,7 @@ TEST_P(DotStrengthReductionTest, DotStrengthReduction) { EXPECT_EQ(has_no_dot, dot_should_be_transformed); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( DotStrengthReductionTestInstantiation, DotStrengthReductionTest, ::testing::Combine(::testing::Values(1, 2), ::testing::Values(1, 2), ::testing::Values(1, 2), ::testing::Bool(), @@ -4479,9 +4479,9 @@ TEST_F(AlgebraicSimplifierTest, DynamicUpdateSliceZeroUpdate) { EXPECT_THAT(computation->root_instruction(), operand); } -INSTANTIATE_TEST_CASE_P(DotOfConcatSimplificationTestInstantiation, - DotOfConcatSimplificationTest, - ::testing::ValuesIn(kDotOfConcatTestSpecs)); +INSTANTIATE_TEST_SUITE_P(DotOfConcatSimplificationTestInstantiation, + DotOfConcatSimplificationTest, + ::testing::ValuesIn(kDotOfConcatTestSpecs)); struct DotOfGatherTestSpec { int64 m; @@ -4687,7 +4687,7 @@ std::vector DotOfGatherPositiveNegativeTests() { return all; } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( DotOfGatherSimplificationTestInstantiation, DotOfGatherSimplificationTest, ::testing::ValuesIn(DotOfGatherPositiveNegativeTests())); diff --git a/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc b/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc index 46c1d4c38e..86cc72dd7c 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc @@ -943,9 +943,10 @@ ENTRY main { return result; } -INSTANTIATE_TEST_CASE_P(GatherLoopFusionTestInstantiation, GatherLoopFusionTest, - ::testing::ValuesIn(GetGatherLoopFusionTestSpecs()), - GatherLoopFusionTestSpec::Name); +INSTANTIATE_TEST_SUITE_P(GatherLoopFusionTestInstantiation, + GatherLoopFusionTest, + ::testing::ValuesIn(GetGatherLoopFusionTestSpecs()), + GatherLoopFusionTestSpec::Name); } // namespace } // namespace cpu } // namespace xla diff --git a/tensorflow/compiler/xla/service/cpu/cpu_runtime_test.cc b/tensorflow/compiler/xla/service/cpu/cpu_runtime_test.cc index 1ae3aa5711..4e8c986783 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_runtime_test.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_runtime_test.cc @@ -162,11 +162,12 @@ TEST_P(EigenMatMulTest, DoIt) { CheckMatrixMultiply(*a, *b, *c); } -INSTANTIATE_TEST_CASE_P(EigenMatMulTestInstantiaion, EigenMatMulTest, - ::testing::Combine(::testing::ValuesIn(MatMulShapes), - ::testing::Bool(), ::testing::Bool(), - ::testing::Bool()), - EigenMatMulTest::Name); +INSTANTIATE_TEST_SUITE_P(EigenMatMulTestInstantiaion, EigenMatMulTest, + ::testing::Combine(::testing::ValuesIn(MatMulShapes), + ::testing::Bool(), + ::testing::Bool(), + ::testing::Bool()), + EigenMatMulTest::Name); #ifdef INTEL_MKL class MKLMatMulTest : public CpuRuntimeTest, diff --git a/tensorflow/compiler/xla/service/cpu/tests/cpu_eigen_dot_operation_test.cc b/tensorflow/compiler/xla/service/cpu/tests/cpu_eigen_dot_operation_test.cc index f8f5f392da..0b4ac9dc29 100644 --- a/tensorflow/compiler/xla/service/cpu/tests/cpu_eigen_dot_operation_test.cc +++ b/tensorflow/compiler/xla/service/cpu/tests/cpu_eigen_dot_operation_test.cc @@ -102,10 +102,10 @@ std::vector GetDotTestCases() { return result; } -INSTANTIATE_TEST_CASE_P(CpuEigenDotOperationTestInstantiation, - CpuEigenDotOperationTest, - ::testing::ValuesIn(GetDotTestCases()), - DotTestSpecToString); +INSTANTIATE_TEST_SUITE_P(CpuEigenDotOperationTestInstantiation, + CpuEigenDotOperationTest, + ::testing::ValuesIn(GetDotTestCases()), + DotTestSpecToString); } // namespace } // namespace cpu diff --git a/tensorflow/compiler/xla/service/cpu/tests/cpu_intrinsic_test.cc b/tensorflow/compiler/xla/service/cpu/tests/cpu_intrinsic_test.cc index 9b10c49f4f..3fb0e3cd91 100644 --- a/tensorflow/compiler/xla/service/cpu/tests/cpu_intrinsic_test.cc +++ b/tensorflow/compiler/xla/service/cpu/tests/cpu_intrinsic_test.cc @@ -140,10 +140,10 @@ IntrinsicTestSpec CpuUnaryIntrinsicTestCases[] = { HloOpcode::kLog, kTriple_android_arm, "", R"(CHECK: fadd fast <4 x float> )"}}; -INSTANTIATE_TEST_CASE_P(CpuUnaryIntrinsicTestInstantiation, - CpuUnaryIntrinsicTest, - ::testing::ValuesIn(CpuUnaryIntrinsicTestCases), - CpuUnaryIntrinsicTest::Name); +INSTANTIATE_TEST_SUITE_P(CpuUnaryIntrinsicTestInstantiation, + CpuUnaryIntrinsicTest, + ::testing::ValuesIn(CpuUnaryIntrinsicTestCases), + CpuUnaryIntrinsicTest::Name); } // namespace } // namespace cpu diff --git a/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc b/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc index 888886865b..4a7c4963b7 100644 --- a/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc +++ b/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc @@ -1901,9 +1901,9 @@ ENTRY %AddDependency (p: f32[3]) -> f32[3] { EXPECT_FALSE(analysis->ValueIsDefinedAt(root)); } -INSTANTIATE_TEST_CASE_P(HloDataflowAnalysisInstantiation, - HloDataflowAnalysisTest, - ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(HloDataflowAnalysisInstantiation, + HloDataflowAnalysisTest, + ::testing::Values(false, true)); class HloDataflowAnalysisTestBase : public HloTestBase { protected: diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc index 674df0016a..d34fa48efb 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc +++ b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc @@ -126,8 +126,8 @@ class HloEvaluatorBf16Test : public ::testing::WithParamInterface, HloEvaluatorBf16Test() : HloEvaluatorTest(/*use_bfloat16=*/GetParam()) {} }; -INSTANTIATE_TEST_CASE_P(HloEvaluatorTest_Instantiation, HloEvaluatorBf16Test, - ::testing::ValuesIn(use_bf16_params)); +INSTANTIATE_TEST_SUITE_P(HloEvaluatorTest_Instantiation, HloEvaluatorBf16Test, + ::testing::ValuesIn(use_bf16_params)); // Verifies that HloEvaluator evaluates a HLO instruction that performs clamp // with 3 operands. diff --git a/tensorflow/compiler/xla/service/hlo_parser_test.cc b/tensorflow/compiler/xla/service/hlo_parser_test.cc index 74ef8a2fba..6ba16cc82a 100644 --- a/tensorflow/compiler/xla/service/hlo_parser_test.cc +++ b/tensorflow/compiler/xla/service/hlo_parser_test.cc @@ -1371,20 +1371,20 @@ TEST_P(HloParserTestLongProto, Run) { ExpectEqual(); } TEST_P(HloParserTestShort, Run) { ExpectEqual(); } TEST_P(HloParserTestShortProto, Run) { ExpectEqual(); } -INSTANTIATE_TEST_CASE_P(HloParserTestSuccessInstantiation, HloParserTestLong, - ::testing::ValuesIn(CreateTestCases()), - TestDataToString); -INSTANTIATE_TEST_CASE_P(HloParserTestSuccessInstantiation, - HloParserTestLongProto, - ::testing::ValuesIn(CreateTestCases()), - TestDataToString); -INSTANTIATE_TEST_CASE_P(HloParserTestSuccessInstantiation, HloParserTestShort, - ::testing::ValuesIn(CreateShortTestCases()), - TestDataToString); -INSTANTIATE_TEST_CASE_P(HloParserTestSuccessInstantiation, - HloParserTestShortProto, - ::testing::ValuesIn(CreateShortTestCases()), - TestDataToString); +INSTANTIATE_TEST_SUITE_P(HloParserTestSuccessInstantiation, HloParserTestLong, + ::testing::ValuesIn(CreateTestCases()), + TestDataToString); +INSTANTIATE_TEST_SUITE_P(HloParserTestSuccessInstantiation, + HloParserTestLongProto, + ::testing::ValuesIn(CreateTestCases()), + TestDataToString); +INSTANTIATE_TEST_SUITE_P(HloParserTestSuccessInstantiation, HloParserTestShort, + ::testing::ValuesIn(CreateShortTestCases()), + TestDataToString); +INSTANTIATE_TEST_SUITE_P(HloParserTestSuccessInstantiation, + HloParserTestShortProto, + ::testing::ValuesIn(CreateShortTestCases()), + TestDataToString); class HloParserTest : public ::testing::Test { protected: diff --git a/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc b/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc index 22c3c40a93..c4bb475619 100644 --- a/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc +++ b/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc @@ -588,8 +588,8 @@ TEST_P(IndirectUseTest, IndirectUseNotRematerialized) { } } -INSTANTIATE_TEST_CASE_P(IndirectUseTestInstantiation, IndirectUseTest, - ::testing::Values(true, false)); +INSTANTIATE_TEST_SUITE_P(IndirectUseTestInstantiation, IndirectUseTest, + ::testing::Values(true, false)); } // namespace -- GitLab From e0c7bf29e7486f1b057abf50baf5cd4058e45eb0 Mon Sep 17 00:00:00 2001 From: Andy Ly Date: Tue, 15 Jan 2019 08:53:33 -0800 Subject: [PATCH 0717/2345] [Grappler] Don't allow creation of self loops on mutation in MutableGraphView. PiperOrigin-RevId: 229379324 --- .../core/grappler/mutable_graph_view.cc | 34 ++++ .../core/grappler/mutable_graph_view_test.cc | 186 +++++++++++++++++- 2 files changed, 218 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/grappler/mutable_graph_view.cc b/tensorflow/core/grappler/mutable_graph_view.cc index 13a530181c..2ba53a685d 100644 --- a/tensorflow/core/grappler/mutable_graph_view.cc +++ b/tensorflow/core/grappler/mutable_graph_view.cc @@ -388,6 +388,10 @@ Status MutableGraphView::AddRegularFanin(absl::string_view node_name, "Can't add$0 fanin '$1' as regular fanin to$2 node '$3'.", fanin_err, fanin.ToString(), node_err, node_name)); } + if (node_name == fanin.node()) { + return errors::Internal(absl::Substitute( + "Can't add fanin '$0' as regular fanin to self.", fanin.ToString())); + } AddFaninInternal(node, {fanin_node, fanin.index()}); return Status::OK(); @@ -406,6 +410,10 @@ Status MutableGraphView::AddControllingFanin(absl::string_view node_name, absl::Substitute("Can't add$0 controlling fanin '$1' to$2 node '$3'.", fanin_err, fanin.ToString(), node_err, node_name)); } + if (node_name == fanin.node()) { + return errors::Internal(absl::Substitute( + "Can't add controlling fanin '$0' to self.", fanin.ToString())); + } if (!IsSwitch(*fanin_node)) { AddFaninInternal(node, {fanin_node, Graph::kControlSlot}); @@ -426,6 +434,12 @@ Status MutableGraphView::AddControllingFanin(absl::string_view node_name, for (auto fanout : fanouts) { if (IsIdentity(*fanout.node) || IsIdentityNSingleInput(*fanout.node)) { if (ParseTensorName(fanout.node->input(0)) == fanin) { + if (fanout.node->name() == node_name) { + return errors::Internal(absl::Substitute( + "Can't add found controlling fanin '$0' from fanin '$1' to " + "self.", + AsControlDependency(fanout.node->name()), fanin.ToString())); + } AddFaninInternal(node, {fanout.node, Graph::kControlSlot}); return Status::OK(); } @@ -435,6 +449,11 @@ Status MutableGraphView::AddControllingFanin(absl::string_view node_name, // dependency: add a new identity node. string ctrl_dep_name = AddPrefixToNodeName( absl::StrCat(fanin.node(), "_", fanin.index()), kMutableGraphViewCtrl); + if (node_name == ctrl_dep_name) { + return errors::Internal(absl::Substitute( + "Can't add generated controlling fanin '$0' from fanin '$1' to self.", + AsControlDependency(ctrl_dep_name), fanin.ToString())); + } // Reuse a previously created node, if possible. NodeDef* ctrl_dep_node = GetNode(ctrl_dep_name); @@ -515,6 +534,11 @@ Status MutableGraphView::RemoveRegularFanin(absl::string_view node_name, "Can't remove$0 fanin '$1' as regular fanin from$2 node '$3'.", fanin_err, fanin.ToString(), node_err, node_name)); } + if (node_name == fanin.node()) { + return errors::Internal( + absl::Substitute("Can't remove fanin '$0' as regular fanin from self.", + fanin.ToString())); + } RemoveRegularFaninInternal(node, {fanin_node, fanin.index()}); return Status::OK(); @@ -550,6 +574,11 @@ Status MutableGraphView::RemoveControllingFanin( "Can't remove$0 controlling fanin '$1' from$2 node '$3'.", fanin_err, AsControlDependency(string(fanin_node_name)), node_err, node_name)); } + if (node_name == fanin_node_name) { + return errors::Internal( + absl::Substitute("Can't remove controlling fanin '$0' from self.", + AsControlDependency(string(fanin_node_name)))); + } RemoveControllingFaninInternal(node, fanin_node); return Status::OK(); @@ -615,6 +644,11 @@ Status MutableGraphView::UpdateFanin(absl::string_view node_name, "Switch control dependency.", from_fanin.ToString(), to_fanin.ToString(), node_name)); } + if (node_name == from_fanin.node() || node_name == to_fanin.node()) { + return errors::Internal(absl::Substitute( + "Can't update fanin '$0' to fanin '$1' in self '$2'.", + from_fanin.ToString(), to_fanin.ToString(), node_name)); + } if (from_fanin == to_fanin) { return Status::OK(); diff --git a/tensorflow/core/grappler/mutable_graph_view_test.cc b/tensorflow/core/grappler/mutable_graph_view_test.cc index 99a69ef263..597bf8a7ee 100644 --- a/tensorflow/core/grappler/mutable_graph_view_test.cc +++ b/tensorflow/core/grappler/mutable_graph_view_test.cc @@ -400,6 +400,12 @@ TEST(MutableGraphViewTest, AddRegularFanin) { "missing node 'foo_missing'."; TestAddRegularFanin("foo_missing", {"bar_missing", Graph::kControlSlot}, /*success=*/false, error_msg, /*expected_node=*/nullptr); + + // Add self to create cycle. + error_msg = "Can't add fanin 'foo_6:2' as regular fanin to self."; + expected_node = NDef("", "", {"^a", "^b"}); + TestAddRegularFanin("foo_6", {"foo_6", 2}, /*success=*/false, error_msg, + &expected_node); } void CheckFanout(const MutableGraphView& graph, const TensorId& fanin, @@ -540,6 +546,12 @@ TEST(MutableGraphViewTest, RemoveRegularFanin) { TestRemoveRegularFanin("foo_missing", {"bar_missing", Graph::kControlSlot}, /*success=*/false, error_msg, /*expected_node=*/nullptr); + + // Remove self. + error_msg = "Can't remove fanin 'foo_6:2' as regular fanin from self."; + expected_node = NDef("", "", {"^a", "^b"}); + TestRemoveRegularFanin("foo_6", {"foo_6", 2}, /*success=*/false, error_msg, + &expected_node); } void TestRemoveAllFanins(absl::string_view node_name, @@ -728,6 +740,22 @@ TEST(MutableGraphViewTest, UpdateFanin) { TestUpdateFanin("foo_missing", {"from_bar_missing", -2}, {"to_bar_missing", -3}, /*success=*/false, error_msg, /*expected_node=*/nullptr); + + // Update to self to create cycle. + expected_node = NDef("", "", {"a", "b:2", "b:2", "^c", "^d"}); + error_msg = "Can't update fanin 'b:2' to fanin 'foo_4:3' in self 'foo_4'."; + TestUpdateFanin("foo_4", {"b", 2}, {"foo_4", 3}, /*success=*/false, error_msg, + &expected_node); + error_msg = "Can't update fanin 'b:2' to fanin '^foo_4' in self 'foo_4'."; + TestUpdateFanin("foo_4", {"b", 2}, {"foo_4", Graph::kControlSlot}, + /*success=*/false, error_msg, &expected_node); + error_msg = "Can't update fanin '^c' to fanin 'foo_4:4' in self 'foo_4'."; + TestUpdateFanin("foo_4", {"c", Graph::kControlSlot}, {"foo_4", 4}, + /*success=*/false, error_msg, &expected_node); + error_msg = "Can't update fanin '^c' to fanin '^foo_4' in self 'foo_4'."; + TestUpdateFanin("foo_4", {"c", Graph::kControlSlot}, + {"foo_4", Graph::kControlSlot}, /*success=*/false, error_msg, + &expected_node); } void TestUpdateFaninFromFaninToNodeAsSwitchControl(const TensorId& fanin) { @@ -1125,6 +1153,30 @@ TEST(MutableGraphViewTest, AddControllingFaninNotSwitch) { CompareNodeInputs(graph, &expected, b); } +TEST(MutableGraphViewTest, AddControllingFaninSwitch) { + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), NDef("b", "Switch", {}, {})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + Status s = graph.AddControllingFanin("a", {"b", Graph::kControlSlot}); + EXPECT_FALSE(s.ok()); + string expected_msg = + "Can't add Switch as controlling fanin '^b' to node 'a'."; + EXPECT_EQ(s.error_message(), expected_msg); + + ASSERT_EQ(graph.graph()->node_size(), 2); + NodeDef* a = graph.GetNode("a"); + ASSERT_NE(a, nullptr); + NodeDef expected = NDef("", "", {}); + CompareNodeInputs(graph, &expected, a); + + NodeDef* b = graph.GetNode("b"); + ASSERT_NE(b, nullptr); + CompareNodeInputs(graph, &expected, b); +} + TEST(MutableGraphViewTest, AddControllingFaninSwitchWithIdentity) { GraphDef graph_def = test::function::GDef( {NDef("a", "NotImportant", {}, {}), NDef("switch", "Switch", {}, {}), @@ -1190,6 +1242,115 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithExistingAddedIdentity) { CompareNodeInputs(graph, &expected, a); } +void TestAddControllingFaninSelfLoops(absl::string_view node_name, + const TensorId& fanin, + const string& error_msg) { + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), + NDef("b", "Switch", {}, {{"T", DT_FLOAT}}), + NDef("c", "Identity", {"b:0"}), NDef("d", "Identity", {"b:1"}), + NDef("e", "NotImportant", {"^a"})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + Status s = graph.AddControllingFanin(node_name, fanin); + EXPECT_FALSE(s.ok()); + EXPECT_EQ(s.error_message(), error_msg); + + EXPECT_EQ(graph.graph()->node_size(), 5); + NodeDef expected; + NodeDef* a = graph.GetNode("a"); + ASSERT_NE(a, nullptr); + expected = NDef("", "", {}, {}); + CompareNodeInputs(graph, &expected, a); + + NodeDef* b = graph.GetNode("b"); + ASSERT_NE(b, nullptr); + CompareNodeInputs(graph, &expected, b); + + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + expected = NDef("", "", {"b:0"}); + CompareNodeInputs(graph, &expected, c); + + NodeDef* d = graph.GetNode("d"); + ASSERT_NE(d, nullptr); + expected = NDef("", "", {"b:1"}); + CompareNodeInputs(graph, &expected, d); + + NodeDef* e = graph.GetNode("e"); + ASSERT_NE(e, nullptr); + expected = NDef("", "", {"^a"}); + CompareNodeInputs(graph, &expected, e); +} + +TEST(MutableGraphViewTest, AddControllingFaninSelfLoops) { + string error_msg = "Can't add controlling fanin '^a' to self."; + TestAddControllingFaninSelfLoops("a", {"a", Graph::kControlSlot}, error_msg); + + // Adding Switch control dependency to Identity consumer. Node `c` is + // consuming `b:0`, so adding `b:0` as a control dependency, because it is a + // Switch, should trigger a lookup of outputs. As `c` is a consumer and an + // Identity, this will introduce a self loop, so no control dependency should + // be added. + error_msg = + "Can't add found controlling fanin '^c' from fanin 'b:0' to self."; + TestAddControllingFaninSelfLoops("c", {"b", 0}, error_msg); + + // Adding Switch control dependency to Identity consumer. Node `d` is + // consuming `b:1`, so adding `b:1` as a control dependency, because it is a + // Switch, should trigger a lookup of outputs. As `d` is a consumer and an + // Identity, this will introduce a self loop, so no control dependency should + // be added. + error_msg = + "Can't add found controlling fanin '^d' from fanin 'b:1' to self."; + TestAddControllingFaninSelfLoops("d", {"b", 1}, error_msg); +} + +TEST(MutableGraphViewTest, AddControllingFaninSelfLoopsGeneratedIdentity) { + GraphDef graph_def = + test::function::GDef({NDef("a", "NotImportant", {}, {}), + NDef("b", "Switch", {}, {{"T", DT_FLOAT}}), + NDef("c", "NotImportant", {}), + NDef("ConstantFoldingCtrl/b_1", "Identity", {})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + // Adding Switch control dependency to Identity node of the same name as a + // generated Identity node for pinning the control dependency. Because there + // are no consumers of `b:1`, there will be an attempt to generate an Identity + // node, with name `ConstantFoldingCtrl/b_1`. As the input node is of the same + // name, we will introduce a self loop, so no control dependency should be + // added. + Status s = graph.AddControllingFanin("ConstantFoldingCtrl/b_1", {"b", 1}); + EXPECT_FALSE(s.ok()); + string expected_msg = + "Can't add generated controlling fanin '^ConstantFoldingCtrl/b_1' from " + "fanin 'b:1' to self."; + EXPECT_EQ(s.error_message(), expected_msg); + + EXPECT_EQ(graph.graph()->node_size(), 4); + NodeDef expected; + NodeDef* a = graph.GetNode("a"); + ASSERT_NE(a, nullptr); + expected = NDef("", "", {}, {}); + CompareNodeInputs(graph, &expected, a); + + NodeDef* b = graph.GetNode("b"); + ASSERT_NE(b, nullptr); + CompareNodeInputs(graph, &expected, b); + + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + CompareNodeInputs(graph, &expected, c); + + NodeDef* d = graph.GetNode("ConstantFoldingCtrl/b_1"); + ASSERT_NE(d, nullptr); + CompareNodeInputs(graph, &expected, d); +} + TEST(MutableGraphViewTest, RemoveControllingFaninMissing) { // Actual node.op() is not important in this test. GraphDef graph_def = test::function::GDef( @@ -1233,7 +1394,7 @@ TEST(MutableGraphViewTest, RemoveControllingFaninOnRegularFanin) { // Actual node.op() is not important in this test. GraphDef graph_def = test::function::GDef( {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"a"}), - NDef("c", "NotImportant", {"a", "b", "^c"})}, + NDef("c", "NotImportant", {"a", "b"})}, /*funcs=*/{}); MutableGraphView graph(&graph_def); @@ -1244,7 +1405,28 @@ TEST(MutableGraphViewTest, RemoveControllingFaninOnRegularFanin) { ASSERT_EQ(graph.graph()->node_size(), 3); NodeDef* c = graph.GetNode("c"); ASSERT_NE(c, nullptr); - NodeDef expected = NDef("", "", {"a", "b", "^c"}); + NodeDef expected = NDef("", "", {"a", "b"}); + CompareNodeInputs(graph, &expected, c); +} + +TEST(MutableGraphViewTest, RemoveControllingFaninSelfLoop) { + // Actual node.op() is not important in this test. + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"a"}), + NDef("c", "NotImportant", {"a", "b"})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + Status s = graph.RemoveControllingFanin("c", "c"); + EXPECT_FALSE(s.ok()); + string expected_msg = "Can't remove controlling fanin '^c' from self."; + EXPECT_EQ(s.error_message(), expected_msg); + + ASSERT_EQ(graph.graph()->node_size(), 3); + NodeDef* c = graph.GetNode("c"); + ASSERT_NE(c, nullptr); + NodeDef expected = NDef("", "", {"a", "b"}); CompareNodeInputs(graph, &expected, c); } -- GitLab From 39ebdee5e7f5fb7d7f0626977d363426ed22e308 Mon Sep 17 00:00:00 2001 From: Sergei Lebedev Date: Tue, 15 Jan 2019 08:53:37 -0800 Subject: [PATCH 0718/2345] Fixed handling of np.longlong in as_dtype PiperOrigin-RevId: 229379334 --- tensorflow/python/framework/dtypes.py | 11 +++++++++++ tensorflow/python/framework/dtypes_test.py | 3 +++ 2 files changed, 14 insertions(+) diff --git a/tensorflow/python/framework/dtypes.py b/tensorflow/python/framework/dtypes.py index 9386cca379..465ea4229b 100644 --- a/tensorflow/python/framework/dtypes.py +++ b/tensorflow/python/framework/dtypes.py @@ -560,6 +560,17 @@ _NP_TO_TF = { _np_qint32: qint32, _np_bfloat16: bfloat16, } + +# On Python 2.X `np.longlong` could be a distinct type used for long +# integers e.g. 42L. See numpy/numpy#9799. +for pdt in [ + np.longlong, + np.ulonglong, +]: + if pdt not in _NP_TO_TF: + _NP_TO_TF[pdt] = next( + _NP_TO_TF[dt] for dt in _NP_TO_TF if dt == pdt().dtype) + _TF_TO_NP = { types_pb2.DT_HALF: np.float16, diff --git a/tensorflow/python/framework/dtypes_test.py b/tensorflow/python/framework/dtypes_test.py index 719fdc0953..7dd2a792d1 100644 --- a/tensorflow/python/framework/dtypes_test.py +++ b/tensorflow/python/framework/dtypes_test.py @@ -295,6 +295,9 @@ class TypesTest(test_util.TensorFlowTestCase): self.assertNotEqual(dtypes.int32, int) self.assertNotEqual(dtypes.float64, 2.1) + def testPythonLongConversion(self): + self.assertIs(dtypes.int64, dtypes.as_dtype(np.array(2**32).dtype)) + def testPythonTypesConversion(self): self.assertIs(dtypes.float32, dtypes.as_dtype(float)) self.assertIs(dtypes.bool, dtypes.as_dtype(bool)) -- GitLab From 4ff0c987c3358df220632320869d48bc8b51262f Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Tue, 15 Jan 2019 08:58:32 -0800 Subject: [PATCH 0719/2345] Account for the possibility that fully qualified name objects may not be normalized. Improve the consistency between function and classes in namer. PiperOrigin-RevId: 229379978 --- tensorflow/python/autograph/core/naming.py | 65 +++++++++++++++---- .../python/autograph/core/naming_test.py | 16 +++++ tensorflow/python/autograph/utils/misc.py | 9 ++- .../python/autograph/utils/misc_test.py | 15 ++++- 4 files changed, 88 insertions(+), 17 deletions(-) diff --git a/tensorflow/python/autograph/core/naming.py b/tensorflow/python/autograph/core/naming.py index b8d79daeba..245795c3d2 100644 --- a/tensorflow/python/autograph/core/naming.py +++ b/tensorflow/python/autograph/core/naming.py @@ -18,8 +18,16 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import enum + from tensorflow.python.autograph.pyct import inspect_utils from tensorflow.python.autograph.pyct import qual_names +from tensorflow.python.autograph.utils import misc + + +class _NamingStyle(enum.Enum): + SNAKE = 1 + CAMEL = 2 class Namer(object): @@ -46,17 +54,52 @@ class Namer(object): self.generated_names = set() + def _as_symbol_name(self, fqn, style=_NamingStyle.SNAKE): + """Returns a symbol name that matches a fully-qualified name. + + The returned name is safe to use for Python symbols. Any special characters + present in fqn are replaced according to the style argument. + + Examples: + + self._as_symbol_name('foo.bar', style=_NamingStyle.CAMEL) == 'FooBar' + self._as_symbol_name('foo.bar', style=_NamingStyle.SNAKE) == 'foo_bar' + + See the unit tests for more examples. + + Args: + fqn: Union[Text, Tuple[Text]] a fully-qualified symbol name. The qualifier + may include module, class names, attributes, etc. + style: _NamingStyle + Returns: + Text + """ + assert style in _NamingStyle + + if isinstance(fqn, tuple): + cn = '.'.join(fqn) + else: + cn = fqn + + # Until we clean up the whole FQN mechanism, `fqn` may not be + # canonical, that is, in can appear as ('foo.bar', 'baz') + # This replaces any characters that might remain because of that. + pieces = cn.split('.') + + if style == _NamingStyle.CAMEL: + pieces = tuple(misc.capitalize_initial(p) for p in pieces) + return ''.join(pieces) + elif style == _NamingStyle.SNAKE: + return '_'.join(pieces) + def compiled_class_name(self, original_fqn, live_entity=None): """See call_trees.FunctionNamer.compiled_class_name.""" if live_entity is not None and live_entity in self.renamed_calls: return self.renamed_calls[live_entity] - if isinstance(original_fqn, tuple): - original_name = '__'.join(original_fqn) - else: - original_name = original_fqn - - new_name_root = 'Tf%s' % original_name + canonical_name = self._as_symbol_name( + original_fqn, style=_NamingStyle.CAMEL) + new_name_root = 'Tf%s' % canonical_name new_name = new_name_root n = 0 while new_name in self.global_namespace: @@ -73,7 +116,6 @@ class Namer(object): live_entity=None, owner_type=None): """See call_trees.FunctionNamer.compiled_function_name.""" - if not self.recursive: return None, False @@ -84,15 +126,12 @@ class Namer(object): # Members are not renamed when part of an entire converted class. return None, False - if isinstance(original_fqn, tuple): - original_name = '__'.join(original_fqn) - else: - original_name = original_fqn - if live_entity is not None and live_entity in self.renamed_calls: return self.renamed_calls[live_entity], True - new_name_root = 'tf__%s' % original_name + canonical_name = self._as_symbol_name( + original_fqn, style=_NamingStyle.SNAKE) + new_name_root = 'tf__%s' % canonical_name new_name = new_name_root n = 0 while new_name in self.global_namespace: diff --git a/tensorflow/python/autograph/core/naming_test.py b/tensorflow/python/autograph/core/naming_test.py index 2db98836d1..cc8c4314a7 100644 --- a/tensorflow/python/autograph/core/naming_test.py +++ b/tensorflow/python/autograph/core/naming_test.py @@ -45,6 +45,22 @@ class NamerTest(test.TestCase): self.assertEqual(('tf__foo', True), namer.compiled_function_name( 'foo', foo)) + def test_compiled_function_name_unsanitized_fqn(self): + namer = naming.Namer({}, True, None, ()) + self.assertEqual(('tf__foo_bar', True), + namer.compiled_function_name('foo.bar')) + self.assertEqual(('tf__foo_bar_baz', True), namer.compiled_function_name( + ('foo.bar', 'baz'))) + + def test_compiled_class_name_basic(self): + namer = naming.Namer({}, True, None, ()) + self.assertEqual('TfFooBar', namer.compiled_class_name(('foo', 'Bar'))) + + def test_compiled_class_name_unsanitized_fqn(self): + namer = naming.Namer({}, True, None, ()) + self.assertEqual('TfFooBarBaz', + namer.compiled_class_name(('foo.bar', 'Baz'))) + def test_compiled_function_name_avoids_global_conflicts(self): def foo(): pass diff --git a/tensorflow/python/autograph/utils/misc.py b/tensorflow/python/autograph/utils/misc.py index 1b06caf0bd..046e6cf97d 100644 --- a/tensorflow/python/autograph/utils/misc.py +++ b/tensorflow/python/autograph/utils/misc.py @@ -23,7 +23,7 @@ from tensorflow.python.ops import array_ops def alias_tensors(*args): - """Wrap any Tensor arguments with an identity op. + """Wraps any Tensor arguments with an identity op. Any other argument, including Variables, is returned unchanged. @@ -48,3 +48,10 @@ def alias_tensors(*args): return alias_if_tensor(args[0]) raise ValueError('at least one argument required') + + +def capitalize_initial(s): + """Capitalizes the initial of a string only.""" + if s: + return s[0].upper() + s[1:] + return s diff --git a/tensorflow/python/autograph/utils/misc_test.py b/tensorflow/python/autograph/utils/misc_test.py index c78df48d62..24b5753a91 100644 --- a/tensorflow/python/autograph/utils/misc_test.py +++ b/tensorflow/python/autograph/utils/misc_test.py @@ -18,7 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.autograph.utils.misc import alias_tensors +from tensorflow.python.autograph.utils import misc from tensorflow.python.framework import test_util from tensorflow.python.framework.constant_op import constant from tensorflow.python.ops.variables import Variable @@ -27,11 +27,20 @@ from tensorflow.python.platform import test class MiscTest(test.TestCase): + def test_capitalize_initial(self): + self.assertEqual('', misc.capitalize_initial('')) + self.assertEqual('A', misc.capitalize_initial('A')) + self.assertEqual('Ab', misc.capitalize_initial('Ab')) + self.assertEqual('AbC', misc.capitalize_initial('AbC')) + self.assertEqual('A', misc.capitalize_initial('a')) + self.assertEqual('Ab', misc.capitalize_initial('ab')) + self.assertEqual('AbC', misc.capitalize_initial('abC')) + @test_util.run_deprecated_v1 def test_alias_single_tensor(self): a = constant(1) - new_a = alias_tensors(a) + new_a = misc.alias_tensors(a) self.assertFalse(new_a is a) with self.cached_session() as sess: self.assertEqual(1, self.evaluate(new_a)) @@ -43,7 +52,7 @@ class MiscTest(test.TestCase): s = 'a' l = [1, 2, 3] - new_a, new_v, new_s, new_l = alias_tensors(a, v, s, l) + new_a, new_v, new_s, new_l = misc.alias_tensors(a, v, s, l) self.assertFalse(new_a is a) self.assertTrue(new_v is v) -- GitLab From da47d5a9a6cb6b10b166d4f3169e50bdafec2683 Mon Sep 17 00:00:00 2001 From: Vojtech Bardiovsky Date: Tue, 15 Jan 2019 09:47:53 -0800 Subject: [PATCH 0720/2345] Refactoring: store structured inputs on FuncGraph, same as structured outputs. PiperOrigin-RevId: 229387670 --- tensorflow/python/eager/def_function.py | 6 ++- tensorflow/python/eager/function.py | 43 +++---------------- tensorflow/python/framework/func_graph.py | 40 +++++++++++++++++ .../saved_model/function_serialization.py | 4 +- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index bd70b9dad0..977f548b61 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -25,6 +25,7 @@ import weakref from tensorflow.python.eager import context from tensorflow.python.eager import function as function_lib from tensorflow.python.eager import lift_to_graph +from tensorflow.python.framework import func_graph as func_graph_module from tensorflow.python.framework import ops from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import math_ops @@ -459,7 +460,8 @@ class PolymorphicFunction(object): for signature in self._cached_input_signatures: flattened = nest.flatten(signature) if any( - isinstance(arg, function_lib.UnknownArgument) for arg in flattened): + isinstance(arg, func_graph_module.UnknownArgument) + for arg in flattened): logging.info("Unsupported signature for serialization: %s.", signature) continue concrete_function = self.get_concrete_function(*signature) @@ -482,7 +484,7 @@ class PolymorphicFunction(object): if self._stateless_fn: concrete_functions.extend(self._stateless_fn._function_cache.values()) for concrete_function in concrete_functions: - signature = concrete_function._python_call_signature + signature, _ = concrete_function.structured_input_signature equal_to_signature = functools.partial( function_lib.is_same_structure, signature, check_values=True) if not any(equal_to_signature(s) for s in seen): diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index b5bda35916..b0140e5c98 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -568,6 +568,11 @@ class Function(object): """Returns tensors in `self.graph` corresponding to arguments.""" return self._func_graph.inputs + @property + def structured_input_signature(self): + """Returns structured signature of the original function.""" + return self._func_graph.structured_input_signature + @property def outputs(self): """Returns tensors in `self.graph` corresponding to returned tensors.""" @@ -824,37 +829,6 @@ class Function(object): return ret -class UnknownArgument(object): - """Signifies an argument which is not currently handled.""" - pass - - -def convert_structure_to_signature(structure): - """Convert a potentially nested structure to a signature. - - Args: - structure: Structure to convert. - - Returns: - Identical structure that has TensorSpec objects instead of Tensors and - UknownArgument instead of any unsupported types. - """ - - def encode_arg(arg, name=None): - """A representation for this argument, for converting into signatures.""" - if isinstance(arg, ops.Tensor): - return tensor_spec.TensorSpec(arg.shape, arg.dtype, name) - if isinstance(arg, (int, float, bool, tensor_spec.TensorSpec)): - return arg - return UnknownArgument() - - # We are using the flattened paths to name the TensorSpecs. We need an - # explicit name for them downstream. - flattened_with_paths = nest.flatten_with_joined_string_paths(structure) - mapped = [encode_arg(arg, path) for path, arg in flattened_with_paths] - return nest.pack_sequence_as(structure, mapped) - - pywrap_tensorflow.RegisterType("Tensor", ops.Tensor) pywrap_tensorflow.RegisterType("IndexedSlices", ops.IndexedSlices) @@ -1347,14 +1321,7 @@ class PolymorphicFunction(object): autograph=self._autograph, arg_names=arg_names), self._function_attributes) - if self._input_signature: - python_call_signature = self._input_signature - else: - python_call_signature = tuple(convert_structure_to_signature(args)) # pylint: disable=protected-access - # Save information about non-Tensor arguments with the concrete - # function. Used to serialize PolymorphicFunctions. - graph_function._python_call_signature = python_call_signature # Tell the Function to clean up its graph once it goes out of # scope. Function does not do this in its constructor since it gets used # in some places (like Keras) where the FuncGraph lives longer than the diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index 9603c1536d..5202e9b8f9 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -58,6 +58,37 @@ WHITELIST_COLLECTIONS = [ ] +class UnknownArgument(object): + """Signifies an argument which is not currently handled.""" + pass + + +def convert_structure_to_signature(structure): + """Convert a potentially nested structure to a signature. + + Args: + structure: Structure to convert. + + Returns: + Identical structure that has TensorSpec objects instead of Tensors and + UknownArgument instead of any unsupported types. + """ + + def encode_arg(arg, name=None): + """A representation for this argument, for converting into signatures.""" + if isinstance(arg, ops.Tensor): + return tensor_spec.TensorSpec(arg.shape, arg.dtype, name) + if isinstance(arg, (int, float, bool, tensor_spec.TensorSpec)): + return arg + return UnknownArgument() + + # We are using the flattened paths to name the TensorSpecs. We need an + # explicit name for them downstream. + flattened_with_paths = nest.flatten_with_joined_string_paths(structure) + mapped = [encode_arg(arg, path) for path, arg in flattened_with_paths] + return nest.pack_sequence_as(structure, mapped) + + class FuncGraph(ops.Graph): """Graph representing a function body. @@ -69,6 +100,9 @@ class FuncGraph(ops.Graph): inputs coming first. outputs: Tensors that will be returned by this function. The tensors are in this FuncGraph. + structured_input_signature: A possibly-nested python object that was + received by this function. Note that this structure might contain Python + `None`s. structured_outputs: A possibly-nested python object which will be returned by this function. The Tensors in this structure are the same as those of self.outputs. Note that this structure might contain Python `None`s. @@ -101,6 +135,7 @@ class FuncGraph(ops.Graph): self.name = name self.inputs = [] self.outputs = [] + self.structured_input_signature = None self.structured_outputs = None self._weak_variables = [] self.outer_graph = ops.get_default_graph() @@ -407,6 +442,11 @@ def func_graph_from_py_func(name, func_args = _get_defun_inputs_from_args(args, arg_names) func_kwargs = _get_defun_inputs_from_kwargs(kwargs) + # Convert all Tensors into TensorSpecs before saving the structured inputs. + func_graph.structured_input_signature = ( + convert_structure_to_signature(func_args), + convert_structure_to_signature(func_kwargs)) + # Note: `nest.flatten` sorts by keys, as does `_deterministic_dict_values`. # Variables to help check whether mutation happens in calling the function # Copy the recursive list, tuple and map structure, but not base objects diff --git a/tensorflow/python/saved_model/function_serialization.py b/tensorflow/python/saved_model/function_serialization.py index e5d3cc4bd2..8e8e3bc856 100644 --- a/tensorflow/python/saved_model/function_serialization.py +++ b/tensorflow/python/saved_model/function_serialization.py @@ -18,7 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.eager import function as defun_lib +from tensorflow.python.framework import func_graph as func_graph_module from tensorflow.python.platform import tf_logging as logging from tensorflow.python.saved_model import nested_structure_coder from tensorflow.python.saved_model import saved_object_graph_pb2 @@ -64,7 +64,7 @@ def serialize_polymorphic_function(polymorphic_function, node_ids): function_proto.concrete_function = concrete_function.name function_proto.canonicalized_input_signature.CopyFrom( coder.encode_structure(signature)) - structured_outputs = defun_lib.convert_structure_to_signature( + structured_outputs = func_graph_module.convert_structure_to_signature( concrete_function.structured_outputs) function_proto.output_signature.CopyFrom( coder.encode_structure(structured_outputs)) -- GitLab From c223fbd116f2266ba7129eb8257c8c8a9eceb499 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 10:15:20 -0800 Subject: [PATCH 0721/2345] Upgrade bazel-toolchain to the latest release Also removed bazel-toolchain support for Bazel version prior to 0.19.0 and updated check_bazel_version_at_least in WORKSPACE file To fix https://github.com/bazelbuild/bazel/issues/7113 PiperOrigin-RevId: 229392830 --- WORKSPACE | 2 +- .../preconfig/generate/archives.bzl | 32 ++++++------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 1c59686f16..957b8d8528 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -73,7 +73,7 @@ swift_rules_dependencies() # files, in case the parsing of those build files depends on the bazel # version we require here. load("//tensorflow:version_check.bzl", "check_bazel_version_at_least") -check_bazel_version_at_least("0.18.0") +check_bazel_version_at_least("0.19.0") load("//tensorflow:workspace.bzl", "tf_workspace") diff --git a/third_party/toolchains/preconfig/generate/archives.bzl b/third_party/toolchains/preconfig/generate/archives.bzl index 0850893589..bafc7d4943 100644 --- a/third_party/toolchains/preconfig/generate/archives.bzl +++ b/third_party/toolchains/preconfig/generate/archives.bzl @@ -2,26 +2,12 @@ load("//tensorflow:version_check.bzl", "parse_bazel_version") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") def bazel_toolchains_archive(): - # Not all bazel versions have set native.bazel_version - if it is not set, - # fall back to the more compatible version of the toolchains archive. - if native.bazel_version and parse_bazel_version(native.bazel_version) >= parse_bazel_version("0.19"): - # This version of the toolchains repo is incompatible with older bazel - # versions - we can remove this once TensorFlow drops support for bazel - # before 0.19. - http_archive( - name = "bazel_toolchains", - sha256 = "41c48a189be489e2d15dec40e0057ea15b95ee5b39cc2a7e6cf663e31432c75e", - strip_prefix = "bazel-toolchains-3f8c58fe530fedc446de04673bc1e32985887dea", - urls = [ - "https://github.com/nlopezgi/bazel-toolchains/archive/3f8c58fe530fedc446de04673bc1e32985887dea.tar.gz", - ], - ) - else: - http_archive( - name = "bazel_toolchains", - sha256 = "15b5858b1b5541ec44df31b94c3b8672815b31d71215a98398761ea9f4c4eedb", - strip_prefix = "bazel-toolchains-6200b238c9c2d137c0d9a7262c80cc71d98e692b", - urls = [ - "https://github.com/bazelbuild/bazel-toolchains/archive/6200b238c9c2d137c0d9a7262c80cc71d98e692b.tar.gz", - ], - ) + http_archive( + name = "bazel_toolchains", + sha256 = "ee854b5de299138c1f4a2edb5573d22b21d975acfc7aa938f36d30b49ef97498", + strip_prefix = "bazel-toolchains-37419a124bdb9af2fec5b99a973d359b6b899b61", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/37419a124bdb9af2fec5b99a973d359b6b899b61.tar.gz", + "https://github.com/bazelbuild/bazel-toolchains/archive/37419a124bdb9af2fec5b99a973d359b6b899b61.tar.gz", + ], + ) -- GitLab From 9d1b0f9ce7f31d9ed217c06e6c5163abf5f2944d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 10:21:55 -0800 Subject: [PATCH 0722/2345] Automated fixes for core/kernels/BUILD: Remove an unreferenced import of the tf_proto_library rule. Remove full package qualifier from deps on targets in the same package. PiperOrigin-RevId: 229394137 --- tensorflow/core/kernels/BUILD | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 4f21b466cd..1be749289b 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -48,7 +48,6 @@ load("//tensorflow:tensorflow.bzl", "tf_cuda_cc_tests") load( "//tensorflow/core:platform/default/build_config.bzl", "tf_kernel_tests_linkstatic", - "tf_proto_library", ) load( "//tensorflow/core:platform/default/build_config_root.bzl", @@ -509,12 +508,12 @@ cc_library( name = "batch_kernels", srcs = ["batch_kernels.cc"], deps = [ + ":concat_lib_hdrs", + ":ops_util_hdrs", + ":split_lib_hdrs", "//tensorflow/core:batch_ops_op_lib", "//tensorflow/core:framework_headers_lib", "//tensorflow/core:protos_all_cc", - "//tensorflow/core/kernels:concat_lib_hdrs", - "//tensorflow/core/kernels:ops_util_hdrs", - "//tensorflow/core/kernels:split_lib_hdrs", "//tensorflow/core/kernels/batching_util:periodic_function_dynamic", "//tensorflow/core/kernels/batching_util:shared_batch_scheduler_hdrs", ], @@ -1147,13 +1146,13 @@ tf_cc_test( size = "small", srcs = ["ragged_gather_op_test.cc"], deps = [ + ":ops_testutil", ":ragged_gather_op", "//tensorflow/core:framework", "//tensorflow/core:ragged_array_ops_op_lib", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:ops_testutil", ], ) @@ -1170,13 +1169,13 @@ tf_cc_test( name = "ragged_range_op_test", srcs = ["ragged_range_op_test.cc"], deps = [ + ":ops_testutil", ":ragged_range_op", "//tensorflow/core:framework", "//tensorflow/core:ragged_math_ops_op_lib", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:ops_testutil", ], ) @@ -1194,6 +1193,7 @@ tf_cc_test( size = "small", srcs = ["ragged_tensor_to_sparse_kernel_test.cc"], deps = [ + ":ops_testutil", ":ragged_tensor_to_sparse_kernel", "//tensorflow/core:framework", "//tensorflow/core:lib", @@ -1201,7 +1201,6 @@ tf_cc_test( "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:ops_testutil", ], ) @@ -1210,13 +1209,13 @@ tf_kernel_library( srcs = ["cudnn_rnn_ops.cc"], visibility = ["//visibility:public"], deps = [ + ":bounds_check_lib", ":gpu_util_hdrs", "//tensorflow/core:cudnn_rnn_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:stream_executor", - "//tensorflow/core/kernels:bounds_check_lib", "//third_party/eigen3", "@farmhash_archive//:farmhash", ], @@ -4857,6 +4856,8 @@ tf_cc_test( size = "small", srcs = ["string_format_op_test.cc"], deps = [ + ":ops_testutil", + ":ops_util", ":string_format_op", "//tensorflow/core:core_cpu", "//tensorflow/core:framework", @@ -4865,8 +4866,6 @@ tf_cc_test( "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:ops_testutil", - "//tensorflow/core/kernels:ops_util", ], ) @@ -4899,6 +4898,8 @@ tf_cc_test( size = "small", srcs = ["regex_replace_op_test.cc"], deps = [ + ":ops_testutil", + ":ops_util", ":regex_replace_op", "//tensorflow/core:core_cpu", "//tensorflow/core:framework", @@ -4907,8 +4908,6 @@ tf_cc_test( "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:ops_testutil", - "//tensorflow/core/kernels:ops_util", ], ) @@ -4923,6 +4922,8 @@ tf_cc_test( size = "small", srcs = ["string_split_op_test.cc"], deps = [ + ":ops_testutil", + ":ops_util", ":string_split_op", "//tensorflow/core:core_cpu", "//tensorflow/core:framework", @@ -4931,8 +4932,6 @@ tf_cc_test( "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:ops_testutil", - "//tensorflow/core/kernels:ops_util", ], ) @@ -4953,6 +4952,8 @@ tf_cc_test( size = "small", srcs = ["substr_op_test.cc"], deps = [ + ":ops_testutil", + ":ops_util", ":substr_op", "//tensorflow/core:core_cpu", "//tensorflow/core:framework", @@ -4962,8 +4963,6 @@ tf_cc_test( "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:ops_testutil", - "//tensorflow/core/kernels:ops_util", ], ) @@ -6618,6 +6617,7 @@ cc_library( srcs = ["remote_fused_graph_execute_op_test_utils.cc"], hdrs = ["remote_fused_graph_execute_op_test_utils.h"], deps = [ + ":cwise_op", ":remote_fused_graph_execute_utils", "//tensorflow/cc:cc_ops", "//tensorflow/cc:ops", @@ -6625,7 +6625,6 @@ cc_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:cwise_op", ], ) @@ -6636,6 +6635,7 @@ tf_cc_test( "remote_fused_graph_execute_utils_test.cc", ], deps = [ + ":cwise_op", ":remote_fused_graph_execute_op_test_utils", ":remote_fused_graph_execute_utils", "//tensorflow/cc:cc_ops", @@ -6651,7 +6651,6 @@ tf_cc_test( "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:cwise_op", ], ) -- GitLab From e0cf2939f71c7742a111de968d9008fa3f880570 Mon Sep 17 00:00:00 2001 From: Guangda Lai <31743510+aaroey@users.noreply.github.com> Date: Tue, 15 Jan 2019 10:33:10 -0800 Subject: [PATCH 0723/2345] Add comments and fix build. --- tensorflow/contrib/tensorrt/BUILD | 3 -- .../contrib/tensorrt/kernels/trt_engine_op.cc | 9 +++- .../contrib/tensorrt/kernels/trt_engine_op.h | 13 ------ .../tensorrt/resources/trt_lru_cache.h | 44 +++++++++---------- .../test/dynamic_input_shapes_test.py | 6 +++ .../test/tf_trt_integration_test_base.py | 33 +++++++++++--- 6 files changed, 61 insertions(+), 47 deletions(-) diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 3df56271de..17717c8403 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -511,11 +511,8 @@ cuda_py_tests( "test/conv2d_test.py", "test/dynamic_input_shapes_test.py", "test/identity_output_test.py", -<<<<<<< ab254f392694eb2f7231526c64fc301ef10a1384 "test/int32_test.py", -======= "test/lru_cache_test.py", ->>>>>>> initial commit (add lru_cache class, enable trt_engine_op to use it) "test/manual_test.py", "test/memory_alignment_test.py", "test/multi_connection_neighbor_engine_test.py", diff --git a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc index 06a62fed37..45f3b8d0e5 100644 --- a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc +++ b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc @@ -249,6 +249,10 @@ bool TRTEngineOp::GetCompatibleCachedEngine( *engine_input_shapes = actual_input_shapes; for (const int cached_batch_size : cached_engine_batch_sizes_) { // Check if compatible: batch <= cached batch. + // + // TODO(laigd): here it only compare the first dim a.k.a the batch size, + // we'll need to to support non-batch dimensions as well. This will be done + // as part of the offline conversion implementation. if (batch_size <= cached_batch_size) { // First case: first compatible engine found // Second case: smaller batch size engine found @@ -466,7 +470,8 @@ EngineContext* TRTEngineOp::GetEngine( // TODO(tmorris): will all inputs have batch size as first dimension?? engine_input_shapes[i].set_dim(0, max_batch_size); } - // Assume engine_input_shapes matches the input shapes of the engine. + // TODO(laigd): here we assume engine_input_shapes matches the actual input + // shapes of the engine, we should verify that. cache.emplace(engine_input_shapes, absl::make_unique( std::move(static_engine), @@ -484,7 +489,7 @@ EngineContext* TRTEngineOp::GetEngine( // See if there is a compatible engine cached. The batch size should be <= the // cached batch size. std::vector engine_input_shapes; - bool matched_successfully = + const bool matched_successfully = GetCompatibleCachedEngine(input_shapes, &engine_input_shapes); // If matched, use that engine. Otherwise, we will look in cache for that // exact shape and possibly create a new engine if it is not in cache. diff --git a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h index 4f39a4c548..c9b325cfd2 100644 --- a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h +++ b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h @@ -53,19 +53,6 @@ class TRTEngineOp : public AsyncOpKernel { private: // TODO(samikama): context should go to a resource manager! - struct EngineContext { - EngineContext() {} // Creates an empty context. - EngineContext( - TrtUniquePtrType&& input_cuda_engine, - TrtUniquePtrType&& input_execution_context) - : cuda_engine(std::move(input_cuda_engine)), - execution_context(std::move(input_execution_context)) {} - - mutex mu; - TrtUniquePtrType cuda_engine; - TrtUniquePtrType execution_context - GUARDED_BY(mu); - }; // Execute calibration void ExecuteCalibration(OpKernelContext* ctx, AsyncHelper* helper); diff --git a/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h b/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h index 57267e5b71..c9648420d0 100644 --- a/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h +++ b/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h @@ -22,8 +22,9 @@ class LRUCache { typedef Value value_type; typedef Key key_type; typedef HashFunction hasher; - typedef typename std::unordered_map::iterator - map_iterator; + typedef typename std::unordered_map map_type; + typedef typename map_type::iterator iterator; + typedef typename map_type::const_iterator const_iterator; LRUCache() : capacity_(0) {} explicit LRUCache(size_t capacity) : capacity_(capacity) {} @@ -39,31 +40,26 @@ class LRUCache { size_t count(const key_type& k) const { return objects_.count(k); } - value_type& at(key_type k, tensorflow::Status* status_ptr = nullptr) { - tensorflow::Status status = Touch(k); - if (!status.ok()) { - if (status_ptr) { - *status_ptr = status; - } - return not_found_value_; - } - return objects_.at(k); + value_type& at(key_type k) { + return Touch(k); } - map_iterator begin() { return objects_.begin(); } + const_iterator begin() const { return objects_.begin(); } + const_iterator end() const { return objects_.end(); } - map_iterator end() { return objects_.end(); } + iterator begin() { return objects_.begin(); } + iterator end() { return objects_.end(); } template - std::pair emplace(Args&&... args) { + std::pair emplace(Args&&... args) { DiscardOld(1); - std::pair result = + std::pair result = objects_.emplace(std::forward(args)...); key_type key = result.first->first; if (result.second) { keys_.push_front(key); } else { - Touch(key); + TouchNoCheck(key); // The key must exist in this case. } return result; } @@ -74,16 +70,20 @@ class LRUCache { size_t capacity_; value_type not_found_value_; - tensorflow::Status Touch(const key_type& key) { - if (!count(key)) { - return tensorflow::errors::NotFound("Key not found in cache."); - } + value_type& Touch(const key_type& key) { + // Check that the key exists, and let it return std::out_of_range error if + // not. + value_type& value = objects_.at(k); + TouchNoCheck(key); + return value; + } + + void TouchNoCheck(const key_type& key) { auto rank = std::find(keys_.begin(), keys_.end(), key); if (rank != keys_.begin()) { keys_.erase(rank); keys_.push_front(key); } - return tensorflow::Status::OK(); } // Creates n free positions in cache @@ -149,7 +149,7 @@ class TRTEngineCacheResource : public tensorflow::ResourceBase { oss << "TRTBaseAllocator = " << hex << allocator_.get() << dec << ", "; oss << "LRUCache = " << hex << &cache_ << dec << endl; oss << "Containing " << cache_.size() << " entries: " << endl; - for (auto& item : cache_) { + for (const auto& item : cache_) { oss << TensorShapeUtils::ShapeListString(item.first) << ": " << hex << "ICudaEngine: " << item.second.get()->cuda_engine.get() << ", " << "IExecutionContext: " << item.second.get()->execution_context.get() diff --git a/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py b/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py index 9985561e1e..1c6d411d11 100644 --- a/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py +++ b/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py @@ -34,6 +34,12 @@ import numpy as np class DynamicInputShapesTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): + # TODO(laigd): we should test the following cases: + # - batch size is not changed, other dims are changing + # - batch size is decreasing, other dims are identical + # - batch size is decreasing, other dims are changing + # - batch size is increasing, other dims are identical + # - batch size is increasing, other dims are changing input_dims = [[[1, 5, 5, 1]], [[10, 5, 5, 1]], [[3, 5, 5, 1]], [[1, 5, 5, 1]], [[1, 3, 1, 1]], [[2, 9, 9, 1]], [[1, 224, 224, 1]], [[1, 128, 224, 1]]] diff --git a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py index a105981298..db6be3828c 100644 --- a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py +++ b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py @@ -40,7 +40,15 @@ from tensorflow.python.ops import math_ops from tensorflow.python.platform import tf_logging as logging TfTrtIntegrationTestParams = namedtuple("TfTrtIntegrationTestParams", [ - "gdef", "input_names", "input_dims", "output_names", "expected_output_dims" + "gdef", + # A list of names of the input placeholder nodes. + "input_names", + # A list of list of output shapes of the input placeholder nodes. + "input_dims", + # A list of names of the output identity nodes. + "output_names", + # A list of list of expected output shapes of the output identity nodes. + "expected_output_dims" ]) RunParams = namedtuple("RunParams", [ @@ -159,10 +167,19 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): def GetConversionParams(self, run_params): """Return a ConversionParams for test.""" + batch_list + for dims_list in self._GetParamsCached().input_dims: + if not len(dims_list): + continue + # Each list of shapes should have same batch size. + input_batches = [dims[0] for dims in dims_list] + assert max(input_batches) == min(input_batches) + batch_list.append(input_batches[0]) return ConversionParams( - max_batch_size=max([ - dims[0] for inp in self._GetParamsCached().input_dims if len(inp) for dims in inp - ]), + # We use the minimum of all the batch sizes, so when multiple different + # input shapes are provided it'll always create new engines in the + # cache, and we can therefore test the cache behavior. + max_batch_size=min(batch_list), max_workspace_size_bytes=1 << 25, precision_mode=run_params.precision_mode, minimum_segment_size=2, @@ -317,8 +334,9 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): output_len = len(params.expected_output_dims[ind]) self.assertEqual(output_len, len(new_val)) for i in range(output_len): - self.assertEqual(list(params.expected_output_dims[ind][i]), - list(new_val[i].shape)) + self.assertEqual( + list(params.expected_output_dims[ind][i]), + list(new_val[i].shape)) if val is not None: self.assertAllClose(val, new_val, atol=1.e-06, rtol=1.e-06) val = new_val @@ -497,7 +515,8 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): # seq = np.arange(np.prod(dims)) # seq.resize(dims) # input_data.append(scale * seq.astype(dtype)) - current_input_data.append((scale * np.random.random_sample(dims)).astype(dtype)) + current_input_data.append( + (scale * np.random.random_sample(dims)).astype(dtype)) inputs_data.append(current_input_data) self._VerifyGraphDef(run_params, input_gdef, GraphState.ORIGINAL) -- GitLab From 431165fc97f12d19d6311dfb8b882e3c36d1ca54 Mon Sep 17 00:00:00 2001 From: Guangda Lai <31743510+aaroey@users.noreply.github.com> Date: Tue, 15 Jan 2019 10:40:05 -0800 Subject: [PATCH 0724/2345] Fix a bug: when building static engine, it should use engine input shapes as key instead of actual input shapes to lookup the cache. Also fix build. --- tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc | 6 ++---- tensorflow/contrib/tensorrt/resources/trt_lru_cache.h | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc index 45f3b8d0e5..f089f2c1cc 100644 --- a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc +++ b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc @@ -482,7 +482,7 @@ EngineContext* TRTEngineOp::GetEngine( if (max_batch_size < batch_size) { return &empty_context; } - return cache.at(input_shapes).get(); + return cache.at(engine_input_shapes).get(); } // static_engine_ // Handle the dynamic engine case. @@ -503,10 +503,8 @@ EngineContext* TRTEngineOp::GetEngine( cached_engine_batch_sizes_.push_back(batch_size); } } - tensorflow::Status engine_found_status; - cache.at(engine_input_shapes, &engine_found_status); - if (!engine_found_status.ok()) { + if (!cache.count(engine_input_shapes)) { TrtUniquePtrType engine; bool convert_successfully = false; LOG(INFO) << "Building a new TensorRT engine for " << name() diff --git a/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h b/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h index c9648420d0..ba455b9b35 100644 --- a/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h +++ b/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h @@ -38,10 +38,10 @@ class LRUCache { size_t size() const { return objects_.size(); } - size_t count(const key_type& k) const { return objects_.count(k); } + size_t count(const key_type& key) const { return objects_.count(key); } - value_type& at(key_type k) { - return Touch(k); + value_type& at(const key_type& key) { + return Touch(key); } const_iterator begin() const { return objects_.begin(); } @@ -73,7 +73,7 @@ class LRUCache { value_type& Touch(const key_type& key) { // Check that the key exists, and let it return std::out_of_range error if // not. - value_type& value = objects_.at(k); + value_type& value = objects_.at(key); TouchNoCheck(key); return value; } -- GitLab From 347bcbd4ffda686e0b4df95b451e0101c29f9a72 Mon Sep 17 00:00:00 2001 From: Shashi Shekhar Date: Tue, 15 Jan 2019 10:37:54 -0800 Subject: [PATCH 0725/2345] Add quantization for some activation only ops that don't have weights. Ops included: - Squeeze - Softmax - Avg and Max pool. PiperOrigin-RevId: 229397364 --- .../tools/optimize/quantize_model_test.cc | 2 +- .../lite/tools/optimize/subgraph_quantizer.cc | 90 +++++++++- .../lite/tools/optimize/subgraph_quantizer.h | 14 ++ .../tools/optimize/subgraph_quantizer_test.cc | 154 ++++++++++++++++-- tensorflow/lite/tools/optimize/test_util.cc | 10 +- tensorflow/lite/tools/optimize/test_util.h | 12 +- .../lite/tools/optimize/testdata/README.md | 23 +++ 7 files changed, 285 insertions(+), 20 deletions(-) create mode 100644 tensorflow/lite/tools/optimize/testdata/README.md diff --git a/tensorflow/lite/tools/optimize/quantize_model_test.cc b/tensorflow/lite/tools/optimize/quantize_model_test.cc index a25f2cc91f..52ed16c0b8 100644 --- a/tensorflow/lite/tools/optimize/quantize_model_test.cc +++ b/tensorflow/lite/tools/optimize/quantize_model_test.cc @@ -38,7 +38,7 @@ namespace { std::unique_ptr ReadTestModel() { auto model_path = tensorflow::io::JoinPath( - *g_test_model_dir, internal::kModelWith0Plus10Weights); + *g_test_model_dir, internal::kConvModelWith0Plus10Weights); return FlatBufferModel::BuildFromFile(model_path.c_str()); } diff --git a/tensorflow/lite/tools/optimize/subgraph_quantizer.cc b/tensorflow/lite/tools/optimize/subgraph_quantizer.cc index 27aee83cfd..56f2e6cf76 100644 --- a/tensorflow/lite/tools/optimize/subgraph_quantizer.cc +++ b/tensorflow/lite/tools/optimize/subgraph_quantizer.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/lite/tools/optimize/subgraph_quantizer.h" +#include #include #include "flatbuffers/flexbuffers.h" @@ -33,6 +34,8 @@ namespace optimize { namespace internal { namespace { +const int8_t kMinQuantizedValue = -127; +const int8_t kMaxQuantizedValue = 127; TfLiteStatus AddQuantizationParams(const SymmetricPerChannelParams& params, const uint8_t* buffer_data, @@ -131,9 +134,7 @@ TfLiteStatus SymmetricPerChannelQuantizeTensor(ModelT* model, TensorT* tensor, // Calculate scales per channel std::vector scales(channel_dim_size); std::vector scale_invs(channel_dim_size); - const int8_t kMinValue = -127; - const int8_t kMaxValue = 127; - const float half_scale = kMaxValue; + const float half_scale = kMaxQuantizedValue; for (size_t channel_idx = 0; channel_idx < channel_dim_size; channel_idx++) { const float half_range = std::max(std::abs(min_vals[channel_idx]), std::abs(max_vals[channel_idx])); @@ -159,7 +160,8 @@ TfLiteStatus SymmetricPerChannelQuantizeTensor(ModelT* model, TensorT* tensor, const int32_t quantized_value = static_cast(TfLiteRound(val * scale_invs[channel_idx])); final_buffer[index] = std::min( - kMaxValue, std::max(kMinValue, quantized_value)); + kMaxQuantizedValue, + std::max(kMinQuantizedValue, quantized_value)); } } } @@ -288,7 +290,9 @@ TfLiteStatus SubgraphQuantizer::QuantizeOpWithBias(BuiltinOperator op_code, return kTfLiteError; } auto input_tensor_idx = op->inputs[op_tensor_info->activation_input_index]; - TF_LITE_ENSURE_STATUS(AsymmetricQuantizeTensor(op_code, input_tensor_idx)); + if (IsSubgraphInput(input_tensor_idx)) { + TF_LITE_ENSURE_STATUS(AsymmetricQuantizeTensor(op_code, input_tensor_idx)); + } auto weights_tensor_idx = op->inputs[op_tensor_info->weights_input_index]; TensorT* weights_tensor = subgraph_->tensors[weights_tensor_idx].get(); @@ -317,6 +321,72 @@ TfLiteStatus SubgraphQuantizer::QuantizeOpWithBias(BuiltinOperator op_code, return kTfLiteOk; } +TfLiteStatus SubgraphQuantizer::PropagateMinMaxForAvgAndMaxPool( + BuiltinOperator op_code, OperatorT* op) { + TF_LITE_ENSURE_EQ(this->error_reporter_, op->inputs.size(), 1); + + if (IsSubgraphInput(op->inputs[0])) { + TF_LITE_ENSURE_STATUS(AsymmetricQuantizeTensor(op_code, op->inputs[0])); + } + + auto output_tensor = subgraph_->tensors[op->outputs[0]].get(); + if (output_tensor->type != TensorType_FLOAT32) { + return kTfLiteOk; + } + auto input_tensor = subgraph_->tensors[op->inputs[0]].get(); + if (!input_tensor->quantization) { + error_reporter_->Report( + "Missing required min/max information for input of operation: %s", + EnumNameBuiltinOperator(op_code)); + return kTfLiteError; + } + if (input_tensor->quantization->min.size() != 1 || + input_tensor->quantization->max.size() != 1 || + input_tensor->quantization->scale.size() != 1 || + input_tensor->quantization->zero_point.size() != 1) { + error_reporter_->Report( + "Invalid quantization information for Op: %s, tensor: %s", + EnumNameBuiltinOperator(op_code), input_tensor->name.c_str()); + return kTfLiteError; + } + auto quant_params = absl::make_unique(); + // Nudge min, max to include the floating point zero. + const float min = std::min(0.f, input_tensor->quantization->min[0]); + const float max = std::max(0.f, input_tensor->quantization->max[0]); + quant_params->min.push_back(min); + quant_params->max.push_back(max); + quant_params->scale.push_back(input_tensor->quantization->scale[0]); + quant_params->zero_point.push_back(input_tensor->quantization->zero_point[0]); + // TODO(shashishekhar): Log a warning here if overriding existing + // min/max/scales differ from input scales. + output_tensor->quantization = std::move(quant_params); + output_tensor->type = TensorType_INT8; + return kTfLiteOk; +} + +TfLiteStatus SubgraphQuantizer::AsymmetricQuantizeSingleInputOutputOp( + BuiltinOperator op_code, OperatorT* op) { + TF_LITE_ENSURE_EQ(this->error_reporter_, op->inputs.size(), 1); + TF_LITE_ENSURE_EQ(this->error_reporter_, op->outputs.size(), 1); + + if (IsSubgraphInput(op->inputs[0])) { + TF_LITE_ENSURE_STATUS(AsymmetricQuantizeTensor(op_code, op->inputs[0])); + } + + auto output_tensor = subgraph_->tensors[op->outputs[0]].get(); + if (output_tensor->type != TensorType_FLOAT32) { + return kTfLiteOk; + } + auto quant_params = absl::make_unique(); + TF_LITE_ENSURE_STATUS(AsymmetricQuantizeTensor(op_code, op->outputs[0])); + return kTfLiteOk; +} + +bool SubgraphQuantizer::IsSubgraphInput(int32_t tensor_idx) const { + return std::find(subgraph_->inputs.begin(), subgraph_->inputs.end(), + tensor_idx) != subgraph_->inputs.end(); +} + TfLiteStatus SubgraphQuantizer::QuantizeOperator(int op_idx) { OperatorT* op = subgraph_->operators[op_idx].get(); const BuiltinOperator op_code = @@ -324,6 +394,16 @@ TfLiteStatus SubgraphQuantizer::QuantizeOperator(int op_idx) { if (OpHasOptionalBiasTensor(op_code)) { return QuantizeOpWithBias(op_code, op); } + switch (op_code) { + case BuiltinOperator_AVERAGE_POOL_2D: + case BuiltinOperator_MAX_POOL_2D: + return PropagateMinMaxForAvgAndMaxPool(op_code, op); + case BuiltinOperator_SQUEEZE: + case BuiltinOperator_SOFTMAX: + return AsymmetricQuantizeSingleInputOutputOp(op_code, op); + default: + return kTfLiteError; + } return kTfLiteError; } diff --git a/tensorflow/lite/tools/optimize/subgraph_quantizer.h b/tensorflow/lite/tools/optimize/subgraph_quantizer.h index 2543dccea9..9d6ca7fad5 100644 --- a/tensorflow/lite/tools/optimize/subgraph_quantizer.h +++ b/tensorflow/lite/tools/optimize/subgraph_quantizer.h @@ -40,9 +40,23 @@ class SubgraphQuantizer { private: // Quantizes ops with bias tensors. TfLiteStatus QuantizeOpWithBias(BuiltinOperator op_code, OperatorT* op); + + // Average and Max pool need special treatement. The scales are propagated + // from inputs to outputs. + TfLiteStatus PropagateMinMaxForAvgAndMaxPool(BuiltinOperator op_code, + OperatorT* op); + + // Asymmetric quantizes inputs and outputs of an Op that has single input and + // single output. E.g. Squeeze. + TfLiteStatus AsymmetricQuantizeSingleInputOutputOp(BuiltinOperator op_code, + OperatorT* op); + TfLiteStatus AsymmetricQuantizeTensor(BuiltinOperator op_code, int32_t tensor_idx); + // Returns true if |tensor_idx| is one of the inputs in the subgraph. + bool IsSubgraphInput(int32_t tensor_idx) const; + ModelT* model_; SubGraphT* subgraph_; ErrorReporter* error_reporter_; diff --git a/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc b/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc index 7652429e32..96ba20d4d9 100644 --- a/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc +++ b/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc @@ -12,13 +12,15 @@ 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/tools/optimize/subgraph_quantizer.h" +#include + #include #include #include "tensorflow/core/lib/io/path.h" #include "tensorflow/core/platform/init_main.h" #include "tensorflow/core/util/command_line_flags.h" #include "tensorflow/lite/schema/schema_generated.h" +#include "tensorflow/lite/tools/optimize/subgraph_quantizer.h" #include "tensorflow/lite/tools/optimize/symmetric_per_channel_params.h" #include "tensorflow/lite/tools/optimize/test_util.h" @@ -31,22 +33,31 @@ namespace optimize { namespace internal { namespace { -std::unique_ptr ReadTestModel1() { - auto model_path = tensorflow::io::JoinPath(*g_test_model_dir, - kModelWithMinus128Plus127Weights); +std::unique_ptr ReadModel(const char* model) { + auto model_path = tensorflow::io::JoinPath(*g_test_model_dir, model); return FlatBufferModel::BuildFromFile(model_path.c_str()); } -std::unique_ptr ReadTestModel2() { - auto model_path = - tensorflow::io::JoinPath(*g_test_model_dir, kModelWith0Plus10Weights); - return FlatBufferModel::BuildFromFile(model_path.c_str()); +std::unique_ptr ReadConvModel1() { + return ReadModel(kConvModelWithMinus128Plus127Weights); +} + +std::unique_ptr ReadConvModel2() { + return ReadModel(kConvModelWith0Plus10Weights); +} + +std::unique_ptr ReadSoftmaxModel() { + return ReadModel(kSingleSoftmaxModelMinMinus5MaxPlus5); +} + +std::unique_ptr ReadAvgPoolModel() { + return ReadModel(kSingleAvgPoolModelMinMinus5MaxPlus5); } TEST(SubgraphQuantizerTest, VerifyConvQuantizationWithUnitScale) { ASSERT_TRUE(g_test_model_dir); ASSERT_FALSE(g_test_model_dir->empty()); - auto test_model = ReadTestModel1(); + auto test_model = ReadConvModel1(); ASSERT_TRUE(test_model); auto readonly_model = test_model->GetModel(); ASSERT_TRUE(readonly_model); @@ -145,7 +156,7 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantizationWithUnitScale) { TEST(SubgraphQuantizerTest, VerifyConvQuantization) { ASSERT_TRUE(g_test_model_dir); ASSERT_FALSE(g_test_model_dir->empty()); - auto test_model = ReadTestModel2(); + auto test_model = ReadConvModel2(); ASSERT_TRUE(test_model); auto readonly_model = test_model->GetModel(); ASSERT_TRUE(readonly_model); @@ -237,6 +248,129 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantization) { } } +void VerifyAsymmetricQuantizationScale( + const QuantizationParameters& float_quant_params, + const QuantizationParametersT& quantized_quant_params) { + const float eps = 1e-7; + ASSERT_EQ(float_quant_params.min()->size(), 1); + ASSERT_EQ(float_quant_params.max()->size(), 1); + float float_min = std::min(0.f, float_quant_params.min()->Get(0)); + float float_max = std::max(0.f, float_quant_params.max()->Get(0)); + + ASSERT_EQ(quantized_quant_params.scale.size(), 1); + ASSERT_EQ(quantized_quant_params.zero_point.size(), 1); + + float scale = (float_max - float_min) / 255; + EXPECT_NEAR(scale, quantized_quant_params.scale[0], eps); +} + +TEST(SubgraphQuantizerTest, VerifySoftmaxQuantization) { + ASSERT_TRUE(g_test_model_dir); + ASSERT_FALSE(g_test_model_dir->empty()); + auto test_model = ReadSoftmaxModel(); + ASSERT_TRUE(test_model); + auto readonly_model = test_model->GetModel(); + ASSERT_TRUE(readonly_model); + ASSERT_TRUE(readonly_model->subgraphs()); + ASSERT_GE(readonly_model->subgraphs()->size(), 1); + tflite::ModelT model; + readonly_model->UnPackTo(&model); + auto subgraph = model.subgraphs[0].get(); + FailOnErrorReporter error_reporter; + SubgraphQuantizer quantizer(&model, subgraph, &error_reporter); + auto status = quantizer.QuantizeOperator(0); + ASSERT_EQ(kTfLiteOk, status); + + auto op = subgraph->operators[0].get(); + // Model has a single softmax op. + ASSERT_EQ(op->opcode_index, 0); + ASSERT_EQ(model.operator_codes[0].get()->builtin_code, + BuiltinOperator_SOFTMAX); + + ASSERT_EQ(op->inputs.size(), 1); + ASSERT_EQ(op->outputs.size(), 1); + auto float_graph = readonly_model->subgraphs()->Get(0); + + ASSERT_EQ(float_graph->tensors()->Get(op->inputs[0])->type(), + TensorType_FLOAT32); + ASSERT_EQ(float_graph->tensors()->Get(op->outputs[0])->type(), + TensorType_FLOAT32); + + EXPECT_EQ(subgraph->tensors[op->inputs[0]].get()->type, TensorType_INT8); + EXPECT_EQ(subgraph->tensors[op->outputs[0]].get()->type, TensorType_INT8); + + auto float_input_quant_params = + float_graph->tensors()->Get(op->inputs[0])->quantization(); + auto input_quant_params = + subgraph->tensors[op->inputs[0]]->quantization.get(); + VerifyAsymmetricQuantizationScale(*float_input_quant_params, + *input_quant_params); + + auto float_output_quant_params = + float_graph->tensors()->Get(op->outputs[0])->quantization(); + auto output_quant_params = + subgraph->tensors[op->outputs[0]]->quantization.get(); + VerifyAsymmetricQuantizationScale(*float_output_quant_params, + *output_quant_params); +} + +TEST(SubgraphQuantizerTest, VerifyAvgPoolQuantization) { + ASSERT_TRUE(g_test_model_dir); + ASSERT_FALSE(g_test_model_dir->empty()); + auto test_model = ReadAvgPoolModel(); + ASSERT_TRUE(test_model); + auto readonly_model = test_model->GetModel(); + ASSERT_TRUE(readonly_model); + ASSERT_TRUE(readonly_model->subgraphs()); + ASSERT_GE(readonly_model->subgraphs()->size(), 1); + tflite::ModelT model; + readonly_model->UnPackTo(&model); + auto subgraph = model.subgraphs[0].get(); + FailOnErrorReporter error_reporter; + SubgraphQuantizer quantizer(&model, subgraph, &error_reporter); + auto status = quantizer.QuantizeOperator(0); + ASSERT_EQ(kTfLiteOk, status); + + auto op = subgraph->operators[0].get(); + // Model has a single AveragePool op. + ASSERT_EQ(op->opcode_index, 0); + ASSERT_EQ(model.operator_codes[0].get()->builtin_code, + BuiltinOperator_AVERAGE_POOL_2D); + + ASSERT_EQ(op->inputs.size(), 1); + ASSERT_EQ(op->outputs.size(), 1); + + auto float_graph = readonly_model->subgraphs()->Get(0); + ASSERT_EQ(float_graph->tensors()->Get(op->inputs[0])->type(), + TensorType_FLOAT32); + ASSERT_EQ(float_graph->tensors()->Get(op->outputs[0])->type(), + TensorType_FLOAT32); + + EXPECT_EQ(subgraph->tensors[op->inputs[0]].get()->type, TensorType_INT8); + EXPECT_EQ(subgraph->tensors[op->outputs[0]].get()->type, TensorType_INT8); + + auto float_input_quant_params = + float_graph->tensors()->Get(op->inputs[0])->quantization(); + auto input_quant_params = + subgraph->tensors[op->inputs[0]]->quantization.get(); + VerifyAsymmetricQuantizationScale(*float_input_quant_params, + *input_quant_params); + + auto float_output_quant_params = + float_graph->tensors()->Get(op->outputs[0])->quantization(); + auto output_quant_params = + subgraph->tensors[op->outputs[0]]->quantization.get(); + ASSERT_EQ(float_output_quant_params->min()->size(), 1); + ASSERT_EQ(float_output_quant_params->max()->size(), 1); + ASSERT_EQ(output_quant_params->min.size(), 1); + ASSERT_EQ(output_quant_params->max.size(), 1); + + // Make sure the input min/maxes are propagated to outputs. + EXPECT_EQ(input_quant_params->min[0], output_quant_params->min[0]); + EXPECT_EQ(input_quant_params->max[0], output_quant_params->max[0]); + EXPECT_EQ(input_quant_params->scale[0], output_quant_params->scale[0]); +} + } // namespace } // namespace internal } // namespace optimize diff --git a/tensorflow/lite/tools/optimize/test_util.cc b/tensorflow/lite/tools/optimize/test_util.cc index 83b9f0dcd3..3506142901 100644 --- a/tensorflow/lite/tools/optimize/test_util.cc +++ b/tensorflow/lite/tools/optimize/test_util.cc @@ -19,12 +19,18 @@ limitations under the License. namespace tflite { namespace optimize { namespace internal { -const char* kModelWithMinus128Plus127Weights = +const char* kConvModelWithMinus128Plus127Weights = "single_conv_weights_min_minus_127_max_plus_127.tflite"; -const char* kModelWith0Plus10Weights = +const char* kConvModelWith0Plus10Weights = "single_conv_weights_min_0_max_plus_10.tflite"; +const char* kSingleSoftmaxModelMinMinus5MaxPlus5 = + "single_softmax_min_minus_5_max_plus_5.tflite"; + +const char* kSingleAvgPoolModelMinMinus5MaxPlus5 = + "single_avg_pool_min_minus_5_max_plus_5.tflite"; + int FailOnErrorReporter::Report(const char* format, va_list args) { char buf[1024]; vsnprintf(buf, sizeof(buf), format, args); diff --git a/tensorflow/lite/tools/optimize/test_util.h b/tensorflow/lite/tools/optimize/test_util.h index 26da41e9cc..1981068acc 100644 --- a/tensorflow/lite/tools/optimize/test_util.h +++ b/tensorflow/lite/tools/optimize/test_util.h @@ -26,13 +26,21 @@ namespace internal { // channel has at least one weight as -127 and one weight as 127. // The activations are all in range: [-128, 127] // This means all bias computations should result in 1.0 scale. -extern const char* kModelWithMinus128Plus127Weights; +extern const char* kConvModelWithMinus128Plus127Weights; // Test model with single convolution where all weights are integers between // [0, 10] weights are randomly distributed. It is not guaranteed that min max // for weights are going to appear in each channel. // Activations have min = 0, max = 10. -extern const char* kModelWith0Plus10Weights; +extern const char* kConvModelWith0Plus10Weights; + +// A floating point model with a single softmax. The input tensor has min +// and max in range [-5, 5], not necessarily -5 or +5. +extern const char* kSingleSoftmaxModelMinMinus5MaxPlus5; + +// A floating point model with a single average pool. The input tensor has min +// and max in range [-5, 5], not necessarily -5 or +5. +extern const char* kSingleAvgPoolModelMinMinus5MaxPlus5; // An error reporter that fails on testing. class FailOnErrorReporter : public ErrorReporter { diff --git a/tensorflow/lite/tools/optimize/testdata/README.md b/tensorflow/lite/tools/optimize/testdata/README.md new file mode 100644 index 0000000000..b6b331b828 --- /dev/null +++ b/tensorflow/lite/tools/optimize/testdata/README.md @@ -0,0 +1,23 @@ +# Test models for testing quantization + +This directory contains test models for testing quantization. + +## Models + +* `single_conv_weights_min_0_max_plus_10.tflite` \ + A floating point model with single convolution where all weights are + integers between [0, 10] weights are randomly distributed. It is not + guaranteed that min max for weights are going to appear in each channel. + All activations have min maxes and activations are in range [0,10]. +* `single_conv_weights_min_minus_127_max_plus_127.tflite` \ + A floating point model with a single convolution where weights of the model + are all integers that lie in range[-127, 127]. The weights have been put in + such a way that each channel has at least one weight as -127 and one weight + as 127. The activations are all in range: [-128, 127]. + This means all bias computations should result in 1.0 scale. +* `single_softmax_min_minus_5_max_5.tflite` \ + A floating point model with a single softmax. The input tensor has min + and max in range [-5, 5], not necessarily -5 or +5. +* `single_avg_pool_input_min_minus_5_max_5.tflite` \ + A floating point model with a single average pool. The input tensor has min + and max in range [-5, 5], not necessarily -5 or +5. -- GitLab From 1cbfa21e2a3f18b0396369e7194e3dbb4bedb08c Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Tue, 15 Jan 2019 10:39:11 -0800 Subject: [PATCH 0726/2345] Support Python 2 slicing on ListWrapper. "Deprecated since version 2.0: Support slice objects as parameters to the __getitem__() method. (However, built-in types in CPython currently still implement __getslice__(). Therefore, you have to override it in derived classes when implementing slicing.)" PiperOrigin-RevId: 229397602 --- .../python/training/checkpointable/data_structures.py | 3 +++ .../python/training/checkpointable/data_structures_test.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/tensorflow/python/training/checkpointable/data_structures.py b/tensorflow/python/training/checkpointable/data_structures.py index 759709aa3d..d1e617ed46 100644 --- a/tensorflow/python/training/checkpointable/data_structures.py +++ b/tensorflow/python/training/checkpointable/data_structures.py @@ -304,6 +304,9 @@ class List(CheckpointableDataStructure, collections.Sequence): def __getitem__(self, key): return self._storage[key] + def __getslice__(self, i, j): + return self._storage[slice(i, j)] + def __len__(self): return len(self._storage) diff --git a/tensorflow/python/training/checkpointable/data_structures_test.py b/tensorflow/python/training/checkpointable/data_structures_test.py index bcec6e0100..79191f3448 100644 --- a/tensorflow/python/training/checkpointable/data_structures_test.py +++ b/tensorflow/python/training/checkpointable/data_structures_test.py @@ -271,6 +271,12 @@ class ListTests(test.TestCase): with self.assertRaises(TypeError): has_sequences.add(data_structures._ListWrapper([])) + def testSlicing(self): + l = data_structures._ListWrapper([1, 2, 3, 4]) + self.assertEqual(l[1:], [2, 3, 4]) + self.assertEqual(l[1:-1], [2, 3]) + self.assertEqual(l[:-1], [1, 2, 3]) + class HasMapping(training.Model): -- GitLab From 2e5ee61e2e0d876f91a66a1fe6279105b14d7534 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 10:45:54 -0800 Subject: [PATCH 0727/2345] Delete deprecated and unused methods from distribute_lib. These methods all had the `@doc_controls.do_not_generate_docs` decorator since we knew they were going away as soon as references to them were updated to point to the official APIs decided in the accepted RFC: https://github.com/tensorflow/community/blob/master/rfcs/20181016-replicator.md PiperOrigin-RevId: 229398974 --- .../python/distribute/distribute_lib.py | 161 +----------------- .../python/distribute/mirrored_strategy.py | 4 +- ...orflow.distribute.-mirrored-strategy.pbtxt | 56 ------ ...nsorflow.distribute.-replica-context.pbtxt | 4 - .../v1/tensorflow.distribute.-strategy.pbtxt | 56 ------ ...orflow.distribute.-mirrored-strategy.pbtxt | 56 ------ ...nsorflow.distribute.-replica-context.pbtxt | 4 - .../v2/tensorflow.distribute.-strategy.pbtxt | 56 ------ 8 files changed, 4 insertions(+), 393 deletions(-) diff --git a/tensorflow/python/distribute/distribute_lib.py b/tensorflow/python/distribute/distribute_lib.py index 421b2a6520..f112787ba2 100644 --- a/tensorflow/python/distribute/distribute_lib.py +++ b/tensorflow/python/distribute/distribute_lib.py @@ -505,33 +505,6 @@ class DistributionStrategy(object): """DEPRECATED: use extended.broadcast_to() instead.""" return self._extended.broadcast_to(tensor, destinations) - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def run_steps_on_dataset(self, fn, iterator, iterations=1, - initial_loop_values=None): - """DEPRECATED: use extended.experimental_run_steps_on_iterator() instead.""" - return self._extended.experimental_run_steps_on_iterator( - fn, iterator, iterations, initial_loop_values) - - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def call_for_each_replica(self, fn, *args, **kwargs): - """DEPRECATED: use extended.call_for_each_replica() instead.""" - # Handle old *args, **kwargs, and new args=(...), kwargs={...}, to - # allow transition. - a = kwargs.pop("args", None) - if a is not None: - if args: - raise ValueError( - "Can't pass *args and args=... to call_for_each_replica") - args = a - k = kwargs.pop("kwargs", None) - if k is not None: - if kwargs: - raise ValueError( - "Can't pass **kwargs and kwargs=... to call_for_each_replica") - kwargs = k - kwargs.pop("run_concurrently", None) # Ignore old option. - return self._extended.call_for_each_replica(fn, args, kwargs) - def reduce(self, reduce_op, value): """Reduce `value` across replicas. @@ -546,58 +519,6 @@ class DistributionStrategy(object): _require_cross_replica_context_extended(self._extended) return self._extended._reduce(reduce_op, value) # pylint: disable=protected-access - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def batch_reduce(self, aggregation, value_destination_pairs): - """DEPRECATED: use extended.batch_reduce_to() instead.""" - return self._extended.batch_reduce_to(aggregation, value_destination_pairs) - - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def update(self, var, fn, *args, **kwargs): - """DEPRECATED: use extended.update() instead.""" - group = kwargs.pop("group", True) - # We temporarily support "grouped" in addition to "group" for backward- - # compatibility. - group = kwargs.pop("grouped", True) and group - # Handle old *args, **kwargs, and new args=(...), kwargs={...}, to - # allow transition. - a = kwargs.pop("args", None) - if a is not None: - if args: - raise ValueError( - "Can't pass *args and args=... to update") - args = a - k = kwargs.pop("kwargs", None) - if k is not None: - if kwargs: - raise ValueError( - "Can't pass **kwargs and kwargs=... to update") - kwargs = k - return self._extended.update(var, fn, args, kwargs, group) - - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def update_non_slot(self, colocate_with, fn, *args, **kwargs): - """DEPRECATED: use extended.update_non_slot() instead.""" - group = kwargs.pop("group", True) - # We temporarily support "grouped" in addition to "group" for backward- - # compatibility. - group = kwargs.pop("grouped", True) and group - # Handle old *args, **kwargs, and new args=(...), kwargs={...}, to - # allow transition. - a = kwargs.pop("args", None) - if a is not None: - if args: - raise ValueError( - "Can't pass *args and args=... to update_non_slot") - args = a - k = kwargs.pop("kwargs", None) - if k is not None: - if kwargs: - raise ValueError( - "Can't pass **kwargs and kwargs=... to update_non_slot") - kwargs = k - return self._extended.update_non_slot( - colocate_with, fn, args, kwargs, group) - @doc_controls.do_not_generate_docs # DEPRECATED, -> `DistributedValues` def unwrap(self, value): """Returns the list of all per-replica values contained in `value`. @@ -612,50 +533,16 @@ class DistributionStrategy(object): """ return self._extended._unwrap(value) # pylint: disable=protected-access - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def value_container(self, value): - """DEPRECATED: use extended.value_container() instead.""" - return self._extended.value_container(value) - @doc_controls.do_not_generate_docs # DEPRECATED, -> `DistributedValues` def group(self, value, name=None): """Shortcut for `tf.group(self.unwrap(value))`.""" return self._extended._group(value, name) # pylint: disable=protected-access - @property - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def require_static_shapes(self): - """DEPRECATED: use extended.require_static_shapes instead.""" - return self._extended.experimental_require_static_shapes - @property def num_replicas_in_sync(self): """Returns number of replicas over which gradients are aggregated.""" return self._extended._num_replicas_in_sync # pylint: disable=protected-access - @property - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def worker_devices(self): - """DEPRECATED: use extended.worker_devices instead.""" - return self._extended.worker_devices - - @property - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def parameter_devices(self): - """DEPRECATED: use extended.parameter_devices instead.""" - return self._extended.parameter_devices - - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def non_slot_devices(self, var_list): - """DEPRECATED: use extended.non_slot_devices instead.""" - return self._extended.non_slot_devices(var_list) - - @property - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def between_graph(self): - """DEPRECATED: use extended.experimental_between_graph instead.""" - return self._extended.experimental_between_graph - @doc_controls.do_not_generate_docs # DEPRECATED, being replaced by a new API. def configure(self, session_config=None, @@ -691,24 +578,6 @@ class DistributionStrategy(object): """ return self._extended._update_config_proto(config_proto) # pylint: disable=protected-access - @property - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def should_init(self): - """DEPRECATED: use extended.should_init instead.""" - return self._extended.experimental_should_init - - @property - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def should_checkpoint(self): - """DEPRECATED: use extended.should_checkpoint instead.""" - return self._extended.should_checkpoint - - @property - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def should_save_summary(self): - """DEPRECATED: use extended.should_save_summary instead.""" - return self._extended.should_save_summary - def __deepcopy__(self, memo): # First do a regular deepcopy of `self`. cls = self.__class__ @@ -1241,9 +1110,6 @@ class DistributionStrategyExtended(object): Args: reduce_op: Reduction type, an instance of `tf.distribute.ReduceOp` enum. - DEPRECATED but still accepted values: - `tf.VariableAggregation.SUM`, - `tf.VariableAggregation.MEAN`, value: A per-replica value with one value per replica. destinations: A mirrored variable, a per-replica tensor, or a device string. The return value will be copied to all destination devices (or @@ -1256,14 +1122,7 @@ class DistributionStrategyExtended(object): # TODO(josh11b): More docstring _require_cross_replica_context_extended(self) assert not isinstance(destinations, (list, tuple)) - - # TODO(priyag): Remove this when all callers have been updated. - if isinstance(reduce_op, variable_scope.VariableAggregation): - assert reduce_op in ( - variable_scope.VariableAggregation.SUM, - variable_scope.VariableAggregation.MEAN, - ) - reduce_op = reduce_util.ReduceOp.from_variable_aggregation(reduce_op) + assert not isinstance(reduce_op, variable_scope.VariableAggregation) assert (reduce_op == reduce_util.ReduceOp.SUM or reduce_op == reduce_util.ReduceOp.MEAN) return self._reduce_to(reduce_op, value, destinations) @@ -1276,9 +1135,6 @@ class DistributionStrategyExtended(object): Args: reduce_op: Reduction type, an instance of `tf.distribute.ReduceOp` enum. - DEPRECATED but still accepted values: - `tf.VariableAggregation.SUM`, - `tf.VariableAggregation.MEAN`, value_destination_pairs: A sequence of (value, destinations) pairs. See `reduce_to()` for a description. @@ -1287,14 +1143,7 @@ class DistributionStrategyExtended(object): """ # TODO(josh11b): More docstring _require_cross_replica_context_extended(self) - - # TODO(priyag): Remove this when all callers have been updated. - if isinstance(reduce_op, variable_scope.VariableAggregation): - assert reduce_op in [ - variable_scope.VariableAggregation.SUM, - variable_scope.VariableAggregation.MEAN, - ] - reduce_op = reduce_util.ReduceOp.from_variable_aggregation(reduce_op) + assert not isinstance(reduce_op, variable_scope.VariableAggregation) return self._batch_reduce_to(reduce_op, value_destination_pairs) def _batch_reduce_to(self, reduce_op, value_destination_pairs): @@ -1565,12 +1414,6 @@ class ReplicaContext(object): require_replica_context(self) return self._replica_id_in_sync_group - @property - @doc_controls.do_not_generate_docs # DEPRECATED, use `strategy` - def distribution_strategy(self): - """DEPRECATED: use `self.strategy` instead.""" - return self._strategy - @property def strategy(self): """The current `tf.distribute.Strategy` object.""" diff --git a/tensorflow/python/distribute/mirrored_strategy.py b/tensorflow/python/distribute/mirrored_strategy.py index 1ed7eef1ed..f279c3ba1b 100644 --- a/tensorflow/python/distribute/mirrored_strategy.py +++ b/tensorflow/python/distribute/mirrored_strategy.py @@ -880,13 +880,13 @@ class _MirroredReplicaThread(threading.Thread): class MirroredReplicaContext(distribute_lib.ReplicaContext): - """ReplicaContext used in MirroredStrategy.call_for_each_replica(). + """ReplicaContext used in MirroredStrategy.extended.call_for_each_replica(). Opened in `_MirroredReplicaThread`, to allow the user to invoke `MirroredStrategy`'s specific implementation of `merge_call()`, which works by delegating the function and its arguments to the main thread (the one that invoked - `MirroredStrategy.call_for_each_replica()`). + `MirroredStrategy.extended.call_for_each_replica()`). """ def _merge_call(self, fn, args, kwargs): diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt index 863ee9c210..c554df489a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt @@ -3,10 +3,6 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - member { - name: "between_graph" - mtype: "" - } member { name: "extended" mtype: "" @@ -15,46 +11,14 @@ tf_class { name: "num_replicas_in_sync" mtype: "" } - member { - name: "parameter_devices" - mtype: "" - } - member { - name: "require_static_shapes" - mtype: "" - } - member { - name: "should_checkpoint" - mtype: "" - } - member { - name: "should_init" - mtype: "" - } - member { - name: "should_save_summary" - mtype: "" - } - member { - name: "worker_devices" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'devices\', \'cross_device_ops\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " } - member_method { - name: "batch_reduce" - argspec: "args=[\'self\', \'aggregation\', \'value_destination_pairs\'], varargs=None, keywords=None, defaults=None" - } member_method { name: "broadcast" argspec: "args=[\'self\', \'tensor\', \'destinations\'], varargs=None, keywords=None, defaults=[\'None\'], " } - member_method { - name: "call_for_each_replica" - argspec: "args=[\'self\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } member_method { name: "colocate_vars_with" argspec: "args=[\'self\', \'colocate_with_variable\'], varargs=None, keywords=None, defaults=None" @@ -87,18 +51,10 @@ tf_class { name: "make_input_fn_iterator" argspec: "args=[\'self\', \'input_fn\', \'replication_mode\'], varargs=None, keywords=None, defaults=[\'InputReplicationMode.PER_WORKER\'], " } - member_method { - name: "non_slot_devices" - argspec: "args=[\'self\', \'var_list\'], varargs=None, keywords=None, defaults=None" - } member_method { name: "reduce" argspec: "args=[\'self\', \'reduce_op\', \'value\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "run_steps_on_dataset" - argspec: "args=[\'self\', \'fn\', \'iterator\', \'iterations\', \'initial_loop_values\'], varargs=None, keywords=None, defaults=[\'1\', \'None\'], " - } member_method { name: "scope" argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" @@ -107,20 +63,8 @@ tf_class { name: "unwrap" argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "update" - argspec: "args=[\'self\', \'var\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } member_method { name: "update_config_proto" argspec: "args=[\'self\', \'config_proto\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "update_non_slot" - argspec: "args=[\'self\', \'colocate_with\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } - member_method { - name: "value_container" - argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" - } } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-replica-context.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-replica-context.pbtxt index 22f8160c96..c3b7991175 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-replica-context.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-replica-context.pbtxt @@ -6,10 +6,6 @@ tf_class { name: "devices" mtype: "" } - member { - name: "distribution_strategy" - mtype: "" - } member { name: "num_replicas_in_sync" mtype: "" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt index 1c06f7873e..cce83be3d0 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt @@ -2,10 +2,6 @@ path: "tensorflow.distribute.Strategy" tf_class { is_instance: "" is_instance: "" - member { - name: "between_graph" - mtype: "" - } member { name: "extended" mtype: "" @@ -14,46 +10,14 @@ tf_class { name: "num_replicas_in_sync" mtype: "" } - member { - name: "parameter_devices" - mtype: "" - } - member { - name: "require_static_shapes" - mtype: "" - } - member { - name: "should_checkpoint" - mtype: "" - } - member { - name: "should_init" - mtype: "" - } - member { - name: "should_save_summary" - mtype: "" - } - member { - name: "worker_devices" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'extended\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "batch_reduce" - argspec: "args=[\'self\', \'aggregation\', \'value_destination_pairs\'], varargs=None, keywords=None, defaults=None" - } member_method { name: "broadcast" argspec: "args=[\'self\', \'tensor\', \'destinations\'], varargs=None, keywords=None, defaults=[\'None\'], " } - member_method { - name: "call_for_each_replica" - argspec: "args=[\'self\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } member_method { name: "colocate_vars_with" argspec: "args=[\'self\', \'colocate_with_variable\'], varargs=None, keywords=None, defaults=None" @@ -86,18 +50,10 @@ tf_class { name: "make_input_fn_iterator" argspec: "args=[\'self\', \'input_fn\', \'replication_mode\'], varargs=None, keywords=None, defaults=[\'InputReplicationMode.PER_WORKER\'], " } - member_method { - name: "non_slot_devices" - argspec: "args=[\'self\', \'var_list\'], varargs=None, keywords=None, defaults=None" - } member_method { name: "reduce" argspec: "args=[\'self\', \'reduce_op\', \'value\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "run_steps_on_dataset" - argspec: "args=[\'self\', \'fn\', \'iterator\', \'iterations\', \'initial_loop_values\'], varargs=None, keywords=None, defaults=[\'1\', \'None\'], " - } member_method { name: "scope" argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" @@ -106,20 +62,8 @@ tf_class { name: "unwrap" argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "update" - argspec: "args=[\'self\', \'var\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } member_method { name: "update_config_proto" argspec: "args=[\'self\', \'config_proto\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "update_non_slot" - argspec: "args=[\'self\', \'colocate_with\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } - member_method { - name: "value_container" - argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" - } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt index 863ee9c210..c554df489a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt @@ -3,10 +3,6 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - member { - name: "between_graph" - mtype: "" - } member { name: "extended" mtype: "" @@ -15,46 +11,14 @@ tf_class { name: "num_replicas_in_sync" mtype: "" } - member { - name: "parameter_devices" - mtype: "" - } - member { - name: "require_static_shapes" - mtype: "" - } - member { - name: "should_checkpoint" - mtype: "" - } - member { - name: "should_init" - mtype: "" - } - member { - name: "should_save_summary" - mtype: "" - } - member { - name: "worker_devices" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'devices\', \'cross_device_ops\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " } - member_method { - name: "batch_reduce" - argspec: "args=[\'self\', \'aggregation\', \'value_destination_pairs\'], varargs=None, keywords=None, defaults=None" - } member_method { name: "broadcast" argspec: "args=[\'self\', \'tensor\', \'destinations\'], varargs=None, keywords=None, defaults=[\'None\'], " } - member_method { - name: "call_for_each_replica" - argspec: "args=[\'self\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } member_method { name: "colocate_vars_with" argspec: "args=[\'self\', \'colocate_with_variable\'], varargs=None, keywords=None, defaults=None" @@ -87,18 +51,10 @@ tf_class { name: "make_input_fn_iterator" argspec: "args=[\'self\', \'input_fn\', \'replication_mode\'], varargs=None, keywords=None, defaults=[\'InputReplicationMode.PER_WORKER\'], " } - member_method { - name: "non_slot_devices" - argspec: "args=[\'self\', \'var_list\'], varargs=None, keywords=None, defaults=None" - } member_method { name: "reduce" argspec: "args=[\'self\', \'reduce_op\', \'value\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "run_steps_on_dataset" - argspec: "args=[\'self\', \'fn\', \'iterator\', \'iterations\', \'initial_loop_values\'], varargs=None, keywords=None, defaults=[\'1\', \'None\'], " - } member_method { name: "scope" argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" @@ -107,20 +63,8 @@ tf_class { name: "unwrap" argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "update" - argspec: "args=[\'self\', \'var\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } member_method { name: "update_config_proto" argspec: "args=[\'self\', \'config_proto\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "update_non_slot" - argspec: "args=[\'self\', \'colocate_with\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } - member_method { - name: "value_container" - argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" - } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-replica-context.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-replica-context.pbtxt index 22f8160c96..c3b7991175 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-replica-context.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-replica-context.pbtxt @@ -6,10 +6,6 @@ tf_class { name: "devices" mtype: "" } - member { - name: "distribution_strategy" - mtype: "" - } member { name: "num_replicas_in_sync" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt index 1c06f7873e..cce83be3d0 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt @@ -2,10 +2,6 @@ path: "tensorflow.distribute.Strategy" tf_class { is_instance: "" is_instance: "" - member { - name: "between_graph" - mtype: "" - } member { name: "extended" mtype: "" @@ -14,46 +10,14 @@ tf_class { name: "num_replicas_in_sync" mtype: "" } - member { - name: "parameter_devices" - mtype: "" - } - member { - name: "require_static_shapes" - mtype: "" - } - member { - name: "should_checkpoint" - mtype: "" - } - member { - name: "should_init" - mtype: "" - } - member { - name: "should_save_summary" - mtype: "" - } - member { - name: "worker_devices" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'extended\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "batch_reduce" - argspec: "args=[\'self\', \'aggregation\', \'value_destination_pairs\'], varargs=None, keywords=None, defaults=None" - } member_method { name: "broadcast" argspec: "args=[\'self\', \'tensor\', \'destinations\'], varargs=None, keywords=None, defaults=[\'None\'], " } - member_method { - name: "call_for_each_replica" - argspec: "args=[\'self\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } member_method { name: "colocate_vars_with" argspec: "args=[\'self\', \'colocate_with_variable\'], varargs=None, keywords=None, defaults=None" @@ -86,18 +50,10 @@ tf_class { name: "make_input_fn_iterator" argspec: "args=[\'self\', \'input_fn\', \'replication_mode\'], varargs=None, keywords=None, defaults=[\'InputReplicationMode.PER_WORKER\'], " } - member_method { - name: "non_slot_devices" - argspec: "args=[\'self\', \'var_list\'], varargs=None, keywords=None, defaults=None" - } member_method { name: "reduce" argspec: "args=[\'self\', \'reduce_op\', \'value\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "run_steps_on_dataset" - argspec: "args=[\'self\', \'fn\', \'iterator\', \'iterations\', \'initial_loop_values\'], varargs=None, keywords=None, defaults=[\'1\', \'None\'], " - } member_method { name: "scope" argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" @@ -106,20 +62,8 @@ tf_class { name: "unwrap" argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "update" - argspec: "args=[\'self\', \'var\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } member_method { name: "update_config_proto" argspec: "args=[\'self\', \'config_proto\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "update_non_slot" - argspec: "args=[\'self\', \'colocate_with\', \'fn\'], varargs=args, keywords=kwargs, defaults=None" - } - member_method { - name: "value_container" - argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" - } } -- GitLab From 0bdf7e66fcf04707e3094b1ac4648c06bceb2db5 Mon Sep 17 00:00:00 2001 From: Yunlu Li Date: Tue, 15 Jan 2019 10:47:16 -0800 Subject: [PATCH 0728/2345] Update test data multi_add.json. PiperOrigin-RevId: 229399229 --- tensorflow/lite/testdata/multi_add.json | 119 ++++++++++++++++++++---- 1 file changed, 102 insertions(+), 17 deletions(-) diff --git a/tensorflow/lite/testdata/multi_add.json b/tensorflow/lite/testdata/multi_add.json index 97b931dba8..ae559255a8 100644 --- a/tensorflow/lite/testdata/multi_add.json +++ b/tensorflow/lite/testdata/multi_add.json @@ -1,46 +1,131 @@ { - "version": 1, + "version": 3, "operator_codes": [ { - "builtin_code": "ADD" } ], "subgraphs": [ { "tensors": [ - { "shape": [ 1, 8, 8, 3 ], "name": "a" }, - { "shape": [ 1, 8, 8, 3 ], "name": "b" }, - { "shape": [ 1, 8, 8, 3 ], "name": "c" }, - { "shape": [ 1, 8, 8, 3 ], "name": "d" }, - { "shape": [ 1, 8, 8, 3 ], "name": "i" }, - { "shape": [ 1, 8, 8, 3 ], "name": "x" }, - { "shape": [ 1, 8, 8, 3 ], "name": "y" } + { + "shape": [ + 1, + 8, + 8, + 3 + ], + "name": "a" + }, + { + "shape": [ + 1, + 8, + 8, + 3 + ], + "name": "b" + }, + { + "shape": [ + 1, + 8, + 8, + 3 + ], + "name": "c" + }, + { + "shape": [ + 1, + 8, + 8, + 3 + ], + "name": "d" + }, + { + "shape": [ + 1, + 8, + 8, + 3 + ], + "name": "i" + }, + { + "shape": [ + 1, + 8, + 8, + 3 + ], + "name": "x" + }, + { + "shape": [ + 1, + 8, + 8, + 3 + ], + "name": "y" + } + ], + "inputs": [ + 0, + 1, + 2, + 3 + ], + "outputs": [ + 5, + 6 ], - "inputs": [ 0, 1, 2, 3 ], - "outputs": [ 5, 6 ], "operators": [ { - "inputs": [ 1, 2 ], - "outputs": [ 4 ], + "inputs": [ + 1, + 2 + ], + "outputs": [ + 4 + ], "builtin_options_type": "AddOptions", "builtin_options": { } }, { - "inputs": [ 0, 4 ], - "outputs": [ 5 ], + "inputs": [ + 0, + 4 + ], + "outputs": [ + 5 + ], "builtin_options_type": "AddOptions", "builtin_options": { } }, { - "inputs": [ 3, 4 ], - "outputs": [ 6 ], + "inputs": [ + 3, + 4 + ], + "outputs": [ + 6 + ], "builtin_options_type": "AddOptions", "builtin_options": { } } ] } + ], + "buffers": [ + { + "data": [ + + ] + } ] } -- GitLab From 158af766b665509ff5e47a092f64279848549d5c Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Tue, 15 Jan 2019 10:47:23 -0800 Subject: [PATCH 0729/2345] Use the correct nest in autograph PiperOrigin-RevId: 229399245 --- tensorflow/python/autograph/impl/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/autograph/impl/api.py b/tensorflow/python/autograph/impl/api.py index b1c16b1169..6633441e9f 100644 --- a/tensorflow/python/autograph/impl/api.py +++ b/tensorflow/python/autograph/impl/api.py @@ -35,7 +35,7 @@ from tensorflow.python.autograph.operators import py_builtins from tensorflow.python.autograph.pyct import compiler from tensorflow.python.autograph.pyct import inspect_utils from tensorflow.python.autograph.utils import py_func -from tensorflow.python.data.util import nest +from tensorflow.python.util import nest from tensorflow.python.framework import tensor_util from tensorflow.python.platform import tf_logging as logging from tensorflow.python.util import tf_decorator -- GitLab From e7af439def1647a7b8da9e4a5aebfc2b80f998ce Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Tue, 15 Jan 2019 10:54:12 -0800 Subject: [PATCH 0730/2345] Use `strategy.make_input_fn_iterator` in estimator instead of the deprecated `strategy.distribute_dataset`. PiperOrigin-RevId: 229400527 --- .../contrib/distribute/python/tpu_strategy.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tensorflow/contrib/distribute/python/tpu_strategy.py b/tensorflow/contrib/distribute/python/tpu_strategy.py index 518c704b89..b9d3924a24 100644 --- a/tensorflow/contrib/distribute/python/tpu_strategy.py +++ b/tensorflow/contrib/distribute/python/tpu_strategy.py @@ -303,6 +303,20 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended): functools.partial(self._call_dataset_fn, dataset_fn), self._input_workers) + 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)), -- GitLab From 556ac5a846e5b03859eda02e17349704387c465a Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Tue, 15 Jan 2019 10:55:40 -0800 Subject: [PATCH 0731/2345] Add helper to get mode strategy and replica context together. PiperOrigin-RevId: 229400832 --- .../distribution_strategy_context.py | 5 +++ tensorflow/python/eager/function.py | 4 +-- tensorflow/python/eager/tape.py | 33 ++++++++++++++++--- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/tensorflow/python/distribute/distribution_strategy_context.py b/tensorflow/python/distribute/distribution_strategy_context.py index e6648bf7c4..6c1e250f96 100644 --- a/tensorflow/python/distribute/distribution_strategy_context.py +++ b/tensorflow/python/distribute/distribution_strategy_context.py @@ -201,6 +201,11 @@ def has_strategy(): return get_strategy() is not _get_default_strategy() +def get_strategy_and_replica_context(): + per_thread_mode = _get_per_thread_mode() + return (per_thread_mode.strategy, per_thread_mode.replica_context) + + # ------------------------------------------------------------------------------ # Defaults that are used when no tf.distribute.Strategy is explicitly created. # We create them lazily in a function so that we can workaround the circular diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index b0140e5c98..0ebc7c915d 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -471,9 +471,7 @@ class Function(object): """ ctx = context.context() - for v in self._func_graph.variables: - if v.trainable: - tape.variable_accessed(v) + tape.variables_accessed(self._func_graph.variables) tensor_inputs = [] for i, arg in enumerate(args): diff --git a/tensorflow/python/eager/tape.py b/tensorflow/python/eager/tape.py index 56b68b9eea..56d696bf92 100644 --- a/tensorflow/python/eager/tape.py +++ b/tensorflow/python/eager/tape.py @@ -61,8 +61,9 @@ def watch(tape, tensor): def watch_variable(tape, variable): """Marks this variable to be watched by the given tape.""" - strategy = distribution_strategy_context.get_strategy() - if distribution_strategy_context.get_replica_context(): + strategy, context = ( + distribution_strategy_context.get_strategy_and_replica_context()) + if context: variables = [strategy.extended.value_container(variable)] else: variables = strategy.unwrap(variable) @@ -76,8 +77,9 @@ def variable_accessed(variable): Args: variable: variable to be watched. """ - strategy = distribution_strategy_context.get_strategy() - if distribution_strategy_context.get_replica_context(): + strategy, context = ( + distribution_strategy_context.get_strategy_and_replica_context()) + if context: variables = [strategy.extended.value_container(variable)] else: variables = strategy.unwrap(variable) @@ -85,6 +87,29 @@ def variable_accessed(variable): pywrap_tensorflow.TFE_Py_TapeVariableAccessed(var) +def variables_accessed(variables): + """Notifies all tapes in the stack that variables have been accessed. + + Only trainable variables are marked as accessed. + + Args: + variables: iterable of variables to mark as accessed. + """ + strategy, context = ( + distribution_strategy_context.get_strategy_and_replica_context()) + accessed = [] + if context: + accessed = [strategy.extended.value_container(variable) + for variable in variables if variable.trainable] + else: + for variable in variables: + if variable.trainable: + accessed.extend(strategy.unwrap(variable)) + + for var in accessed: + pywrap_tensorflow.TFE_Py_TapeVariableAccessed(var) + + def pop_tape(tape): """Pops the top tape in the stack, if any.""" pywrap_tensorflow.TFE_Py_TapeSetRemove(tape._tape) # pylint: disable=protected-access -- GitLab From b0ce2166c0807e538ef346dad1d647d1983e0d4c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 11:07:45 -0800 Subject: [PATCH 0732/2345] Remove unnecessary dependencies from core/kernels:resource_variable_ops to :mutex_ops and :state. PiperOrigin-RevId: 229403710 --- tensorflow/core/BUILD | 1 + tensorflow/core/kernels/BUILD | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index caf7b4cdbc..1fc917a5f2 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -1406,6 +1406,7 @@ cc_library( "//tensorflow/core/kernels:manip", "//tensorflow/core/kernels:math", "//tensorflow/core/kernels:multinomial_op", + "//tensorflow/core/kernels:mutex_ops", "//tensorflow/core/kernels:nn", "//tensorflow/core/kernels:parameterized_truncated_normal_op", "//tensorflow/core/kernels:parsing", diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 1be749289b..ea6343f24d 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -2282,9 +2282,7 @@ tf_kernel_library( ":bounds_check", ":dense_update_functor", ":gather_functor", - ":mutex_ops", ":scatter_functor", - ":state", ":training_op_helpers", ":variable_ops", "//tensorflow/core:core_cpu_lib", -- GitLab From 3e0268dc76f8cc36e72a5cff980d8d0f27c609b9 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Tue, 15 Jan 2019 11:08:09 -0800 Subject: [PATCH 0733/2345] Adds v2 Mean IOU metric. PiperOrigin-RevId: 229403786 --- tensorflow/python/keras/metrics.py | 129 +++++++++++++++++- tensorflow/python/keras/metrics_test.py | 96 +++++++++++++ .../tensorflow.keras.metrics.-accuracy.pbtxt | 2 +- ...rflow.keras.metrics.-binary-accuracy.pbtxt | 2 +- ....keras.metrics.-categorical-accuracy.pbtxt | 2 +- ...low.keras.metrics.-categorical-hinge.pbtxt | 2 +- ...flow.keras.metrics.-cosine-proximity.pbtxt | 2 +- ...rflow.keras.metrics.-false-negatives.pbtxt | 2 +- ...rflow.keras.metrics.-false-positives.pbtxt | 2 +- .../v1/tensorflow.keras.metrics.-hinge.pbtxt | 2 +- ...w.keras.metrics.-mean-absolute-error.pbtxt | 2 +- ...rics.-mean-absolute-percentage-error.pbtxt | 2 +- ...ow.keras.metrics.-mean-squared-error.pbtxt | 2 +- ...rics.-mean-squared-logarithmic-error.pbtxt | 2 +- .../v1/tensorflow.keras.metrics.-mean.pbtxt | 2 +- .../tensorflow.keras.metrics.-precision.pbtxt | 2 +- .../v1/tensorflow.keras.metrics.-recall.pbtxt | 2 +- ....metrics.-sensitivity-at-specificity.pbtxt | 2 +- ...metrics.-sparse-categorical-accuracy.pbtxt | 2 +- ....metrics.-specificity-at-sensitivity.pbtxt | 2 +- ...sorflow.keras.metrics.-squared-hinge.pbtxt | 2 +- ...orflow.keras.metrics.-true-negatives.pbtxt | 2 +- ...orflow.keras.metrics.-true-positives.pbtxt | 2 +- .../tensorflow.keras.metrics.-accuracy.pbtxt | 2 +- ...rflow.keras.metrics.-binary-accuracy.pbtxt | 2 +- ....keras.metrics.-categorical-accuracy.pbtxt | 2 +- ...low.keras.metrics.-categorical-hinge.pbtxt | 2 +- ...flow.keras.metrics.-cosine-proximity.pbtxt | 2 +- ...rflow.keras.metrics.-false-negatives.pbtxt | 2 +- ...rflow.keras.metrics.-false-positives.pbtxt | 2 +- .../v2/tensorflow.keras.metrics.-hinge.pbtxt | 2 +- ...w.keras.metrics.-mean-absolute-error.pbtxt | 2 +- ...rics.-mean-absolute-percentage-error.pbtxt | 2 +- ...ow.keras.metrics.-mean-squared-error.pbtxt | 2 +- ...rics.-mean-squared-logarithmic-error.pbtxt | 2 +- .../v2/tensorflow.keras.metrics.-mean.pbtxt | 2 +- .../tensorflow.keras.metrics.-precision.pbtxt | 2 +- .../v2/tensorflow.keras.metrics.-recall.pbtxt | 2 +- ....metrics.-sensitivity-at-specificity.pbtxt | 2 +- ...metrics.-sparse-categorical-accuracy.pbtxt | 2 +- ....metrics.-specificity-at-sensitivity.pbtxt | 2 +- ...sorflow.keras.metrics.-squared-hinge.pbtxt | 2 +- ...orflow.keras.metrics.-true-negatives.pbtxt | 2 +- ...orflow.keras.metrics.-true-positives.pbtxt | 2 +- 44 files changed, 265 insertions(+), 44 deletions(-) diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index ec09ad9ba8..245dffe99e 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -241,12 +241,13 @@ class Metric(Layer): shape=(), aggregation=tf_variables.VariableAggregation.SUM, synchronization=tf_variables.VariableSynchronization.ON_READ, - initializer=None): + initializer=None, + dtype=None): """Adds state variable. Only for use by subclasses.""" return super(Metric, self).add_weight( name=name, shape=shape, - dtype=self._dtype, + dtype=self._dtype if dtype is None else dtype, trainable=False, initializer=initializer, collections=[], @@ -2175,6 +2176,130 @@ class KullbackLeiblerDivergence(MeanMetricWrapper): return super(KullbackLeiblerDivergence, cls).from_config(config) +class MeanIoU(Metric): + """Computes the mean Intersection-Over-Union metric. + + Mean Intersection-Over-Union is a common evaluation metric for semantic image + segmentation, which first computes the IOU for each semantic class and then + computes the average over classes. IOU is defined as follows: + IOU = true_positive / (true_positive + false_positive + false_negative). + The predictions are accumulated in a confusion matrix, weighted by + `sample_weight` and the metric is then calculated from it. + + If `sample_weight` is `None`, weights default to 1. + Use `sample_weight` of 0 to mask values. + + Usage: + + ```python + m = tf.keras.metrics.MeanIoU(num_classes=2) + m.update_state([0, 0, 1, 1], [0, 1, 0, 1]) + + # cm = [[1, 1], + [1, 1]] + # sum_row = [2, 2], sum_col = [2, 2], true_positives = [1, 1] + # iou = true_positives / (sum_row + sum_col - true_positives)) + # result = (1 / (2 + 2 - 1) + 1 / (2 + 2 - 1)) / 2 = 0.33 + print('Final result: ', m.result().numpy()) # Final result: 0.33 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile( + 'sgd', + loss='mse', + metrics=[tf.keras.metrics.MeanIoU(num_classes=2)]) + ``` + """ + + def __init__(self, num_classes, name=None, dtype=None): + """Creates a `MeanIoU` instance. + + Args: + num_classes: The possible number of labels the prediction task can have. + This value must be provided, since a confusion matrix of dimension = + [num_classes, num_classes] will be allocated. + name: (Optional) string name of the metric instance. + dtype: (Optional) data type of the metric result. + """ + super(MeanIoU, self).__init__(name=name, dtype=dtype) + self.num_classes = num_classes + + # Variable to accumulate the predictions in the confusion matrix. Setting + # the type to be `float64` as required by confusion_matrix_ops. + self.total_cm = self.add_weight( + 'total_confusion_matrix', + shape=(num_classes, num_classes), + initializer=init_ops.zeros_initializer, + dtype=dtypes.float64) + + def update_state(self, y_true, y_pred, sample_weight=None): + """Accumulates the confusion matrix statistics. + + Args: + y_true: The ground truth values. + y_pred: The predicted values. + sample_weight: Optional weighting of each example. Defaults to 1. Can be a + `Tensor` whose rank is either 0, or the same rank as `y_true`, and must + be broadcastable to `y_true`. + + Returns: + Update op. + """ + # Flatten the input if its rank > 1. + if y_pred.shape.ndims > 1: + y_pred = array_ops.reshape(y_pred, [-1]) + + if y_true.shape.ndims > 1: + y_true = array_ops.reshape(y_true, [-1]) + + if sample_weight is not None and sample_weight.shape.ndims > 1: + sample_weight = array_ops.reshape(sample_weight, [-1]) + + # Accumulate the prediction to current confusion matrix. + current_cm = confusion_matrix.confusion_matrix( + y_true, + y_pred, + self.num_classes, + weights=sample_weight, + dtype=dtypes.float64) + return state_ops.assign_add(self.total_cm, current_cm) + + def result(self): + """Compute the mean intersection-over-union via the confusion matrix.""" + sum_over_row = math_ops.cast( + math_ops.reduce_sum(self.total_cm, axis=0), dtype=self._dtype) + sum_over_col = math_ops.cast( + math_ops.reduce_sum(self.total_cm, axis=1), dtype=self._dtype) + true_positives = math_ops.cast( + array_ops.diag_part(self.total_cm), dtype=self._dtype) + + # sum_over_row + sum_over_col = + # 2 * true_positives + false_positives + false_negatives. + denominator = sum_over_row + sum_over_col - true_positives + + # The mean is only computed over classes that appear in the + # label or prediction tensor. If the denominator is 0, we need to + # ignore the class. + num_valid_entries = math_ops.reduce_sum( + math_ops.cast(math_ops.not_equal(denominator, 0), dtype=self._dtype)) + + iou = math_ops.div_no_nan(true_positives, denominator) + + return math_ops.div_no_nan( + math_ops.reduce_sum(iou, name='mean_iou'), num_valid_entries) + + def reset_states(self): + K.set_value(self.total_cm, np.zeros((self.num_classes, self.num_classes))) + + def get_config(self): + config = {'num_classes': self.num_classes} + base_config = super(MeanIoU, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + def accuracy(y_true, y_pred): y_pred.get_shape().assert_is_compatible_with(y_true.get_shape()) if y_true.dtype != y_pred.dtype: diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index d28a615c02..bf2f4d88fa 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -967,6 +967,89 @@ class MeanRelativeErrorTest(test.TestCase): self.assertEqual(self.evaluate(result), 0) +@test_util.run_all_in_graph_and_eager_modes +class MeanIoUTest(test.TestCase): + + def test_config(self): + m_obj = metrics.MeanIoU(num_classes=2, name='mean_iou') + self.assertEqual(m_obj.name, 'mean_iou') + self.assertEqual(m_obj.num_classes, 2) + + m_obj2 = metrics.MeanIoU.from_config(m_obj.get_config()) + self.assertEqual(m_obj2.name, 'mean_iou') + self.assertEqual(m_obj2.num_classes, 2) + + def test_unweighted(self): + y_pred = constant_op.constant([0, 1, 0, 1], dtype=dtypes.float32) + y_true = constant_op.constant([0, 0, 1, 1]) + + m_obj = metrics.MeanIoU(num_classes=2) + self.evaluate(variables.variables_initializer(m_obj.variables)) + + result = m_obj(y_true, y_pred) + + # cm = [[1, 1], + # [1, 1]] + # sum_row = [2, 2], sum_col = [2, 2], true_positives = [1, 1] + # iou = true_positives / (sum_row + sum_col - true_positives)) + expected_result = (1 / (2 + 2 - 1) + 1 / (2 + 2 - 1)) / 2 + self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) + + def test_weighted(self): + y_pred = constant_op.constant([0, 1, 0, 1], dtype=dtypes.float32) + y_true = constant_op.constant([0, 0, 1, 1]) + sample_weight = constant_op.constant([0.2, 0.3, 0.4, 0.1]) + + m_obj = metrics.MeanIoU(num_classes=2) + self.evaluate(variables.variables_initializer(m_obj.variables)) + + result = m_obj(y_true, y_pred, sample_weight=sample_weight) + + # cm = [[0.2, 0.3], + # [0.4, 0.1]] + # sum_row = [0.6, 0.4], sum_col = [0.5, 0.5], true_positives = [0.2, 0.1] + # iou = true_positives / (sum_row + sum_col - true_positives)) + expected_result = (0.2 / (0.6 + 0.5 - 0.2) + 0.1 / (0.4 + 0.5 - 0.1)) / 2 + self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) + + def test_multi_dim_input(self): + y_pred = constant_op.constant([[0, 1], [0, 1]], dtype=dtypes.float32) + y_true = constant_op.constant([[0, 0], [1, 1]]) + sample_weight = constant_op.constant([[0.2, 0.3], [0.4, 0.1]]) + + m_obj = metrics.MeanIoU(num_classes=2) + self.evaluate(variables.variables_initializer(m_obj.variables)) + + result = m_obj(y_true, y_pred, sample_weight=sample_weight) + + # cm = [[0.2, 0.3], + # [0.4, 0.1]] + # sum_row = [0.6, 0.4], sum_col = [0.5, 0.5], true_positives = [0.2, 0.1] + # iou = true_positives / (sum_row + sum_col - true_positives)) + expected_result = (0.2 / (0.6 + 0.5 - 0.2) + 0.1 / (0.4 + 0.5 - 0.1)) / 2 + self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) + + def test_zero_valid_entries(self): + m_obj = metrics.MeanIoU(num_classes=2) + self.evaluate(variables.variables_initializer(m_obj.variables)) + self.assertAllClose(self.evaluate(m_obj.result()), 0, atol=1e-3) + + def test_zero_and_non_zero_entries(self): + y_pred = constant_op.constant([1], dtype=dtypes.float32) + y_true = constant_op.constant([1]) + + m_obj = metrics.MeanIoU(num_classes=2) + self.evaluate(variables.variables_initializer(m_obj.variables)) + result = m_obj(y_true, y_pred) + + # cm = [[0, 0], + # [0, 1]] + # sum_row = [0, 1], sum_col = [0, 1], true_positives = [0, 1] + # iou = true_positives / (sum_row + sum_col - true_positives)) + expected_result = (0 + 1 / (1 + 1 - 1)) / 1 + self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) + + def _get_model(compile_metrics): model_layers = [ layers.Dense(3, activation='relu', kernel_initializer='ones'), @@ -1094,6 +1177,19 @@ class ResetStatesTest(keras_parameterized.TestCase): self.assertEqual(self.evaluate(auc_obj.false_negatives[1]), 25.) self.assertEqual(self.evaluate(auc_obj.true_negatives[1]), 25.) + def test_reset_states_mean_iou(self): + m_obj = metrics.MeanIoU(num_classes=2) + model = _get_model([m_obj]) + x = np.asarray([[0, 0, 0, 0], [1, 1, 1, 1], [1, 0, 1, 0], [0, 1, 0, 1]], + dtype=np.float32) + y = np.asarray([[0], [1], [1], [1]], dtype=np.float32) + model.evaluate(x, y) + self.assertArrayNear(self.evaluate(m_obj.total_cm)[0], [1, 0], 1e-1) + self.assertArrayNear(self.evaluate(m_obj.total_cm)[1], [3, 0], 1e-1) + model.evaluate(x, y) + self.assertArrayNear(self.evaluate(m_obj.total_cm)[0], [1, 0], 1e-1) + self.assertArrayNear(self.evaluate(m_obj.total_cm)[1], [3, 0], 1e-1) + if __name__ == '__main__': test.main() diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-accuracy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-accuracy.pbtxt index bad488f59b..e3e8dc00fe 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-accuracy.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-binary-accuracy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-binary-accuracy.pbtxt index a1e7601a51..60b4f992c0 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-binary-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-binary-accuracy.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-accuracy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-accuracy.pbtxt index 5f2c2f9807..deab5a7a70 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-accuracy.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-hinge.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-hinge.pbtxt index d173fc879f..10477e6662 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-hinge.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt index c50638740f..cdb3e1a79a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-negatives.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-negatives.pbtxt index c153e9cf4d..f4d067243d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-negatives.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-negatives.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-positives.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-positives.pbtxt index aae2bd9988..f898e00eea 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-positives.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-positives.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-hinge.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-hinge.pbtxt index 7f13c75583..393ba964c4 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-hinge.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-error.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-error.pbtxt index 896320004c..a86b26e618 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-error.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-error.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt index 37ab1a409b..5e8f321a06 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-error.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-error.pbtxt index 9025abbb0a..a2eb10c5f6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-error.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-error.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt index 808853994d..7df45a8caf 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean.pbtxt index 904a2fa9ca..899502cfad 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean.pbtxt @@ -107,7 +107,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-precision.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-precision.pbtxt index 794da447d7..3bdf3a4167 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-precision.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-precision.pbtxt @@ -107,7 +107,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-recall.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-recall.pbtxt index cae610b9ea..1ac2aacca0 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-recall.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-recall.pbtxt @@ -107,7 +107,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt index b70ef32bca..5c4f26929f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt index 2e693269bf..48ac5c2fdc 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt index e62a2df056..702f711722 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-squared-hinge.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-squared-hinge.pbtxt index 38f94048cc..6c3a9748c7 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-squared-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-squared-hinge.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-negatives.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-negatives.pbtxt index 1a524d73c0..3f1a99b83b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-negatives.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-negatives.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-positives.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-positives.pbtxt index b9b4f565c5..e5a4207a32 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-positives.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-positives.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-accuracy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-accuracy.pbtxt index bad488f59b..e3e8dc00fe 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-accuracy.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-binary-accuracy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-binary-accuracy.pbtxt index a1e7601a51..60b4f992c0 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-binary-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-binary-accuracy.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-accuracy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-accuracy.pbtxt index 5f2c2f9807..deab5a7a70 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-accuracy.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-hinge.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-hinge.pbtxt index d173fc879f..10477e6662 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-hinge.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt index c50638740f..cdb3e1a79a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-negatives.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-negatives.pbtxt index c153e9cf4d..f4d067243d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-negatives.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-negatives.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-positives.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-positives.pbtxt index aae2bd9988..f898e00eea 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-positives.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-positives.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-hinge.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-hinge.pbtxt index 7f13c75583..393ba964c4 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-hinge.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-error.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-error.pbtxt index 896320004c..a86b26e618 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-error.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-error.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt index 37ab1a409b..5e8f321a06 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-error.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-error.pbtxt index 9025abbb0a..a2eb10c5f6 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-error.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-error.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt index 808853994d..7df45a8caf 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean.pbtxt index 904a2fa9ca..899502cfad 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean.pbtxt @@ -107,7 +107,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-precision.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-precision.pbtxt index 794da447d7..3bdf3a4167 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-precision.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-precision.pbtxt @@ -107,7 +107,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-recall.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-recall.pbtxt index cae610b9ea..1ac2aacca0 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-recall.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-recall.pbtxt @@ -107,7 +107,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt index b70ef32bca..5c4f26929f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt index 2e693269bf..48ac5c2fdc 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt index e62a2df056..702f711722 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-squared-hinge.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-squared-hinge.pbtxt index 38f94048cc..6c3a9748c7 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-squared-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-squared-hinge.pbtxt @@ -109,7 +109,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-negatives.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-negatives.pbtxt index 1a524d73c0..3f1a99b83b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-negatives.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-negatives.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-positives.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-positives.pbtxt index b9b4f565c5..e5a4207a32 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-positives.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-positives.pbtxt @@ -108,7 +108,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'aggregation\', \'synchronization\', \'initializer\', \'dtype\'], varargs=None, keywords=None, defaults=[\'()\', \'VariableAggregation.SUM\', \'VariableSynchronization.ON_READ\', \'None\', \'None\'], " } member_method { name: "apply" -- GitLab From a082de29448daa8d91f5cc24a3b37a296f5aec4b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 11:09:26 -0800 Subject: [PATCH 0734/2345] Improve batch_group_count documentation in XLA operation semantics PiperOrigin-RevId: 229404046 --- .../compiler/xla/g3doc/operation_semantics.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/xla/g3doc/operation_semantics.md b/tensorflow/compiler/xla/g3doc/operation_semantics.md index 59ba9bb658..c5f9377f98 100644 --- a/tensorflow/compiler/xla/g3doc/operation_semantics.md +++ b/tensorflow/compiler/xla/g3doc/operation_semantics.md @@ -636,11 +636,15 @@ details, see `tf.nn.depthwise_conv2d`. The `batch_group_count` (default value 1) argument can be used for depthwise filters during backpropagation. `batch_group_count` needs to be a divisor of the -size of the `lhs` batch dimension. If `batch_group_count` is greater than 1, it -means that conceptually the output batch dimension is split evenely in -`batch_group_count` groups, such that each group consists of a consecutive -subsequence of batches. Each output batch element is the reduced value of the -batch group size. +size of the `lhs` (input) batch dimension. If `batch_group_count` is greater +than 1, it means that the output batch dimension should be of size +`batch_group_size` where `batch_group_size = input batch / batch_group_count`. +For convolutions with `batch_group_count` greater than 1, the input batch size +must evenly divide into batch_group_size and output feature size, which implies +that the output feature size must be equal to batch_group_count. Conceptually, +this can be achieved by performing the usual convolution, and then scraping +`batch_group_size` number of elements on the diagonal of the matrix formed by +output batch and output feature. The output shape has these dimensions, in this order: -- GitLab From 8154ec0b4a893657e48b838c06e1f988aeac4115 Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Tue, 15 Jan 2019 11:48:17 -0800 Subject: [PATCH 0735/2345] [Grappler] Lower V2 control flow in inlined function body. PiperOrigin-RevId: 229411946 --- .../process_function_library_runtime.cc | 10 +- tensorflow/core/grappler/optimizers/BUILD | 1 + .../grappler/optimizers/function_optimizer.cc | 74 ++++++----- .../optimizers/function_optimizer_test.cc | 122 ++++++++++++++++++ 4 files changed, 169 insertions(+), 38 deletions(-) diff --git a/tensorflow/core/common_runtime/process_function_library_runtime.cc b/tensorflow/core/common_runtime/process_function_library_runtime.cc index b236343a0f..76c75ad3d2 100644 --- a/tensorflow/core/common_runtime/process_function_library_runtime.cc +++ b/tensorflow/core/common_runtime/process_function_library_runtime.cc @@ -568,15 +568,12 @@ Status ProcessFunctionLibraryRuntime::InstantiateMultiDevice( DumpGraph("Before running POST_PLACEMENT passes", graph.get()); TF_RETURN_IF_ERROR(OptimizationPassRegistry::Global()->RunGrouping( OptimizationPassRegistry::POST_PLACEMENT, optimization_options)); - DumpGraph("Before running POST_REWRITE_FOR_EXEC passes", graph.get()); - TF_RETURN_IF_ERROR(OptimizationPassRegistry::Global()->RunGrouping( - OptimizationPassRegistry::POST_REWRITE_FOR_EXEC, optimization_options)); - DumpGraph("After all optimization passes", graph.get()); Device* cpu_device; TF_RETURN_IF_ERROR(device_mgr_->LookupDevice("CPU:0", &cpu_device)); if (options.optimize_graph_fn) { + DumpGraph("Before running graph optimization fn", graph.get()); Status status = options.optimize_graph_fn(std::move(ret_node_names), &data->overlay_lib_, device_set, cpu_device, &graph); @@ -587,6 +584,11 @@ Status ProcessFunctionLibraryRuntime::InstantiateMultiDevice( DumpGraph("After optimization", graph.get()); } + DumpGraph("Before running POST_REWRITE_FOR_EXEC passes", graph.get()); + TF_RETURN_IF_ERROR(OptimizationPassRegistry::Global()->RunGrouping( + OptimizationPassRegistry::POST_REWRITE_FOR_EXEC, optimization_options)); + DumpGraph("After all optimization passes", graph.get()); + std::unordered_map> subgraphs; TF_RETURN_IF_ERROR( PartitionFunctionGraph(device_set, std::move(graph), &subgraphs)); diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index ee216f80f9..70e0f226fc 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -181,6 +181,7 @@ tf_cuda_cc_test( "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler:utils", "//tensorflow/core/grappler/utils:grappler_test", + "@com_google_absl//absl/algorithm:container", ], ) diff --git a/tensorflow/core/grappler/optimizers/function_optimizer.cc b/tensorflow/core/grappler/optimizers/function_optimizer.cc index 8095d6f31d..1394aa5047 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer.cc @@ -27,6 +27,8 @@ limitations under the License. #include "tensorflow/core/common_runtime/device_mgr.h" #include "tensorflow/core/common_runtime/device_set.h" #include "tensorflow/core/common_runtime/function.h" +#include "tensorflow/core/common_runtime/lower_if_while.h" +#include "tensorflow/core/common_runtime/optimization_registry.h" #include "tensorflow/core/common_runtime/placer.h" #include "tensorflow/core/common_runtime/process_function_library_runtime.h" #include "tensorflow/core/framework/attr_value_util.h" @@ -1365,20 +1367,6 @@ Status IsInlinableIndirectFunctionCall(const FunctionOptimizerContext& ctx, SummarizeNodeDef(func_node)); } - // TODO(b/120991525, b/120986912): We need to lower `If` and `While` nodes to - // `Switch` nodes after function inlining (one more PRE_PLACEMENT pass?), but - // because of the reason described above we are not sure that it's safe, for - // now just disable inlining functions with functional control flow. - const auto is_functional_ctrl_flow_op = [](const NodeDef& node) { - return IsIf(node) || IsWhile(node); - }; - if (absl::c_any_of(func.node_def(), is_functional_ctrl_flow_op)) { - return errors::FailedPrecondition( - "Can't inline function with `If` or `While` nodes in the function " - "body: ", - SummarizeNodeDef(func_node)); - } - return Status::OK(); } @@ -1488,22 +1476,46 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, const string prefix = strings::StrCat(func_node.name(), "/"); + // ------------------------------------------------------------------------ // + // Grappler receives the graph after PRE_PLACEMENT, Placer, and POST_PLACEMENT + // passes, so each node has a valid device assignment. Also V2 control + // flow ops (functional If and While) lowered to V1 control flow (Switch and + // Merge nodes). To keep graph valid for execution we must assign device to + // every inlined graph node, and also lower the control flow. + + // Control flow lowering and Placer works with a Graph object. + std::unique_ptr func_body_graph = + absl::make_unique(ctx->function_library()); + + GraphConstructorOptions opts; + TF_RETURN_IF_ERROR( + ConvertGraphDefToGraph(opts, item.graph, func_body_graph.get())); + + GraphOptimizationPassOptions opt_options; + opt_options.graph = &func_body_graph; + opt_options.flib_def = ctx->mutable_function_library(); + + // TODO(ezhulenev): Should we run full PRE_PLACEMENT pass here? And + // POST_PLACEMENT after placer? + LowerIfWhilePass pass; + TF_RETURN_IF_ERROR(pass.Run(opt_options)); + // ------------------------------------------------------------------------ // // Before placing the function body nodes we pin input placeholders to the // same device as their corresponding input nodes. - for (NodeDef& func_body_node : *item.graph.mutable_node()) { + for (Node* func_body_node : func_body_graph->nodes()) { const auto input_placeholder_idx = - input_placeholders_idx.find(func_body_node.name()); + input_placeholders_idx.find(func_body_node->name()); if (input_placeholder_idx != input_placeholders_idx.end()) { const int input_idx = input_placeholder_idx->second; const GraphView::OutputPort output_port = ctx->graph_view().GetRegularFanin({&func_node, input_idx}); - VLOG(3) << "Pin inlined function input node '" << func_body_node.name() + VLOG(3) << "Pin inlined function input node '" << func_body_node->name() << "' to the '" << output_port.node->device() << "' device."; - func_body_node.set_device(output_port.node->device()); + func_body_node->set_requested_device(output_port.node->device()); } } @@ -1511,8 +1523,6 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // After placing nodes corresponding to the function inputs, we need to assign // device placements to all other function body nodes. - GraphDef placed_graph_def; - const DeviceSet* devices = ctx->devices(); if (devices->devices().empty()) { @@ -1522,9 +1532,8 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // of a batch job for graph analysis/optimization. VLOG(3) << "Assign function call node device to all function body nodes. " << "Device: " << func_node.device(); - placed_graph_def = item.mutable_function_body(); - for (NodeDef& node : *placed_graph_def.mutable_node()) { - node.set_device(func_node.device()); + for (Node* func_body_node : func_body_graph->nodes()) { + func_body_node->set_requested_device(func_node.device()); } } else { // If we are running in an active runtime session, Grappler will get the @@ -1536,24 +1545,21 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, [](string* out, const Device* d) { out->append(d->name()); }) << "]"; - // Construct a Graph object from the instantiated function body. - GraphConstructorOptions opts; - Graph graph(ctx->function_library()); - TF_RETURN_IF_ERROR( - ConvertGraphDefToGraph(opts, item.function_body(), &graph)); - // Use function caller node device as a default for placer. const Device* default_device = devices->FindDeviceByName(func_node.device()); - Placer placer(&graph, devices, nullptr, /* No session options */ - default_device); + Placer placer(func_body_graph.get(), devices, + nullptr /* No session options */, default_device); TF_RETURN_IF_ERROR(placer.Run()); - - // Convert Graph back to the GraphDef. - graph.ToGraphDef(&placed_graph_def); } + // TODO(ezhulenev): Should we run POST_PLACEMENT runtime pass here? + + // All following transformations will work with GraphDef. + GraphDef placed_graph_def; + func_body_graph->ToGraphDef(&placed_graph_def); + // ------------------------------------------------------------------------ // // After all nodes placed we need to prepare them for inlining into the // optimized graph: turn placeholders into identities, update nodes diff --git a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc index 827b0658c5..f4752bd584 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc @@ -14,6 +14,8 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/grappler/optimizers/function_optimizer.h" + +#include "absl/algorithm/container.h" #include "tensorflow/cc/ops/functional_ops.h" #include "tensorflow/cc/ops/standard_ops.h" #include "tensorflow/core/framework/function_testlib.h" @@ -1182,6 +1184,126 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithMergedDeadTensors) { test::ExpectTensorEqual(tensors[0], tensors_expected[0]); } +TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithFunctionalControlFlow) { + using test::function::NDef; + using FDH = FunctionDefHelper; + + FunctionOptimizer optimizer(RewriterConfig::AGGRESSIVE); + + FunctionDef add_func = FunctionDefHelper::Create( + "MyAdd", {"x:T", "y:T"}, {"z:T"}, {"T: {float, double}"}, + {{{"add"}, "Add", {"x", "y"}, {{"T", "$T"}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z", "add:z:0"}}); + + FunctionDef mul_func = FunctionDefHelper::Create( + "MyMul", {"x:T", "y:T"}, {"z:T"}, {"T: {float, double}"}, + {{{"mul"}, "Mul", {"x", "y"}, {{"T", "$T"}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z", "mul:z:0"}}); + + // Compute: return cond ? a + b : a * b + FunctionDef add_or_mul_func = FunctionDefHelper::Create( + "AddOrMul", {"cond:bool", "x:float", "y:float"}, {"z:float"}, {}, + { + {{"if_node"}, + "If", + {"cond", "x", "y"}, + { + {"Tcond", DT_BOOL}, + {"Tin", DataTypeSlice{DT_FLOAT, DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT}}, + {"then_branch", FDH::FunctionRef("MyAdd", {{"T", DT_FLOAT}})}, + {"else_branch", FDH::FunctionRef("MyMul", {{"T", DT_FLOAT}})}, + {"_lower_using_switch_merge", true}, + }}, + }, + /* Mapping between function returns and function node outputs. */ + {{"z", "if_node:output:0"}}); + + // Build a computation graph for: + // is_add: bool + // a: float + // b: float + // c = AddOrMul(is_add, a, b) # is_add ? a + b : a * b + // d = Identity(c) + // return d + + // c = MyMul(a, b) + GrapplerItem item; + item.fetch = {"d"}; + item.graph = test::function::GDef( + {NDef("is_add", "Placeholder", {}, {{"dtype", DT_BOOL}}, kDevice), + NDef("a", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + NDef("b", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + + NDef("c", "PartitionedCall", {"is_add", "a", "b"}, + {{"Tin", DataTypeSlice{DT_BOOL, DT_FLOAT, DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT}}, + {"f", FDH::FunctionRef("AddOrMul")}}, + kDevice), + + NDef("d", "Identity", {"c"}, {{"T", DT_FLOAT}}, kDevice)}, + // Function library. + {add_or_mul_func, add_func, mul_func}); + + GraphDef optimized_graph; + TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &optimized_graph)); + + const auto count_nodes_with_op = [&](const string& op) { + return absl::c_count_if(optimized_graph.node(), [&](const NodeDef& node) { + return node.op() == op; + }); + }; + + // All `PartitionedCall` nodes in the optimized graph must be inlined, and + // `If` node must be lowered to `Switch` and `Merge` nodes. + EXPECT_EQ(count_nodes_with_op("PartitionedCallOp"), 0); + EXPECT_EQ(count_nodes_with_op("If"), 0); + EXPECT_EQ(count_nodes_with_op("Switch"), 3); + EXPECT_EQ(count_nodes_with_op("Merge"), 1); + + GrapplerItem optimized = item.WithGraph(std::move(optimized_graph)); + + Tensor one = test::AsScalar(1.0); + Tensor two = test::AsScalar(2.0); + Tensor three = test::AsScalar(3.0); + + const auto feed_args = [&](bool is_add) { + std::vector> feed; + feed.emplace_back("a", one); + feed.emplace_back("b", two); + feed.emplace_back("is_add", test::AsScalar(is_add)); + return feed; + }; + + { // Check 'is_add == true': a + b + item.feed = feed_args(true); + optimized.feed = feed_args(true); + + auto tensors_expected = EvaluateFetchNodes(item); + ASSERT_EQ(tensors_expected.size(), 1); + test::ExpectTensorEqual(tensors_expected[0], three); + + auto tensors = EvaluateFetchNodes(optimized); + ASSERT_EQ(tensors.size(), tensors_expected.size()); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); + } + + { // Check 'is_add == false': a * b + item.feed = feed_args(false); + optimized.feed = feed_args(false); + + auto tensors_expected = EvaluateFetchNodes(item); + ASSERT_EQ(tensors_expected.size(), 1); + test::ExpectTensorEqual(tensors_expected[0], two); + + auto tensors = EvaluateFetchNodes(optimized); + ASSERT_EQ(tensors.size(), tensors_expected.size()); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); + } +} + TEST_F(FunctionOptimizerTest, SpecializeFunctionXTimesTwo) { using test::function::NDef; -- GitLab From 7b8464b172fbe96464e7a800f89883e5796b1458 Mon Sep 17 00:00:00 2001 From: Thomas O'Malley Date: Tue, 15 Jan 2019 11:49:02 -0800 Subject: [PATCH 0736/2345] Adds Multi-Worker Independent Worker Mode to Keras. PiperOrigin-RevId: 229412056 --- .../distribute/distribute_coordinator.py | 15 +-- tensorflow/python/keras/BUILD | 1 + tensorflow/python/keras/engine/training.py | 91 +++++++++++++++---- .../python/keras/engine/training_utils.py | 10 ++ 4 files changed, 91 insertions(+), 26 deletions(-) diff --git a/tensorflow/python/distribute/distribute_coordinator.py b/tensorflow/python/distribute/distribute_coordinator.py index 78c995a578..89bd8537b4 100644 --- a/tensorflow/python/distribute/distribute_coordinator.py +++ b/tensorflow/python/distribute/distribute_coordinator.py @@ -721,7 +721,8 @@ def run_distribute_coordinator(worker_fn, Returns: In the client job, return the value returned by `worker_fn` if - it is in-graph replication; return None otherwise. + it is in-graph replication or INDEPENDENT_WORKER mode; return None + otherwise. """ tf_config = json.loads(os.environ.get("TF_CONFIG", "{}")) if not cluster_spec: @@ -819,19 +820,19 @@ def run_distribute_coordinator(worker_fn, if task_type in [_TaskType.CHIEF, _TaskType.WORKER]: if strategy.extended.experimental_between_graph: # All jobs run `worker_fn` if between-graph. - _run_single_worker(worker_fn, strategy, cluster_spec, task_type, - task_id, session_config, rpc_layer) + return _run_single_worker(worker_fn, strategy, cluster_spec, task_type, + task_id, session_config, rpc_layer) else: # Only one node runs `worker_fn` if in-graph. context = _WorkerContext(strategy, cluster_spec, task_type, task_id) if context.is_chief: - _run_single_worker(worker_fn, strategy, cluster_spec, None, None, - session_config, rpc_layer) + return _run_single_worker(worker_fn, strategy, cluster_spec, None, + None, session_config, rpc_layer) else: server.join() elif task_type == _TaskType.EVALUATOR: - _run_single_worker(eval_fn, eval_strategy, cluster_spec, task_type, - task_id, session_config, rpc_layer) + return _run_single_worker(eval_fn, eval_strategy, cluster_spec, task_type, + task_id, session_config, rpc_layer) else: if task_type != _TaskType.PS: raise ValueError("Unexpected task_type: %r" % task_type) diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index d1ec5f0ac3..9e08c766db 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -152,6 +152,7 @@ py_library( ":regularizers", ":saving", "//tensorflow/python/data", + "//tensorflow/python/distribute:distribute_coordinator", "//tensorflow/python/distribute:reduce_util", "//tensorflow/python/training/checkpointable:data_structures", "//tensorflow/tools/docs:doc_controls", diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index 9f544ceff4..d690df2986 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -24,6 +24,8 @@ import numpy as np from tensorflow.python import tf2 from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.ops import iterator_ops +from tensorflow.python.distribute import distribute_coordinator as dc +from tensorflow.python.distribute import distribute_coordinator_context as dc_context from tensorflow.python.eager import context from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape @@ -128,6 +130,7 @@ class Model(Network): # passing distribution strategy to compile rather than creating the model # under distribution strategy scope. self._compile_distribution = False + self._distributed_session_is_configured = False self.run_eagerly = None @@ -216,6 +219,7 @@ class Model(Network): 'create the model under the distribution strategy scope.') self._distribution_strategy = distribute self._compile_distribution = True + self._distributed_session_is_configured = False else: if distribution_strategy_context.has_strategy(): # When the user builds the model in the DS scope and cross replica @@ -273,9 +277,6 @@ class Model(Network): # Set DistributionStrategy specific parameters. self._distributed_model = None - if self._distribution_strategy is not None: - distributed_training_utils.configure_and_create_session( - self._distribution_strategy) # Initialize model metric attributes. self._init_metric_attributes() if not self.built: @@ -781,22 +782,52 @@ class Model(Network): # Case 1: distribution strategy. if self._distribution_strategy: - return training_distributed.fit_distributed( - self, - x=x, - y=y, - batch_size=batch_size, - epochs=epochs, - verbose=verbose, - callbacks=callbacks, - validation_split=validation_split, - validation_data=validation_data, - shuffle=shuffle, - class_weight=class_weight, - sample_weight=sample_weight, - initial_epoch=initial_epoch, - steps_per_epoch=steps_per_epoch, - validation_steps=validation_steps) + if training_utils.should_run_multi_worker(): + # Multi-Worker mode runs the Keras training loop on multiple + # servers via the Distribute Coordinator. + def _worker_fn(_): + """Run training inside the distributed coordinator.""" + self._configure_distributed_session() + return training_distributed.fit_distributed( + self, + x=x, + y=y, + batch_size=batch_size, + epochs=epochs, + verbose=verbose, + callbacks=callbacks, + validation_split=validation_split, + validation_data=validation_data, + shuffle=shuffle, + class_weight=class_weight, + sample_weight=sample_weight, + initial_epoch=initial_epoch, + steps_per_epoch=steps_per_epoch, + validation_steps=validation_steps) + + # Independent worker only for now. + return dc.run_distribute_coordinator( + _worker_fn, + self._distribution_strategy, + mode=dc.CoordinatorMode.INDEPENDENT_WORKER) + else: + self._configure_distributed_session() + return training_distributed.fit_distributed( + self, + x=x, + y=y, + batch_size=batch_size, + epochs=epochs, + verbose=verbose, + callbacks=callbacks, + validation_split=validation_split, + validation_data=validation_data, + shuffle=shuffle, + class_weight=class_weight, + sample_weight=sample_weight, + initial_epoch=initial_epoch, + steps_per_epoch=steps_per_epoch, + validation_steps=validation_steps) batch_size = self._validate_or_infer_batch_size( batch_size, steps_per_epoch, x) @@ -1007,6 +1038,7 @@ class Model(Network): """ # Case 1: distribution strategy. if self._distribution_strategy: + self._configure_distributed_session() return training_distributed.evaluate_distributed( self, x=x, @@ -1134,6 +1166,7 @@ class Model(Network): """ # Case 1: distribution strategy. if self._distribution_strategy: + self._configure_distributed_session() return training_distributed.predict_distributed(self, x=x, batch_size=batch_size, @@ -2644,6 +2677,26 @@ class Model(Network): self.output_names = training_utils.generic_output_names(outputs) self.built = True + def _configure_distributed_session(self): + """Configure a Session for use with Distribution Strategies. + + Raises: + ValueError: If a non-distributed Session has already been created. + """ + if not self._distributed_session_is_configured: + if (dc_context.get_current_worker_context() is not None and + getattr(K._SESSION, 'session', None) is not None): # pylint: disable=protected-access + raise ValueError('Session was created before `fit`, `evaluate`, ' + 'or `predict` was called. With Multi-Worker ' + 'mode, this is not allowed. Please avoid ' + 'creating a Session outside of these methods. ' + 'The Session may have been created by a call ' + 'to `keras.backend.get_session()` or ' + 'functions that use Sessions, like `load_weights`.') + distributed_training_utils.configure_and_create_session( + self._distribution_strategy) + self._distributed_session_is_configured = True + class DistributedCallbackModel(Model): """Model that is used for callbacks with DistributionStrategy.""" diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py index f7cbf4101a..c36b637829 100644 --- a/tensorflow/python/keras/engine/training_utils.py +++ b/tensorflow/python/keras/engine/training_utils.py @@ -21,6 +21,8 @@ from __future__ import print_function import abc from collections import OrderedDict import copy +import json +import os import numpy as np import six @@ -46,6 +48,7 @@ from tensorflow.python.ops import array_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import weights_broadcast_ops from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.training import server_lib from tensorflow.python.util import nest @@ -1492,3 +1495,10 @@ def convert_eager_tensors_to_numpy(structure): return element return nest.map_structure(_convert, structure) + + +def should_run_multi_worker(): + """Whether a model should be run using DistributedCoordinator.""" + tf_config = json.loads(os.environ.get('TF_CONFIG', '{}')) + cluster_spec = server_lib.ClusterSpec(tf_config.get('cluster', {})) + return tf_config and 'master' not in cluster_spec.jobs -- GitLab From b58039978dda1ddc4fadb2ee528cfdb9878e000a Mon Sep 17 00:00:00 2001 From: Ayush Dubey Date: Tue, 15 Jan 2019 12:06:18 -0800 Subject: [PATCH 0737/2345] Automated rollback of commit 681f6a6ac9344425549c8a38fcbfae854e7deddf PiperOrigin-RevId: 229415244 --- tensorflow/core/BUILD | 2 +- .../base_collective_executor.cc | 33 -- .../common_runtime/base_collective_executor.h | 18 - .../collective_param_resolver_local.cc | 159 +++------ .../collective_param_resolver_local.h | 15 +- .../core/common_runtime/collective_util.cc | 31 -- .../core/common_runtime/collective_util.h | 21 -- .../hierarchical_tree_broadcaster.h | 5 - .../core/common_runtime/ring_reducer.cc | 43 ++- tensorflow/core/common_runtime/ring_reducer.h | 24 +- tensorflow/core/distributed_runtime/BUILD | 1 - .../collective_param_resolver_distributed.cc | 21 +- ...lective_param_resolver_distributed_test.cc | 2 - tensorflow/core/framework/collective.cc | 11 - tensorflow/core/framework/collective.h | 32 +- tensorflow/core/kernels/BUILD | 24 -- .../core/kernels/collective_nccl_reducer.cc | 204 ----------- .../core/kernels/collective_nccl_reducer.h | 49 --- .../kernels/collective_nccl_reducer_test.cc | 331 ------------------ tensorflow/core/kernels/collective_ops.cc | 3 - tensorflow/core/nccl/BUILD | 4 +- tensorflow/core/protobuf/worker.proto | 1 - 22 files changed, 118 insertions(+), 916 deletions(-) delete mode 100644 tensorflow/core/kernels/collective_nccl_reducer.cc delete mode 100644 tensorflow/core/kernels/collective_nccl_reducer.h delete mode 100644 tensorflow/core/kernels/collective_nccl_reducer_test.cc diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 1fc917a5f2..bedc78ac11 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -3714,6 +3714,7 @@ tf_cc_tests( srcs = [ "common_runtime/buf_rendezvous_test.cc", "common_runtime/collective_executor_mgr_test.cc", + "common_runtime/collective_param_resolver_local_test.cc", "common_runtime/collective_rma_local_test.cc", "common_runtime/device_resolver_local_test.cc", "common_runtime/device_set_test.cc", @@ -3829,7 +3830,6 @@ tf_cc_tests( name = "higher_level_tests_needing_kernels", size = "small", srcs = [ - "common_runtime/collective_param_resolver_local_test.cc", "graph/graph_constructor_test.cc", ], linkopts = select({ diff --git a/tensorflow/core/common_runtime/base_collective_executor.cc b/tensorflow/core/common_runtime/base_collective_executor.cc index 03c0e9ce2b..92e56df181 100644 --- a/tensorflow/core/common_runtime/base_collective_executor.cc +++ b/tensorflow/core/common_runtime/base_collective_executor.cc @@ -296,37 +296,4 @@ Status BaseCollectiveExecutor::CreateCollective( return status; } -bool BaseCollectiveExecutor::CheckDependencies( - const CollectiveParams& col_params) { - for (int32 instance : col_params.instance.impl_details.dependencies) { - auto find_iter = launched_.find(instance); - if (find_iter == launched_.end() || find_iter->second != 0) { - return false; - } - } - return true; -} - -void BaseCollectiveExecutor::WaitForDependencies( - const CollectiveParams& col_params) { - mutex_lock l(launch_mu_); - while (!CheckDependencies(col_params)) { - launch_cv_.wait(l); - } -} - -void BaseCollectiveExecutor::Launched(const CollectiveParams& col_params) { - mutex_lock l(launch_mu_); - if (launched_.find(col_params.instance.instance_key) == launched_.end()) { - const string& task_name = - col_params.instance.task_names[col_params.default_rank]; - const int32 num_devices = - col_params.instance.num_devices_per_task.at(task_name); - launched_[col_params.instance.instance_key] = num_devices; - } - if (--launched_[col_params.instance.instance_key] == 0) { - launch_cv_.notify_all(); - } -} - } // namespace tensorflow diff --git a/tensorflow/core/common_runtime/base_collective_executor.h b/tensorflow/core/common_runtime/base_collective_executor.h index b711aa6d50..09826a8814 100644 --- a/tensorflow/core/common_runtime/base_collective_executor.h +++ b/tensorflow/core/common_runtime/base_collective_executor.h @@ -135,33 +135,15 @@ class BaseCollectiveExecutor : public CollectiveExecutor { client_locality, done); } - // If we need to enforce an ordering on any portion of collective - // implementation, and the ordering is encoded via attribute on the collective - // op, this function will block until all dependencies for this collective - // have completed. - void WaitForDependencies(const CollectiveParams& col_params) override; - // Record that this collective has completed the portion of the implementation - // that needs to be ordered wrt other collectives, to unblock any of its - // dependent ops. - void Launched(const CollectiveParams& col_params) override; - protected: const int64 step_id_; const DeviceMgr* dev_mgr_; // Not owned. std::unique_ptr remote_access_; const string* gpu_ring_order_; // Not owned. - mutex launch_mu_; - condition_variable launch_cv_; - // collective instance key -> number of local devices for which NCCL ops have - // been launched. - std::unordered_map launched_ GUARDED_BY(launch_mu_); private: Status CreateCollective(const CollectiveParams& col_params, CollectiveImplementationInterface** col_impl); - // Check if all ops on which this collective depends on have launched. - bool CheckDependencies(const CollectiveParams& col_params) - EXCLUSIVE_LOCKS_REQUIRED(launch_mu_); }; } // namespace tensorflow diff --git a/tensorflow/core/common_runtime/collective_param_resolver_local.cc b/tensorflow/core/common_runtime/collective_param_resolver_local.cc index 8907f6d56a..a8e3f4c881 100644 --- a/tensorflow/core/common_runtime/collective_param_resolver_local.cc +++ b/tensorflow/core/common_runtime/collective_param_resolver_local.cc @@ -26,7 +26,6 @@ limitations under the License. #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/gtl/flatmap.h" -#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/platform/types.h" #include "tensorflow/core/util/device_name_utils.h" @@ -40,10 +39,7 @@ void CollectiveParamResolverLocal::InstanceRec::WaitForOutMu(mutex_lock& lock) { CollectiveParamResolverLocal::CollectiveParamResolverLocal( const DeviceMgr* dev_mgr, DeviceResolverInterface* dev_resolver, const string& task_name) - : nccl_(false), // (b/111897089): turn on NCCL collectives. - dev_mgr_(dev_mgr), - dev_resolver_(dev_resolver), - task_name_(task_name) {} + : dev_mgr_(dev_mgr), dev_resolver_(dev_resolver), task_name_(task_name) {} void CollectiveParamResolverLocal::CompleteGroupAsync( const CompleteGroupRequest* request, CompleteGroupResponse* response, @@ -320,28 +316,29 @@ GlobalDeviceMap EstablishGlobalRank( // cp->same_num_devices_per_task. Requires cp->instance.task_names // be sorted. void SetDevPerTask(CollectiveParams* cp) { - cp->instance.num_devices_per_task.clear(); - const string* last_task_name = &cp->instance.task_names[0]; + cp->instance.same_num_devices_per_task = false; + if (cp->instance.task_names.empty()) return; + int dev_per_task = -1; int count = 0; + const string* last_task_name = &cp->instance.task_names[0]; for (const string& task_name : cp->instance.task_names) { - if (task_name == *last_task_name) { - ++count; - } else { - cp->instance.num_devices_per_task[*last_task_name] = count; + if (task_name != *last_task_name) { + CHECK_GT(count, 0); + if (dev_per_task < 0) { + dev_per_task = count; + } else { + CHECK_GT(dev_per_task, 0); + if (count != dev_per_task) return; + } count = 1; last_task_name = &task_name; + } else { + ++count; } } - cp->instance.num_devices_per_task[*last_task_name] = count; - - cp->instance.same_num_devices_per_task = false; - int dev_per_task = -1; - for (const auto& task_dev : cp->instance.num_devices_per_task) { - if (dev_per_task == -1) { - dev_per_task = task_dev.second; - } else if (dev_per_task != task_dev.second) { - return; - } + CHECK_GT(count, 0); + if ((dev_per_task > 0) && (count != dev_per_task)) { + return; } cp->instance.same_num_devices_per_task = true; CHECK_EQ((cp->group.group_size % cp->group.num_tasks), 0); @@ -401,6 +398,7 @@ void CollectiveParamResolverLocal::SetDefaultRank(const string& device, void CollectiveParamResolverLocal::InitInstanceSharedParams( const GroupRec* gr, const CollectiveParams* cp, InstanceRec* ir, const StatusCallback& done) { + VLOG(1) << "InitInstanceSharedParams " << ir; ir->shared.instance = cp->instance; { mutex_lock gl(gr->mu); @@ -414,8 +412,8 @@ void CollectiveParamResolverLocal::InitInstanceSharedParams( } ir->shared.default_rank = -1; - // Sort device_names lexicographically, keeping task_names in corresponding - // order. Also set number of devices per task. + // Sort devce_names lexicographcally, keeping task_names in + // corresponding order. SortDevicesAndTasks(&ir->shared); // Get Locality data for all devices. @@ -607,25 +605,6 @@ void CollectiveParamResolverLocal::CompleteInstanceAsync( "intended only for non-distributed deployment.")); } -// TODO(b/111897089): we need a better way to pick the collective -// implementation. The ideal way would depend upon the topology and link -// strength before picking a particular implementation. -void CollectiveParamResolverLocal::AssignCollectiveType(CollectiveParams* cp) { - if (cp->instance.type == BROADCAST_COLLECTIVE) { - cp->instance.impl_details.collective_name = "HierarchicalTreeBroadcast"; - } else if (cp->instance.type == REDUCTION_COLLECTIVE) { - if (nccl_) { - cp->instance.impl_details.collective_name = "NcclReduce"; - } else { - cp->instance.impl_details.collective_name = "RingReduce"; - } - } else { - cp->instance.impl_details.collective_name = "undef"; - } - VLOG(1) << "AssignCollectiveType " - << cp->instance.impl_details.collective_name; -} - void CollectiveParamResolverLocal::CompleteInstanceLocal( const string& device, const GroupRec* gr, CollectiveParams* cp, bool is_source, const StatusCallback& done) { @@ -662,57 +641,48 @@ void CollectiveParamResolverLocal::CompleteInstanceFromInitializedIRec( // custom operator= does a deep copy. cp->instance = ir->shared.instance; } - // Populate the fields common across task. - AssignCollectiveType(cp); + // Populate the fields common across task, also default_rank. SetDefaultRank(device, cp); CompleteTaskIsLocal(task_name_, cp); - + // TODO(b/113171733): we need a better way to pick the collective + // implementation. The ideal way would depend upon the topology and link + // strength before picking a particular implementation. + cp->instance.impl_details.collective_name = + (cp->instance.type == BROADCAST_COLLECTIVE) ? "HierarchicalTreeBroadcast" + : "RingReduce"; CollectiveImplementationInterface* col_impl; - Status status = CollectiveRegistry::LookupParamResolverInstance( + Status lookup_status = CollectiveRegistry::LookupParamResolverInstance( cp->instance.impl_details.collective_name, &col_impl); - if (status.ok()) { - status = col_impl->InitializeInstanceBeforeGroupDiscovery(cp); - } - if (!status.ok()) { - done(status); + if (!lookup_status.ok()) { + done(lookup_status); return; } - - // We may need to wait for the group if: - // * this is a broadcast, for source discovery; - // * we are using NCCL with more than 1 worker, for the communicator key from - // rank 0. - bool broadcast = cp->instance.type == BROADCAST_COLLECTIVE; - bool nccl = cp->instance.type == REDUCTION_COLLECTIVE && - cp->instance.impl_details.collective_name == "NcclReduce" && - cp->group.num_tasks > 1; - if (broadcast || nccl) { - WaitForGroup(ir, cp, is_source, broadcast, nccl, - [col_impl, ir, device, cp, done](InstanceRec* irec) { - Status s; - if (ir != irec) { - s = errors::Internal("Expected ir ", ir, " and irec ", - irec, " to be equal"); - } else { - mutex_lock l(irec->out_mu); - irec->WaitForOutMu(l); - s = irec->status; - cp->source_rank = irec->source_rank; - cp->instance.communicator_key = irec->communicator_key; - } - if (s.ok()) { - s = col_impl->InitializeCollectiveParams(cp); - } - done(s); - }); + // If broadcast, may need to wait for source discovery. + if (cp->instance.type == BROADCAST_COLLECTIVE) { + CompleteInstanceSource(ir, cp, is_source, + [col_impl, ir, device, cp, done](InstanceRec* irec) { + CHECK_EQ(ir, irec); + Status s; + { + mutex_lock l(irec->out_mu); + irec->WaitForOutMu(l); + s = irec->status; + cp->source_rank = irec->source_rank; + } + if (s.ok()) { + s = col_impl->InitializeCollectiveParams(cp); + } + done(s); + }); } else { done(col_impl->InitializeCollectiveParams(cp)); } } -void CollectiveParamResolverLocal::WaitForGroup( - InstanceRec* ir, CollectiveParams* cp, bool is_source, bool init_source, - bool init_nccl, const IRConsumer& f) { +void CollectiveParamResolverLocal::CompleteInstanceSource(InstanceRec* ir, + CollectiveParams* cp, + bool is_source, + const IRConsumer& f) { std::vector ready_waiters; { mutex_lock l(ir->out_mu); @@ -722,8 +692,7 @@ void CollectiveParamResolverLocal::WaitForGroup( if (!ir->known[cp->default_rank]) { ir->known[cp->default_rank] = true; ++ir->known_count; - if (init_source && is_source) { - // Initialize source rank. + if (is_source) { if (ir->source_rank >= 0) { ir->status = errors::Internal("Instance ", cp->instance.instance_key, " already has source ", ir->source_rank, @@ -733,26 +702,13 @@ void CollectiveParamResolverLocal::WaitForGroup( ir->source_rank = cp->default_rank; } } - if (init_nccl && cp->default_rank == 0) { - // Initialize communicator key. - if (!ir->communicator_key.empty()) { - ir->status = - errors::Internal("Instance ", cp->instance.instance_key, - " already has communicator_key ", - str_util::CEscape(ir->communicator_key), - ", received second claim from device ", - cp->instance.device_names[cp->default_rank]); - } else { - ir->communicator_key = cp->instance.communicator_key; - } - } } if (ir->known_count < ir->shared.group.group_size) { ir->known_waiters.push_back(f); return; } CHECK_EQ(ir->known_count, ir->shared.group.group_size); - if (init_source && ir->source_rank < 0) { + if (ir->source_rank < 0) { // NOTE(ayushd): changing the error message below would also require // updating CompleteParamsBroadcastForgotSend test in // CollectiveParamResolverLocalTest. @@ -762,13 +718,6 @@ void CollectiveParamResolverLocal::WaitForGroup( "could mean that there were group_size=", ir->known_count, " BcastRecvs but no BcastSend."); } - if (init_nccl && ir->communicator_key.empty()) { - ir->status = errors::Internal( - "Instance ", cp->instance.instance_key, " device ", - cp->instance.device_names[cp->default_rank], - " did not find rank 0 for setting communicator key. This is an " - "internal error in collective param resolution"); - } if (!ir->known_waiters.empty()) { ready_waiters = std::move(ir->known_waiters); } diff --git a/tensorflow/core/common_runtime/collective_param_resolver_local.h b/tensorflow/core/common_runtime/collective_param_resolver_local.h index fd408e4ef3..365bddc787 100644 --- a/tensorflow/core/common_runtime/collective_param_resolver_local.h +++ b/tensorflow/core/common_runtime/collective_param_resolver_local.h @@ -130,10 +130,8 @@ class CollectiveParamResolverLocal : public ParamResolverInterface { Status status GUARDED_BY(out_mu); // These fields are used to count the instances that have called - // in and become known while resolving broadcast source identity and - // communicator key. + // in and become known while resolving broadcast source identity. int source_rank GUARDED_BY(out_mu); - string communicator_key GUARDED_BY(out_mu); int known_count GUARDED_BY(out_mu); std::vector known GUARDED_BY(out_mu); std::vector known_waiters GUARDED_BY(out_mu); @@ -199,10 +197,10 @@ class CollectiveParamResolverLocal : public ParamResolverInterface { const StatusCallback& done) LOCKS_EXCLUDED(ir->out_mu); - // Complete source data and/or nccl communicator key. + // Complete source data for a broadcast instance. // Precondition: *cp has complete group data and default_rank. - void WaitForGroup(InstanceRec* ir, CollectiveParams* cp, bool is_source, - bool init_source, bool init_nccl, const IRConsumer& f) + void CompleteInstanceSource(InstanceRec* ir, CollectiveParams* cp, + bool is_source, const IRConsumer& f) LOCKS_EXCLUDED(ir->out_mu); // If cp.device_names contains only devices local to this process @@ -218,15 +216,10 @@ class CollectiveParamResolverLocal : public ParamResolverInterface { // current ordering of cp->instance.device_names. void SetDefaultRank(const string& device, CollectiveParams* cp); - // Sets cp->instance.type based on collective op type, and attempts to assign - // best implementation. - void AssignCollectiveType(CollectiveParams* cp); - // Helper to grab status under lock, invoke callback out of lock. void CallbackWithStatus(const InstanceRecCallback& done, InstanceRec* irec) LOCKS_EXCLUDED(irec->out_mu); - const bool nccl_; const DeviceMgr* dev_mgr_; DeviceResolverInterface* dev_resolver_; // Not owned. string task_name_; diff --git a/tensorflow/core/common_runtime/collective_util.cc b/tensorflow/core/common_runtime/collective_util.cc index bee4a13d18..195521a078 100644 --- a/tensorflow/core/common_runtime/collective_util.cc +++ b/tensorflow/core/common_runtime/collective_util.cc @@ -79,36 +79,5 @@ string SubdivPermDebugString(const CollectiveParams& col_params) { return buf; } -SubContext::SubContext(OpKernelContext* ctx, OpKernelContext::Params* params, - OpKernel* op, Tensor* output, Tensor* input) - : sub_params_(*params), - sub_inputs_({output, input}), - sub_input_attr_({ctx->input_alloc_attr(0), ctx->input_alloc_attr(0)}), - sub_input_dc_( - {ctx->input_device_context(0), ctx->input_device_context(0)}) { - sub_params_.op_kernel = op; - sub_params_.inputs = &sub_inputs_; - sub_params_.input_alloc_attrs = &sub_input_attr_; - sub_params_.input_device_contexts = &sub_input_dc_; - sub_params_.eigen_gpu_device = nullptr; - sub_params_.ensure_eigen_gpu_device(); - sub_params_.forward_from_array = &forward_from_; - sub_ctx_.reset(new OpKernelContext(&sub_params_, 1)); -} - -Status ComputeBinOp(OpKernelContext* op_ctx, OpKernelContext::Params* params, - Device* device, OpKernel* op, Tensor* output, - Tensor* input) { - // Prepare an OpKernelContext that is identical to that of the original Op - // (i.e. the collective), except for the input output sizes and identities and - // the Op itself. - // TODO(ayushd, tucker): Is it possible to cache and reuse these objects? - // They're mostly identical inside one device execution. - std::unique_ptr sub_ctx( - new SubContext(op_ctx, params, op, output, input)); - device->Compute(op, sub_ctx->sub_ctx_.get()); - return sub_ctx->sub_ctx_->status(); -} - } // namespace collective_util } // namespace tensorflow diff --git a/tensorflow/core/common_runtime/collective_util.h b/tensorflow/core/common_runtime/collective_util.h index 01fb8b8c81..ebb5731bec 100644 --- a/tensorflow/core/common_runtime/collective_util.h +++ b/tensorflow/core/common_runtime/collective_util.h @@ -32,27 +32,6 @@ Status InitializeDeviceAndLocality(const DeviceMgr* dev_mgr, DeviceLocality* device_locality); string SubdivPermDebugString(const CollectiveParams& col_params); -// Used for executing a sub-operation, e.g. a merge_op instance, with -// an OpKernelContext based on the one passed into this Op. -class SubContext { - public: - OpKernelContext::Params sub_params_; - gtl::InlinedVector sub_inputs_; - gtl::InlinedVector sub_input_attr_; - gtl::InlinedVector sub_input_dc_; - // Used only for Binary and Unary Ops for which we require - // the calculation to be in-place on the first input. - int forward_from_ = 0; - std::unique_ptr sub_ctx_; - SubContext(OpKernelContext* ctx, OpKernelContext::Params* params, - OpKernel* op, Tensor* output, Tensor* input); - ~SubContext() = default; -}; - -Status ComputeBinOp(OpKernelContext* op_ctx, OpKernelContext::Params* params, - Device* device, OpKernel* op, Tensor* output, - Tensor* input); - } // namespace collective_util } // namespace tensorflow diff --git a/tensorflow/core/common_runtime/hierarchical_tree_broadcaster.h b/tensorflow/core/common_runtime/hierarchical_tree_broadcaster.h index 76392b8e59..ceb9baad30 100644 --- a/tensorflow/core/common_runtime/hierarchical_tree_broadcaster.h +++ b/tensorflow/core/common_runtime/hierarchical_tree_broadcaster.h @@ -41,11 +41,6 @@ class HierarchicalTreeBroadcaster : public CollectiveImplementationInterface { // and device_locality. Also saves the CollectiveContext in this object. Status InitializeCollectiveContext(CollectiveContext* col_ctx) override; - // No-op for hierarchical tree broadcaster. - Status InitializeInstanceBeforeGroupDiscovery(CollectiveParams*) override { - return Status::OK(); - } - // Begins async execution of the hierarchical tree broadcast. // Must be called in a blockable thread. // TODO(b/80529858): remove the previous warning when we have a dedicated diff --git a/tensorflow/core/common_runtime/ring_reducer.cc b/tensorflow/core/common_runtime/ring_reducer.cc index 8ed2fc2f1c..092f15e49e 100644 --- a/tensorflow/core/common_runtime/ring_reducer.cc +++ b/tensorflow/core/common_runtime/ring_reducer.cc @@ -394,6 +394,37 @@ void RingReducer::Finish(bool ok) { done_(s); } +RingReducer::SubContext::SubContext(OpKernelContext* ctx, + OpKernelContext::Params* params, + OpKernel* op, Tensor* output, Tensor* input) + : sub_params_(*params), + sub_inputs_({output, input}), + sub_input_attr_({ctx->input_alloc_attr(0), ctx->input_alloc_attr(0)}), + sub_input_dc_( + {ctx->input_device_context(0), ctx->input_device_context(0)}) { + sub_params_.op_kernel = op; + sub_params_.inputs = &sub_inputs_; + sub_params_.input_alloc_attrs = &sub_input_attr_; + sub_params_.input_device_contexts = &sub_input_dc_; + sub_params_.eigen_gpu_device = nullptr; + sub_params_.ensure_eigen_gpu_device(); + sub_params_.forward_from_array = &forward_from_; + sub_ctx_ = new OpKernelContext(&sub_params_, 1); +} + +Status RingReducer::ComputeBinOp(Device* device, OpKernel* op, Tensor* output, + Tensor* input) { + // Prepare an OpKernelContext that is identical to that of the original Op + // (i.e. the collective), except for the input output sizes and identities and + // the Op itself. + // TODO(tucker): Is it possible to cache and reuse these objects? They're + // mostly identical inside one device execution. + std::unique_ptr sub_ctx( + new SubContext(col_ctx_->op_ctx, col_ctx_->op_params, op, output, input)); + device->Compute(op, sub_ctx->sub_ctx_); + return sub_ctx->sub_ctx_->status(); +} + // At the beginning of the algorithm initialize a RingField struct for // every independent field of the tensor. void RingReducer::InitRingField(RingField* rf, int chunk_idx, int subdiv_idx, @@ -601,9 +632,9 @@ bool RingReducer::RunAsyncParts() { --recv_pending_count; if (!rf->second_pass) { rf->action = RF_REDUCE; - Status s = collective_util::ComputeBinOp( - col_ctx_->op_ctx, col_ctx_->op_params, col_ctx_->device, - col_params_->merge_op.get(), &rf->chunk, &rf->tmp_chunk); + Status s = + ComputeBinOp(col_ctx_->device, col_params_->merge_op.get(), + &rf->chunk, &rf->tmp_chunk); if (!s.ok()) { aborted = true; StartAbort(s); @@ -616,9 +647,9 @@ bool RingReducer::RunAsyncParts() { if (!rf->second_pass && col_params_->final_op.get() && rf->is_final) { rf->action = RF_FINALIZE; group_size_tensor_ready_.WaitForNotification(); - Status s = collective_util::ComputeBinOp( - col_ctx_->op_ctx, col_ctx_->op_params, col_ctx_->device, - col_params_->final_op.get(), &rf->chunk, &group_size_tensor_); + Status s = + ComputeBinOp(col_ctx_->device, col_params_->final_op.get(), + &rf->chunk, &group_size_tensor_); if (!s.ok()) { aborted = true; StartAbort(s); diff --git a/tensorflow/core/common_runtime/ring_reducer.h b/tensorflow/core/common_runtime/ring_reducer.h index a5aa8fad70..0848e37b52 100644 --- a/tensorflow/core/common_runtime/ring_reducer.h +++ b/tensorflow/core/common_runtime/ring_reducer.h @@ -40,11 +40,6 @@ class RingReducer : public CollectiveImplementationInterface { // and device_locality. Also saves the CollectiveContext in this object. Status InitializeCollectiveContext(CollectiveContext* col_ctx) override; - // No-op for ring reducer. - Status InitializeInstanceBeforeGroupDiscovery(CollectiveParams*) override { - return Status::OK(); - } - // Begins async execution of the ring reduce algorithm. // Must be called in a blockable thread. // TODO(b/80529858): remove the previous warning when we have a dedicated @@ -57,8 +52,27 @@ class RingReducer : public CollectiveImplementationInterface { void StartAbort(const Status& s); void ContinueAfterInputCopy(); void Finish(bool ok); + Status ComputeBinOp(Device* device, OpKernel* op, Tensor* output, + Tensor* input); bool RunAsyncParts(); + // Used for executing a sub-operation, e.g. a merge_op instance, with + // an OpKernelContext based on the one passed into this Op. + class SubContext { + public: + OpKernelContext::Params sub_params_; + gtl::InlinedVector sub_inputs_; + gtl::InlinedVector sub_input_attr_; + gtl::InlinedVector sub_input_dc_; + // Used only for Binary and Unary Ops for which we require + // the calculation to be in-place on the first input. + int forward_from_ = 0; + OpKernelContext* sub_ctx_; + SubContext(OpKernelContext* ctx, OpKernelContext::Params* params, + OpKernel* op, Tensor* output, Tensor* input); + ~SubContext() { delete sub_ctx_; } + }; + // Current status of a RingField enum RingFieldAction { RF_INIT = 0, // Just initialized for a pass diff --git a/tensorflow/core/distributed_runtime/BUILD b/tensorflow/core/distributed_runtime/BUILD index 197a3f24d4..e388d3e6f0 100644 --- a/tensorflow/core/distributed_runtime/BUILD +++ b/tensorflow/core/distributed_runtime/BUILD @@ -594,7 +594,6 @@ tf_cc_test( "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", - "//tensorflow/core/kernels:collective_ops", ], ) diff --git a/tensorflow/core/distributed_runtime/collective_param_resolver_distributed.cc b/tensorflow/core/distributed_runtime/collective_param_resolver_distributed.cc index 9f94a24fcd..1dd10d309b 100644 --- a/tensorflow/core/distributed_runtime/collective_param_resolver_distributed.cc +++ b/tensorflow/core/distributed_runtime/collective_param_resolver_distributed.cc @@ -181,10 +181,6 @@ void CollectiveParamResolverDistributed::CompleteInstanceAsync( ir->WaitForOutMu(l); response->set_instance_key(cp->instance.instance_key); response->set_source_rank(ir->source_rank); - if (!cp->instance.communicator_key.empty()) { - response->set_communicator_key( - cp->instance.communicator_key); - } done_and_cleanup(fi_status); } else { done_and_cleanup(fi_status); @@ -287,10 +283,8 @@ void CollectiveParamResolverDistributed::UpdateInstanceCache( using InstanceRecPointer = InstanceRec*; InstanceRecPointer* irp = new InstanceRecPointer(nullptr); int32 source_rank = resp.source_rank(); - string communicator_key = resp.communicator_key(); - auto continue_with_ir = [cp, irp, source_rank, communicator_key, - done](const Status& s) { + auto continue_with_ir = [this, cp, irp, source_rank, done](const Status& s) { if (!s.ok()) { done(s); delete irp; @@ -312,19 +306,6 @@ void CollectiveParamResolverDistributed::UpdateInstanceCache( } ir->source_rank = source_rank; } - if (ir->communicator_key != communicator_key) { - if (!ir->communicator_key.empty()) { - ir->status = errors::Internal( - "UpdateInstanceCache: CompleteInstanceResponse for instance ", - cp->instance.instance_key, - " gives communicator_key with size =", communicator_key.size(), - " but cache already holds communicator_key with size=", - ir->communicator_key.size()); - status = ir->status; - break; - } - ir->communicator_key = communicator_key; - } if (ir->known_count < cp->group.group_size) { ir->known_count = cp->group.group_size; if (ir->known.size() != cp->group.group_size) { 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..40b18d321a 100644 --- a/tensorflow/core/distributed_runtime/collective_param_resolver_distributed_test.cc +++ b/tensorflow/core/distributed_runtime/collective_param_resolver_distributed_test.cc @@ -268,8 +268,6 @@ class DeviceResDistTest : public ::testing::Test { EXPECT_EQ(cp_[idx].instance.device_names[idx], device_name); EXPECT_EQ(cp_[idx].instance.task_names[idx], task_name); if (idx > 0) { - EXPECT_EQ(cp_[0].instance.communicator_key, - cp_[idx].instance.communicator_key); for (int i = 0; i < dev_count; ++i) { EXPECT_EQ(cp_[0].instance.device_names[i], cp_[idx].instance.device_names[i]); diff --git a/tensorflow/core/framework/collective.cc b/tensorflow/core/framework/collective.cc index b83d183f14..7fa58347f2 100644 --- a/tensorflow/core/framework/collective.cc +++ b/tensorflow/core/framework/collective.cc @@ -17,7 +17,6 @@ limitations under the License. #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/hash/hash.h" -#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/lib/strings/strcat.h" namespace tensorflow { @@ -65,9 +64,7 @@ CollInstanceParams& CollInstanceParams::operator=( device_names.assign(other.device_names.begin(), other.device_names.end()); task_names.assign(other.task_names.begin(), other.task_names.end()); same_num_devices_per_task = other.same_num_devices_per_task; - num_devices_per_task = other.num_devices_per_task; gpu_ring_order = other.gpu_ring_order; - communicator_key = other.communicator_key; impl_details.subdiv_offsets.assign( other.impl_details.subdiv_offsets.begin(), other.impl_details.subdiv_offsets.end()); @@ -79,7 +76,6 @@ CollInstanceParams& CollInstanceParams::operator=( impl_details.subdiv_source_rank.assign( other.impl_details.subdiv_source_rank.begin(), other.impl_details.subdiv_source_rank.end()); - impl_details.dependencies = other.impl_details.dependencies; } return *this; } @@ -95,13 +91,6 @@ string CollInstanceParams::ToString() const { for (const auto& n : task_names) { strings::StrAppend(&v, n, ", "); } - strings::StrAppend(&v, "} num_devices_per_task={"); - for (const auto dpt : num_devices_per_task) { - strings::StrAppend(&v, dpt.first, ": ", dpt.second, ", "); - } - strings::StrAppend(&v, "}, collective_name=", impl_details.collective_name, - ", communicator_key=", str_util::CEscape(communicator_key), - ", subdiv_offsets={"); strings::StrAppend(&v, "}, subdiv_offsets={"); for (const auto& d : impl_details.subdiv_offsets) { strings::StrAppend(&v, d, ","); diff --git a/tensorflow/core/framework/collective.h b/tensorflow/core/framework/collective.h index 546e3938a8..0321429702 100644 --- a/tensorflow/core/framework/collective.h +++ b/tensorflow/core/framework/collective.h @@ -70,8 +70,6 @@ struct CollImplDetails { std::vector> subdiv_permutations; std::vector subdiv_offsets; std::vector subdiv_source_rank; // rank of source in each subdiv - std::vector - dependencies; // collective instances on which this node depends }; // Data common to all members of a collective instance. @@ -87,13 +85,9 @@ struct CollInstanceParams { std::vector task_names; // True if every task has the same number of devices. bool same_num_devices_per_task = false; - // Task -> number of devices on that task. - std::unordered_map num_devices_per_task; // If passed in to GPUOptions in ConfigProto, defines a good ring order for // GPUs. Assumes same GPU configuration at each worker. string gpu_ring_order = ""; - // Valid when using a communicator-based collective mechanism, e.g. NCCL. - string communicator_key; CollImplDetails impl_details; string ToString() const; CollInstanceParams& operator=(const struct CollInstanceParams& other); @@ -275,21 +269,6 @@ class CollectiveExecutor : public PeerAccessInterface, public core::RefCounted { virtual PerStepCollectiveRemoteAccess* remote_access() { return nullptr; } - // `WaitForDependencies` and `Launched` are used for fine-grained control of - // execution order between collective instances. These functions are intended - // to be called in `Run` function of collective implementations, and may be - // used to make part, or whole, of the collective execution ordered with - // respect to other collective instances. - // - // `WaitForDependencies` will block until it is safe to continue the callee's - // execution, where safety is defined as: ordered with respect to the - // collective instances defined in the callee's `wait_for` attribute. - virtual void WaitForDependencies(const CollectiveParams& col_params) {} - // `Launched` unblocks the dependent collective instances by recording that - // this callee device has completed the critical portion of the collective - // execution. - virtual void Launched(const CollectiveParams& col_params) {} - // Used to designate an invalid group or instance key. static int64 kInvalidId; @@ -368,8 +347,7 @@ class CollectiveImplementationInterface { // Initializes the portions of `col_params` specific to this // implementation. Called exactly once for every Collective instance during - // the CollectiveParams resolution process when the graph is first executed, - // at the end of `CompleteInstanceLocal()`. + // the CollectiveParams resolution process when the graph is first executed. // NOTE(ayushd): This is effectively a static function because it modifies the // `col_params` passed in and should not manipulate any data members. However // because it is virtual and needs to be implemented by every derived class we @@ -382,14 +360,6 @@ class CollectiveImplementationInterface { // object. virtual Status InitializeCollectiveContext(CollectiveContext* col_ctx) = 0; - // Initializes instance params at the beginning of `CompleteInstanceLocal()`, - // unlike `InitializeCollectiveParams` which is called at the end. This - // function is called before all devices in the instance are discovered, and - // may be used to broadcast data via the shared `InstanceRec` object in - // collective param resolution to all devices. - virtual Status InitializeInstanceBeforeGroupDiscovery( - CollectiveParams* col_params) = 0; - // Processes and moves data according to the logic of this Collective // implementation. Relies on appropriate initialization of op-specific // CollectiveParams in InitializeCollectiveParams(), as well as appropriate diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index ea6343f24d..358d3d656e 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -180,36 +180,12 @@ tf_cc_test( tf_kernel_library( name = "collective_ops", - srcs = if_cuda([ - "collective_nccl_reducer.h", - "collective_nccl_reducer.cc", - ]), prefix = "collective_ops", deps = [ "//tensorflow/core:collective_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", - ] + if_cuda([ - "@local_config_nccl//:nccl", - "//tensorflow/core/nccl:nccl_lib", - ]), -) - -tf_cuda_cc_test( - name = "collective_nccl_reducer_test", - size = "small", - srcs = ["collective_nccl_reducer_test.cc"], - tags = tf_cuda_tests_tags() + ["no_cuda_on_cpu_tap"], - deps = [ - "//tensorflow/core:all_kernels", - "//tensorflow/core:core_cpu", - "//tensorflow/core:framework", - "//tensorflow/core:lib", - "//tensorflow/core:protos_all_cc", - "//tensorflow/core:test", - "//tensorflow/core:test_main", - "//tensorflow/core:testlib", ], ) diff --git a/tensorflow/core/kernels/collective_nccl_reducer.cc b/tensorflow/core/kernels/collective_nccl_reducer.cc deleted file mode 100644 index 113f1487f0..0000000000 --- a/tensorflow/core/kernels/collective_nccl_reducer.cc +++ /dev/null @@ -1,204 +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/core/kernels/collective_nccl_reducer.h" - -#ifdef GOOGLE_CUDA - -#include "tensorflow/core/common_runtime/collective_util.h" -#include "tensorflow/core/nccl/nccl_manager.h" - -namespace tensorflow { -namespace { -string NcclCollectiveKey(const string& exec_key, int step_id) { - return strings::StrCat(exec_key, ":", step_id); -} -} // namespace - -NcclReducer::NcclReducer() : col_ctx_(nullptr), col_params_(nullptr) {} - -Status NcclReducer::InitializeCollectiveParams(CollectiveParams* col_params) { - if (col_params->instance.type != REDUCTION_COLLECTIVE || - col_params->instance.impl_details.collective_name != "NcclReduce") { - return errors::Internal("Unexpected collective type ", - col_params->instance.type, " expected ", - REDUCTION_COLLECTIVE, "; or collective name ", - col_params->instance.impl_details.collective_name, - " expected NcclReduce"); - } else { - return Status::OK(); - } -} - -Status NcclReducer::InitializeCollectiveContext(CollectiveContext* col_ctx) { - col_ctx_ = col_ctx; - col_params_ = &col_ctx->col_params; - return collective_util::InitializeDeviceAndLocality( - col_ctx->dev_mgr, col_ctx->device_name, &col_ctx->device, - &col_ctx->device_locality); -} - -Status NcclReducer::InitializeInstanceBeforeGroupDiscovery( - CollectiveParams* col_params) { - if (col_params->default_rank == 0 && col_params->group.num_tasks > 1) { - col_params->instance.communicator_key = - NcclManager::instance()->GenerateCommunicatorKey(); - } - return Status::OK(); -} - -Status ReductionOp(const string& merge_op, ncclRedOp_t* reduction_op) { - if (merge_op == "Add") { - *reduction_op = ncclSum; - return Status::OK(); - } else if (merge_op == "Mul") { - *reduction_op = ncclProd; - return Status::OK(); - } else { - return errors::Internal("Expected merge_op to be either Add or Mul, found ", - merge_op); - } -} - -void NcclReducer::Run(StatusCallback done) { - ncclRedOp_t reduction_op; - Status s = ReductionOp(col_params_->merge_op->type_string(), &reduction_op); - if (!s.ok()) { - done(s); - return; - } - - Tensor group_size; - Notification group_size_ready; - Status group_size_status; - if (col_params_->final_op) { - // Create an on-device scalar value from group_size_. - // TODO(ayushd, tucker): avoid this copy by either reusing across - // invocations or providing the scalar to the kernel in host memory. - Tensor group_size_val(col_ctx_->output->dtype(), TensorShape({})); - switch (col_ctx_->output->dtype()) { - case DT_FLOAT: - group_size_val.scalar()() = col_params_->group.group_size; - break; - case DT_DOUBLE: - group_size_val.scalar()() = col_params_->group.group_size; - break; - case DT_INT32: - group_size_val.scalar()() = col_params_->group.group_size; - break; - case DT_INT64: - group_size_val.scalar()() = col_params_->group.group_size; - break; - default: - done(errors::Internal("Unsupported type ", col_ctx_->output->dtype())); - return; - } - group_size = Tensor( - col_ctx_->device->GetAllocator(col_ctx_->op_ctx->input_alloc_attr(0)), - col_ctx_->output->dtype(), TensorShape({})); - DeviceContext* op_dev_ctx = col_ctx_->op_ctx->op_device_context(); - // Enqueue copy on gpu stream. - op_dev_ctx->CopyCPUTensorToDevice( - &group_size_val, col_ctx_->device, &group_size, - [&group_size_ready, &group_size_status](const Status& s) { - group_size_status = s; - group_size_ready.Notify(); - }); - } else { - group_size_ready.Notify(); - } - - Notification nccl_done; - Status nccl_status; - auto* compute_stream = col_ctx_->op_ctx->op_device_context()->stream(); - auto* gpu_info = col_ctx_->op_ctx->device()->tensorflow_gpu_device_info(); - // `AddToAllReduce` performs consistency checks for the NCCL call and enqueues - // the `Participant` struct locally. When all local participants with this - // `nccl_collective_key` have called `AddToAllReduce` and - // `SignalMultiNodeReady`, all devices at this worker are ready to process - // this NCCL op. - // - // The `NcclManager` uses a dedicated CUDA stream for NCCL kernels. At this - // point, it synchronizes the NCCL stream with the compute stream, and then - // enqueues the NCCL kernel on the NCCL stream. - const int num_global_devices = col_params_->group.group_size; - const int num_local_devices = col_params_->instance.num_devices_per_task.at( - col_params_->instance.task_names[col_params_->default_rank]); - const string nccl_collective_key = - NcclCollectiveKey(col_ctx_->exec_key, col_ctx_->step_id); - auto done_callback = [&nccl_done, &nccl_status](const Status& s) { - nccl_status = s; - nccl_done.Notify(); - }; - auto participant = absl::make_unique( - compute_stream->parent(), compute_stream, gpu_info->event_mgr, - gpu_info->gpu_id, col_ctx_->input, col_ctx_->output, - col_params_->default_rank, std::move(done_callback)); - VLOG(1) << "NcclReducer calling NcclManager::AddToAllReduce num_tasks " - << col_params_->group.num_tasks << " current task " - << col_params_->instance.task_names[col_params_->default_rank] - << " num local devices " << num_local_devices - << " num global devices " << num_global_devices; - NcclManager::instance()->AddToAllReduce( - std::move(participant), - {nccl_collective_key, num_local_devices, num_global_devices, - col_params_->instance.communicator_key}, - reduction_op); - - // NOTE(ayushd): We need to synchronize NCCL launches across nodes to prevent - // deadlocks. In the current implementation, we define a deterministic - // sequential launch order between potentially concurrent collective instances - // by introducing control information during static graph analysis in - // graph/collective_order.cc. This can be either in the form of explicit - // control edges or via `wait_for` attribute on the collective op. - // - // The other end of the design spectrum would have a distinguished node - // dynamically signal the next collective to launch to all other participants. - // This has higher degree of runtime coordination, but it may be able to - // achieve better performance if the (arbitrary) static execution order - // assigned in the first approach turns out to not be good from a scheduling - // perspective. e.g. consider a graph in which c1, c2, and c3 are three - // concurrent collective instances, and the static ordering assigns c1 -> c2 - // -> c3. In practice, it could turn out that c3 is always ready to execute - // before c1 or c2. - // - // `WaitForDependencies` may block if the collective instances on which this - // op depends have not yet launched. When this function returns, this op is - // ready to go. - col_ctx_->col_exec->WaitForDependencies(*col_params_); - NcclManager::instance()->SignalMultiNodeReady(nccl_collective_key); - // When all devices at this worker have called `SignalMultiNodeReady`, the - // `NcclManager` will enqueue the NCCL kernel on the NCCL stream. Thus the - // implementation of `Launched` keeps track of the number of devices that have - // launched. - col_ctx_->col_exec->Launched(*col_params_); - - // Wait for nccl op and group_size copy to succeed, then do final_op. - group_size_ready.WaitForNotification(); - nccl_done.WaitForNotification(); - Status final_status = - group_size_status.ok() ? nccl_status : group_size_status; - if (final_status.ok() && col_params_->final_op) { - final_status = collective_util::ComputeBinOp( - col_ctx_->op_ctx, col_ctx_->op_params, col_ctx_->device, - col_params_->final_op.get(), col_ctx_->output, &group_size); - } - done(final_status); -} - -REGISTER_COLLECTIVE(NcclReduce, NcclReducer); - -} // namespace tensorflow - -#endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/collective_nccl_reducer.h b/tensorflow/core/kernels/collective_nccl_reducer.h deleted file mode 100644 index dc70b280c5..0000000000 --- a/tensorflow/core/kernels/collective_nccl_reducer.h +++ /dev/null @@ -1,49 +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. -==============================================================================*/ -#ifndef TENSORFLOW_CORE_KERNELS_COLLECTIVE_NCCL_REDUCER_H_ -#define TENSORFLOW_CORE_KERNELS_COLLECTIVE_NCCL_REDUCER_H_ - -#include "tensorflow/core/framework/collective.h" - -namespace tensorflow { -#ifdef GOOGLE_CUDA - -class NcclReducer : public CollectiveImplementationInterface { - public: - NcclReducer(); - ~NcclReducer() override = default; - - // No-op for this collective implementation. - Status InitializeCollectiveParams(CollectiveParams* col_params) override; - - // Initializes the device objects and device localities. - Status InitializeCollectiveContext(CollectiveContext* col_ctx) override; - - // Initialize nccl communicator key. - Status InitializeInstanceBeforeGroupDiscovery( - CollectiveParams* col_params) override; - - // Hands off all reduce to NcclManager. - void Run(StatusCallback done) override; - - private: - CollectiveContext* col_ctx_; // Not owned - const CollectiveParams* col_params_; // Not owned -}; - -#endif // GOOGLE_CUDA -} // namespace tensorflow - -#endif // TENSORFLOW_CORE_KERNELS_COLLECTIVE_NCCL_REDUCER_H_ diff --git a/tensorflow/core/kernels/collective_nccl_reducer_test.cc b/tensorflow/core/kernels/collective_nccl_reducer_test.cc deleted file mode 100644 index ff33c1322c..0000000000 --- a/tensorflow/core/kernels/collective_nccl_reducer_test.cc +++ /dev/null @@ -1,331 +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/core/kernels/collective_nccl_reducer.h" - -#include -#include "absl/memory/memory.h" -#include "tensorflow/core/common_runtime/base_collective_executor.h" -#include "tensorflow/core/common_runtime/device.h" -#include "tensorflow/core/common_runtime/device_factory.h" -#include "tensorflow/core/common_runtime/device_mgr.h" -#include "tensorflow/core/common_runtime/device_resolver_local.h" -#include "tensorflow/core/common_runtime/process_util.h" -#include "tensorflow/core/common_runtime/test_collective_executor_mgr.h" -#include "tensorflow/core/framework/collective.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/lib/core/notification.h" -#include "tensorflow/core/lib/core/status_test_util.h" -#include "tensorflow/core/platform/test.h" -#include "tensorflow/core/public/session_options.h" -#include "tensorflow/core/public/version.h" - -#ifdef GOOGLE_CUDA - -namespace tensorflow { -static constexpr int kStepId = 10; - -std::unique_ptr GetKernel(const NodeDef& node, DeviceBase* device) { - Status status; - std::unique_ptr k = CreateOpKernel( - DEVICE_GPU, device, device->GetAllocator(AllocatorAttributes()), node, - TF_GRAPH_DEF_VERSION, &status); - if (!status.ok()) LOG(FATAL) << status; - return k; -} - -std::unique_ptr GetAdd(DeviceBase* device) { - NodeDef node_def; - NodeDefBuilder builder("add_node", "Add"); - TF_CHECK_OK(builder.Attr("T", DT_FLOAT) - .Input(FakeInput(DT_FLOAT)) - .Input(FakeInput(DT_FLOAT)) - .Finalize(&node_def)); - return GetKernel(node_def, device); -} - -std::unique_ptr GetDiv(DeviceBase* device) { - NodeDef node_def; - NodeDefBuilder builder("add_node", "Div"); - TF_CHECK_OK(builder.Attr("T", DT_FLOAT) - .Input(FakeInput(DT_FLOAT)) - .Input(FakeInput(DT_FLOAT)) - .Finalize(&node_def)); - return GetKernel(node_def, device); -} - -class NcclReducerTest : public ::testing::Test { - protected: - ~NcclReducerTest() override { - if (col_exec_) col_exec_->Unref(); - } - - void InitGPUDevices() { - std::vector> all_devices; - SessionOptions session_options; - session_options.config.mutable_gpu_options() - ->set_per_process_gpu_memory_fraction(0.1); - session_options.env = Env::Default(); - Status s = DeviceFactory::GetFactory(DEVICE_GPU) - ->AddDevices(session_options, "", &all_devices); - TF_CHECK_OK(s); - for (std::unique_ptr& d : all_devices) { - if (d->device_type() == "GPU") { - gpus_.emplace_back(std::move(d)); - } - } - } - - void Init(int num_ranks) { - setenv("NCCL_DEBUG", "INFO", 1 /* replace */); - setenv("NCCL_LAUNCH_MODE", "PARALLEL", 1 /* replace */); - InitGPUDevices(); - std::vector> local_devices; - std::vector device_names; - for (int rank = 0; rank < num_ranks; ++rank) { - if (rank < gpus_.size()) { - local_devices.emplace_back(std::move(gpus_[rank])); - } - } - int num_gpus = local_devices.size(); - for (const auto& device : local_devices) { - device_names.push_back(device->name()); - VLOG(2) << device->name(); - } - if (!dev_mgr_) dev_mgr_.reset(new DeviceMgr(std::move(local_devices))); - col_exec_ = new BaseCollectiveExecutor( - &col_exec_mgr_, /*remote_access=*/nullptr, kStepId, dev_mgr_.get(), - /*gpu_ring_order=*/nullptr); - - // Initialize collective params. - col_params_.name = "test_nccl_collective_op"; - const int group_key = 5; - col_params_.group.group_key = group_key; - col_params_.group.device_type = DEVICE_GPU; - col_params_.group.group_size = num_ranks; - const int instance_key = 23; - col_params_.instance.instance_key = instance_key; - col_params_.instance.type = REDUCTION_COLLECTIVE; - col_params_.instance.data_type = DT_FLOAT; - col_params_.instance.impl_details.collective_name = "NcclReduce"; - const string task_name = "/job:worker/replica:0/task:0"; - col_params_.instance.num_devices_per_task[task_name] = num_ranks; - for (int rank = 0; rank < num_ranks; ++rank) { - col_params_.instance.device_names.push_back( - device_names[rank % num_gpus]); - col_params_.instance.task_names.push_back(task_name); - } - for (int rank = 0; rank < num_ranks; ++rank) { - instances_.push_back(absl::make_unique( - rank, col_params_.instance.device_names[rank], this)); - } - } - - void Reduce() { - int done = 0; - mutex done_mu; - condition_variable done_cv; - for (const auto& instance : instances_) { - DeviceInstance* di = instance.get(); - SchedClosure([di, &done, &done_mu, &done_cv] { - di->DoReduce(); - mutex_lock l(done_mu); - ++done; - done_cv.notify_all(); - }); - } - - mutex_lock l(done_mu); - while (done < instances_.size()) done_cv.wait(l); - } - - void RunTest(int num_ranks, int tensor_length) { - Init(num_ranks); - std::vector expected(tensor_length, 0.0); - for (int rank = 0; rank < num_ranks; ++rank) { - DeviceInstance* instance = instances_[rank].get(); - instance->InitTensor(DT_FLOAT, TensorShape({tensor_length}), - [&expected, rank](Tensor* t) { - for (size_t i = 0; i < t->NumElements(); ++i) { - float value = pow(10, rank) * i; - t->flat()(i) = value; - expected[i] += value; - } - }); - } - Reduce(); - // Confirm that every rank computed the same correct value. - for (int i = 0; i < tensor_length; ++i) { - expected[i] /= num_ranks; - } - for (int rank = 0; rank < instances_.size(); ++rank) { - TF_ASSERT_OK(instances_[rank]->status_); - Tensor* dev_tensor = &instances_[rank]->tensor_; - Tensor actual(DT_FLOAT, TensorShape({tensor_length})); - Notification note; - Device* dev = instances_[rank]->device_; - auto* dev_info = dev->tensorflow_gpu_device_info(); - dev_info->default_context->CopyDeviceTensorToCPU( - dev_tensor, /*tensor_name=*/"", dev, &actual, - [¬e](const Status&) { note.Notify(); }); - note.WaitForNotification(); - for (int i = 0; i < tensor_length; ++i) { - EXPECT_FLOAT_EQ(expected[i], actual.template flat()(i)) - << "Mismatch at rank " << rank << " index " << i; - } - } - } - - std::unique_ptr GetCollectiveReduce(const CollectiveParams& params, - Tensor* input, - DeviceBase* device) { - mutex_lock l(mu_); - NodeDef node_def; - NodeDefBuilder builder( - strings::StrCat("collective_reduce_", reduce_counter_++), - "CollectiveReduce"); - TF_CHECK_OK( - builder.Attr("T", params.instance.data_type) - .Attr("merge_op", "Add") - .Attr("final_op", "Div") - .Attr("group_size", params.group.group_size) - .Attr("group_key", params.group.group_key) - .Attr("instance_key", params.instance.instance_key) - .Attr("subdiv_offsets", params.instance.impl_details.subdiv_offsets) - .Input(FakeInput(params.instance.data_type)) - .Finalize(&node_def)); - return GetKernel(node_def, device); - } - - class DeviceInstance { - public: - DeviceInstance(int rank, const string& device_name, NcclReducerTest* parent) - : parent_(parent), device_name_(device_name), rank_(rank) { - TF_CHECK_OK(parent_->dev_mgr_->LookupDevice(device_name_, &device_)) - << "Could not find device " << device_name_ << " existing devices " - << parent_->dev_mgr_->DebugString(); - col_params_.name = parent_->col_params_.name; - col_params_.default_rank = rank; - col_params_.group.group_key = parent_->col_params_.group.group_key; - col_params_.group.device_type = parent_->col_params_.group.device_type; - col_params_.group.group_size = parent_->col_params_.group.group_size; - col_params_.instance = parent->col_params_.instance; - } - - void InitTensor(DataType dtype, const TensorShape& shape, - const std::function& init_f) { - tensor_ = - Tensor(device_->GetAllocator(AllocatorAttributes()), dtype, shape); - Tensor cpu_tensor(dtype, shape); - init_f(&cpu_tensor); - VLOG(2) << "cpu_tensor " << cpu_tensor.DebugString(); - auto* dev_info = device_->tensorflow_gpu_device_info(); - Notification note; - dev_info->default_context->CopyCPUTensorToDevice( - &cpu_tensor, device_, &tensor_, - [¬e](const Status&) { note.Notify(); }); - note.WaitForNotification(); - } - - void DoReduce() { - col_params_.merge_op = GetAdd(device_); - col_params_.final_op = GetDiv(device_); - - // Prepare an OpKernelContext. - OpKernelContext::Params op_params; - op_params.step_id = kStepId; - op_params.device = device_; - gtl::InlinedVector inputs; - inputs.push_back(TensorValue(&tensor_)); - op_params.inputs = &inputs; - gtl::InlinedVector input_aa( - {AllocatorAttributes()}); - op_params.input_alloc_attrs = &input_aa; - gtl::InlinedVector input_dc; - DeviceContext* dev_ctx = nullptr; - auto* dev_info = device_->tensorflow_gpu_device_info(); - if (dev_info) { - dev_ctx = dev_info->default_context; - dev_ctx->Ref(); - } else { - dev_ctx = new DeviceContext; - } - input_dc.push_back(dev_ctx); - op_params.input_device_contexts = &input_dc; - op_params.op_device_context = dev_ctx; - int forward_from = 0; - op_params.forward_from_array = &forward_from; - AllocatorAttributes generic_alloc_attr; - op_params.output_attr_array = &generic_alloc_attr; - std::unique_ptr op = - parent_->GetCollectiveReduce(col_params_, &tensor_, device_); - op_params.op_kernel = op.get(); - OpKernelContext ctx(&op_params, 1); - - // We never actually execute the kernel, so we need to do the output - // allocation it would do, ourselves. - Tensor* output_tensor_ptr = nullptr; - TF_CHECK_OK(ctx.forward_input_or_allocate_output({0}, 0, tensor_.shape(), - &output_tensor_ptr)); - CHECK_EQ(output_tensor_ptr, ctx.mutable_output(0)); - - // Prepare a NcclReducer instance. - string exec_key = - strings::StrCat(col_params_.instance.instance_key, ":0:0"); - NcclReducer reducer; - CollectiveContext col_ctx(parent_->col_exec_, parent_->dev_mgr_.get(), - &ctx, &op_params, col_params_, exec_key, - kStepId, &tensor_, &tensor_); - TF_CHECK_OK(reducer.InitializeCollectiveContext(&col_ctx)); - - // Run the all-reduce. - reducer.Run([this](Status s) { status_ = s; }); - if (status_.ok()) { - CHECK(tensor_.CopyFrom(*ctx.mutable_output(0), tensor_.shape())); - } - - dev_ctx->Unref(); - } - - NcclReducerTest* parent_; - string device_name_; - int rank_; - Tensor tensor_; - Device* device_; - CollectiveParams col_params_; - Status status_; - }; - - std::vector> gpus_; - TestCollectiveExecutorMgr col_exec_mgr_; - CollectiveExecutor* col_exec_; - std::unique_ptr dev_mgr_; - std::vector> instances_; - CollectiveParams col_params_; - mutex mu_; - int32 reduce_counter_ GUARDED_BY(mu_) = 0; -}; - -TEST_F(NcclReducerTest, Test2Dev16Len) { RunTest(2, 16); } -TEST_F(NcclReducerTest, Test4Dev16Len) { RunTest(4, 16); } -TEST_F(NcclReducerTest, Test8Dev16Len) { RunTest(8, 16); } -TEST_F(NcclReducerTest, Test8Dev128Len) { RunTest(8, 128); } -TEST_F(NcclReducerTest, Test8Dev1045991Len) { RunTest(8, 1048576); } - -} // namespace tensorflow - -#endif diff --git a/tensorflow/core/kernels/collective_ops.cc b/tensorflow/core/kernels/collective_ops.cc index ea582836c2..82e2913b64 100644 --- a/tensorflow/core/kernels/collective_ops.cc +++ b/tensorflow/core/kernels/collective_ops.cc @@ -87,9 +87,6 @@ class CollectiveReduceOpKernel : public CollectiveOpKernel { "final_op must be one of {\"Id\", \"Div\"} but got ", final_op_name)); OP_REQUIRES_OK(c, c->GetAttr("T", &col_params_.instance.data_type)); - OP_REQUIRES_OK(c, - c->GetAttr("wait_for", - &col_params_.instance.impl_details.dependencies)); const NodeDef& real_node = c->def(); col_params_.name = strings::StrCat(real_node.name(), ": Reduce(", diff --git a/tensorflow/core/nccl/BUILD b/tensorflow/core/nccl/BUILD index a19e1af888..4be33b2a0c 100644 --- a/tensorflow/core/nccl/BUILD +++ b/tensorflow/core/nccl/BUILD @@ -20,10 +20,8 @@ cc_library( name = "nccl_lib", srcs = if_cuda([ "nccl_manager.cc", - "nccl_rewrite.cc", - ]), - hdrs = if_cuda([ "nccl_manager.h", + "nccl_rewrite.cc", ]), copts = tf_copts(), deps = if_cuda([ diff --git a/tensorflow/core/protobuf/worker.proto b/tensorflow/core/protobuf/worker.proto index 4284dd119e..74058c8465 100644 --- a/tensorflow/core/protobuf/worker.proto +++ b/tensorflow/core/protobuf/worker.proto @@ -535,7 +535,6 @@ message CompleteInstanceRequest { message CompleteInstanceResponse { int32 instance_key = 1; int32 source_rank = 2; - bytes communicator_key = 3; } // Request for next agreed-upon step_id for the specified graph_keys. -- GitLab From bdfdaea03edaa8c91cb9d9606e283afed38eaf23 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Tue, 15 Jan 2019 12:46:18 -0800 Subject: [PATCH 0738/2345] Fix buildifier check --- tensorflow/contrib/tensorrt/BUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 17717c8403..58c188b314 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -221,8 +221,8 @@ tf_cuda_library( ], hdrs = [ "resources/trt_int8_calibrator.h", - "resources/trt_resource_manager.h", "resources/trt_lru_cache.h", + "resources/trt_resource_manager.h", "resources/trt_resources.h", ], deps = [ @@ -568,7 +568,7 @@ cc_library( copts = tf_copts(), deps = [ "//tensorflow/core:lib", - "//tensorflow/core:framework" + "//tensorflow/core:framework", ], ) -- GitLab From cee860e07b3b3daaefb7628f8b31748aed3aaf05 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Tue, 15 Jan 2019 12:21:35 -0800 Subject: [PATCH 0739/2345] Remove the old mnist_keras example PiperOrigin-RevId: 229417867 --- tensorflow/contrib/tpu/BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/contrib/tpu/BUILD b/tensorflow/contrib/tpu/BUILD index 0fe9d0cdfc..d898f05405 100644 --- a/tensorflow/contrib/tpu/BUILD +++ b/tensorflow/contrib/tpu/BUILD @@ -263,7 +263,6 @@ py_library( "//learning/brain:__subpackages__", "//tensorflow:__subpackages__", "//third_party/cloud_tpu/models/keras_colab:__subpackages__", - "//third_party/cloud_tpu/models/mnist_keras:__subpackages__", "//third_party/cloud_tpu/models/resnet50_keras:__subpackages__", ], deps = [ -- GitLab From 97aa5daaa5e2b7728d8b2a19695a39884fce0147 Mon Sep 17 00:00:00 2001 From: Martin Wicke Date: Tue, 15 Jan 2019 12:43:06 -0800 Subject: [PATCH 0740/2345] Increase version of google_pasta to 0.1.1 (for Byte string fix). PiperOrigin-RevId: 229421387 --- 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 55b7046e30..6e182a5e5c 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -51,7 +51,7 @@ REQUIRED_PACKAGES = [ 'absl-py >= 0.1.6', 'astor >= 0.6.0', 'gast >= 0.2.0', - 'google_pasta >= 0.1.0', + 'google_pasta >= 0.1.1', 'keras_applications >= 1.0.6', 'keras_preprocessing >= 1.0.5', 'numpy >= 1.13.3', -- GitLab From 8d8fee60bd2215858f574b27dcfd07aac4473860 Mon Sep 17 00:00:00 2001 From: Haoyu Zhang Date: Tue, 15 Jan 2019 12:48:04 -0800 Subject: [PATCH 0741/2345] Add fp16 support to color distortion ops for better performance on GPU. These ops are typically used for image preprocessing in data input pipeline. When the input pipeline gets complicated and becomes the bottleneck (for example, in the SSD (Single Shot multibox Detector) model), offloading partial image preprocessing to accelerators can be useful to alleviate CPU workload. Adding fp16 support can further accelerate the processing on GPU and allow the model to have larger batch sizes. The ops that are affected in this CL are: tf.image.random_brightness tf.image.random_contrast tf.image.random_saturation tf.image.random_hue tf.image.adjust_brightness tf.image.adjust_contrast tf.image.adjust_saturation tf.image.adjust_hue PiperOrigin-RevId: 229422273 --- .../compiler/tf2xla/kernels/image_ops.cc | 54 ++++++----- tensorflow/core/kernels/adjust_contrast_op.cc | 59 +++++++---- tensorflow/core/kernels/adjust_contrast_op.h | 24 +++-- .../core/kernels/adjust_contrast_op_gpu.cu.cc | 3 +- tensorflow/core/kernels/adjust_hsv_gpu.cu.h | 17 ++-- tensorflow/core/kernels/adjust_hue_op.cc | 32 +++--- tensorflow/core/kernels/adjust_hue_op.h | 5 +- .../core/kernels/adjust_hue_op_gpu.cu.cc | 14 ++- .../core/kernels/adjust_saturation_op.cc | 32 +++--- .../core/kernels/adjust_saturation_op.h | 5 +- .../kernels/adjust_saturation_op_gpu.cu.cc | 17 ++-- tensorflow/core/ops/image_ops.cc | 15 +-- tensorflow/python/ops/image_ops_impl.py | 97 +++++++++++-------- tensorflow/python/ops/image_ops_test.py | 56 +++-------- 14 files changed, 240 insertions(+), 190 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/image_ops.cc b/tensorflow/compiler/tf2xla/kernels/image_ops.cc index a31b5a2cfd..92b20fe0ba 100644 --- a/tensorflow/compiler/tf2xla/kernels/image_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/image_ops.cc @@ -26,6 +26,7 @@ limitations under the License. #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/compiler/xla/shape_util.h" #include "tensorflow/core/framework/tensor_shape.h" +#include "tensorflow/core/framework/types.pb.h" namespace tensorflow { namespace { @@ -186,19 +187,20 @@ class AdjustContrastOpV2 : public XlaOpKernel { factor_shape.DebugString())); xla::XlaBuilder* b = context->builder(); - xla::XlaOp input = context->Input(0); - xla::XlaOp factor = context->Input(1); - DataType type = context->input_type(0); + xla::XlaOp input = context->Input(0); + xla::XlaOp factor = XlaHelpers::ConvertElementType(context->Input(1), type); + const DataType accumulation_type = XlaHelpers::SumAccumulationType(type); auto converted = XlaHelpers::ConvertElementType(input, accumulation_type); auto reduce = xla::Reduce(converted, XlaHelpers::Zero(b, accumulation_type), *context->GetOrCreateAdd(accumulation_type), {height_dim, width_dim}); - auto output = XlaHelpers::ConvertElementType(reduce, type); - output = - xla::Div(output, XlaHelpers::FloatLiteral(b, type, height * width)); + + auto output = xla::Div( + reduce, XlaHelpers::FloatLiteral(b, accumulation_type, height * width)); + output = XlaHelpers::ConvertElementType(output, type); std::vector broadcast_dims(input_shape.dims() - 2); std::iota(broadcast_dims.begin(), broadcast_dims.end(), 0); @@ -234,8 +236,10 @@ class AdjustSaturationOp : public XlaOpKernel { channels, " channels.")); xla::XlaBuilder* b = context->builder(); - xla::XlaOp input = context->Input(0); - xla::XlaOp scale = context->Input(1); + xla::XlaOp input = + XlaHelpers::ConvertElementType(context->Input(0), DT_FLOAT); + xla::XlaOp scale = + XlaHelpers::ConvertElementType(context->Input(1), DT_FLOAT); DataType type = context->input_type(0); @@ -250,15 +254,17 @@ class AdjustSaturationOp : public XlaOpKernel { /*dimno=*/channel_dim); TensorShape channel_shape = input_shape; channel_shape.set_dim(channel_dim, 1); - auto hsv = RGBToHSV(context, b, {red, green, blue}, context->input_type(0), - channel_shape); + auto hsv = + RGBToHSV(context, b, {red, green, blue}, DT_FLOAT, channel_shape); - hsv[1] = xla::Clamp(XlaHelpers::Zero(b, type), xla::Mul(hsv[1], scale), - XlaHelpers::One(b, type)); + hsv[1] = xla::Clamp(XlaHelpers::Zero(b, DT_FLOAT), xla::Mul(hsv[1], scale), + XlaHelpers::One(b, DT_FLOAT)); - auto rgb = HSVToRGB(context->builder(), hsv, context->input_type(0)); + auto rgb = HSVToRGB(context->builder(), hsv, DT_FLOAT); - context->SetOutput(0, xla::ConcatInDim(b, rgb, channel_dim)); + auto output = XlaHelpers::ConvertElementType( + xla::ConcatInDim(b, rgb, channel_dim), type); + context->SetOutput(0, output); } }; REGISTER_XLA_OP(Name("AdjustSaturation"), AdjustSaturationOp); @@ -284,8 +290,10 @@ class AdjustHueOp : public XlaOpKernel { channels, " channels.")); xla::XlaBuilder* b = context->builder(); - xla::XlaOp input = context->Input(0); - xla::XlaOp delta = context->Input(1); + xla::XlaOp input = + XlaHelpers::ConvertElementType(context->Input(0), DT_FLOAT); + xla::XlaOp delta = + XlaHelpers::ConvertElementType(context->Input(1), DT_FLOAT); DataType type = context->input_type(0); @@ -300,20 +308,22 @@ class AdjustHueOp : public XlaOpKernel { /*dimno=*/channel_dim); TensorShape channel_shape = input_shape; channel_shape.set_dim(channel_dim, 1); - auto hsv = RGBToHSV(context, b, {red, green, blue}, context->input_type(0), - channel_shape); + auto hsv = + RGBToHSV(context, b, {red, green, blue}, DT_FLOAT, channel_shape); - auto zero = XlaHelpers::Zero(b, type); - auto one = XlaHelpers::One(b, type); + auto zero = XlaHelpers::Zero(b, DT_FLOAT); + auto one = XlaHelpers::One(b, DT_FLOAT); auto& hue = hsv[0]; hue = xla::Rem(xla::Add(hsv[0], delta), one); hue = xla::Select(xla::Lt(hue, zero), xla::Rem(xla::Add(one, hue), one), hue); - auto rgb = HSVToRGB(context->builder(), hsv, context->input_type(0)); + auto rgb = HSVToRGB(context->builder(), hsv, DT_FLOAT); - context->SetOutput(0, xla::ConcatInDim(b, rgb, channel_dim)); + auto output = XlaHelpers::ConvertElementType( + xla::ConcatInDim(b, rgb, channel_dim), type); + context->SetOutput(0, output); } }; REGISTER_XLA_OP(Name("AdjustHue"), AdjustHueOp); diff --git a/tensorflow/core/kernels/adjust_contrast_op.cc b/tensorflow/core/kernels/adjust_contrast_op.cc index 47e10f56df..1aef0060b0 100644 --- a/tensorflow/core/kernels/adjust_contrast_op.cc +++ b/tensorflow/core/kernels/adjust_contrast_op.cc @@ -189,11 +189,11 @@ class AdjustContrastOpV2Base : public OpKernel { const ComputeOptions& options) = 0; }; -template +template class AdjustContrastOpv2; template <> -class AdjustContrastOpv2 : public AdjustContrastOpV2Base { +class AdjustContrastOpv2 : public AdjustContrastOpV2Base { public: explicit AdjustContrastOpv2(OpKernelConstruction* context) : AdjustContrastOpV2Base(context) {} @@ -378,23 +378,32 @@ class AdjustContrastOpv2 : public AdjustContrastOpV2Base { } }; -REGISTER_KERNEL_BUILDER(Name("AdjustContrastv2").Device(DEVICE_CPU), - AdjustContrastOpv2); +REGISTER_KERNEL_BUILDER( + Name("AdjustContrastv2").Device(DEVICE_CPU).TypeConstraint("T"), + AdjustContrastOpv2); #if GOOGLE_CUDA // Forward declarations of the function specializations for GPU (to prevent // building the GPU versions here, they will be built compiling _gpu.cu.cc). namespace functor { -template <> -void AdjustContrastv2::operator()( - const GPUDevice& d, typename TTypes::ConstTensor input, - typename TTypes::ConstScalar contrast_factor, - typename TTypes::Tensor output); -extern template struct AdjustContrastv2; + +#define DECLARE_GPU_SPEC(T) \ + template <> \ + void AdjustContrastv2::operator()( \ + const GPUDevice& d, typename TTypes::ConstTensor input, \ + typename TTypes::ConstScalar contrast_factor, \ + typename TTypes::Tensor output); \ + extern template struct AdjustContrastv2; + +DECLARE_GPU_SPEC(float); +DECLARE_GPU_SPEC(Eigen::half); + +#undef DECLARE_GPU_SPEC + } // namespace functor -template <> -class AdjustContrastOpv2 : public AdjustContrastOpV2Base { +template +class AdjustContrastOpv2 : public AdjustContrastOpV2Base { public: explicit AdjustContrastOpv2(OpKernelConstruction* context) : AdjustContrastOpV2Base(context) {} @@ -403,20 +412,27 @@ class AdjustContrastOpv2 : public AdjustContrastOpV2Base { const ComputeOptions& options) override { const int64 shape[4] = {options.batch, options.height, options.width, options.channels}; - functor::AdjustContrastv2()( - context->eigen_device(), - options.input->shaped(shape), options.factor->scalar(), - options.output->shaped(shape)); + functor::AdjustContrastv2()( + context->eigen_device(), options.input->shaped(shape), + options.factor->scalar(), options.output->shaped(shape)); } }; -REGISTER_KERNEL_BUILDER(Name("AdjustContrastv2").Device(DEVICE_GPU), - AdjustContrastOpv2); +#define REGISTER_GPU(T) \ + REGISTER_KERNEL_BUILDER( \ + Name("AdjustContrastv2").Device(DEVICE_GPU).TypeConstraint("T"), \ + AdjustContrastOpv2); + +REGISTER_GPU(float) +REGISTER_GPU(Eigen::half) + +#undef REGISTER_GPU + #endif // GOOGLE_CUDA #ifdef TENSORFLOW_USE_SYCL template <> -class AdjustContrastOpv2 : public AdjustContrastOpV2Base { +class AdjustContrastOpv2 : public AdjustContrastOpV2Base { public: explicit AdjustContrastOpv2(OpKernelConstruction* context) : AdjustContrastOpV2Base(context) {} @@ -431,8 +447,9 @@ class AdjustContrastOpv2 : public AdjustContrastOpV2Base { options.output->shaped(shape)); } }; -REGISTER_KERNEL_BUILDER(Name("AdjustContrastv2").Device(DEVICE_SYCL), - AdjustContrastOpv2); +REGISTER_KERNEL_BUILDER( + Name("AdjustContrastv2").Device(DEVICE_SYCL).TypeConstraint("T"), + AdjustContrastOpv2); #endif // TENSORFLOW_USE_SYCL } // namespace tensorflow diff --git a/tensorflow/core/kernels/adjust_contrast_op.h b/tensorflow/core/kernels/adjust_contrast_op.h index f4a53c2ef9..3e501bccee 100644 --- a/tensorflow/core/kernels/adjust_contrast_op.h +++ b/tensorflow/core/kernels/adjust_contrast_op.h @@ -87,11 +87,11 @@ struct AdjustContrast { }; // Functor used by AdjustContrastOpv2 to do the computations. -template +template struct AdjustContrastv2 { - void operator()(const Device& d, typename TTypes::ConstTensor input, + void operator()(const Device& d, typename TTypes::ConstTensor input, typename TTypes::ConstScalar contrast_factor, - typename TTypes::Tensor output) { + typename TTypes::Tensor output) { const int batch = input.dimension(0); const int height = input.dimension(1); const int width = input.dimension(2); @@ -138,15 +138,19 @@ struct AdjustContrastv2 { #endif Eigen::Sizes<1, 1, 1, 1> scalar; float num_reduced_coeffs = height * width; - output.device(d) = - (input.shuffle(reduced_dims_first).sum(reduction_axis).eval() / - num_reduced_coeffs) - .reshape(reshape_dims) - .broadcast(broadcast_dims); + output.device(d) = (input.template cast() + .shuffle(reduced_dims_first) + .sum(reduction_axis) + .eval() / + num_reduced_coeffs) + .template cast() + .reshape(reshape_dims) + .broadcast(broadcast_dims); auto contrast_factor_tensor = contrast_factor.reshape(scalar).broadcast(scalar_broadcast); - auto adjusted = (input - output) * contrast_factor_tensor; - output.device(d) += adjusted; + auto adjusted = + (input - output).template cast() * contrast_factor_tensor; + output.device(d) += adjusted.template cast(); } }; diff --git a/tensorflow/core/kernels/adjust_contrast_op_gpu.cu.cc b/tensorflow/core/kernels/adjust_contrast_op_gpu.cu.cc index a451bfe29c..1a1c2a4e1e 100644 --- a/tensorflow/core/kernels/adjust_contrast_op_gpu.cu.cc +++ b/tensorflow/core/kernels/adjust_contrast_op_gpu.cu.cc @@ -26,7 +26,8 @@ namespace tensorflow { typedef Eigen::GpuDevice GPUDevice; // this is for v2 -template struct functor::AdjustContrastv2; +template struct functor::AdjustContrastv2; +template struct functor::AdjustContrastv2; // these are for v1 template struct functor::AdjustContrast; diff --git a/tensorflow/core/kernels/adjust_hsv_gpu.cu.h b/tensorflow/core/kernels/adjust_hsv_gpu.cu.h index 49df5ae296..dede7d249d 100644 --- a/tensorflow/core/kernels/adjust_hsv_gpu.cu.h +++ b/tensorflow/core/kernels/adjust_hsv_gpu.cu.h @@ -91,11 +91,10 @@ inline __device__ RgbTuple hsv2rgb_cuda(const float h, const float s, return tuple; } -template +template __global__ void adjust_hsv_nhwc(const int64 number_elements, - const float* const __restrict__ input, - float* const output, - const float* const hue_delta, + const T* const __restrict__ input, + T* const output, const float* const hue_delta, const float* const saturation_scale, const float* const value_scale) { // multiply by 3 since we're dealing with contiguous RGB bytes for each pixel @@ -111,7 +110,9 @@ __global__ void adjust_hsv_nhwc(const int64 number_elements, output[idx + 2] = input[idx + 2]; return; } - const HsvTuple hsv = rgb2hsv_cuda(input[idx], input[idx + 1], input[idx + 2]); + const HsvTuple hsv = rgb2hsv_cuda(static_cast(input[idx]), + static_cast(input[idx + 1]), + static_cast(input[idx + 2])); float new_h = hsv.h; float new_s = hsv.s; float new_v = hsv.v; @@ -134,9 +135,9 @@ __global__ void adjust_hsv_nhwc(const int64 number_elements, new_v = hsv.v * scale; } const RgbTuple rgb = hsv2rgb_cuda(new_h, new_s, new_v); - output[idx] = rgb.r; - output[idx + 1] = rgb.g; - output[idx + 2] = rgb.b; + output[idx] = static_cast(rgb.r); + output[idx + 1] = static_cast(rgb.g); + output[idx + 2] = static_cast(rgb.b); } } // namespace internal diff --git a/tensorflow/core/kernels/adjust_hue_op.cc b/tensorflow/core/kernels/adjust_hue_op.cc index 52dec94305..06de5ea3fb 100644 --- a/tensorflow/core/kernels/adjust_hue_op.cc +++ b/tensorflow/core/kernels/adjust_hue_op.cc @@ -82,7 +82,7 @@ class AdjustHueOpBase : public OpKernel { } }; -template +template class AdjustHueOp; namespace internal { @@ -196,7 +196,7 @@ static void hv_range_to_rgb(float h, float v_min, float v_max, float* r, } // namespace internal template <> -class AdjustHueOp : public AdjustHueOpBase { +class AdjustHueOp : public AdjustHueOpBase { public: explicit AdjustHueOp(OpKernelConstruction* context) : AdjustHueOpBase(context) {} @@ -245,12 +245,13 @@ class AdjustHueOp : public AdjustHueOpBase { } }; -REGISTER_KERNEL_BUILDER(Name("AdjustHue").Device(DEVICE_CPU), - AdjustHueOp); +REGISTER_KERNEL_BUILDER( + Name("AdjustHue").Device(DEVICE_CPU).TypeConstraint("T"), + AdjustHueOp); #if GOOGLE_CUDA -template <> -class AdjustHueOp : public AdjustHueOpBase { +template +class AdjustHueOp : public AdjustHueOpBase { public: explicit AdjustHueOp(OpKernelConstruction* context) : AdjustHueOpBase(context) {} @@ -265,17 +266,24 @@ class AdjustHueOp : public AdjustHueOpBase { const auto stream = device.stream(); OP_REQUIRES(context, stream, errors::Internal("No GPU stream available.")); if (number_of_elements > 0) { - const float* input_data = input->flat().data(); + const T* input_data = input->flat().data(); const float* delta_h = delta->flat().data(); - float* const output_data = output->flat().data(); - functor::AdjustHueGPU()(&device, number_of_elements, input_data, delta_h, - output_data); + T* const output_data = output->flat().data(); + functor::AdjustHueGPU()(&device, number_of_elements, input_data, + delta_h, output_data); } } }; -REGISTER_KERNEL_BUILDER(Name("AdjustHue").Device(DEVICE_GPU), - AdjustHueOp); +#define REGISTER_GPU(T) \ + REGISTER_KERNEL_BUILDER( \ + Name("AdjustHue").Device(DEVICE_GPU).TypeConstraint("T"), \ + AdjustHueOp); + +REGISTER_GPU(float) +REGISTER_GPU(Eigen::half) + +#undef REGISTER_GPU #endif diff --git a/tensorflow/core/kernels/adjust_hue_op.h b/tensorflow/core/kernels/adjust_hue_op.h index 983a4072bf..6d6699de3f 100644 --- a/tensorflow/core/kernels/adjust_hue_op.h +++ b/tensorflow/core/kernels/adjust_hue_op.h @@ -27,10 +27,11 @@ typedef Eigen::GpuDevice GPUDevice; namespace functor { +template struct AdjustHueGPU { void operator()(GPUDevice* device, const int64 number_of_elements, - const float* const input, const float* const delta, - float* const output); + const T* const input, const float* const delta, + T* const output); }; } // namespace functor diff --git a/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc b/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc index a4fe5f755c..c30085269c 100644 --- a/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc +++ b/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc @@ -24,19 +24,25 @@ namespace tensorflow { namespace functor { -void AdjustHueGPU::operator()(GPUDevice* device, const int64 number_of_elements, - const float* const input, - const float* const delta, float* const output) { +template +void AdjustHueGPU::operator()(GPUDevice* device, + const int64 number_of_elements, + const T* const input, const float* const delta, + T* const output) { const auto stream = device->stream(); const CudaLaunchConfig config = GetCudaLaunchConfig(number_of_elements, *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 + internal::adjust_hsv_nhwc <<>>( number_of_elements, input, output, delta, nullptr, nullptr); } + +template struct AdjustHueGPU; +template struct AdjustHueGPU; + } // namespace functor } // namespace tensorflow #endif // GOOGLE_CUDA diff --git a/tensorflow/core/kernels/adjust_saturation_op.cc b/tensorflow/core/kernels/adjust_saturation_op.cc index f0c6ae499d..87d34fcfcc 100644 --- a/tensorflow/core/kernels/adjust_saturation_op.cc +++ b/tensorflow/core/kernels/adjust_saturation_op.cc @@ -81,7 +81,7 @@ class AdjustSaturationOpBase : public OpKernel { } }; -template +template class AdjustSaturationOp; namespace internal { @@ -173,7 +173,7 @@ static void hsv_to_rgb(float h, float s, float v, float* r, float* g, } // namespace internal template <> -class AdjustSaturationOp : public AdjustSaturationOpBase { +class AdjustSaturationOp : public AdjustSaturationOpBase { public: explicit AdjustSaturationOp(OpKernelConstruction* context) : AdjustSaturationOpBase(context) {} @@ -211,12 +211,13 @@ class AdjustSaturationOp : public AdjustSaturationOpBase { } }; -REGISTER_KERNEL_BUILDER(Name("AdjustSaturation").Device(DEVICE_CPU), - AdjustSaturationOp); +REGISTER_KERNEL_BUILDER( + Name("AdjustSaturation").Device(DEVICE_CPU).TypeConstraint("T"), + AdjustSaturationOp); #if GOOGLE_CUDA -template <> -class AdjustSaturationOp : public AdjustSaturationOpBase { +template +class AdjustSaturationOp : public AdjustSaturationOpBase { public: explicit AdjustSaturationOp(OpKernelConstruction* context) : AdjustSaturationOpBase(context) {} @@ -231,17 +232,24 @@ class AdjustSaturationOp : public AdjustSaturationOpBase { const auto stream = device.stream(); OP_REQUIRES(context, stream, errors::Internal("No GPU stream available.")); if (number_of_elements > 0) { - const float* input_data = input->flat().data(); + const T* input_data = input->flat().data(); const float* scale_data = scale->flat().data(); - float* const output_data = output->flat().data(); - functor::AdjustSaturationGPU()(&device, number_of_elements, input_data, - scale_data, output_data); + T* const output_data = output->flat().data(); + functor::AdjustSaturationGPU()(&device, number_of_elements, input_data, + scale_data, output_data); } } }; -REGISTER_KERNEL_BUILDER(Name("AdjustSaturation").Device(DEVICE_GPU), - AdjustSaturationOp); +#define REGISTER_GPU(T) \ + REGISTER_KERNEL_BUILDER( \ + Name("AdjustSaturation").Device(DEVICE_GPU).TypeConstraint("T"), \ + AdjustSaturationOp); + +REGISTER_GPU(float) +REGISTER_GPU(Eigen::half) + +#undef REGISTER_GPU #endif diff --git a/tensorflow/core/kernels/adjust_saturation_op.h b/tensorflow/core/kernels/adjust_saturation_op.h index fd28ba536f..c21ce4e360 100644 --- a/tensorflow/core/kernels/adjust_saturation_op.h +++ b/tensorflow/core/kernels/adjust_saturation_op.h @@ -27,10 +27,11 @@ typedef Eigen::GpuDevice GPUDevice; namespace functor { +template struct AdjustSaturationGPU { void operator()(GPUDevice* device, const int64 number_of_elements, - const float* const input, const float* const scale, - float* const output); + const T* const input, const float* const scale, + T* const output); }; } // namespace functor diff --git a/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc b/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc index 37cfb26a47..6c70490d46 100644 --- a/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc +++ b/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc @@ -24,21 +24,26 @@ namespace tensorflow { namespace functor { -void AdjustSaturationGPU::operator()(GPUDevice* device, - const int64 number_of_elements, - const float* const input, - const float* const scale, - float* const output) { +template +void AdjustSaturationGPU::operator()(GPUDevice* device, + const int64 number_of_elements, + const T* const input, + const float* const scale, + T* const output) { const auto stream = device->stream(); const CudaLaunchConfig config = GetCudaLaunchConfig(number_of_elements, *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 + internal::adjust_hsv_nhwc <<>>( number_of_elements, input, output, nullptr, scale, nullptr); } + +template struct AdjustSaturationGPU; +template struct AdjustSaturationGPU; + } // namespace functor } // namespace tensorflow #endif // GOOGLE_CUDA diff --git a/tensorflow/core/ops/image_ops.cc b/tensorflow/core/ops/image_ops.cc index ee8b1e58d6..0f1555f49c 100644 --- a/tensorflow/core/ops/image_ops.cc +++ b/tensorflow/core/ops/image_ops.cc @@ -408,9 +408,10 @@ REGISTER_OP("AdjustContrast") // -------------------------------------------------------------------------- REGISTER_OP("AdjustContrastv2") - .Input("images: float") + .Input("images: T") .Input("contrast_factor: float") - .Output("output: float") + .Output("output: T") + .Attr("T: {half, float} = DT_FLOAT") .SetShapeFn([](InferenceContext* c) { // The contrast_factor should be scalar only. ShapeHandle unused; @@ -420,18 +421,20 @@ REGISTER_OP("AdjustContrastv2") // -------------------------------------------------------------------------- REGISTER_OP("AdjustHue") - .Input("images: float") + .Input("images: T") .Input("delta: float") - .Output("output: float") + .Output("output: T") + .Attr("T: {half, float} = DT_FLOAT") .SetShapeFn([](InferenceContext* c) { return shape_inference::UnchangedShapeWithRankAtLeast(c, 3); }); // -------------------------------------------------------------------------- REGISTER_OP("AdjustSaturation") - .Input("images: float") + .Input("images: T") .Input("scale: float") - .Output("output: float") + .Output("output: T") + .Attr("T: {half, float} = DT_FLOAT") .SetShapeFn([](InferenceContext* c) { return shape_inference::UnchangedShapeWithRankAtLeast(c, 3); }); diff --git a/tensorflow/python/ops/image_ops_impl.py b/tensorflow/python/ops/image_ops_impl.py index 24d049b726..8047743cfa 100644 --- a/tensorflow/python/ops/image_ops_impl.py +++ b/tensorflow/python/ops/image_ops_impl.py @@ -1250,14 +1250,14 @@ def random_brightness(image, max_delta, seed=None): interval `[-max_delta, max_delta)`. Args: - image: An image. + image: An image or images to adjust. max_delta: float, must be non-negative. seed: A Python integer. Used to create a random seed. See `tf.set_random_seed` for behavior. Returns: - The brightness-adjusted image. + The brightness-adjusted image(s). Raises: ValueError: if `max_delta` is negative. @@ -1271,7 +1271,7 @@ def random_brightness(image, max_delta, seed=None): @tf_export('image.random_contrast') def random_contrast(image, lower, upper, seed=None): - """Adjust the contrast of an image by a random factor. + """Adjust the contrast of an image or images by a random factor. Equivalent to `adjust_contrast()` but uses a `contrast_factor` randomly picked in the interval `[lower, upper]`. @@ -1281,11 +1281,10 @@ def random_contrast(image, lower, upper, seed=None): lower: float. Lower bound for the random contrast factor. upper: float. Upper bound for the random contrast factor. seed: A Python integer. Used to create a random seed. See - `tf.set_random_seed` - for behavior. + `tf.set_random_seed` for behavior. Returns: - The contrast-adjusted tensor. + The contrast-adjusted image(s). Raises: ValueError: if `upper <= lower` or if `lower < 0`. @@ -1305,19 +1304,19 @@ def random_contrast(image, lower, upper, seed=None): def adjust_brightness(image, delta): """Adjust the brightness of RGB or Grayscale images. - This is a convenience method that converts an RGB image to float - representation, adjusts its brightness, and then converts it back to the - original data type. If several adjustments are chained it is advisable to + This is a convenience method that converts RGB images to float + representation, adjusts their brightness, and then converts them back to the + original data type. If several adjustments are chained, it is advisable to minimize the number of redundant conversions. - The value `delta` is added to all components of the tensor `image`. Both - `image` and `delta` are converted to `float` before adding (and `image` is - scaled appropriately if it is in fixed-point representation). For regular + The value `delta` is added to all components of the tensor `image`. `image` is + converted to `float` and scaled appropriately if it is in fixed-point + representation, and `delta` is converted to the same data type. For regular images, `delta` should be in the range `[0,1)`, as it is added to the image in floating point representation, where pixel values are in the `[0,1)` range. Args: - image: A tensor. + image: RGB image or images to adjust. delta: A scalar. Amount to add to the pixel values. Returns: @@ -1327,10 +1326,14 @@ def adjust_brightness(image, delta): image = ops.convert_to_tensor(image, name='image') # Remember original dtype to so we can convert back if needed orig_dtype = image.dtype - flt_image = convert_image_dtype(image, dtypes.float32) + + if orig_dtype in [dtypes.float16, dtypes.float32]: + flt_image = image + else: + flt_image = convert_image_dtype(image, dtypes.float32) adjusted = math_ops.add( - flt_image, math_ops.cast(delta, dtypes.float32), name=name) + flt_image, math_ops.cast(delta, flt_image.dtype), name=name) return convert_image_dtype(adjusted, orig_dtype, saturate=True) @@ -1339,9 +1342,9 @@ def adjust_brightness(image, delta): def adjust_contrast(images, contrast_factor): """Adjust contrast of RGB or grayscale images. - This is a convenience method that converts an RGB image to float - representation, adjusts its contrast, and then converts it back to the - original data type. If several adjustments are chained it is advisable to + This is a convenience method that converts RGB images to float + representation, adjusts their contrast, and then converts them back to the + original data type. If several adjustments are chained, it is advisable to minimize the number of redundant conversions. `images` is a tensor of at least 3 dimensions. The last 3 dimensions are @@ -1366,7 +1369,11 @@ def adjust_contrast(images, contrast_factor): images = ops.convert_to_tensor(images, name='images') # Remember original dtype to so we can convert back if needed orig_dtype = images.dtype - flt_images = convert_image_dtype(images, dtypes.float32) + + if orig_dtype in (dtypes.float16, dtypes.float32): + flt_images = images + else: + flt_images = convert_image_dtype(images, dtypes.float32) adjusted = gen_image_ops.adjust_contrastv2( flt_images, contrast_factor=contrast_factor, name=name) @@ -1560,7 +1567,7 @@ def grayscale_to_rgb(images, name=None): # pylint: disable=invalid-name @tf_export('image.random_hue') def random_hue(image, max_delta, seed=None): - """Adjust the hue of an RGB image by a random factor. + """Adjust the hue of RGB images by a random factor. Equivalent to `adjust_hue()` but uses a `delta` randomly picked in the interval `[-max_delta, max_delta]`. @@ -1570,10 +1577,10 @@ def random_hue(image, max_delta, seed=None): Args: image: RGB image or images. Size of the last dimension must be 3. max_delta: float. Maximum value for the random delta. - seed: An operation-specific seed. It will be used in conjunction - with the graph-level seed to determine the real seeds that will be - used in this operation. Please see the documentation of - set_random_seed for its interaction with the graph-level random seed. + seed: An operation-specific seed. It will be used in conjunction with the + graph-level seed to determine the real seeds that will be used in this + operation. Please see the documentation of set_random_seed for its + interaction with the graph-level random seed. Returns: Adjusted image(s), same shape and DType as `image`. @@ -1593,7 +1600,7 @@ def random_hue(image, max_delta, seed=None): @tf_export('image.adjust_hue') def adjust_hue(image, delta, name=None): - """Adjust hue of an RGB image. + """Adjust hue of RGB images. This is a convenience method that converts an RGB image to float representation, converts it to HSV, add an offset to the hue channel, converts @@ -1601,7 +1608,7 @@ def adjust_hue(image, delta, name=None): are chained it is advisable to minimize the number of redundant conversions. `image` is an RGB image. The image hue is adjusted by converting the - image to HSV and rotating the hue channel (H) by + image(s) to HSV and rotating the hue channel (H) by `delta`. The image is then converted back to RGB. `delta` must be in the interval `[-1, 1]`. @@ -1618,7 +1625,10 @@ def adjust_hue(image, delta, name=None): image = ops.convert_to_tensor(image, name='image') # Remember original dtype to so we can convert back if needed orig_dtype = image.dtype - flt_image = convert_image_dtype(image, dtypes.float32) + if orig_dtype in (dtypes.float16, dtypes.float32): + flt_image = image + else: + flt_image = convert_image_dtype(image, dtypes.float32) rgb_altered = gen_image_ops.adjust_hue(flt_image, delta) @@ -1696,7 +1706,7 @@ def adjust_jpeg_quality(image, jpeg_quality, name=None): @tf_export('image.random_saturation') def random_saturation(image, lower, upper, seed=None): - """Adjust the saturation of an RGB image by a random factor. + """Adjust the saturation of RGB images by a random factor. Equivalent to `adjust_saturation()` but uses a `saturation_factor` randomly picked in the interval `[lower, upper]`. @@ -1705,10 +1715,10 @@ def random_saturation(image, lower, upper, seed=None): image: RGB image or images. Size of the last dimension must be 3. lower: float. Lower bound for the random saturation factor. upper: float. Upper bound for the random saturation factor. - seed: An operation-specific seed. It will be used in conjunction - with the graph-level seed to determine the real seeds that will be - used in this operation. Please see the documentation of - set_random_seed for its interaction with the graph-level random seed. + seed: An operation-specific seed. It will be used in conjunction with the + graph-level seed to determine the real seeds that will be used in this + operation. Please see the documentation of set_random_seed for its + interaction with the graph-level random seed. Returns: Adjusted image(s), same shape and DType as `image`. @@ -1729,17 +1739,17 @@ def random_saturation(image, lower, upper, seed=None): @tf_export('image.adjust_saturation') def adjust_saturation(image, saturation_factor, name=None): - """Adjust saturation of an RGB image. + """Adjust saturation of RGB images. - This is a convenience method that converts an RGB image to float - representation, converts it to HSV, add an offset to the saturation channel, + This is a convenience method that converts RGB images to float + representation, converts them to HSV, add an offset to the saturation channel, converts back to RGB and then back to the original data type. If several adjustments are chained it is advisable to minimize the number of redundant conversions. - `image` is an RGB image. The image saturation is adjusted by converting the - image to HSV and multiplying the saturation (S) channel by - `saturation_factor` and clipping. The image is then converted back to RGB. + `image` is an RGB image or images. The image saturation is adjusted by + converting the images to HSV and multiplying the saturation (S) channel by + `saturation_factor` and clipping. The images are then converted back to RGB. Args: image: RGB image or images. Size of the last dimension must be 3. @@ -1753,11 +1763,14 @@ def adjust_saturation(image, saturation_factor, name=None): image = ops.convert_to_tensor(image, name='image') # Remember original dtype to so we can convert back if needed orig_dtype = image.dtype - flt_image = convert_image_dtype(image, dtypes.float32) + if orig_dtype in (dtypes.float16, dtypes.float32): + flt_image = image + else: + flt_image = convert_image_dtype(image, dtypes.float32) + + adjusted = gen_image_ops.adjust_saturation(flt_image, saturation_factor) - return convert_image_dtype( - gen_image_ops.adjust_saturation(flt_image, saturation_factor), - orig_dtype) + return convert_image_dtype(adjusted, orig_dtype) @tf_export('io.is_jpeg', 'image.is_jpeg', v1=['io.is_jpeg', 'image.is_jpeg']) diff --git a/tensorflow/python/ops/image_ops_test.py b/tensorflow/python/ops/image_ops_test.py index e7249333bd..361befabce 100644 --- a/tensorflow/python/ops/image_ops_test.py +++ b/tensorflow/python/ops/image_ops_test.py @@ -886,44 +886,6 @@ class AdjustSaturationTest(test_util.TensorFlowTestCase): y_tf = self.evaluate(y) self.assertAllEqual(y_tf, y_np) - def _adjust_saturation(self, image, saturation_factor): - image = ops.convert_to_tensor(image, name="image") - orig_dtype = image.dtype - flt_image = image_ops.convert_image_dtype(image, dtypes.float32) - saturation_adjusted_image = gen_image_ops.adjust_saturation( - flt_image, saturation_factor) - return image_ops.convert_image_dtype(saturation_adjusted_image, orig_dtype) - - def testHalfSaturationFused(self): - x_shape = [2, 2, 3] - x_rgb_data = [0, 5, 13, 54, 135, 226, 37, 8, 234, 90, 255, 1] - x_np = np.array(x_rgb_data, dtype=np.uint8).reshape(x_shape) - - saturation_factor = 0.5 - y_rgb_data = [6, 9, 13, 140, 180, 226, 135, 121, 234, 172, 255, 128] - y_np = np.array(y_rgb_data, dtype=np.uint8).reshape(x_shape) - - with self.test_session(use_gpu=True): - x = constant_op.constant(x_np, shape=x_shape) - y = self._adjust_saturation(x, saturation_factor) - y_tf = self.evaluate(y) - self.assertAllEqual(y_tf, y_np) - - def testTwiceSaturationFused(self): - x_shape = [2, 2, 3] - x_data = [0, 5, 13, 54, 135, 226, 37, 8, 234, 90, 255, 1] - x_np = np.array(x_data, dtype=np.uint8).reshape(x_shape) - - saturation_factor = 2.0 - y_data = [0, 5, 13, 0, 106, 226, 30, 0, 234, 89, 255, 0] - y_np = np.array(y_data, dtype=np.uint8).reshape(x_shape) - - with self.test_session(use_gpu=True): - x = constant_op.constant(x_np, shape=x_shape) - y = self._adjust_saturation(x, saturation_factor) - y_tf = self.evaluate(y) - self.assertAllEqual(y_tf, y_np) - def _adjustSaturationNp(self, x_np, scale): self.assertEqual(x_np.shape[-1], 3) x_v = x_np.reshape([-1, 3]) @@ -977,7 +939,7 @@ class AdjustSaturationTest(test_util.TensorFlowTestCase): else: raise AssertionError("Invalid test style: %s" % (test_style)) y_baseline = self._adjustSaturationNp(x_np, scale) - y_fused = self._adjust_saturation(x_np, scale).eval() + y_fused = image_ops.adjust_saturation(x_np, scale).eval() self.assertAllClose(y_fused, y_baseline, rtol=2e-5, atol=1e-5) @@ -1438,12 +1400,12 @@ class AdjustContrastTest(test_util.TensorFlowTestCase): class AdjustBrightnessTest(test_util.TensorFlowTestCase): - def _testBrightness(self, x_np, y_np, delta): + def _testBrightness(self, x_np, y_np, delta, tol=1e-6): with self.test_session(use_gpu=True): x = constant_op.constant(x_np, shape=x_np.shape) y = image_ops.adjust_brightness(x, delta) y_tf = self.evaluate(y) - self.assertAllClose(y_tf, y_np, 1e-6) + self.assertAllClose(y_tf, y_np, tol) def testPositiveDeltaUint8(self): x_shape = [2, 2, 3] @@ -1455,7 +1417,7 @@ class AdjustBrightnessTest(test_util.TensorFlowTestCase): self._testBrightness(x_np, y_np, delta=10. / 255.) - def testPositiveDeltaFloat(self): + def testPositiveDeltaFloat32(self): x_shape = [2, 2, 3] x_data = [0, 5, 13, 54, 135, 226, 37, 8, 234, 90, 255, 1] x_np = np.array(x_data, dtype=np.float32).reshape(x_shape) / 255. @@ -1465,6 +1427,16 @@ class AdjustBrightnessTest(test_util.TensorFlowTestCase): self._testBrightness(x_np, y_np, delta=10. / 255.) + def testPositiveDeltaFloat16(self): + x_shape = [2, 2, 3] + x_data = [0, 5, 13, 54, 135, 226, 37, 8, 234, 90, 255, 1] + x_np = np.array(x_data, dtype=np.float16).reshape(x_shape) / 255. + + y_data = [10, 15, 23, 64, 145, 236, 47, 18, 244, 100, 265, 11] + y_np = np.array(y_data, dtype=np.float16).reshape(x_shape) / 255. + + self._testBrightness(x_np, y_np, delta=10. / 255., tol=1e-3) + def testNegativeDelta(self): x_shape = [2, 2, 3] x_data = [0, 5, 13, 54, 135, 226, 37, 8, 234, 90, 255, 1] -- GitLab From 43bb4d1f6d3d91a2f0019d187204f665f52c3955 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Tue, 15 Jan 2019 12:49:03 -0800 Subject: [PATCH 0742/2345] Avoid using a temp directory with a space to workaround bazel errors. PiperOrigin-RevId: 229422438 --- tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh b/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh index 7dfee8f371..9c05db974b 100644 --- a/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh +++ b/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh @@ -41,7 +41,7 @@ run_configure_for_gpu_build # build_libtensorflow_tarball in ../builds/libtensorflow.sh # cannot be used on Windows since it relies on pkg_tar rules. # So we do something special here -bazel build -c opt --copt=/arch:AVX --announce_rc \ +bazel --output_user_root=${TMPDIR} build -c opt --copt=/arch:AVX --announce_rc \ tensorflow:libtensorflow.so \ tensorflow/tools/lib_package:clicenses_generate \ tensorflow/java:libtensorflow_jni.so \ -- GitLab From 0336900e0c9b9047444027a31bdbf5bae4fa3695 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Tue, 15 Jan 2019 12:59:27 -0800 Subject: [PATCH 0743/2345] Make XlaCompiledCpuFunction::StaticData inaccessible; NFC This CL will prevent tfcompile users from accidentally depending on StaticData internals. PiperOrigin-RevId: 229424173 --- tensorflow/compiler/aot/codegen.cc | 28 +++-- tensorflow/compiler/aot/codegen_test_h.golden | 23 ++-- .../tf2xla/xla_compiled_cpu_function.h | 116 ++++++++++++------ .../tf2xla/xla_jit_compiled_cpu_function.cc | 34 +++-- 4 files changed, 126 insertions(+), 75 deletions(-) diff --git a/tensorflow/compiler/aot/codegen.cc b/tensorflow/compiler/aot/codegen.cc index 347a365dcb..d016632da2 100644 --- a/tensorflow/compiler/aot/codegen.cc +++ b/tensorflow/compiler/aot/codegen.cc @@ -384,8 +384,9 @@ Status GenerateHeader(const CodegenOpts& opts, const tf2xla::Config& config, // calling HloProfilePrinter::profile_counters_size. const string assign_profile_counters_size = opts.gen_hlo_profile_printer_data - ? "data->set_profile_counters_size(" - "data->hlo_profile_printer_data()->profile_counters_size());" + ? "set_static_data_profile_counters_size(data, " + "get_static_data_hlo_profile_printer_data(data)->" + "profile_counters_size());" : ""; // Use a poor-man's text templating mechanism; first populate the full header @@ -449,7 +450,7 @@ extern "C" void {{ENTRY}}( // arg bytes aligned: {{ARG_BYTES_ALIGNED}} // temp bytes total: {{TEMP_BYTES_TOTAL}} // temp bytes aligned: {{TEMP_BYTES_ALIGNED}} -class {{CLASS}} : public tensorflow::XlaCompiledCpuFunction { +class {{CLASS}} final : public tensorflow::XlaCompiledCpuFunction { public: // Number of input arguments for the compiled computation. static constexpr size_t kNumArgs = {{ARG_NUM}}; @@ -464,16 +465,17 @@ class {{CLASS}} : public tensorflow::XlaCompiledCpuFunction { static XlaCompiledCpuFunction::StaticData* kStaticData = [](){ XlaCompiledCpuFunction::StaticData* data = new XlaCompiledCpuFunction::StaticData; - data->set_raw_function({{ENTRY}}); - data->set_buffer_infos(BufferInfos()); - data->set_num_buffers(kNumBuffers); - data->set_arg_index_table(ArgIndexToBufferIndex()); - data->set_num_args(kNumArgs); - data->set_result_index(kResultIndex); - data->set_arg_names(StaticArgNames()); - data->set_result_names(StaticResultNames()); - data->set_program_shape(StaticProgramShape()); - data->set_hlo_profile_printer_data(StaticHloProfilePrinterData()); + set_static_data_raw_function(data, {{ENTRY}}); + set_static_data_buffer_infos(data, BufferInfos()); + set_static_data_num_buffers(data, kNumBuffers); + set_static_data_arg_index_table(data, ArgIndexToBufferIndex()); + set_static_data_num_args(data, kNumArgs); + set_static_data_result_index(data, kResultIndex); + set_static_data_arg_names(data, StaticArgNames()); + set_static_data_result_names(data, StaticResultNames()); + set_static_data_program_shape(data, StaticProgramShape()); + set_static_data_hlo_profile_printer_data( + data, StaticHloProfilePrinterData()); {{ASSIGN_PROFILE_COUNTERS_SIZE}} return data; }(); diff --git a/tensorflow/compiler/aot/codegen_test_h.golden b/tensorflow/compiler/aot/codegen_test_h.golden index 7fbf83af33..35994fc785 100644 --- a/tensorflow/compiler/aot/codegen_test_h.golden +++ b/tensorflow/compiler/aot/codegen_test_h.golden @@ -59,7 +59,7 @@ namespace bar { // arg bytes aligned: 192 // temp bytes total: 126 // temp bytes aligned: 320 -class MyClass : public tensorflow::XlaCompiledCpuFunction { +class MyClass final : public tensorflow::XlaCompiledCpuFunction { public: // Number of input arguments for the compiled computation. static constexpr size_t kNumArgs = 2; @@ -74,16 +74,17 @@ class MyClass : public tensorflow::XlaCompiledCpuFunction { static XlaCompiledCpuFunction::StaticData* kStaticData = [](){ XlaCompiledCpuFunction::StaticData* data = new XlaCompiledCpuFunction::StaticData; - data->set_raw_function(entry_point); - data->set_buffer_infos(BufferInfos()); - data->set_num_buffers(kNumBuffers); - data->set_arg_index_table(ArgIndexToBufferIndex()); - data->set_num_args(kNumArgs); - data->set_result_index(kResultIndex); - data->set_arg_names(StaticArgNames()); - data->set_result_names(StaticResultNames()); - data->set_program_shape(StaticProgramShape()); - data->set_hlo_profile_printer_data(StaticHloProfilePrinterData()); + set_static_data_raw_function(data, entry_point); + set_static_data_buffer_infos(data, BufferInfos()); + set_static_data_num_buffers(data, kNumBuffers); + set_static_data_arg_index_table(data, ArgIndexToBufferIndex()); + set_static_data_num_args(data, kNumArgs); + set_static_data_result_index(data, kResultIndex); + set_static_data_arg_names(data, StaticArgNames()); + set_static_data_result_names(data, StaticResultNames()); + set_static_data_program_shape(data, StaticProgramShape()); + set_static_data_hlo_profile_printer_data( + data, StaticHloProfilePrinterData()); return data; }(); diff --git a/tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h b/tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h index c7341cf8b9..e1da19cf1d 100644 --- a/tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h +++ b/tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h @@ -59,45 +59,8 @@ class XlaCompiledCpuFunction { // AOT this is backed by data compiled into the object file. // // The contents of StaticData are XLA-internal implementation details and - // should not be relied on by clients. - // - // TODO(sanjoy): Come up with a cleaner way to express the contraint we want - // here: generated XlaCompiledCpuFunction subclasses should be able to create - // instances of StaticData but only XlaCompiledCpuFunction should be able to - // read from StaticData instances. + // should not be relied on by clients (and therefore are private). class StaticData { - public: - void set_raw_function(RawFunction raw_function) { - raw_function_ = raw_function; - } - void set_buffer_infos( - const cpu_function_runtime::BufferInfo* buffer_infos) { - buffer_infos_ = buffer_infos; - } - void set_num_buffers(size_t num_buffers) { num_buffers_ = num_buffers; } - void set_arg_index_table(const int32* arg_index_table) { - arg_index_table_ = arg_index_table; - } - void set_num_args(int64 num_args) { num_args_ = num_args; } - void set_result_index(size_t result_index) { result_index_ = result_index; } - void set_arg_names(const char** arg_names) { arg_names_ = arg_names; } - void set_result_names(const char** result_names) { - result_names_ = result_names; - } - void set_program_shape(const xla::ProgramShapeProto* program_shape) { - program_shape_ = program_shape; - } - const xla::HloProfilePrinterData* hlo_profile_printer_data() const { - return hlo_profile_printer_data_; - } - void set_hlo_profile_printer_data( - const xla::HloProfilePrinterData* hlo_profile_printer_data) { - hlo_profile_printer_data_ = hlo_profile_printer_data; - } - void set_profile_counters_size(int64 profile_counters_size) { - profile_counters_size_ = profile_counters_size; - } - private: // The raw function to call. RawFunction raw_function_; @@ -134,7 +97,8 @@ class XlaCompiledCpuFunction { // declared so we don't have access to that information here. int64 profile_counters_size_ = 0; - // Only XlaCompiledCpuFunction is allowed to read the above fields. + // Only XlaCompiledCpuFunction is allowed to read and write the above + // fields. friend class XlaCompiledCpuFunction; }; @@ -280,6 +244,76 @@ class XlaCompiledCpuFunction { return *hlo_profile_printer_data_; } + protected: + // --------------------------------------------------------------------------- + // Accessors for reading from and writing to instances of `StaticData`. + // + // Classes generated by tfcompile can call these because the generated classes + // inherit from `XlaCompiledCpuFunction`. `XlaJitCompiledCpuFunction` can + // call these because it is explicitly added as a friend. + + static void set_static_data_raw_function(StaticData* static_data, + RawFunction raw_function) { + static_data->raw_function_ = raw_function; + } + + static void set_static_data_buffer_infos( + StaticData* static_data, + const cpu_function_runtime::BufferInfo* buffer_infos) { + static_data->buffer_infos_ = buffer_infos; + } + + static void set_static_data_num_buffers(StaticData* static_data, + size_t num_buffers) { + static_data->num_buffers_ = num_buffers; + } + + static void set_static_data_arg_index_table(StaticData* static_data, + const int32* arg_index_table) { + static_data->arg_index_table_ = arg_index_table; + } + + static void set_static_data_num_args(StaticData* static_data, + int64 num_args) { + static_data->num_args_ = num_args; + } + + static void set_static_data_result_index(StaticData* static_data, + size_t result_index) { + static_data->result_index_ = result_index; + } + + static void set_static_data_arg_names(StaticData* static_data, + const char** arg_names) { + static_data->arg_names_ = arg_names; + } + + static void set_static_data_result_names(StaticData* static_data, + const char** result_names) { + static_data->result_names_ = result_names; + } + + static void set_static_data_program_shape( + StaticData* static_data, const xla::ProgramShapeProto* program_shape) { + static_data->program_shape_ = program_shape; + } + + static void set_static_data_hlo_profile_printer_data( + StaticData* static_data, + const xla::HloProfilePrinterData* hlo_profile_printer_data) { + static_data->hlo_profile_printer_data_ = hlo_profile_printer_data; + } + + static const xla::HloProfilePrinterData* + get_static_data_hlo_profile_printer_data(StaticData* static_data) { + return static_data->hlo_profile_printer_data_; + } + + static void set_static_data_profile_counters_size( + StaticData* static_data, int64 profile_counters_size) { + static_data->profile_counters_size_ = profile_counters_size; + } + private: const RawFunction raw_function_; const size_t result_index_; @@ -313,6 +347,10 @@ class XlaCompiledCpuFunction { const char** result_names_ = nullptr; const xla::ProgramShapeProto* program_shape_ = nullptr; const xla::HloProfilePrinterData* hlo_profile_printer_data_ = nullptr; + + // Add `XlaJitCompiledCpuFunction` as a friend so that it can access the + // `set_static_data_*` static methods above. + friend class XlaJitCompiledCpuFunction; }; } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/xla_jit_compiled_cpu_function.cc b/tensorflow/compiler/tf2xla/xla_jit_compiled_cpu_function.cc index fabbcd04fe..884dc45cb1 100644 --- a/tensorflow/compiler/tf2xla/xla_jit_compiled_cpu_function.cc +++ b/tensorflow/compiler/tf2xla/xla_jit_compiled_cpu_function.cc @@ -135,24 +135,34 @@ XlaJitCompiledCpuFunction::Compile( jit->arg_index_table_ = std::move(arg_index_table); jit->program_shape_ = absl::make_unique(program_shape->ToProto()); - jit->static_data_.set_raw_function(raw_function); - jit->static_data_.set_buffer_infos(jit->buffer_infos_.data()); - jit->static_data_.set_num_buffers(jit->buffer_infos_.size()); - jit->static_data_.set_arg_index_table(jit->arg_index_table_.data()); - jit->static_data_.set_num_args(jit->arg_index_table_.size()); - jit->static_data_.set_result_index(result_index); + XlaCompiledCpuFunction::set_static_data_raw_function(&jit->static_data_, + raw_function); + XlaCompiledCpuFunction::set_static_data_buffer_infos( + &jit->static_data_, jit->buffer_infos_.data()); + XlaCompiledCpuFunction::set_static_data_num_buffers( + &jit->static_data_, jit->buffer_infos_.size()); + XlaCompiledCpuFunction::set_static_data_arg_index_table( + &jit->static_data_, jit->arg_index_table_.data()); + XlaCompiledCpuFunction::set_static_data_num_args( + &jit->static_data_, jit->arg_index_table_.size()); + XlaCompiledCpuFunction::set_static_data_result_index(&jit->static_data_, + result_index); // Optional metadata is collected and set below. CollectNames(config.feed(), &jit->nonempty_arg_names_, &jit->arg_names_); CollectNames(config.fetch(), &jit->nonempty_result_names_, &jit->result_names_); - jit->static_data_.set_arg_names(jit->arg_names_.data()); - jit->static_data_.set_result_names(jit->result_names_.data()); - jit->static_data_.set_program_shape(jit->program_shape_.get()); + XlaCompiledCpuFunction::set_static_data_arg_names(&jit->static_data_, + jit->arg_names_.data()); + XlaCompiledCpuFunction::set_static_data_result_names( + &jit->static_data_, jit->result_names_.data()); + XlaCompiledCpuFunction::set_static_data_program_shape( + &jit->static_data_, jit->program_shape_.get()); if (cpu_executable->hlo_profiling_enabled()) { - jit->static_data_.set_hlo_profile_printer_data( - &cpu_executable->hlo_profile_printer_data()); - jit->static_data_.set_profile_counters_size( + XlaCompiledCpuFunction::set_static_data_hlo_profile_printer_data( + &jit->static_data_, &cpu_executable->hlo_profile_printer_data()); + XlaCompiledCpuFunction::set_static_data_profile_counters_size( + &jit->static_data_, cpu_executable->hlo_profile_printer_data().profile_counters_size()); } -- GitLab From eadd8aca53dedb40f5664d616288db665dd95652 Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Tue, 15 Jan 2019 13:06:12 -0800 Subject: [PATCH 0744/2345] Making sure dataset "output" ops are not pruned from function graphs as they might have side effects even though the ops are not marked as stateful. PiperOrigin-RevId: 229425577 --- tensorflow/core/common_runtime/function.cc | 2 +- tensorflow/core/graph/graph.cc | 4 ++++ tensorflow/core/graph/graph.h | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/common_runtime/function.cc b/tensorflow/core/common_runtime/function.cc index 48f32df275..3df09eac29 100644 --- a/tensorflow/core/common_runtime/function.cc +++ b/tensorflow/core/common_runtime/function.cc @@ -812,7 +812,7 @@ void PruneFunctionBody(Graph* g) { // TODO(mrry): Investigate whether the `n->IsControlFlow()` test is // still needed. It would be preferable to prune entire loops and/or // conditionals if they are not used in the graph. - if (n->IsControlFlow() || + if (n->IsControlFlow() || n->IsDataset() || (n->op_def().is_stateful() && n->type_string() != kArgOp)) { nodes.insert(n); } diff --git a/tensorflow/core/graph/graph.cc b/tensorflow/core/graph/graph.cc index 3ea222c13c..00d3549312 100644 --- a/tensorflow/core/graph/graph.cc +++ b/tensorflow/core/graph/graph.cc @@ -85,6 +85,10 @@ const std::unordered_map& Node::kNodeClassTable = {"CollectiveBcastSend", NC_COLLECTIVE}, {"CollectiveBcastRecv", NC_COLLECTIVE}, {"FakeParam", NC_FAKE_PARAM}, + {"IteratorGetNext", NC_DATASET}, + {"IteratorGetNextSync", NC_DATASET}, + {"DatasetToSingleElement", NC_DATASET}, + {"ReduceDataset", NC_DATASET}, }); #undef REF_CLASS diff --git a/tensorflow/core/graph/graph.h b/tensorflow/core/graph/graph.h index 289a3d2a23..f65e4b921e 100644 --- a/tensorflow/core/graph/graph.h +++ b/tensorflow/core/graph/graph.h @@ -174,6 +174,8 @@ class Node { bool IsMetadata() const { return class_ == NC_METADATA; } bool IsFakeParam() const { return class_ == NC_FAKE_PARAM; } + bool IsDataset() const { return class_ == NC_DATASET; } + template void AddAttr(const string& name, const T& val) { SetAttrValue(val, AddAttrHelper(name)); @@ -254,6 +256,7 @@ class Node { NC_SCOPED_ALLOCATOR, NC_COLLECTIVE, NC_FAKE_PARAM, + NC_DATASET, NC_OTHER // Not a special kind of node }; -- GitLab From 75d9981d6c79b178340c0e011921f4b407f59341 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 13:09:06 -0800 Subject: [PATCH 0745/2345] Add LSTM and embedding models to keras correctness test. PiperOrigin-RevId: 229426039 --- tensorflow/contrib/distribute/python/BUILD | 40 ++++++++++ .../contrib/distribute/python/combinations.py | 14 +++- .../python/keras_correctness_test_base.py | 75 +++++++++++++++++-- .../python/keras_dnn_correctness_test.py | 20 +++-- .../keras_embedding_model_correctness_test.py | 75 +++++++++++++++++++ .../keras_lstm_model_correctness_test.py | 65 ++++++++++++++++ 6 files changed, 273 insertions(+), 16 deletions(-) create mode 100644 tensorflow/contrib/distribute/python/keras_embedding_model_correctness_test.py create mode 100644 tensorflow/contrib/distribute/python/keras_lstm_model_correctness_test.py diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 4c25f49f16..1a54cd167e 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -660,7 +660,9 @@ py_library( srcs = [ "keras_correctness_test_base.py", "keras_dnn_correctness_test.py", + "keras_embedding_model_correctness_test.py", "keras_image_model_correctness_test.py", + "keras_lstm_model_correctness_test.py", ], deps = [ ":combinations", @@ -714,6 +716,44 @@ cuda_py_test( ], ) +cuda_py_test( + name = "keras_embedding_model_correctness_test", + size = "medium", + srcs = ["keras_embedding_model_correctness_test.py"], + additional_deps = [ + ":keras_correctness_test_lib", + ], + # Shard count is set to an odd number to distribute tasks across + # shards more evenly. + shard_count = 31, + tags = [ + "multi_and_single_gpu", + "no_oss", # TODO(b/117919883): Fix python error. + "no_pip", + "no_windows_gpu", + "notsan", + ], +) + +cuda_py_test( + name = "keras_lstm_model_correctness_test", + size = "medium", + srcs = ["keras_lstm_model_correctness_test.py"], + additional_deps = [ + ":keras_correctness_test_lib", + ], + # Shard count is set to an odd number to distribute tasks across + # shards more evenly. + shard_count = 31, + tags = [ + "multi_and_single_gpu", + "no_oss", # TODO(b/117919883): Fix python error. + "no_pip", + "no_windows_gpu", + "notsan", + ], +) + py_library( name = "metrics_v1_test_lib", testonly = 1, diff --git a/tensorflow/contrib/distribute/python/combinations.py b/tensorflow/contrib/distribute/python/combinations.py index f6c4291659..304762e69e 100644 --- a/tensorflow/contrib/distribute/python/combinations.py +++ b/tensorflow/contrib/distribute/python/combinations.py @@ -321,11 +321,12 @@ class NamedDistribution(object): return self._required_tpu -def _get_tpu_strategy_creator(steps_per_run): +def _get_tpu_strategy_creator(steps_per_run, **kwargs): def _create_tpu_strategy(): resolver = cluster_resolver.TPUClusterResolver("") tpu_lib.initialize_tpu_system(resolver) - strategy = tpu_lib.TPUStrategy(resolver, steps_per_run=steps_per_run) + strategy = tpu_lib.TPUStrategy(resolver, steps_per_run=steps_per_run, + **kwargs) return strategy return _create_tpu_strategy @@ -344,6 +345,15 @@ tpu_strategy = NamedDistribution( tpu_strategy_one_step = NamedDistribution( "TPUOneStep", _get_tpu_strategy_creator(steps_per_run=1), required_tpu=True) +# TODO(b/122327153): Remove below two NamedDistributions. +tpu_strategy_loop_on_device = NamedDistribution( + "TPULoopOnDevice", _get_tpu_strategy_creator( + steps_per_run=2, _disable_training_loop_on_host=True), + required_tpu=True) +tpu_strategy_one_step_loop_on_device = NamedDistribution( + "TPUOneStepLoopOnDevice", _get_tpu_strategy_creator( + steps_per_run=1, _disable_training_loop_on_host=True), + required_tpu=True) mirrored_strategy_with_one_cpu = NamedDistribution( "Mirrored1CPU", diff --git a/tensorflow/contrib/distribute/python/keras_correctness_test_base.py b/tensorflow/contrib/distribute/python/keras_correctness_test_base.py index 9f3cab49a8..08ed933f29 100644 --- a/tensorflow/contrib/distribute/python/keras_correctness_test_base.py +++ b/tensorflow/contrib/distribute/python/keras_correctness_test_base.py @@ -51,22 +51,46 @@ all_strategies = [ ] -def all_strategy_combinations_with_eager_and_graph_modes(): - return combinations.combine(distribution=all_strategies, - mode=['graph', 'eager']) +def eager_mode_test_configuration(): + return combinations.combine(mode='eager', + use_numpy=False, + use_validation_data=False) -def all_strategy_combinations_with_graph_mode(): - return combinations.combine(distribution=all_strategies, mode=['graph']) +def graph_mode_test_configuration(): + return combinations.combine(mode='graph', + use_numpy=[True, False], + use_validation_data=[True, False]) def all_strategy_and_input_config_combinations(): return ( combinations.times( combinations.combine(distribution=all_strategies), - combinations.combine(mode=['graph', 'eager'], - use_numpy=[True, False], - use_validation_data=[True, False]))) + eager_mode_test_configuration() + graph_mode_test_configuration())) + + +def strategies_for_embedding_models(): + """Returns distribution strategies to test for embedding models. + + Since embedding models take longer to train, we disregard OneDeviceStrategy + and DefaultStrategy in order to prevent testing timeouts. + """ + + strategies = [s for s in all_strategies + if not s.required_tpu and s.required_gpus is not None] + strategies.append(combinations.tpu_strategy_loop_on_device) + strategies.append(combinations.tpu_strategy_one_step_loop_on_device) + return strategies + + +def test_combinations_for_embedding_model(): + return ( + combinations.times( + combinations.combine(distribution= + strategies_for_embedding_models()), + (graph_mode_test_configuration() + + eager_mode_test_configuration()))) class MaybeDistributionScope(object): @@ -393,5 +417,40 @@ class TestDistributionStrategyCorrectnessBase(test.TestCase, testcase=self) +class TestDistributionStrategyEmbeddingModelCorrectnessBase( + TestDistributionStrategyCorrectnessBase): + """Base class to test correctness of Keras models with embedding layers.""" + + def get_data(self, + count=(_GLOBAL_BATCH_SIZE * _EVAL_STEPS), + min_words=5, + max_words=10, + max_word_id=19, + num_classes=2): + distribution = [] + for _ in range(num_classes): + dist = np.abs(np.random.randn(max_word_id)) + dist /= np.sum(dist) + distribution.append(dist) + + features = [] + labels = [] + for _ in range(count): + label = np.random.randint(0, num_classes, size=1)[0] + num_words = np.random.randint(min_words, max_words, size=1)[0] + word_ids = np.random.choice( + max_word_id, size=num_words, replace=True, p=distribution[label]) + word_ids = word_ids + labels.append(label) + features.append(word_ids) + + features = keras.preprocessing.sequence.pad_sequences( + features, maxlen=max_words) + x_train = np.asarray(features, dtype=np.float32) + y_train = np.asarray(labels, dtype=np.int32).reshape((count, 1)) + x_predict = x_train + return x_train, y_train, x_predict + + if __name__ == '__main__': test.main() diff --git a/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py b/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py index f21b1c6752..7afacab0dd 100644 --- a/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py +++ b/tensorflow/contrib/distribute/python/keras_dnn_correctness_test.py @@ -28,6 +28,17 @@ from tensorflow.python.keras.optimizer_v2 import gradient_descent as gradient_de from tensorflow.python.training import gradient_descent +def all_strategy_combinations_with_eager_and_graph_modes(): + return combinations.combine(distribution=keras_correctness_test_base. + all_strategies, + mode=['graph', 'eager']) + + +def all_strategy_combinations_with_graph_mode(): + return combinations.combine(distribution=keras_correctness_test_base. + all_strategies, mode=['graph']) + + class TestDistributionStrategyDnnCorrectness( keras_correctness_test_base.TestDistributionStrategyCorrectnessBase): @@ -65,8 +76,7 @@ class TestDistributionStrategyDnnCorrectness( def test_dnn_correctness(self, distribution, use_numpy, use_validation_data): self.run_correctness_test(distribution, use_numpy, use_validation_data) - @combinations.generate(keras_correctness_test_base. - all_strategy_combinations_with_graph_mode()) + @combinations.generate(all_strategy_combinations_with_graph_mode()) def test_dnn_with_dynamic_learning_rate(self, distribution): self.run_dynamic_lr_test(distribution) @@ -104,8 +114,7 @@ class TestDistributionStrategyDnnMetricCorrectness( history = model.fit(x=train_dataset, epochs=2, steps_per_epoch=10) self.assertEqual(history.history['binary_accuracy'], [1.0, 1.0]) - @combinations.generate(keras_correctness_test_base. - all_strategy_combinations_with_eager_and_graph_modes()) + @combinations.generate(all_strategy_combinations_with_eager_and_graph_modes()) def test_simple_dnn_metric_correctness(self, distribution): self.run_metric_correctness_test(distribution) @@ -153,8 +162,7 @@ class TestDistributionStrategyDnnMetricEvalCorrectness( self.assertEqual(outs[1], 0.) self.assertEqual(outs[2], 0.) - @combinations.generate(keras_correctness_test_base. - all_strategy_combinations_with_eager_and_graph_modes()) + @combinations.generate(all_strategy_combinations_with_eager_and_graph_modes()) def test_identity_model_metric_eval_correctness(self, distribution): self.run_eval_metrics_correctness_test(distribution) diff --git a/tensorflow/contrib/distribute/python/keras_embedding_model_correctness_test.py b/tensorflow/contrib/distribute/python/keras_embedding_model_correctness_test.py new file mode 100644 index 0000000000..3913f9bc0c --- /dev/null +++ b/tensorflow/contrib/distribute/python/keras_embedding_model_correctness_test.py @@ -0,0 +1,75 @@ +# 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. +# ============================================================================== +"""Correctness test for tf.keras Embedding models using DistributionStrategy.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.contrib.distribute.python import keras_correctness_test_base +from tensorflow.python import keras +from tensorflow.python.eager import test +from tensorflow.python.training import gradient_descent + + +class DistributionStrategyEmbeddingModelCorrectnessTest( + keras_correctness_test_base. + TestDistributionStrategyEmbeddingModelCorrectnessBase): + + def get_model(self, max_words=10, initial_weights=None, distribution=None): + with keras_correctness_test_base.MaybeDistributionScope(distribution): + word_ids = keras.layers.Input( + shape=(max_words,), dtype=np.int32, name='words') + word_embed = keras.layers.Embedding(input_dim=20, + output_dim=10)(word_ids) + if self.use_distributed_dense: + word_embed = keras.layers.TimeDistributed(keras.layers.Dense(4))( + word_embed) + avg = keras.layers.GlobalAveragePooling1D()(word_embed) + preds = keras.layers.Dense(2, activation='softmax')(avg) + model = keras.Model(inputs=[word_ids], outputs=[preds]) + + if initial_weights: + model.set_weights(initial_weights) + + model.compile( + optimizer=gradient_descent.GradientDescentOptimizer( + learning_rate=0.1), + loss='sparse_categorical_crossentropy', + metrics=['sparse_categorical_accuracy']) + return model + + @combinations.generate(keras_correctness_test_base. + test_combinations_for_embedding_model()) + def test_embedding_model_correctness(self, distribution, use_numpy, + use_validation_data): + + self.use_distributed_dense = False + self.run_correctness_test(distribution, use_numpy, use_validation_data) + + @combinations.generate(keras_correctness_test_base. + test_combinations_for_embedding_model()) + def test_embedding_time_distributed_model_correctness(self, + distribution, + use_numpy, + use_validation_data): + self.use_distributed_dense = True + self.run_correctness_test(distribution, use_numpy, use_validation_data) + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/contrib/distribute/python/keras_lstm_model_correctness_test.py b/tensorflow/contrib/distribute/python/keras_lstm_model_correctness_test.py new file mode 100644 index 0000000000..9ed2dfa206 --- /dev/null +++ b/tensorflow/contrib/distribute/python/keras_lstm_model_correctness_test.py @@ -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. +# ============================================================================== +"""Correctness tests for tf.keras LSTM model using DistributionStrategy.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.contrib.distribute.python import keras_correctness_test_base +from tensorflow.python import keras +from tensorflow.python.eager import test +from tensorflow.python.training import gradient_descent + + +class DistributionStrategyLstmModelCorrectnessTest( + keras_correctness_test_base. + TestDistributionStrategyEmbeddingModelCorrectnessBase): + + def get_model(self, max_words=10, initial_weights=None, distribution=None): + with keras_correctness_test_base.MaybeDistributionScope(distribution): + word_ids = keras.layers.Input( + shape=(max_words,), dtype=np.int32, name='words') + word_embed = keras.layers.Embedding(input_dim=20, + output_dim=10)(word_ids) + lstm_embed = keras.layers.LSTM(units=4, + return_sequences=False)(word_embed) + + preds = keras.layers.Dense(2, activation='softmax')(lstm_embed) + model = keras.Model(inputs=[word_ids], outputs=[preds]) + + if initial_weights: + model.set_weights(initial_weights) + + model.compile( + optimizer=gradient_descent.GradientDescentOptimizer( + learning_rate=0.1), + loss='sparse_categorical_crossentropy', + metrics=['sparse_categorical_accuracy']) + return model + + @combinations.generate(keras_correctness_test_base. + test_combinations_for_embedding_model()) + def test_lstm_model_correctness(self, + distribution, + use_numpy, + use_validation_data): + self.run_correctness_test(distribution, use_numpy, use_validation_data) + + +if __name__ == '__main__': + test.main() -- GitLab From dae187c176cd0d59f597e694ae193ff6a5dfbdd2 Mon Sep 17 00:00:00 2001 From: Peng Wang Date: Tue, 15 Jan 2019 13:26:33 -0800 Subject: [PATCH 0746/2345] A partial implementation of RFC "Random numbers in TensorFlow 2.0" (https://github.com/tensorflow/community/pull/38). PiperOrigin-RevId: 229428968 --- tensorflow/core/BUILD | 3 + .../api_def_StatefulStandardNormal.pbtxt | 31 ++ .../api_def_StatefulStandardNormal.pbtxt | 4 + tensorflow/core/kernels/BUILD | 25 ++ .../core/kernels/stateful_random_ops.cc | 265 ++++++++++++++++++ tensorflow/core/lib/random/philox_random.h | 7 + tensorflow/core/ops/stateful_random_ops.cc | 36 +++ tensorflow/python/BUILD | 32 +++ tensorflow/python/ops/stateful_random_ops.py | 241 ++++++++++++++++ .../python/ops/stateful_random_ops_test.py | 156 +++++++++++ 10 files changed, 800 insertions(+) create mode 100644 tensorflow/core/api_def/base_api/api_def_StatefulStandardNormal.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_StatefulStandardNormal.pbtxt create mode 100644 tensorflow/core/kernels/stateful_random_ops.cc create mode 100644 tensorflow/core/ops/stateful_random_ops.cc create mode 100644 tensorflow/python/ops/stateful_random_ops.py create mode 100644 tensorflow/python/ops/stateful_random_ops_test.py diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index bedc78ac11..7ba9afb5a8 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -1101,6 +1101,7 @@ tf_gen_op_libs( "parsing_ops", "random_grad", "random_ops", + "stateful_random_ops", "remote_fused_graph_ops", "rpc_ops", "scoped_allocator_ops", @@ -1256,6 +1257,7 @@ cc_library( ":parsing_ops_op_lib", ":ragged_ops", ":random_ops_op_lib", + ":stateful_random_ops_op_lib", ":remote_fused_graph_ops_op_lib", ":resource_variable_ops_op_lib", ":rpc_ops_op_lib", @@ -1414,6 +1416,7 @@ cc_library( "//tensorflow/core/kernels:pooling_ops", "//tensorflow/core/kernels:ragged_ops", "//tensorflow/core/kernels:random_ops", + "//tensorflow/core/kernels:stateful_random_ops", "//tensorflow/core/kernels:random_poisson_op", "//tensorflow/core/kernels:remote_fused_graph_ops", "//tensorflow/core/kernels:required", diff --git a/tensorflow/core/api_def/base_api/api_def_StatefulStandardNormal.pbtxt b/tensorflow/core/api_def/base_api/api_def_StatefulStandardNormal.pbtxt new file mode 100644 index 0000000000..d963c55278 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_StatefulStandardNormal.pbtxt @@ -0,0 +1,31 @@ +op { + graph_op_name: "StatefulStandardNormal" + in_arg { + name: "resource" + description: <
MT&u!Va{Yu3rD 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 t3A~iAbYuCK}&~bg^tTJ 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
l?mH}^O@aJFbZwmQJka-dt- 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Ⓢ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 z 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 q{}Mrg?c{oMgfx@J)fnRl0k9;uq>IsfX$zPqX7 zLb(zi)3no=!r}6H*a6?!*&Ei1d80!?lF7e0rxAWXE@V_F`aG3e})XZMdd%jOIt*?sRh`FM(oqLAZ zx?VwpwM^@_e7>O>-WU^h;j!byo+rCCPRUnYXj(36s8z-q?A3B?rNN3jH}}tz~KhJpanbg*08Mh42 zJUk+H_Q={@2U4ZQ%_Qf{n5o@%ZLeKfTc`2q>Q* zJPFS@Z>1(7`kA}@&*y2JUy60#C|)^H!0D=$B^;=?sMD~+$NH2eU?t?EF)7R_OHJ+RX0yx5p^`Cc9m8b`_`+4jK2aBo;F1&ukiLL zpQSR*p68kV+Ao(>s`g22>8(9EBtq5Og8!#(E&3(1>hOcKFj1GQ ziTTC971aKxTS$NCO?vF6ed|KpjoJFQMco%yooPGvv!mH=U61Iwb9(QLJ6(Epx9Bn$ z?^(#x`s--v#4d?{DN0=BcXk)@D>eAIh&!tUcWE4*Z+vJ%denP3qJSlHsV?G zSaFholS-81#R{cm&X$+EyNgfroQv4GaF*tuoyJ!_HJ$uu9Bm-{XXDI2zbEwtbhQ8I z7h2iAD_Qo%#9hBCyWb|Q`rkZjWBugGp9ADiF5tFwp4q)a_}2mBoeBKRS$oz6E!Zfu z+HuZ;Ew{FCRxNNf?s0Z8nRLU%!+6c|i;H9zEy!PWq^!&2z(t$fO$yr{G3~j?Uv07T z>SPh2D8*3jY1bC>rAAv`Q)rFU=6U#f$B|!q=Wz4I9kz^7=?s-rN{&8w=mGBqi(U

Nqb4>bu@alzG|BQLwI~g)Mx*oe-!JuyrDgq{ctR!}J1pUT zctg8(r0U06v%-wlpHOs&j9ULfVb0MVJE}sof6Q7Ff93ea2~2tgyFO^l^0nyQpRp+2 z$514Dr%233CS|u)7v7s6j{00toV3F-#&hcbj?2B=%A5j|-Jb{S>;B=PsN=-r<+kME z9@SHPsUq79PpqFZ`Ow7;?cZLtoMP;+p2fFcdg=bnlDTUn4_fp5+t_8>$!)u5mBU8X zwZDwl1u2}EY~#DwhHKTH?H6ohyDe_a@bcTeXU(1CtLF5v|Jf7FxQ|DLljkFMP&03+ z$-)x>X3AUkrLA5rkbOFQjqA}HijgM!j%rN3cbh5ZPLup4*VR9~qYSrfS6CCMIj1J% z^m&bGhcXlywR_GaIbIR)es_WM(`UEv4Khz2EYd%;E$7P2{5KZ!7xuIyyDy1U`0C^v zU8Mgcz~M)g!!NDglTK%jeDFG~!5F#bEQbkC6k@cic$ndN(6mQUcx72b|h zYj!>@@pz^q-LYn?_#anUk%n!O%RdzGos#73(_wz`!Echu#|Bf7@8>xZ!;C@~WVMW_yYy}vc*nM|=d|7QCU z4fkIwSQ&KAp6HZW9ysNz7GK`UAeIsb&O1BUn+_Q8%AA?KhV$%2N>)diCieE7YEE=?a?BJL|OVjTIiAf@?ZnFZ0{nyKN@_TM>p}kL9g0yX;sE zOlo<9PV_9_tn>S{=iv<@pD(WU)zvt3c(V1$$^LsejvU_X5NYFFXtQ36nL{hUE&Isn z*9|N7xO>iaaGB!OX|jl;HAMN=)wO5NMn7WWG?=wrs=rFYRnWCpsCH|jA5*Y zI$Kxf|GgyQ%d{0VtX1onO%%KyrbL*UD>ATiSelpu$9Wi*lr$g|! z-HtcYGB?RwU$D^QVbgn=lglGd_bEFbRnS>HQ|!8e{}d&2JxlrHf?Uh)&iCIc{AY{5 z&6ZiKOhcDES;Y{0b)(T1N0R^-Z|$E>0q)J~bGt5ZuiDc4IMn0Fk)GrWt#3_?YcH(U z;n{Wd><-t}KGhedtqtuyVjFu)kwd6q+b@rniP}2_7lzol2E`}_|G9Z1dg0NCzNZrc zuD-P1;pJw=bGKN3t$OssWF70B?$irBPeZSqxU(yiXLs%G{jPRP zlW#_*#x2!uG`hi%*ret$Gx!nf$^DLj5zHsA8lPO~EpQ9`44^X+}^d--Rp5E z^V$nRAHD9Z*y3Kj<(JlD{j)v z_d(C@gX~vAIbC*X&S(%+=8P_2KfvXG#Uf^>(v9TAJ026CAFf^h)PZkFVzW?A%yyTa zpdA-)U)Y*+_v-Ig54#y+xnyIX?NyRLyD;!a_b=Z^lZ0>n*PX<);^rltT!+oS_Qc3N zzOVcE{?Z+f?>?BQ_HxoOZ=M`hyUWsI%BC+@=uFxnnh~6Oto3>6z9|u1ygRFwh8aDw z{kzPYEomLg@(;b&Kdj9tjMDjhv{%w+HK+EY-k2-=((y;G@IGnC*wu7By^&*&pj4bQ z-wK}RY^$Ee{#&?do3!E`)lHj{O--M!{+(fRG%Ppi_K^h-I#D$@yzADuiLx5jkl|tFZW{Ze;c|^Al#2H~+x}vql2%^DsjO$2%zJO}*xQ*UDjkv*i_>4bgF7j( z=kd+I`>g(#@b6V*WY$;Eaq0`?e4g@c%bG0;oadsq9D4RpPut+>4$knj2gkg)-oNL5 z|KX`yJm*)n_PGvwo~GsLC9kg2@BghIdL@!OFaO94&lh~#mh7B*c*JaYo zX-Ug`6PnlBneV=sKZ(I)omXC7Ci8QLt1P}-j0*aK6imLy1{ObJVD^74b>A!J?h%{* zo?UidQuaT~)hkR+FH6=h3$E;q>6)@DGx0#-%R^>mKlfg(6Kj~cMW#UAXU4MwbL*QPLO8M`ft;Tq;Ij&S4_3W**y%H1jWE@qY51Xbxnc0JyK%NFKr z6!?7hkwUXmX74wLivO<)TCE+X=QJ zpXX1tzkEm}sz{#K@jKs9Mb?~8c`xTY>EwOIx#C6Fyf2>Xd6&MkxITd|TFUcq-j(@x z6E42Ezw4EIP{C(s!$aG`7JTzs?((={=F!J9L=VZGDizah%{Jt7lJ@zo(VS5BAR#V7 zczXaZd)n);-`wHRJM;6>rail;P}%$M{gDSxh3X6b$6fTeEp~Uul>YT)DtX^{t9fso za6i3s>T&yh-7`)~oA;dL-?q^7bfKix+3$RQ3Pm4}ZMZ2=n;71+#ZxD5FGt+w@+%y< z-t7CLHog;m`~A>{PvZP}t+H-YwLAY`l|8oe*ZRYiFVbvmPiJ{neLBMPq$b~S!T%$R zO1iSnZe)Gwv7j-lS(fQ(zy5+3(|0^7XbtyRuu-URn)v^Z_CCsfMYFFRUw*N7dfAio zCl}7L9SgHmdU>bm+VYY~3{_HBo@}47VJl;YCKDT1hU*7~LoKN+69fX|-|P%t-fk&}~UsG9~kA+Je<)F>zKR4_+nAoW0b-LMOPXyFriH zddfo1F9|C<*u8h`I(#8fvn$4;Ao9hrtB3hwl*$Au12UX8#2wEQnf-Bv+ZM^QlR~{O zymi>Y^kmZ9?m)e$T^#*dwbm0ZI~dhz3W?0QSP=97h-mgP)!h|_iA@cX=5Aa&g^!(N zGT~QQx+7%v3bySz0y~~QU}KD4<}<_k>lTK2(v}=H9#)EP8rKHDK6WE&u?S<33Qvep zBA2+cC*zZjtf@g)9sldkEDQj%atU~dru}axR^$=h1v@q5p(CiXByz*^|AEA0{x^X>2uq^t$Y!1L9r{ttx@QT z_)?}2t(QU5l$OZHw;tOjn4YY-EMj`o#1qTImxPI2oiuyru0>AFiETICws@7jc2u0| zbWCh--h{Y*_KVY`#9z*36N%c%y8E%i%8y@k99RAoi!C@Ps@^Chw4v?Ja!s`-i6uc! z6?e-xHyWKPd%A6UtoF^+BVJ`|CVZJ2F7BHy=M*{Vl6FnY(n{{+k1IVUO3vm}3DXR} zIZ@4z`|I;#P6_XhCM=!lS7~@mGJDU*78O00M-ggCE?NuSZOxR9*L!tbOZo3%B>g7h z)TbXsZZ1VFGTY6wU4o}EZRrx&p>^`vyAzt-KPGx=C|)@>ZONV|$NW4v*(B>kL^Y;F z#;$!aIVwglib;~UQg+IyA+6SOy}QhL`jR`#W$ngamvK*+<*;n)#7xc$9zhO5kyT{^tU@l0{IfjVJT9*P*SW&#r;vlKK$GgF z2@m~vBd@w}h=|Y2SnKE&d3(v3G|i0*3^dxDLT;Y=w&<;DmcbmajxdH)bBRf;L5fWB z{aVYMJ|ubt+kFvhb1Aat&=rjIu{m*tNlkKN$dkf`g)y#L;+FFto(nZ@k`2h4VtMrg zi-*I*lYxH5thdhaX{=IV$oY%pw#-oGTjL_|dkL4O#;L$%{Zmp6=d|XDi}mTW zxiojj24o!;`!DReroOK@G5OC7@5Vhk=DW^xXbC$xv|M z9o+91a@JjXBDQl&Z`o3lkT#2#x_jrcF1Yw}sblz0m*7*WoqQL4LiXJ_cUI=;!k*w4 z4^LcLrOI^7_}d?)H)h*gulZ<~Z2B3KKkKL^SjbcLbn^y~ZzC5X)d3J)H?5mLW z7g@&BZwKhg9^+i|_h@MMWEt_}O*>jLHtw3cP(vbl``T$rElYGvqY}BjgHxowm2Sx9 z3z)}zW8(Po#WI>502PZKJ^Ce6Rl*jq=C695&zCV4>JO#mmX`P(+;M7Va#^ zk4K}fvaVa!zkq!%_y0dqiV0aMT-UmkD_w&knl^-U26Gf0lem3S)m3bh%_^b5{lD** zTTZeTDu@c%5nv|pwcTK4tG~;G&l?v>JXtw$PVmhlrj5pyERnp1UFXl;kqF+tNn{T^6aK+Y|mrbTXt(t=gu(>YJhKx`ob% zcX(w~zbl)#;Q8(sA4O*wZ2sJJra(n@=ONR3y|Ud!?zIyF_7?5?UM$(mR1wj$V&fkD ztvB6ntQL&8Z^*eTvLd#kcVd_?dy|03nZRwo-@oW-6uFn8g$Q=lLp! ztWV7I*LXDb;MJNR4uLnfR)s3bdwWzpcQxqSK0~0%qVQ8) z+zhwp7OM-D{M5JPwR9|RZ2Gfx8vmiTz$bl;e@kKA4TYBxN1BEHnGejT6dBoVa)8Cl1fC8na6CT5?S$mt=o zjLRU1iDOb7qlN;5v(o~%1(6ffzE(||UmGo5HFu&GyPxtc8E@T`2@e}tP8*7+s$TQ! zxNz>OXjPpnXOqc$o#$v+i6ftFzp&-ps|Tf{U(Q=>GWTfDo$G%m zpE%)aU^-2HtI)EmtS+h3OG{@>Drh-zQudMUwG#!6zAL5N&ItZE7*%z2ir}}q+jd;! zX#Ib6UFFfoe?{ClqDv=s9}_&{xusEPLSsYkyv98)laCzVI&%?hP&n%0!|gZjMm=o%{_=MBfwn1um#?iA`drwPrlPb? zf#slJyaxwc!k?f+lI%wmTh0n_GHw(%5>0e^GWY1l_+PDy7&;dHwv;@U*z)h|ncKW9 zX@d6^CQW*B)%7Ing8G9;PUSX=TeY0u=q|0o!Mu$!nM|8YyrB8CTxkJc{ot$n~Q)%&N=rS9wX|1ZPeeUNwbP&~Y`k?HL$ zk&MLUj%N>B^hkTm|Kxg!TWFElqg(0+=Q*$Gj9$QH|1&Po75Pi+{q0I46d-gg5=)d>d>wg{X>g$}Wum z>Dcl6=oHOE@p}U#d~}YP6uP|USQ@UN@-bDog5!WjkK?1csZLv(7=-Sz%|EDPR>)b0lQ&Q7INADl=CL~$+|tC%=P4vB-7-;A^FG$U z_UXd?2iaoIcmy^j$Ig)5y>QaI2mjswxhU~Ii)-tUdf0kPX&cv@4adHnO)gY8QXsdC_*+Xe!{e%}WcOvKZruq2<)E#*kk@EOzr-zx` ztlYm7A{Dx8{wCKbDS4*39AJDJFzZyhU6{v;|FN>W?((Kx+20zOnGr9Wdb;7&-5fV| zJ3bcy1(#`D4h$L%g*n_C8Mu#2xIKI+AGK^HLk@eyJdXD~Pb12NwemV`9or>8c-A{a z_?~$l?sFq}r(DAg_Jm!Bv>8rp=3UizqvhpV&d{|>A{IWmc2;^xS*+K$d++bg_F1<4 z`%S;!sX7R$X^^kgVaB z+LXkS_h^}_og~vli5$lkw-5JQlg^YMc~mFnVm-x0_h0`zfe_A*E}I-rJDoV@>(gtp zm;LY*2bQ`eb8fISi}-vFaS1)>nH3wGwEfmOh)}TldVhl_fys<@}aY6O1%jPu`t$;9OyY=C(bz1iM_O z-&1p9chZK8Kjubc@uU(tj;`T72%3UdUa^eJ+^_9WJVn?;4~_)FjM$DP8XGShob^Pu z$m77hxV{&yA6EM4W~N1OvaP6Gdi&M#@OQ$!Do@q;G;GhQ<$ZeUR4{*z(2+j$3a2KN8y7Hk0wL zp4E|yx0X(JIU4-e#c$0*29DTEcKs1E9CH&hlJ918Jh>p0x5`a|JCY~fXVRp&E1Du_ zvz%t6-7!qcx}^}*^Gtf*;%|+aA$dpavW`sSb7|T&|6WwST~_yfPwls1^XhZiXC=9m zuJGRUPUXGCZKmESCk@%oi?Pg#nR-6vs?&{w%t5z(j<~#>Di)Tow2|ZS!YEf)n-kI| zM`yozndERrDvZPE((47KS+%Qj6_-6&98~a8(Pe?I3;TylCwgD`{hO+gbpHFc*>#Jw zR8EPmlvsMOGHv6TDNG&?Oa*IXcg{bNr5~xnz>sj{YgTvYvZWpimb=Nd{B%0;sLJ*6 zssEyp+YWBF(@s6g>b;{$b_x^chEpuPCuaVP3G~e~_{AexGQn?0nD>qMe~KQ?d%H3= zXSTnI^6eb~+<}66S6zFf9<%%oyOij1^M_~bsW7&tj1rOQ(n2B!{`IDoo%4Fgp>WA3 zcS)!p-2)O%p@N5oC7|!MImJ?Crnr~eck-uq56?3 zjlL_@A4Tzrvpo6n?aU*)xWaR5biXT#)VfsMU=m*boMl2|jmxpn9ZllG#y%_@0b35~ z?l`nPb)Nn+`LbyTLw7n&3sGqeJ)#?OL+-z?(XuSXq__N<((V=$PtM7zh}w`d)0K0A zsJp?Y3B3j!)Bmqs7RI0cO2wvFYDI^?M-0d9dtO|puVzh}&vc~V*UQ{*c`m%&E_~ss z=k7eX?Rh!)%h892UfJ?TYwHwFv&q(8!;*PWKTPLjP0O)oTbWo&Z-;H^^4pPUXV?=l zWIb-=oc|U%0*t$cZ?s ztMNR`NyXu-m#I_EOU?8nA#9A_UggfPn`u6K(hn=esToFd%G!QKDT;eAh{q&0YM|1TU0TRy9HE}h6~ZT}%d^Y+OU!n~HnWu9>G!QfdE$^IWC=gFM0X5@<3Z}nH%et(B+^7WSMb^fMq z$7MfH>%6eVxj^XMCLw{Z?S7BjAMSE{mf|;M)3amy3bs3JYmqr^(=M@4_mPac-+`m6 zqFpx~n7J~sV@^g9o2uw~-g60oTKbO z(qWzXv{b^SY)QU;^h4ckjM9%x-`c7>X`Fa{QK0Lmz~_ytznhpZ-Ew00-z5tZ{Xe`4 z$Tu}tyV`es!$p>y29^o(KV>f)&b?oCbi;4i{}a;Af4<+-m-=3C|K5)iC$}m+;uliq zOWg7{vrb$y=E$9CWnbJ%?|VIUG+6P;%k$sTCr&bX(}d3|b2sGd)^w98blSw|7Th87 zp+&k$W0}hLm|4ZLc9nY{Wgq*pw7YTk;XUr+w=ZX3XnW0byddTD&$A&y4)4E2-O<>s zX84yYYH!cap3IFkr*Cd6Pq-X9f$dnJ#L5!I_lepQd2gMXFiB{=O+JH!#fCghEoBY2 zE597p$ZN4Pd~3`6Z@QYl+eKIY-Ue&E*7jSG^IJ~+<@$V4J;AnXvDXHTg-a4ITdaxc zh}v*b>T5;6c0>RE58T2gJeMEukXBs7*uKTyGKWR$&aa)P%>HMuPIBMecEqFbr{nEk z4o?c)UhHQ0V7*_yWwPnYNj5D0dy?*Ked9J~{iBKN-#Z+4m$-T<$5UL|?YBrx+>?MC zS6KWaZB~lg_|>dPdS+Oo_U!YFvu4V(f~M>dSh{DP&3Q9<*euxgNA-tmM_-R6C+Uusys50i`4YRi?bJ9a$u==`^` zd&(BeGeO5&F6%rgWoekGD!Jvo(1CUsi;f3@&O zMxp8MFAc2S=UG$MU+gRX-QR3IL7jo8zxtP}m6M0VG&cvK2?y2I_=$Kp3W?ONU*i(H z=N99}PZL)M9dzPM-~GEfKX$dWU&iVUQ?uI+7ddXbVSP!M<(txx{q8ao58K(Q3BG8$ zap;_c`;Wc%`@18`gs1Tw5i;f#UvMXIrpqM}7XA1wx;6^h``j}Z%1CcG<|0F4>tH$u~A; zUGMs@d9H&kI`8JA4NB8Bna;`GQAk*GaHINuGoR{$oZGG9kDsajo^aWti*4tWm#0rY zaA{7_dvtWg1Ow-zOjfcwTQ__>bh64YJqocG5f>3>Y&RmrC}@={5!*oy9b1 zik|mIo|$G!tatQ+FTT6eta4K-M|nYLOxUcin-X`tR48s=+$F72y5X8*G+Xy{q3qRD zb%QRlPS7k}9jg7}uC?5odw+K`M6b!Z`pIZT@6N&L+j(B3u{j`T{RyiD_kb1QxU~E*9CKcv$R_=px4558m6yx!>a0e(R9(!X=aa zvND@yB&_m`nk`~*NnJHT*wCLbo^1P|YcmFYaqZZo|asN;4w|e0z4)4f@KSy+KDi|I;^J#&R{G%i$RXcA5jw?+S91P}9rv!KU zCRY2NpRHN*?Itfvm?sz4!Kzs+R~}8R;?~pIYI3Qq?=<(G$8E1?E%X#G(zAHxCfS$9 zXtvXD;p=s`le+Gj#~wB~~ox9S6f!#t~H?;gHv|3_?FbJ?WIg;B3uox00B5|7V+d_&Ti zt1-*7>zkr`W_j$5Y{jJ~TBGI%b8e2wzxT6k$N!X$CB5_V@1^ehm)v2Xz4VW7!%L4< zId5l5&&spX+m;Z_X#VhwPJ`^u37VaAu7zjR%~>&3vp!GO%XUxDnk94A=B}-(RFvK= zVKk?;WVxLGEQfnL4z%r_+QbqQaBW3aAit5Ow>O*fROuJN79q9kdDlJY(|(iK9;CzP z-S)D>!{LyEohzSj|pE#Ar{8ZuARJzdcC;zj{45rFYzNVtY32_?n{% zB_ao+P8n@%e^IDcw$c0hn(y=96(&_Z%7%$Js48hakLm&w&W;0CS3MM0`!d!RE$-I6*c5oiVtvNKaZ~Y0DO&=zl_t(!9)?sC6m?P)1e37B*_ASM3yeps0<7*Vo`Y$1+w}s<_ z!rmMXMrEc@RwK{pf-m_Vo>Ds*c=M3h_LYjJJF~c}11|F#O8Z4x8ch7N$H#saw~wR+ z#~fBk?Z}5eg@4_ta9(+^W&N*U2kmkvH^EYF^;MhG(>QhuT&->Gap`gD>{&GB#Iu#% zzl6N~H!VGU=|ak8OV8NWkR~DSt|cP58LiSW58K}DSi~b7>=PwY7PwWwXIt9hh^Pfy zRBpxTSnRBd@RT`xyS?U3IKSq~xW3&+o?6otr^o0es%`e{@F-V&Jh|_+g)wVDU(l2) zPKNAd2Ttub`%-DS@~QI=v8y{Kyqd;6^Ge9eN%tmfJ7Ig_3cqAb;Ji;hYDFPNJ_$T3 z|AX2mJa9B$GvT>IyW&Mtki)!ydXy;_sOI^eQ@sbxo=`>H_Iuix}%I$wBPB=h85Esy?f=FFdI zT>D;~`m#$!`LKwJK*QI@OA1zRUpliq6INdN%;oH4hLSbW4v&mZ?)q@luXI&#EN8rz(vCel4t$kL&+#jLbt{KkaD%Li z<;%0vnU=9X(BrIFs`>B$i$%eMBDEPMQbiT*y&S?q>-F3&HeM8)rnF9O;vxCj6Ar1p zxY{MR^7V!{ZgTeieNWpmW^8;UG=1^W(&f5eGK7s5T`w$~5^ueQ>6*^IqmJ(sUe7&W z@tk*}e)&9^H|-B3j`K{;oiMR@0ZZZfiQSD=Un@+d_tcmy5qp=>p`xy5hDMaV zwr~5R(nr}z!j%H18(r@Ix*+i;kbBXFrE>oyj*6^IlGEgQY4J~@dCx6H$L1SJB7**l zn%Se3BC@nM1+Mt(IEm@iq9=7%*gBowEf!c`@$-ax$U-kR;}`vPKNiXV`_Qic#&MG9 z?&H(8{kyVm?mIDSyUYZ$w9}CS*S^@sZPC>Y7)+Sw*HWBx`GnJx}zC&rxXjcOrNRDzu>|3 z#G7G3m*Tc#L32(i%z5>`s54he>Au^+T^^01|FEdIRb0{g)YKYu+`Ja($W<0xNN>+@g7uJI@xlRLzF`-8%&%#<&z6V~-j7Fd?Z z;;AUMa^ed0o$mtD>duyWG^HJTUg&kqKf~#iL-M-{uihu@y*Juj7~EFQa7k!zVpeWa zF}pBr6_?hU)xxX^YeW{wb{)U`MwKz@xW2t?FE6{EbbhI zEwXL`aVhQ`wS}6Sn7-Duh?q3S{aOBP#z&0`{qG*WiZc#r{%^?_yPc|aqfzXIVsq8P z&qpr&JP^pkpTr}7NM7ua_@74YTL;8$1Tfs1sF;&`?7>CZmxW)W7$=tWI!GMqyT!C^ z9b3}8JjEL2ex^^a-dxzeV}YUCBJP-F{DLeuX1uw@aKT8%Gj$#Bt_{9_SDd}+rnTE) zrLWwhD6KW==XjM0wBDWK?Ol`l<;H8i$O{Ue)-rfrcp=6#V~&7Yil0u|677Fkr87LZ z6nHdry&2{;#r`>#_Z&n5JRS{$F( zMkOYe_jZ%s)nx7y53HKbDg5v9JNAcH z=}F+VCm(*9sTqIK_{%g=@z0BwJ6v9gHF3!a{Sy;@rKVmbw|J?-ChxeeiRV~!&%IMF z_u9B+O`V@-?>j%^B?%!jywZ-nVHS5{F-)$pQavluB&x#jEl#l}3w4waNIh=l zjc^QI)X;T9v3Z`8$FW6XOPa(Z9l0EIOh4vnFL_{bOu;QeQM0dE)a>E+Ee&oajBa)7 zZM@%pt2xhg^|SffLY}aPKeiQ$PwoGC=;F^A{tUksihnS+=S|YHJSl$1nft@)hBpVq z*e;7&99B#+TFuwi$EIq?{;^*y>37=eKU`_oTe|;#aZ$j(pBq^5qt*L}`mg+Y;Gh z4SPOuKD}mQvoDxoldF;| z@+;kvVoF>2_LfCZ+JzKuF~d7gT<%^8VM@+ql3e8OpB|jMEq_*uV{*v#MTgBcDr-(k z;NF&`ZnE(4vJ>)?3auv=MDQ;1lIED{_E59!s?8}O-FPJzg{ICW7j?ELh?y!@EK>?F z?g+RW^z%@G%tYn*_on)9LgW8`HZ%TU7Pt7aXjD)BorE@>4sM<@raK)6?wn9Q!nswZ zoICA}Sj(amwYCiNuH?r}Arl`5JC`jL(KP<0t>-p9eB$BPcGq7Cxzre?ExW{G@Zg=X zh{N)I0T>-S8*P;jw*-_r=+Lh;G(f6hAp^U`-aRtNi!!Etw|D+-h< zTD@0z@HPLeVf_DTijTHe-Ed^8;N#0an+ipI3Iim-4$v;9mqlR~oBXRop;P&B)cBNn2z@X;!< z4(*UYu6>JR_IPlsC}=J@z!kiF!3m3K8L!i~PMVk`3m=-8|8lyXXi~jz_+le}F0T&7 zcMaTT4ctj$-R9xk=G;a%(DDgd}80WRqDs89Uqmyb6mIW-Sd$MkYmAS~?<-)b*OEzfh%RRMj zF^%A4yJxpV&ER$2p6g8iS*Kmo$Y4CCxZ&u$2}#_q#2x;>FXeeE5hvR;OZm_&-bZzB zrzt+!p?FQy-uQ4le^bEr1Yx$z`m7Bsavnh|GmrB41#}$dJCVTjqasmVVS+QOp{$bb zp+n|U#@EZ%C>%T#eDb~ixkLVTl}lXG{?+v;uErd75dP?$= z_V_JM-_@-y;We*m*}FQ)_i;vnUtRTAby-dmb8k9wFvPI;wy&z|lNCyb!Yp+vwU{cU!bj$hiTg^y@iI4o9wr?$|e=b6!o!g4FA6;U7%RCXxZW| zIX}xIojCKP?YM zhg%q4lrvi^sj>Yz=cFpLbFFAn%kPPLA2_@OD*Em$N)mgfufX zW=f9lxbyIDhG!i&e?{7a2O5wcNhdXR_l}9k)Tz+Y^RfF56p#ZAugsoo=x9ggT2? zd!?l~Z@KwDewtF@jcJ=s8#@?fRGJ)geY|2`S?-_PtCW)VFPw44@%qxJq`>Uds% zj_4EB!_Q?{o-S?Fx})$cTv3W6?px3cu|?1G9tLtw{ zYxJ2=v165ch3KK2Ys;$SS*_0Tys1&DG?NzJ+WPL2tdiFIuM$q(H^se=O%Qc?c=)&D z!{3UQzY`xQ#xmSE_<;AH#ecSW@9h7?%EU5kE4=hmR?Eu&e(jM5*^M%_jj~4+E~Xxn zVZE~a>!K^NkF1o+c1St%H~LPRy=jhi64$g8p(xqL&YDKWomUI@DlWTisJ*P3;ib|K z{!^ji0={0)^@S%(Xr}!-?P9xgV!*T8%1XB;F7^>fW)=%fcWY)`D?9D*Q|Asd=HMvX z9cp)GdREwqzo|Ryy5g{v2jju|dGi+u>A&BTzjUYkwu!PQ3T0gu#+v-Gc#*jBrDDaO zhkx6bysHj8TwA~)I8W?aqo`TU5k{xrwRKEfVkxF>!mF$P@mv#fT|LjSlTABH$K%5y z2BYAdotqYaR65b5%sPdoa`_@{#cOB9!f?d>vM&Y;CKDQN4DPzQ0w)Dvm8-C@Z$wnQe!l|)~D zaI4Ae<-Q|cn-lJ>irJwND8TUHs!9&;q^zymKe(E8iN5#EnJD_WYwa!04KV`7l80C= zwF4%JeTn4Eu#{%8JvF@{PAdEA>Z5u}YfD$(5L{!LH8rn;DI+t=b^SFh#)GbFt8(|u zdms2F;^i`R2NCV2H%B%I8+bA7j=J#r>*{^0rX6g%6?suPe?$2GHc8!W`Sn-DSM#p* ziYrMLs842G7j<>*j~NWJ6Ru7P@|(yuHGo-c2Wud+Q|SMTeoQ_JtbSr{5f|B*G9o6l zNqJQWhseZC5%H^id*ymv=)t8IBUqQp`wqOb#d!)V}oc+ij~YYdU3%|6bYLrSbKG@hOdeUxK^U1bN*B zmi9QgoG+i+cZq+=q;H3BX)U(6{?hSz>=wECm2oNlx~5mAr})++ z?)5sVZo(-g54UvshU9L%9PFiP$QpFwUy4YFx9E$I4yFL_wK12ow*J=&53A(9=4u&d z`fk-TtCykS(krV&eGE>eicJ(_-EmPwm_=*`182_8rLKR&wz|&HS-X42+{v5;1s=I# zQ)CafbnPri-P;u&DBaR2Ce-BlpflZ7DB;e`g#5|)BL?kg0@L(F7uzhuvWRD{Ce-Mb52hURRo<^U%kwItvsMgf zN`CQOL?%mRVT)%W$0Enx=vKuiqP;tm_M}ghz7*~-?O;;q+!90a=S)*3DZkJc6Zx+a z;~eJtey8Hwt;*uUe^O^ThGtDk4V}#qBk?|bYUkA%2gIN3J{-v!ec_;4V9v!ks_Ub6 zcg@LN_{wG9MW$66uG!OP+aK=r^k3A@r_PYOXouO0g*q~#@iYS#^R}xE?g~& z3VvEkbW(5fYLq#jdT?l}-u0ENaq%-xNAyfte$KNmI(;U0KhGsg>2nhjY*K{7a-4(} zqEZq~UUJ{#5whJTut8NU@l3r~n4r3qX5#8ai@b9hCA)Q8O@us>LMQ#$*p%VJFz4kW z3EfY7H2%9daBp?+cU^V3uZ<^NVdAX5ilWV2j)BK*w%qVrmA7qHP~h6EcZMDRbso9( zny5?^b>zRU*CcoKMJBhLW`}Op#ns+%lT^26xT@bi&=s)B(P&A^(b8?7XN7d7`=m5T z`)d3+bwX6dcap&b32OtK6TL z{tIMOT@SFSHTX|r`rjmw!>WC2nM2&wL*ic(GF@kGJJDp)BJw@Owam=0ZTf+;O8uKT zwt6({^jR!jZZ4Vl_+F=laM)$f<2ivZbeIlbE(~e4ud7wbOf2x`39Lk73qYujq@HmIZEEC@Z|#Lwd;sh6xK>li42L@ZYngl~=$uKAHEV z|0DrM%?(YqVxCK{EpgjAllN0#`;}W+0+o8Fy^2;nPHYu9CUELmj=IG`&!>xP3io&H zQ(qi4JIXPkSbXnu^$omDSL-+xKQ_$QxIugRd?r9o-}5 zH|^%sWpUhv7oMzq`pIOm^wHPuZ2vW#gDeDqo;J&EP98NbZ?&9OPv#@Ul$3g z6_$mDze*Bc_+m*_mdpNHrC>W#qXm05Bnq!lVg1aEmc(Ay3$oc+i2C1p3d@JbM^e!mEKs$ z#j0U1ndtF{ZQ*i}>n)$Rc6DxBBv|0M^~4*N*QM)!9@)Xr(f&g;DUvBfPkpm1(~0aJ zUx~}twtL-N`@cpg?8>RtWi2bkB9FXw-4h|W_t?&;pc9kYc2%UCs412!zD{6SRFKGM z@JXlHt$!h_Zj#ICEfXZdE-o;yKH=EETuk*_`+pVVMT!4fEtYlXb)*_|MF(~KQcXU! z@{WfiUtQSJX=dxZ3KrfF@r+mzd71T9g7&4D6)eThVX5IJ+(CDbymVPGH+iDDzEJ2! zol2e57cJ(oN0c)1`R1}seE%`&bjr4v&hv@79t=k+td~z{+@E&p+qzAQ&exlrQTw;& zgxH=ZRw=QZ>YM=#jIU!QG%dJ}Xdm;kcQag3H~V(X*N&M>Zf5dZY=x{;CdmE?Wo!bEiaE1tWOqmtaJ33dM9Z166ebkT~FTd`F3FAqff^&il4nY z^gj9w&%uh+)cFk^bTT5@GAfOe^M0VAylh zh^^X0YT*i@isYS-XT9%WRBGBXIrVmOWSw}*=KN&C+kfxcH}9!^%a?gh`eupaT&DbO zW*a^Inw>(rGa9#N22E3`dSh`f?ns;q=bEh{U(Ref^K@_ILd*VBp~}jEtR7Ew?}pBa z6}S`_9hcDp9)!8(X(^8+osLI-4E>aD+@Wb>nlo&FV61q zRBYZOs?6$}QgU=AUqT1>BfBVv<>re|@h%fNC9~RerLD{}t2?`&eYHzXOuK9E{dsS{ zX<5t3W!%Tl<)l|nk9hUDsam)}jN$pKg~)}^SzRs zIZIQoO%db0QT#GgG^{N*FIhb%z~Akt-Q+_oAs z22W6p{G=NDMKUeKjme=|j9ujOWYd7pQmX}}FN90~6|if%q`^AT_{|5S8-iMmON=5v z*Y=b}PH4$!KPD!5kT0qrus1|hUaYRW&Ge3C2SN%GKsw;qy$FToMrR*Te1n=JeXDC0-(K zfeo$A%L0yHjKu_-zL5L zn7Q7OzxIjLde#ETjgxMr3+k{&vpVu|boN#yW{Ddr_+|>sQec?1fi<8rxYB&8#X{B! z3EfEw|3z618BYYoT)3*(u*110ICfKuQfpx94~jT(wq zHBT-KxU_xZ`oNyNAZgyjVB4Lhh3s_^hg+YP3(73xv=Odbe=%USLapm#^+k`=?;hq` z^gv^((WFUkg7*cZKcw5NaYzuqQcxW(9H6QAJzQ{>LVGoHy?BX){zsh)k&1^yW1<^E zzDndbO?C?WCU4m^rJB>vI;HR8~osjE^&tz62(*l_YHoll`i4$f$bj1+AHh z5^5FR9#UF$WAqMH zm-pseE=St}I7LDu1(#iMbLkW))nF})(5<*s*s7?gY^2oeG0p9o=)+bq%PZvt2U#bs zD8ADo=C;XdLHqPauDO}3re|m6zK9g9IhMO*1578vTbJxe@2$%EA6&^e)gwzV&tf%`QS@?4`&B{QCP$9N zz1Gr+`igUGB4uAc$eg-Lj(4F{&(#*0rICIbt-&3O@>D#(J>cdH=ie5`IyI#r)zsgjUoIL3X{}V?!L(U~jCIoW^7J67sl~SC$?w8beVWGgs+;1+J z&)&co^g%VgQ|FXwk<>y(-If>|SIes+6#^Z;)2}bJ{k{2=o3h>%k#lN(*&CG&H!9D) z5&H4#vKd@%2|*J3%Eg|Pi7KwJj$HlUe^0R5ZfBcE2W{3er2BlEVZYFXRZ);-;lZ%1@KnT%kV&3*utel2i8!ffg2$)Vkm9+S_K=g#EbsQGWOD&kE_oe*o zd%-!c%dxUkLdEn`?7p@*&p>4-7T0T1im%(l9Jb5f%4$?h460wbUF5O#ldR?i5BKam z+@G>!0Z&%5IHM8AqHyntQibjUVnpqd0W29ID^%ki&@aLv+2KWua$sc>o&8m8|*XJ&p4W?cyRku z;lk`o8!!DVD5{8F+~g&0ow26SBSnC<cK{&I?yd5g_ViRCMf>u+mbm+>VqC$ajrRrRw&^Y5Lq-e=C0qg=gQ zQT2@0Ubc8sBW zyZBjnR=_@n5CQX(V$-%|7){;xH%s8H_l9i~3g)To*A3+Q{8~s+!tCOn{lBEmHZKy& zjS)yn=C)$o5b>CQ%|n^&39@?3T8&RKA6G}!HJP zv40P=N{#xY8K$jG6Euxpv&U?j!^F*5Gla7bZl2C;`E<$2xqD(~2ru`MULodqv3#f1 zC($G2E0tfoSiA1f_gQvCi_qGsU(={7d{o5Pk~8P5%vEiv4G}Zk&OFmzv{TmM z;px3vP68j!G~Z#IwNX&|a=WY`!%bGhX5McxY!4MRV=nA5Q)0?3DhgEM@6~%^B$|Cu zeAn#5kCptS_WC`(lKOP-Ma759Y;&epDlsn15Qq+1zHpCx*yZEtroE5U0do1&eyr7<$XRnY~?!#}10^Yf2l_rXHX0t96`{MT zwdnXh#^_rodG?>P31TYVzvl0){Jw{R^UnU-DF1eu)22bs&g@!_3)Z#5S_OkOPb;N081 z90A{0!NRzAp&PGorf^4ZP(PHp^ZBAcU*^Xbk8{;-dGt#zXlBtP{iRXuHFdEPQv)3m zMV&Hl7BqDRYKp=0S!s`loqss2uCGMWFzME<_8q`|p6XM9-B$}@ghxAtAG%|pFh^^^l{ zc~5S)Upw&V^_Q21cb0_S>8J}xIQ_rb{PPP&`+dyyo1UF!Q}pS&s_n*kU*Y0fiT2>T zuau^#GvyeTIM2Qzab{WUY9&AWJEvsMMxNml)tEZL@6qd=z!`VE#7(|l+4m`m?_*w7 zy=aM7*LG%!5F;bAi2OLAnq(Kx>wU-eC|%i}puM9>h;53{{QNIkCERkM5Bw`Uif$V_ z-QeEXc=`9eN4E~j1`0oBynnUzcInRbtMZgy3Ox&ETrqFX)?HE$V{;?*rkVeW*V=N6 z_g>V;c^+5#g-_nOz4FN6%OC4xpSJp*`cNts_C!TM#4zlf+Qnok{>c2v^WLm9Z_V&iO2Zw1?FYQMj}zItBX ze?F7PyCoVy3y*in2r9|0*z&@Iahr+jf*B46S~R7XMLd}`;q-y^(jr9<13s>1&`Z}W zdSx}?%mE{j$4YMwg*k|~r*l@^?Sy+3^?0|yk#$HA44XXl9 zEeo5hyKUi#%1zH3*C_0-wT?77dWJJN-zy+;VG{#WqI!pH^#Z2VoFW_(4%{w(-?Hwu znxw_j#Dh)cEO&$!#4IR!E;5mSipK@3&o3_vy3M~4R{6EiK~w9|OM~I=SkacbH!0+9J&cx!NgzR=OXU&-LYn!&w0@O-D9$>DC=xQ*I=6=?dK9 zTHB%};-R}$YRQU2T>H5M54U&;H<&O!nswS)EIMTMM)Bw)*@hdk?rd;nT%w?C+IU0Y zz;QWtrb}xt)`~~aS4KXKTWWHd!lz9sKDT05)(J856Au!&_@Xu{pR>%pnbb42V3E81tT?L>hG|7i zS?bK(A_p{=&2jQj@ir~a3bs5^VGve)u;hT3PGf+q$JG$lgwm^xrzV9q{NHpb zsoz%5i9PA%}|yi@R0q4|z)hh^i7uN!)dq7H74YrONIC*GDd zgE_!bTFJR9?8Zm^1C!ZgE-de4ty;f`HS|^2%54_QG#+=&V5(GDX5)5Ze$=7`r<63+ zu7oyltG^ELj_8hWf5u20wgprqv5&G`4@(dBcSqPmnOTUTWn zEso-`eZuzoVv~jLveJa+F)hv>wxT&|6r{rU@qLUk=Ot+6hnXuNm2#*ry7Z)?jLNeyL zc8Tqmd1l~vY;)rO=oYcHsuLH9-g@LCyz-f?*i>%CpqfYPW~ppj5s<0yHskQETUYcL zd%P^No5WpRrV4Nz4CrN^$e^jVUByOI&+n=xGv}Wt7Dh5lME`7x*Y-HjYG3p~dz%LH z{8u|f!$l^sd{(;8_QSN5|JX_%=?90TBd<&d%V-ptDb(R^ame%HL_I~Bk588OC3&4_ z_*BgMh{Gdd;U@h}nF=YBn!*G=tqR|ptH7EwHR4p$gs8?0KbICBbp~Pa1rL|%Z+o>+ zfVIh`yXNi8F3WokOO{W_E?Z@^Hc|QTlB3Ek4aO$1FXMg)CE2k}RLokkg0Da4;XbiP z9!<|Z1(;S|?%L#Vgm>)**86<_pLdygH1HH&x-8~lz^XTay(u-}!Q*!uT6rflHcIb! zpt5tq{zetPG?@%%CiM+FVsARRD|-ugnwrh*nQ7v<{Bn<%=B2)?rLLUP9kEVHV&|hp z3l%m$mV8%bG4tueDSo@8J&bI(v|iEqs2jkX5Ps;{UX@bbuArx0I~UCo$P9b3>V7xZ zO(7*#lXFF$pI>wRIS}Ew^DV#437!_wS}Az|ED3_?d2}iy~1e9ghWkm z=@g@SkJMDLM%gzRGlJQk3WaTW+O@94bo2jX0-Lpq6S;f89XiwU;oI_KtGG9JJQsh# zyT0n&ldjDxI?q1emmhU0lq2ZvSNn?z<*R%?T?=sxp0&m;x8{X#_?1&LXO=gGu8@;j z@$b{qXF6xo4eiURvPHD5=$W^lP~nqwDFj9tSQk{aVc{wuM<-Lu-Su zXba24T))=Y>JH&87epuip04`XN-}yvA~*A*$(awG=STffx7#8yzw>&xK=lSCP6L?} zx6*DIaUXnBW-h|v-+1Ejd?C#foWCA+_x>U|nmCK*RZn@5`u4!~U9wB^3Um^6LwDqB~Mn>x9?cTy?Hf_2&^#;R(+FQ$fgqBRWTF`lU4kx2{$^}bT z@kJ}#y=*3$AW(s+Kzj+@>U7*l>(&1$EgO`gtzdUIBJLmYCf@j*f zJDNLOVy3kfFW@MiRxao>=YK-HEHRGDQniWie%)3HE!WKZWp`0|9#VDKEbOw zu3G~)hlivnU+&SqBgVkQ*3{DI!D#YeN?OQn)>*p6;%B1md_&r&lof7G&Azv}HM~-V zb;a(K?2L|)7CXJ>#I1!QHNF>{nZ>VLeB7=m z`h5C)sZ9@0ChgkD#5gC-%(u!q@JUR+Dzm0&k^HyLC1R_eozyt8fvf*ZlIXg$pAs&H zU4bX|$R)ox;eVsB`j^0d^+hRlPA+#Z8M-VqO25?WbS_1!>h+Hmq3b8;E8cpX7{2%T zgv9^r0?YR(2{ZERS@XS%eIhh-f!gYfU2C72+-CCq72I+=nc;iIA=cEhH;i%>^}7^W zONAP~&YUGTd3$c)qBf1q<(1NHtHqCNS$r#4VtqjUj5hD48%&j+bL^|QD|fd3NK)Zb z-YeK8^M}(??1{WMk3@?FN5_mF$u%5RJEe^-v~+uRgnroDxS;*x!;a7sEy15TX5F@I zT)`grfuB)=b&*7?;e-Crh}IdmJMVwqxrTeK+7auYD-9)YHAxC}tlc4yv{-ljkB#3B z@6!1)L3Ot9dgi9}-Oefnv%(TsR}?Cp-*8yVXwv_QvoscKt(-J#=VT+XLLvRbDusuv zIZ~8aBb1FB6^;Hc=Gaiwdf>HnW5L{Y0Sp_PjhUQQH)}E5D0c5yxkD&H!|j)F$dlO} zA2wK6?ltuh2$I-1-$9^pL940^Yt#eIK!N7TH*6np+f@lLtKaE-ytuv2!mjKCN0>mX z#+QC!p+j0J6E7T`66mzFZ{v)xhNX8;2$?j>uF2qz+r@QOfP3SL3Gq%Gw>iyt0=bu8 z=1DW!wqU2?*Uwz`G5b#z%!|9&{jzi2k&J1vo=j!cM+>FQ8Sh9cTohBa^nd^3`X$_MP6qCA_m`gUUn^&&DkV&4vpvB(gtT97Gb%7=7fMsKZQt~agH2|*SZ43h0#J+75SDpFVC>1S6>(^a0Bcyr)^KjO?-Cq_01(qvMI_BNsn8tFd zD{;!S3ezjA0&dt$+wU@2^MIYKw6j>Ck%`L09TnU{p=Nppf~w9M)pr6cUwU;-2xOFK zUh+unyaKDn<-?A5=Dj_%iDjV@|4zS|-0F-8y%CqJZ*usY@L=lVvXPRWdOJXvQGwAy z*gtwlv-VNxKYwgaRQO1Qa)f8_U)VIG@5bIaA`Hmv0f^+$T%bu0K z0mXY4O6#sWArkRY^D zA6PXbW-i^bkDt}~t#r6yioKDDz3eJ0K|^WYmm%v6CN7W?d*XAI<>Ri#4c9jq3$FX= z%sOY6ilayKpTLsW7Zn~(%YC@KYDZw<&S_GMJNRNRMFe$xU7~j?;gU(DOc;+!hm*G6 zQ4Qu*2ak8@9QwH7*qix3uJFXB3bgFhF}lFy|C(djht?kkUCci@t#i4S|JS&zdNHiK zBeGkOGr;)r0@e`54eaq7+I1E9eBM}Z&x|tOc~(aI`Z^Bx+g==@g5B#i7Q}EaGybE? z#(8e8q2Ssrl8;K0|c;Es1OS63?X zynERddGs{z-CoWUd_Pna>UQeaD|Y-!TJbeOfw{Vyi)lk=^ZCCmj_Vi#&bIQLTibDB zA;-C^^P@YCPG+9_eaAJ?LxR&c&HE8xS+;^TETJ{@c8lQ!CWAQwy$OVY4oQnWs!udoQuvWC&+ju||RUT*Za}5fyGF8Ma3aK@~dghZdap9Q4s= zHE-+YtCu-!mx^38G-SPYIm_ho`Jcg8vbxS1sn%~0nP3sMYfWp}io>ls^L1~u+%dIU zJljzCnOkJg?3Nw-Tv?CbDG`m^JuzNr*~7P8cV69UIkDfKN3Qg?uW=~hXek_|SqxGOy; zGCXQ%dcEG&=%kp;Nmb3T?uO*=QGH<<{ogruojh=D+N-W9kCJ;MJEtbJswT1CJ{qfe zp-Ce`R@302>whs(_jw!d{|IXd2)Y+)RWfDnlGTb^EW*9+&h=W_@kDWwe2}w7UfhWd z*I&qTtb5F>s?_sZ<#cn4Q0t$0k#XC1KGxfvE3`+JBju(24j%Pyg3C(^Q`SvLDUWp7 z;o0B(gX;w698L>~wHww5|J?ldtK1P~lXD6;3p{Un8->04!Bu_Z;JmK_7qp(w7mW%j z;#$0web<`Zj%ux+7!#LfuN5jwF$!eLJM!Fnj;iX?Cz__hE?r8dM*yY8%)k_Ph9g-@{J!FWNnUq%A2)unr@=_ z)?mXJo36pO~Hn?grA60C&G*;ko;yz-v!Mj-dSdn=6 zleG4Zw?}+Be)x*%X0&(zyxheQRw>cRCstzX|kkui1!yE zW6#-3d4&ybx|umX%$1qyJa^q&G3_^xRR#Yq%@&)ucKr&`D93Ky2_cVVmpzDe|Lz@E z_f&F^!!q&ONuoyul*;V4mD+DfHGAfpv~B{!>#RqO5-&B@brhcLF}u-nS%S5dS6$O% z_O`l>rw;VqouH6vHe-=ncSlhlo7Y5M?vJ&i!>wHU=`L^v7=RPJ)g-;v#vOW58&TQk@I&C|r`LiyK#V6Kr zNdCGIKJoRfTTlG1X>NGRwMT4YSI7Rn_rwf^8#EM{HGfLU&gD5c?a^VrNo!~AdXrgv zwjgt=bpJgK;eE}YI$|fA+z$?LeBC-2d+?w~U@6IFEoPZd9$&F6DD=+y$1T6wQd|szq%D(Ntq$|e#bJ7PiZeoeB8gdg?~F_bWdv&pXBX?o%u5>?{z(U-)lFo z?-~!wg(X|>oSINJ;e*P7H;tJmPINrqHa(Yz;f6iO(*?8>6AT;Es@n$TaKnjh`oa^6s}eZ&X!o6y)V-U*(kU@amk9b11`d|0_m{!WH5L=hksJtgI>y zv|amt=A^yC&n>3&zq$V~GLT*RMdPu3hLXb0QyG?*|GIr);uN-`_1Bo5hJD>qp7-2O zVBcpYuYac_-gmuB6V$M*5q8{xny%Vj0l%!C#u_FU_cRsjt4MtjeYx8FV~<}}`53IFEjX4$)F@)JGB?*^O66)2r|1AK4S4T$cD`W8IUJ zWk+KE>rd`j8~QTqQ~zNbE9pldzYl9-W-op z3mdAovh0c3q1wozDj=-k5n#}GuE~>2>xZU8qYx`omef>%jmsI9arZJUwK#g#DOzKF z(%G(0elF2vT(@?ye#>?=Uw3PYR&W=OxL&lG&rAc?CMM3H7fNfROBSAOs@>}JhP!nA z!ToKl8i8JcD;SPN$ughg`nu}T32SEeH9eM#d>59fb1>*8oXR-euwCL#!h~*yt4smn zDY+94J87|I9{;a*;$(@75nJu|e{-*vM^A|j`>Yqi_VnGFz#A<-dUs1o?y35V$y~jt z;K;bx#xCdTsjMSW(M@J&Yox9>}gr<-MgYeKFMK?`PIO-{rl+ZeHw? z&D{~`CK)qfBdd6Dj>1tEA)f^ftrb^8r&phRd1axaT-A;cM{a+UxaTOABcs4zW%g0Q~B|l7Td8${Y*%Xo73F(sc z5q~o$gf!1t$yq{c_zYSBgbD%xr1Lq}8n(XNAt5-x?;+zf?1Gan_cL$7bc&b16s{Ei+X= zkat#(yrF=+C`58 zJ6oR23hBSKF?GYetVqdPK9Bj1pH-{EqOuDQGA=3Dd&g)_rJ!l#ydtBz&^h%B*C?GT zc^qZrr_|LI`b1fs{pzuT+fkKkicZOF4%;91RI1N>)v?XzH|%s3%${>X?QN)ydrNg5 zn`rEpEKhCeuM;l)?3*OAct-4|Pf@u_4xevHio`kH+1O<6z2~Q``HaLFq(c@+ zb4{0{?PsYjVJ&n(sd@-g-H>@$B?UwXJdD`mqs{fOCzudj(KQ<{DA zO}gson+K;XlUdBEp2@Eh`96$=h0#XSD^TmiyiH*f`Ro2`wlo~ODIj+#A@l#^w9|P(!z13z0yn(i4kJ_bm^#%;8`E zB3KH|yNn!9W zQ-Ar!D8%1Uod0E$1TzzNT7+jpj?jwTt9-l7ZM-MtNSQc3j0tY~e|VwCM=qn2*;c)? zBWKQ9Wwvl#>A~YUaZTsS_?#y7yBx{BypdzZg)i$Au1;uJwkSnUmsx#INucff#>L{S zS(aTnJ}ENU6BD)sDIHZQG2}R#^29*Z>X5*dUQ?#tj7OU$#6LCE-kQQIUtDaQq4s*> z9!9^-t4?-4-Jm+_;+tg*UEj{+r@2-LdNN5qndv#n@w|dc$Za>FAloSum=>{dKKtZ1 zbN@*L)>R6|fgQhYo=!~SGYJ2y8NU0}RZX#FsS7w?hHh(cUo*!f;@GFfO2RK4)_-za zE^sj+&7;w&HO4oV|E7@b&snPN6Ef#U8E3hit6U_O^`zBU=S|P6ZJUn#C|)@$`oC6) z$r8Dvu^SggOx0P=aq5cpzaYPasX6O51*xB#!qx6ya`A;lvfJqluh74{r)C)zOWfKe zSdu^IaYvepV)UZy{+q8JZ%hgon6J4yviVMIfxyd{rJCLz=VLr1_iWXf*O|S`IVjKN zgr~B|x)aOec(Ve;gW}foKCcq+ToqRFzHCg%C6O-H7nE5! zgEbDV`>LrXv~kA&UzN|SA6uTU;(M0yeYL+vSk`>8O%qtm3>d!_Edz@TN!wzgq3@d5RmP>2R zKf<>Ds94IzAP%F(<)FdzTU4 z?Cq!7iZTmBk3}1%8LghXCa|Yx5~r`i-F7}1ajOHZLQ@#;efoAb)=WyO-)^UC(7n5_ zb!=sCGcKRCX}8$4?Kb~EWIWuW;9oUWBxP;7=){GUVOm*UEvu3`&pdJAGVKldoAd7F zp6fT}R9l2Ew$i&GHRZUxx$fqfdqd{1I21O=r7jdUYu%>P(P63IEZLLNls{GB;A)n2 z>jQpW+#+Thc`igDzvi3tlFxbv=dr0zI#QWwSY3JW;<64Aj-ss1vAT!37pfms+;m8& zE#=CrHyx`^*Cj1&jAhuykS|@f)+b@R$F0w*JM|wnJLg=MnD*D?X{I3aRI^Ff!cwkm zTqksXhU{zePtVLY?Ye%pv24Hfy;;-CC4CYE&WkT$u?uS0>wEo4|Eyy#MGAc%q$pi} z8l+v$?mvxtE#Er*WZCIfo`)p#PJaLX-)4!*+1o4yJC2>~l_^zReCCHic;BS+C!abp z$o+i3dj9jfw@?2p6#f$MK%`hqviO;pRmq`ibBpyfH2&Je@8^7^%W!eJ! zjMH(2K~pybyOgj`=eRbNLu~ch>mCP=-%^=&<6+d*53`OpUVE0}AARRe-iG#vveH{~ z_*#=FRtwe)REZwydJC)5NfXDZ2^}h$hKB z&s_MdQdICNOU4sUch%T^d)afhoIAheXq4mK(1PiCCe!~jKRdQWc7e~{<5zAZvbZ$H z&TRP_*|v3uKwzlMrI7Pnn-cmz9y_M!wn&pz@{ViR%sbZ#Pq-BNop23FQBaZ(n$Msr zcQ5o>z?w@xG<$x<#9f#>|CgpmlgN?t2k-ymY|u)Z>v&>5!-aV5Q}H@YapGI#-|Mhw z?u(8-&?$1@Sp8G!22P3d7Q(O2EWD@~t1#6uN|L8V)%T) zazmHphUB!{zUPm2N`=i4zGZR8TT`$6%@;Q~1+D2GN*#yQd!bmn$dkC+l@&QCG-?|4k~VpK|gE z-E|FAab#j~U}9)Yy5f;Kg>T=)sUBzDOhRuil?+$T^-hryk~!w~Hc_hBHN2&Cx}$<4 zONlIZ==|#{XPr9~Tu$875M+_OqLBPXw$`yF()U2{r^`D7p4tUn5iv^KS$p5pK zrC(nkozwKtD_Z#XY`n{{`xB-(u>YB(Qxz8@c0^}gQncBF3jwW?91U@NC!0zdoaLf+ z@y=p85|gOLz##K1k&PjV$$>%Tpr>!+=_4JP3;MF2@LXQm@ya(UnwQrlWJy?!n*XBE z8@4V}UHY#tZ1JD^jJ3`hS6G64R!qtXHMlA^N$^RxRs7Sy10n$p&Q;IF4oSDv%IS1vxx}b^jClNWgKKn% zWXl=XBoS8sE$K`wj?<2%GcX*SeB;$*h6AiuBWkO}*<4*}zqyFy9E?80^ueD}_3v`mPP{*WYD(%AGR=KS8J702TH+vdG6^{sF^GU3wF z7rO0-zs2~@JXqL~dZBgE|AaV>Gm$H+<1&+8Wp#SFZ&SO?@M!tV1%3H{)%y>yW_d(5 z9X#&2Qm)8jafyw1X`|Ncb1y%r+;MVgnAv%^bHy$7(z8`_@4EKPni=3}Q6*th$>Q`x ztzx2+|CS1>Xr!l!y8ZE)czR}6uwu$CwRxpo7mF@U5>?~bUE1JM zm6h(~va&6%e}cN;#?}W5(qkG@0&?CY=`E|_(%YaVvtIAWdc8Z-vRrGm)*ZT-z9yql ztmal)!Ij9p$2V{|oSrpheD5oObzQSE)RI;^cIZ6(_Nx6( zy6`^Uq=JvymXhrxN*+wXPi zm2FzAFCR^u;2^b%|KW}-zYWW$UVF8c>)kp#P9}+W?J9=9_q}gkxOn<%{VMdp9DWqJ#gc)xI*y!Peg*AtP_ zd;IU5$-ecgHQFG4=`QW=Wj)_~rYh!L+hY~l>cZxJH6~)(qkpFFmzTvo?@XU?O!W|3 zrq-@hZ9Yw}3#xxJqmGmvJn|`tead}xp=X-?OMdQCUn{ik@0Rz6_vQFj(gMIMip7Uv4AYuA8@(K4WoiQ7>(u5%A%qTgyk^ zm>e&5>52dj%e7YLB1MAMCjTv{vvYN@ROJ4yEb>YyGgCLSHD0bf{@S_CFOPm;32-Q1 zmCEaEw6gD2sK%O=Qk=~n3|6ZNda!M*7Ave_TD?xMFva0xiOm)UpOXJB?A!)fPLm#f zQq$?%tGW8ATiYuR@r^Rmb5vd|No6hP4PyL$A}>Lb$>V^S#46+MADN|#ukKw}RoGsY zC|{Gqf98*2fWqYPt3jH)lO^(&YxF(+#FituAW7x8YX81?ouI7~>*VgvI-sNWGED#Y zeyf+Face$Q$!l>N{;JZc)f5Tlc>P$crF^Gp*S^x|V3!!@79ID(*yv-EZya6j=@`GE zQ^dnDWf7l;W`h3r{8<6A66r^cTlH9EH4y+>|*l1q~gI($%3|J8~Ezhb_+z5f!m>7V4w%NL&NF>RRG@#*(hCH8=ZLr;o-FDRO7wCuQ?+Mj!0 zJ8zgOJ}wOnydO5zLVx0$-Wx}HPF&1jF)O&*)6mCuTIqOs_>GPGM13sFSR}uhKJ2?= zwZOMFN@jU^2EUkdN{eayjOy<8__td)V~trpXR<8s?mF6iLzSN+`(m+~bbsPh?$tg5 zZ3Vm7X8m7Ypp+whgz3l!+vU=%DHXZb3x41H$@~9`!(x9ap>yuPbp4lX@>sN9(E8+B zv!iQ2t&-I{!dB|>$jF2(vc%EHq-68fE}xB0iUKCR{W@KouPl4JSaPdmnqHFmHifft z<=;7y;{%0!I(26Z+3=$C3HQ^ZK_}aeai8uCJfqPn zP@%c#{;#hwO%D0mj}}EgTJ%@eeWlSGCyh1V%1l>id0eQhe!#L!^Y9%_bG74j7Rg;d z9KXJc-M_v#TIu+f-^GU|m~7O%Ld<)89RHsBA@OPXB~7zc^N-20a^@^q*?8|>iOlEy z^-*iqMrAF!73a!-tHk*KcBegSlUb`LZ%ZvGP84im-|zN!U0L`>%YA-sPcvW5aZZ_g zTKAh*h0BYwuSu?MT~D9xY;{-j^Z0aR&9b6tLWZvL8yChu(&nyxm-MJ>_OT_wESL7X z7j2($MDe!&m5!c}y#3j%6Kqy*KJ?_pwn8D71If3pt_iL>s#-I(At-Fip>>Xn>Z}d- zGW~uhRsB(^+PTcJ(LDdRVuf$$r>5)kze=qPtDf1c{qihd?|!!@O%3@G{LgksFWcGt zTTKlSYr0X}iW>J|TAIM?(6?Q%)>} ztUqTi+H&bf#m5b8W^b2dmZZHEo_&7W-v41P?ap^5XYMJV8t-~JsqFCo^{E0b*=Z89 zayIVUZJchnaC&fyBti~k8#s1vD`jt&NIRVZEZH-4nI3>}%J=9)@-DtNC=iH%vRA%O%7U@*wO% z%XX$G$Bt?w>K)`{y61LPy8I9eUvR~=2SLw1JTy}KziUe5ouw@d$0n+~zf({!I>@KC zXP1WZgW`B9;|vI!--m5a8puw@E}}XU`h$|H6zd2ma_TiR6L->U4>|4bpfsAfW zulyJ+f1Tg1qBLOl9uvG%3clI#Uv{mzwz6h;w2vwxJ1;Zv&8i;(_LopW6?ad zS}p5V{a@!beVAKrXm`HeY~nCg$i#brAG_4*RiXM?t9d!>)lN7yy4+jQ+Z*ZmKeAyG z>y00)S7preVyJd{7m#Y?ATv>a$GxCH1FZ@z9yiIU^ESrJT;ST%J$Km+2Tx5E0mH^M z^LZMwwU>&BpKjvHTCMwVf&#~mEf-bJc|E$~wqVh#y2-b#T27tmTV!Hrboq=itJ8_a z`!+s`Ufi=yM5C+2XwoC^{YO>JeSRoK>{xOtTUF!2WVcmTCmRI+cB#46PU^q7!kvAk z#_yh}xfkz+sCzJJh8@!inUL{sMcMt7dtA;F!qk}cBblYwY&a}sy|3k%ocF&QLT#cM zn@{Ulhc%qmYpz@Hx_QrY`|pnb3@45;%;PPLx^P6oY$L0$2V;N2qQ`TkDwaPn zNauN@a3XSBLyIGmYe9Tv3d8?tdW|{HTUj(uR%=4H;=#Eto$b#LuNql}Gb|bjcAN{=M{ix&`@5*vySRwOBJjcyjc+Qfx*mxti#raf zo_C;FUHpQd}nizj`?k>wMvC`CtHZA@9Va8t`ENA5|QrjkRbt21Of+Ql+vR(YCn$7dX{N%lN@<4oX%qKc z^fgaxdi?*yE%V>8DwC40sxvI!z^VJ+#fh6&@~D=dC?}&wQ+>wo=Rj0#XMvR|)pKqid>$gUab}))CU^IbZDNPtbf&H{_suBsk8<;s zGoG0_gRj7G_R7RwCY1?x*K4?Uzc0?Q-FaJ7bB8GxPoamdh$ch(smVE!XZ;jwoiFVF zHFeUiM(H~Zo9QsOP`aR^E@zO&-%`ehtwIc`0z7;}V-@ zfr6XeArCRu4M+8g8dfG8i3*vYa*ToDVRpC{$Ml}bGJju9?fa9gUnj3V*}UuY-iXd{ zhVK>*9T7)jqb9U4$Y^f&()_Y-8&5-utE+pJ<-V@G$ zV++#vQEe4x)pTEJmUy>SUw60A!hqI_qtDJPjqrLUe{a@NvDHefes;;eX-UQ5vIbGx zmPyZk-E_J(A^6Lx^Olo#6fNDueDg^8;(~~`M?PBoj9snDeJZ|LY?H32=lU6+rk`K0 zuw_%UL}$5-)=Dlf11*ltC%j$KHC9Q!XYKM%NX8T;Tpe6r5^NTNadgs!E)=u0QP@j<_D1DjbMEn-1$sIB*O4AL^-yEJa{nWOl zzgBK<z0}S{`D^7 zR+GvSgRZDG%uYMk9+gkuTO5D;yc24;=fy^<-`Bo7pS=TzB@Wd+gES@PFQC z9{=<8?l(2JZn?BHAhOZdTA4lBpqW4BA$xSo;eh(R?u=(#7yB<)my{D~y~HEGklF{g2Zu6u(H+7AK+rB`_&XS~>+sxwM?F%)Iri)fs?q=9GAf#|FcH+Wv1Z9Dtth3EBG zuYY$4Fq-{e|7D2+*FDZJ4>GxD_3K~mV-C@I#1cCp)nHMo*&~OjgFih!C>y0r ztVmm7<-+x3sh=6s<9+@GJcq6cH9dKjw))w*)u&#q(n;Fb5D@di^~n)6MVY?G9Ixw@ z`0Bs;@B8&!NVwtLw-;Q2dHr%53-#7bda(YK8o$rBuJtVjYYhCwFKWFI(q>qDY;M=O z{#h3{ozc`W(mpT77MQ|%@Yx6LoI`;HNxZHn=B(1wFjJbgXp#ND4cmJ1w;fn#F85Z3 zyJPvKkJ?rGy3YcGc>E)_ZyB%F1+1z$`+Jt!`%~-xJURbd z=6MX$($9yIxs*GUG&Q(SKH&Pgl$-O=LaU-zXHIiFUuvD@%b41`@Q^YW+lT!|NuGUr zOv(X=ZUiVK^|w4=zO(PpVWVTrpHs`tShQC$336W4i8@n$wZA`d zF*jUvPEK0=jN|hRosBF4^GqCXa&2I+O;OnQf!i_3fcdjB*JrgI%mG)H{$*)<$#%@# z#bc_;Dc;ZyX5E~STTK0GPc?3DY}liu;rc(oF=^QkhQRGnAKt83EPwUMZXQD`A0@?b z;rul%bKY#6mn6)2Hec9$owpD}z`PHiS4`-S;ZW|pY+(7xN#L@QO%eBWVI8?sSA7nB zIT4_y(?KYUNQb?(NMNYyVsSG;wGSMv($w3 zqeYOKW7I;~oQ1M#DZ(e7ZP>!4cQq^j$0<*i*XDfBpPMZbF?5&Ty-EA3l6dFDvpI)F zZ!dY4)OY>KyN$>C*E2kK5MQrTnU{M>CHKuUd+R)xaKYzO7JL8GFHUmj;{Tv;d`Wr# z1*@ZH&8GxgzCL3xQ&`1b%SPzUye|0~d}_7ow_5(7DdLgp#*fO=w`|?u z++ugCb<<=UPG8+HwiLZ4TiegJ`b$KEj$M2spz&{UN`hOOQvk!S;z=LYPwMh@T`RQV ziHL>D0!5`IvL_sVKNER$Lcge~^pO~cx%nf_=>?73`1Nf0K5H@MnQXBT%YLDvpgQ%U z{_KhUI^G5wliW8e$2@d)PgRm)Px?IXqskVA%?U^K-CllaSfsZ2TyXvWLnRgmOY};W zKTK8DDOFzLpxt)rwa>G%54~RgY}QLcwuiC4O$gOGR^qGgsq*MZ$_thxNS zmDW#aGHbMO4AXMiW+~5>{Cefh9nJ^XCQQ~^^_9!*>xzl~+cpRN^I^&lyJMtTy6c6q zt7~HTgya$r9XHbuan6kBhj%?bYbPwz(pe(nCfc=oT4gu{-OQCD&ApV9yovJ`|K{d`GAx`SO%+6wQ|WlW~TeeapC{Z z#y{Nbb^W9Ank)RZ@0E(Oot0EHc{v15Hstw#(|UX^fB)h+DcfVXG*91H!D-3wZ{P24 zKh0wGhL~T@jJ#hhH!=r{T-DaoE^2I2`K|1s?_OB=E#VK3fu>Fg@AN}SLOj+}1Iea}<{Ce21er^ct7Zd~bF@Z{)+AJb06l}K(?C|ExsT6?*{^taRIO)r$?YB6r& zDe_L*XR6~8!5)4i<#~%T*K{5;-j@;6lovI<%rOi6;ZhRdIrUX?P=%As>R^Q>>-aYE zDGT^EM*lyW(9fS37*MiY+SHaY1p2HDazEN@ebwr&vJG=u|K3H@ijM#E2`*vX zX>xpBTGeu$GhbM5C$11s5xU;ulXbQ3m)eaEgD1Dl8U#!l&g4wkk@BdGS@DjE#fobV zf2W_}^KWE-l(V7BOq_eF@{WAnh<>gD3wOoo-aA?;!}R=mYS5VXw~)o@{e!DWz)`2c&$5kcEmlN z$h0R%W*Qy}^~~A!=sWAh%cb^eOh$sgPvqD0dfoWuefrNtdFGc>!y^^8=ug^oKIhD_ zsc+X$wLIjy*h`;#!t}fx%T@Ln4H`o`h7MfkXU1!3i1vfb?f1H{r z+sGs+&hpFX(bBrpiH|G0UrLsr|9{NfavE26@%+d}k)BCg{C96*S4#C;y6*qXt^XtA z48xXK{&uq9{Vi@ib77st-8A^t0qb*-!N@_n|dMp>pYF@NnbNZr;?wmQw^5sI!)i1?ElA`nZ;?A z{-HKsn|4+uPuo!5_@FxsyVdSAr|BzcHvK3m`l|G-^I}WpJw7pwGaHTb({D9$T$w#5 z)abWht3m#E6{eh*IZZZOLXVzkJ#~kd`|i4X3UO*rf}M6;+Bx;xql<-C7Vj%}VZC*F=~dCKYxhQ} zO|oP^{OIawtIlOI+iu<{bgihe&J5E#G>cj7dg<&SMoKy^TreXGFE47FY~B;j{dLs zIsSvPmAvk}8z-+W$q3!`#^J7(z!NPEn^RloZOhGEpx;#K9dzmVfw>%KCRZ(1-T1v~ zbMDrhmv57;KU>vWmS(@rlJ#?>~O37W>F<((EQIi_@}$x0() z)%>cxyo;tj{k_+B-hr;Q3jA~baPly0-75Ki_a)uCOEyZ_Ph1gthEMs=Kc!!}P9`i0 zw>Ts;3KXQnm{}EkR!m4d)XFApRdOP2k!$-By(pQf6COMCGdX0*XeL}fVyMR2*|n6OE8AzN>dke|SYkOLyi#b39P#uY)RC{pxD(@*M_Z) z-@a_lWih|o+YE!3`Tod%c*a8UpB$&<6YpL_{ixC%GCMv$7n|)o-!InTl18)x_k_CQ zu1TG)+kGPK#9rTc^+K}ke%?pTszpmG#C^_}r~ExC%>8Mt{)NJ{eN0<}% zq0r#s#G&y*AjaXw)`Vtmz9|n{nL=g+wD9mvabfc0P?#bTm9g=tTGdO{2VHNsTzb^a zv}N(dTBZxv`0{rjOiTFRYx8N^)+-L)z8|j4J}>HeH9R)A@Lq6yYw5k>_@$wD zQ<-nF-t)0rcP-kjO>3F*lpa47NzZK&*H*ZCw5Yj?L9>q8MXOKvu6WiY^Mj{e#ep zE>isM%@;-cCQYAE&6hYkprbKu8>jRJR?mR7ViRw#a$bAp_zbr-Ay4F;-& z>9)1|WWlz*;o8%i4x9>%Ni2aADjxlpSiEA#qHd;Zmy&i&e*0sw^BTV=1xJ{!2qzzt z&emCcTxNUn?V~nEAs<~H3GKWv@y(<5Ck{HFR|&hDJmy;JYhHSCg0IDCo4He~bhGca z2d(kEw9#$%%V(}3zE@0Uab`Z98p#(Jc=ak%LBra4mz++!M&$UOjlIEdwdA2gpuv(> zhMBKgKQ5LCRF8W*lXC^U`-x)AUjy1_PU4~0OI()uvWPkk2eU|?v99K_$$~y5nXpdj1b}Zw1 zPpiis(@Iiz&8T2W4|x?Czb!#&i|A_0Y;CT93C|{&J>|0qnR)r$8vff-(Coj5w+it(>>ocXY)qHojif2rG(k3!<(F2KX4sE_ATBb{)<_HVP2(g?v)o+W15HnXo0}o@TM6TIUqj{ND`ww?+(5MjKwMWnRoo82d z*CQPpLv_X2<{&BAOQyNKEv;8BGIc#VJfUPEe;TuwymQKyO^r+5c)0%0y6*5!Y+~b{ zlsL03(`{FzcUN`j$M9B1@T$7omb~??eQ!J=TgYfrf8-LmEj#5Gr_2G7{)C-f+nz8j+_6KrK*clK$*p*ipJz|l>q$JS>MJGceibVnd?UX^ z?59igHMY>pf%jI5c+CyXJW|^lC0;e5eR6qci@Nf`3}bUE*F_>rGp1H4Z`x34y`rX| zT;pkmX{D=gZ^`-G?}l=IBDZXgY4k7p6m`!h#CWE!+r=bnrb)lkJWkDzI#TC9NB)0a zgy;T{yYE+WE==uR(sd=oF0k&$>eM4{l5-}m;GV&gDekv$`K3*DPgHbvb};U(5KeiQ zW4$6wrxoho#g7)J+CJAV~W=-4nKM4Nsl5} zs#4bSMbABQrsd1GhSj&5HpF!>2&jr>*yK#qo#hVLO2 z<=`g2^~=OQo(~VK6#l=5C1z&IQh!+{Be|!?Og9{vbJ%L7R==g`Dx>D4u*jL4PCA&@ zZ&-7`e)a#V&_8xxly+TNs>#o}zEwtLjljAL$!e#Eo=?BcSXrv=tG1H$B1eKx>D|?C zUw$PnO}MRUcl* z|L&?f@bd__sm0B;+bh1_zGAmLL*>_om`Me9rg}N-e6uj@9bb0(gFAzxD$sSi{=(KX}`QPR6<1FX3iobvcI_#N?p7#PwS6u5GR$bo z-ntiu`)#k?nqe)-zi5V4qwk3X_M{ud*LIwb_L4Eq769 zKhd$eXg1Hz43_Akvt`rIB|0CP7o2VxA7d+aGN>fbTqwCqO;i4s@%D*E6W-?>FKet5 zmfakBY@3Eb_g}1-l2oBu~6$W6QduZvHJs>;K7^uY!L1*+;oNruteQo0C@MPG^GM-UY9FDZa4wtLlc# zLs#5Wvhqc4t=_+yL0EL#frG9K9w%6c`V_heO$u~a`Bcy~#D(W#L)O=@jHf~;w^T@M ztB5sXb6uQq=Tk*ak^8r6j_RyVGmlqFC7SM=bsT+ z3Kp0ZI|?-^RdY^Y*wdb@Jkj>~R?hE?MKV+MH?tKnSd=Xdu`N2TEN^ZY>+03osC@Ce zW!pmm?dvM7&szh9iZ2JrUSVN)V6K*46mwFQ!ONkJ^Ly^OqL}1^O$Q8%P1=ML6{}wu z8aSyjd`b_q`ce7shxC!Q|6VREwK=YDMMv1y>@Us<6FIyc zA4Q$tZWuYkZXHw3M$tsx#D)fTRojWRqGDDlPc-;Ca`+>xguiv=n_8za)m49uxU|vn zvx#8P0u%LVlEpjfPF*w!c~JSxtUavIug)mhG)=7JqU49P>i_=^2?VG|I9vNL% z3{?ac>RC?dx>0Ur{JidFdYJikvyI8QObY(*rkgmViz;{cmAtTSEYnRDZ7y4;*l6K@ zAxwYyw)kHW8NNTtTudd+5A}yebZtx$*xJ;^-XYn(U0YAQd!boYc$3n}M%9!fVf<4j zm%FeCs($Fo{3ERl(IL)df#M`D$lsq=6;QzAjIUkz?uIIb370j2Mbt9}U;Gn>H zrH;Lat*TALZZJ)~kl1X#uzfevZ08@{|DRY)h=^x8&=IknURG- zLHq?L1ILDW3>)UXojK1zLflKT=im>upv0!&iFr|yvK9+{&uQppHAsI zri9YX8+j|P_sum_l)Mz7%4Q&{ly509-A_q_?UZDi66-gKnWza38h3m&d;r7&$PL3*4=3qS2(6&&hGmru;=f}SufS5D!r0X+*y)cDVTpFbLY3B zsTG1#B|7_G7zeN}_3xOz`IyQNm*{zAMZJp}CKpY%TsT>6VnwV{mhcL}3!7ONFKE16 zlAC^VUg<%_o@?`j9hWmaXzDdn@_szar`Xj0a9xC>>63-(D`(2ZT#%f8QNNwVFS|B-2^x)v^l7iXfCxrdO};% zL#Srg>i>41t(q^Enj3a5$eMK7ap~2IOOnh5{H44%9*J997Nz*z`|v^=lTT@f9VcJ8 zHMhCk!E~38MA{TN3D!$0Epu*7HD95sGM9j>)Qz&=-lz`DRQ~hPV z8>b5ht&)_QwUV`JBbV34cO@A!Zt1>{*eJ6*S&7@ffRn-PGTT3i_3T?#14mh z6c}kWH`-;csDzv3(V{C4>&}JNN-8qmFqB%L*qpXvv2a4&qk}pN%AJn7Ezr^SDVr)~ z>==LVmCOUFrTaA3O%zUuqda6kGOgeKYN?{4u~eJz>1z!^L5nj( zi_eK!xqcLqTT!|@y!ZbM#?rZhEL;ug)ma6A?ke<}KGo3=K~baTOJ zF)L@j`cW+&*!J$(wBBu1;;*N&ziOXWt;6)FP{lD#wtZD{_pXo5t>+xm$|YOZWo=uV z71}*>QTIyo83&ifWGpjRTP{*BJvs=Tq^}dif8o94f-TTYbIdbXtD#k)(?$OI%hd!Jt`OI~Fq4E^* z6++^Ps`;;FQnpUeJ}5hPnb#!@-MYUxi%qZP9@`-s`%(0)M|;OrxmQOr zbXdgSUdt1m#L)DquV-7yTa{gZxWxaL9IU>1!l}rxcmC}@Jsyp>OXkgNWN-^s**aU& zGG?iY<079QHF~=bUr>|@xVG!Z!`;uFqKyL!`xg1Aer(w8v|S~!{p$0uLsBl5<<*Kz zESo&rPJL}_4qL%)vi4?nm{3B?{G{w(GqyfBy)~+OU3B(3=Fs@>Tl?MKcUG9(Alu=M;PAvQR^*iBpgMU$uPZ@<}XD!|Pr@)H(2Z^U-X# zEu1!~k+Qxg#n~IzKDPEgd*qmS;=Wg>&&5g~-==N3_>__RqHV2$J~ex^ZJbZ!t`0VVTQ?`cD{>w&9k?(S|42yo*dt}!FRKmi0n@0k3CW!H<*8v zoIksFRk)dMPVZHXU3X5LlKQh#y?ViiExl>Q8x>A&)GO7gYg~1H;^Fc+mw$Jh_TxBW z8|!wubE(;E*LuOUzk4=MsjR&geyq5)TT11u-dkhqs=B2ww;Wh{MqQIJ`h(u9nsd2x zLPJiKsW5KKKO?Yn=GsXT+a??iS+=#_HzV!Ra~XybE!NEL3lGC|)3^UW$z%I>cC>4w zG4r2dxrpYTJtZ$*>c2d>exCKN9@|SCr`nt+o;dnJyEpI6 z)>!XryTr<0^$H*OdFx)XRcTKE08dFy*n& z|Hn3OzWiTkzj;pTS@zX?e!mG&y7T?tk?ZHa#_bpNzWw$vQ}OAK`9cSdhVE-tuX{aZ zF++r&`vN-wr75po6gsWl`89Zp)%7Dk&poN$SJxk!yYAZCKi`XT-ly9MrS4Ai)4%SN zSO4&8`JY~A&pw&9d#?Mg?w>s0xqPqerbo2Hy#X1wJ4maGgCo)j*Lm#J&(4_M~#-8nj*w7b27sL z(O_X2j}5+A*SHh8Gm1ZcN^RTfzxc|HsSj2hV|n^bY{SY2)8;Ibe5ZCJjA3`+tfWm| z-ZP$knW4W?Kj!OYwX=b%JNW+Z_qw1FxhdInk@)JY6%31AZktbCFSv@~x69sV<^KC? zE5CQMY9us7m8Qxyyq7&H?N{CF9P2piz?EsbYi}qu=oe1hG>2jF1fwpGl#;Kr9qV!} z9&~Cfn;|S2U2ySH%C^X`fS&D!B{FVpoJ$t_EuOhZ&}*@->O?owNr@^Qnqu1pvrb)A zcCeOSb%am*wLsbAu5`|&Ps2GrxrhYDiLoqeN=wX4Yx?~(J;70FUwYi^eT5p&HQ0?< zt~q6DMJ#o+`TA0Lc75I!_37zKW^N^KCB9tCY!_V>;&Z@IWMxFohDG6y2Q;?@O8ZPY z5xBhA(`_b$;i(l$8FM?sCd8bas~YXN?&yCvwrr28SJz!<&f9ijiOi(HJH3{S%a$;R z>Z!|WUgX`yRd%{*yN>_rHAx~Bte(-AL#~}N(e-_ud3Eh#nYGvF1^(kSShv$}?(>kU z2isQP+bt`);!1_-*Cnxui`RsDFJilT`IN>4u_Y^KGUtbE*!^J9u|?fFlcL;u*?bdc z9$7Z2F#7P4up_M=>(6-vOyD(qa?E4nxoJ`U!E z_NkEM-H~!tC4SkVvmX~uExi=8e)faGOkzqYUZPZy-$qRb5E7g%!=vY+UPhxdfQE>#nL9fQd1^;ip*$;z4ppO zSvth%Bvb3Xiw-56$F)}+)(cB?JiF1^ODyEnN#Tm@yxIq!IF0;G4_(OLBKIZd{LD*V z3zYuk?m7G1r1qZqMVHf7TR05tbl+S#p+)Ff|B@Ke%?zI7*L`OcpKl{P#|eE;(+ocRO` zJ3Q7T3LT%eO-V?%BRM%vWZ{p)tu@|Vc7OOI=gl}Q`RhVfqf??^$c_n)Yd6F(HFH{L zIP#lq+9>vAg{y58v!l#3MP!^J{A`f2Ys$gpkF|%QQmgFz{xSEVc44Vrdd!iS^qy!Ro`;C5H01TGsa@ z?`-0EUEY}JD&jWLrb0E<;=mcHA61Jv1w|)2&zcctG*Po5MNio3S>-chVHV%lM_94cP@KgyME?FlM`A>Dn3=cNAkj^NZZV3-;n0}yyj)JyzQ@z z|Ib(bSlsuI<%NCWrT%qq-0HS|aQ*wwR9#J0M@_>ptby;_iT_JA8VoKeIBOhZ?aRv1 zlll50#w^VGX2+2!8zecL0)7hgrKl(|ZH&-nuUz!zDZje$X7*OLaw**tPn3X)S9A2e~*m1TXpE8ly*3KmvrOEnW-^=6Njqe)=sS~Fp%h`C(htQSHz1zm0T zT&oqrJL#&{tj!A9rW|2UcZKdwOj_}U@AOtI4si^7vs6^%?TN=Smj$OgUh)x` z*z-Ijb5-mA8|#d=>CQbKAnJAEPFC`Ih$34x{95zeAad-3NLcVSK}y(V_Z|dH>Ebe`u(rC@0-N; zJ!{pjIiD@E;*$B^&%7IIuHL@)W7qZrpMCYK?=QZ`d|iA$Z`*sPZ{8Dr&ANIpt}s2C z+xpB?y%pN4E#!aRaO|(0wL_kX_l14<8-Kd$$ptNv(9 zSYa=8?EmuiC~c1`USXf&MEAt9zAyj2hEw-TBtzY|l?xbsUpPwd zQfOD}@Ruxl;U@m;Vqag$LSw}YFXmg9q7IlW`F|(FS6k}h1hp^A?cY96V~aY!V%h7a z@Z4+r*By#n?Vyt+`uo^1uW#2XWjZHIY|8GlSb3d=@ta$fqea8c$~uG0)ZH0XYQlF_ zgU?*!P|B3rkTtu^tmx#o*qFmB&88d*pZ#XZ;u34|#wl*$%RcsKGJiQBD!3%{UC!FX zuFsBhDhfhBdHZNDdXT;~ODTRRf2O5;FaMyixPXE@W#nU#t;8seU zq<`eg3Uv=L54l#46h-0H^?zU8wXVJ~x#pYt{=M%q>kDrm-}imv`MqU}`kNjmya+j}+;*Z|4IB=aT=TjNu8UNIe74<4gNIk5t2^3i5s`K-Ey;P&r#!Fmt z1@pEo4U}Q5TxKk^VzJdi5vgT?UUMI694)mz_)to5ljW2pX-OYbb~4B2F0@;#z$&o4 z$iSnJ=|H}?d$X}d^UUyOLGxzo8I^V?n(aLVW~#Ta8n)PYv^Yi7L<+EaooMl>Xfckc z39KmcvuO3!X!WaT4O!6=aG@pUN2~hv)@X~isE9V76D>&*ZES`KcU{%jf6MyrlGO9G zgrPM_$1FhWT9T<*WXyJfY7c?Wwa|OZEmwSw#*C^n_b- zR*1=O;Y?PjK-fI=T zH)izSvS_}Q(R=4b^Ti*%_iyw*+R=NjqVMjEzUv&VOTJhARpYvH)cY=bUD20_;t=;8 zX?3g@v;XYq-?gn&DzRgqn%L|19GS+Xw(WG{n&G2LHY=*{vhZ+6V80JlET(rbWLQ$*LD8@1<&*bH#?CIj~{%;fb za;?*Q(WHiQsh?>ATNPLzMu-RPs96;*;GWqsb2@9lhi2cOQyg~G*mbakO0xQAPL0%L zh~7EXxPv7|k~JZ7YO?0E#FbOiZca_}oW^uuT3qI|_(*{S%NB2rqR^*p?(7E2%j|rw z`v)yg3f!KpuhzkBIAc#(S(E0BmJ@X?H%pDeG(yq^+GYyNG6nJrDisIHENRf+YSQrQ z`ApV|iPJ5FxQ!j^)EShv1_}oYh%_6f2n2CErb;w+g+H6D*cc?WFo{^s^o!M#GIuHp&*&Sm|7PYAv!7yHmIENmsjq^ZC(bK$p3k$GW4JxmfGmkH~g zRIuet=fT5jdo$l5q*%NACvRVjN^rPe5F$yTWG zPZn7`#a>~Tp!vxd&6y(Sk0xuc$PoN6$BJ>PD@V&)%jUP1(;l8&8aHcREZ4FNlb6a~ zZ2faAzdW_1Kq5(Tng8Ak{$(@T%U>k1zic;4TRv%};AGF`-3u4CN=#^2B`nT0LB^3^ zc2n}_#buK@G?e};rJPx+RH!~{v8DK$C7sfWvls1HEM=H#-??&4N-Dod%(6?ViDE`^ zY|`sC%B(XKa1;_$Rp@37?A{Tace6~5;kxkAKt=YfinSY9!#pz{NVG^jocm~}z=M@I--0Y?9yQ+zn8r?4%!yFIHB#WEVQhk~-dw8+@KMsCq7zN-ejv5)%2*$+d-X zS%r*Kl>MS9d6Ny7XeoKt|JGbrD1M;Z*Zluhuc^u2y&1+W&H}RymYS@d;&F1HpXQp^ zo%3v8&nsFf_%?Id;Tf!MuiHGbqiQOX{H&L6)L!w6Yip9>ipc5?p2n?Wzh+csZG9Ju5JBgyk|?b+R}iPdYd|#4s;40p0#o1%IRIR zjP?l5|7Gx2P34&AM5#t2mW_LlnC?5UQ{;Qurfr7=_cxcZ7_vRw(PCyj_t>ww_c?cl zubQ&%`r7w9Y9D;u`L&|oKYZP@u=LMYvySo8f)CfX{@x^$AIN?G^lq+>$xZ*aOuV#h5!+==k;p)m z)e^NE#p^DzZDeiCpRFWjoqAz)ilnrccBi$_?vq{@*_PGFwC~aCJj1q0rSe>~1eKB>dK^oJ?RqL|^w!WyiIjt*wm^W?OhfFuf({ z#M$am4TKk_~KlKzl<1I4|XgK8@|9|pQ zXuHrhgBcdFW&0ciOg~>{H9Rm~-CLrku{Xd@<&VS?|J@s$1<9WoktWw5=uX?cTOJ=(<&p z>XFy>j9Uzh4|NJPuRO5#kY0}RH=XUqF?Y74Y+rxKLGp0!N<~lRjjNXL5}05pxc%Yb zr(#E#A6=0CZz$uwXTll*+3zzVOJ5wV@yRLWxqS7{nUtC<+HZGd^+a;SoMrMnzW18j zk1Th=KbIyy<^Ol!3R8eFD`UNFZvDs0d`lN723+D}kK?k?y{LC=!L+Hjn0FrkyXe-N zy$`3%efa$Qy442td4=_2NAFtvoX~wRXN|%njgK38ABwAGF1BU7R&YBexL~8zDNl<_ zk}D5+O17%DrduqY5~DhM@{CQBtuCrHR=Itl#kFV(eH#)q0rSMmu!?Rc232{peOEWa%NtIY~`288dr5jeQ zaZKwn7vI{YdQU}cSCw(gLe4D#k@|P_ZQpWRJ-xi{9XE5&!8xACU8dVe0}z3 z)#?EL#R*gW@0BL#>}u#(aNw}_!!<{e0{Crf`8$*NMcy4-pep|D!sR87#ti>dmOkM7 zv^e;m6W`~<_sibt{WoD-azU)+&ID7=a$`${J(mt1EEHtVxV?O7eMoN1I>EeC+Ezk8 z-Y3>7+urI-v+dHV+vR{ z_Q-+rGRe&^xVIJvZJQ7Omp^+^jpc9bbZ3IzxVBA#s4Y1<@mwP`7y>SbqCkZXZmV4*V=xHyMI7` z{VlP19s4%U>^hYEyI5*7@4oNN;*%#WT60y9eez3=;BphA?M6C=z4PuYy&-(!?)PK0 zGSbdLn`cQIHVNrG?NSY1wdUh?ox?^7Oon{()0Co4G%^~rRlF82QnX6tE_=W=U;fvE zeD3@AF25^ZxN6;{>7nb{-t1aw|5PTBdu9W}%mD7d>HTXy%v*fm{?`RZ&fl05bwKD% z!_?IVkq7!%I0RUa>N9+KFy*LyMgJd0=Gocv{5rb2 z_R^a357pm3e8zvjw9!5GbO&3Y@y~q{=X5niPo8j;Ia&DLBa`LohE3uO7ne<8U`(+n zbUfkO;MB?@Yu6$XsB}b^Nh?gHTswO08;%W^pS`-ea9h^T zrXL{(f~I^?xz-%@dEt%Dl$lO%j;<54jauZEndIs(BBcA~j#J8X){`1rswSogx^~B{ z4UC$=5uu(CGDYL-th7aQRK3>LZC&7!=z49QZqT}4e`_<=i)luGoi-^?cIuXfvTy(2 z{B?dkVQtjCz1L^et`^$yzVpqo39DSEB)krr9l@F4JUL|bY#~p<`noAU0@`l|YJLA_ zuqWRrddCd?%s?g4dvA>M=07s9b8MA3T~PTmNO7hnkE&L_=gy!AtqvAOo+%vmGdM0~ z?!I?SfzNWwEyWWWOPahnbr{_?ALR?bQ}C3p>5Rg}1?F*uCq%7tHnJ|-`7EhbHtbqt z>XbkiLFV;$LtZ^M>AbUQsoY|PRqUFE1v0Ior>kUCn>4m`sBA8*l4Nc2m@=nZtnA1t zRbdvBhdEQvFSA+e&d4I{e8NZNbGzKx2tn1Zz+aKi zzMfxpB18SZR#Vi>b8_~wtJM9ksGTxa+E%o5H(s?d|@Hm zO%0AkA32h)s)ouYZ50iZy22US;c+r_QFx?dsK!hd4c7%md_tOgTOEWNB0KrAJe7Oq z)N1V0HJkC?hH-+a#-bE)uhK=oYn;FCWK-Q8(D3?%#M507=}uc?XVuK=P;Xzi_`Al! zj#W>0a;v`$Vc4*0)hRx67LCKbExR=qa~liUMYuBF;@}PPH97srSY?p~57*f(GcGG` zpQiM4;n5o_(=D2AM7jza8eLSH9C)KJL)Ory&xQNc1Oe6EffE`Sxv`Kj~YBkac{K`vdf>Y};xk58y_u3pD&t#tcD-CC3DdBP0acwcXH zb=!YPs{37MS+tkV-qOdrHTS>eyAiaH^VV<0D-kzUG~RJIWeb~mD5l1hE;io2Yo?me ztE=x=F2sB;u&>W>_0k+pzTX=YV@ zMmYoL6DG%Y0h^bqv5xak6y5ve>Sx-qBKoM2FYC5+XEEC+cYB-p&72$B*<#D5`cUGVD7=J{$8y>aD{_k3)6X;%SSED7`l)OX z(BLy6Ya`b|jt04ZD};m&TwA=%V#5FAz!NTg#|*g6?Qs}0X<5Lcw-TMA_G6nM}H-rjImJ7_DEFt$;{Q8bG z(VHqfx^l){NqK<`TYmJY9f&+PDYn;la%0mZ1tZCGrm3>>E>6t6I}BAcJWY;hu?B7u z-(JV7sgdY>TI%LSDG$xfIW68PZkL%>EoWGxnXuB;`d6y9o8cMN+jjw%RnJTK9Ao_hgHA3yrSAANl4G60B*gADlV9z9VhleMt zrv8$CY&Tu{#Jr;gOh1$&{;;?hX%#v<TGKKtHlyI z>5hJ_g~INhK5t#aolDD=mJ83iqY`G>cvAhTYJ=|qbEO}%?mhZ-)Y4+7>W)=2V-JPA znP6mdMse$pqx`HN_^bacl3n}psH@?XexDFs^A9~u8ckb#7##vUzlpROasDsz%j0^P zBzL+cR^)Kh@eZfs_NSRnEn;BwebaW_ZeLvB0)|$-Vm;%3i(QlX4xhVoMae6&AStjw zrR$rk%5kxy#g?|Jn+{qoJuh)L$J&2Rz?@cY}bi8nU)f( z{x`65t`}c>d}sSl0iT_n`r@{zzzS}+0m2*E~eUFc0!3+MAC6{&w zG{u?OD&6>{nyavMU2x+&%epCx!|i|WoONu~%{ziA3f_qmS~x;)UW!`cI^$>v!~Z33 zIO68|i`V4di@6lNGwaI(Ve7=(heWp*Z2WQW z0oyu}xPlx;zQ_-r-OKJw)i=5Mee#D*eJc(IZ+xrL@%$*m$E0t`hP{o4jz%&%R=#?+ zwdLdS%Vy0tXa4vS6+NY1EDkmZPS%KMU-m+XrD4IGhjS7S zO}@N<{j`#K!(*dAj@?EJg*0{u$=ou^OOouoJd68PyWxi!iv;^?Z%>l{X~B1BN@286 z`JolkTzZErcTPLeE5);A=>>uRGo-nn2`!#^dqTWK@5>^a2P0i!tX4O{}EdG7xgn% za`q{#>Ny}bU&p(EYes zc+V&9%_o|A4oa{!SzKE+amB_dWpg;TXw3HCxah^_%?8YK-Pd$YykzJ0Smh(Py;pXJ zxA6(b5c>`Vm#GmRpS?MEPwwEI(6m5+%S>tO|JNKgl`}PYK2QJgX=+IKk!c#u7ZbIj zs`*)KIIcWgxn=V-_7>URCX>k9rqgRUrU`I$#HeyS=}THUH+i@9j2p7wRvDx#_Z?~6 z=D3peam0>E-5qxrl+0f+@qb|2sy@|*ZzZRcKwm|S?I2mIPcC0=?(_&cnx6-nZ*`sEVp;* z@fF&H-`JV-a_9S5vU)EVPA)lfa*of3pj~_~n9hb+Efa2VDd|`we0J&={>9EJ*DS3r zt8|38QOt|7yp?tb?xO{7PC0cK2(nSdE)=it^>&jWh2jMZh6d^ zUO88H1{0I^x$VlnO*3})J2({1blA1nb!x>HBL~sgmma^E<_1maDHZHb65&^TymxnH zN16(Uz5>VAQ-^OzSjtIuACTZV7uCuv#1I(4y1QbMT-O>62H|_FyBA(OSen4v=REVN zboXTsJ7H~e2j)4`9BgY*ZiOD1G132dqH=*;kF z$Qb}`rV_sJ9du9z;9 z3W`tOm_3>E%_c8yoq6oSOT<|Zp7!Jn$z$5MR%-$1rKJHL2o$$t|)0gR7 zXT-d;%TuQtosPc~)U`u=riJ*m2jcG~0yslC!xK2SRxvZ4I4ogp@>L|fDnl}xMf9}7 ztSNz;Dp(~ld1e^t9CkWgt-VhJ%e3rtS+~aUOaC+<J zul(_OlU!Sz09}e1ZU6&-;$g56M2tutm*v6?D?Ld|9s-snI?00 zoKE=I!!cv&O@6j(odFzEL;7d_<-7m0&-=4ylY!g>4>Mg=4*owIe!VgfWSOwy?V>la z-R?_vwFqh22wuN;;o-C&N_Bg$ThttWV|ZqEv)-J;mf!v^-Lp63#FLPP!bkqyF_vf%;)=mibA6%0~f>2jX$mLPGC7UO~8-Cjw^1a#C2KO z2^#v$DnY@e9Xn4aWGil1zPUqlMuW3o%F8AE69ru7N!>f=dbA+3*?{d7TiMm`M&3Us zUpu9^BqBMAZ6(Lc2Z{Z)e0u_Ry4)3zQl26IXwA|OYjqqXD9+RnzK``UgT ze``IpF(ksP_(ozHXGiJ9jtMtX=H2*{`mARv$KOqUhGCqGpN4)*;^dog;#(;P*F(_& zYw^mDRx=vzalQ-uSTOadWxB<#2a|Y$R9L60*Pav)bqFBR-Sv z`LI0*ne=ehhfZnXmF)(6Pq=n&y)m({_(kQB345=c{=ahaEbH!_GP{kp7wK)kCGfUT z`DN6Z*QXBedFyzC;+&S#6%KG8b zmA$;{^=^bLe0Jx2*|Yg!CuWxA8=GbEPJ2I9PRo}uVGCejQ{FS|YA6*Nyn;LXaM*5=RE4`VMUiLQLd}{jY zsoP_g1h-GEs?Su-pQ-(i|GjOA#@?LH1<#V(lry$H(V6zlq3)U9@}iq5KDT`Oek3

&1vGPub1@9y88Nxo)c5{%Q^kLKaQ~c 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 z®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

eAEJI-YFPSUka~^rB4m?LAl3uH63-nBdEmGFR~b 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ΝhYF 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%0AZ;HTY;8`Bo*rA9oBQh~Yxs$c=~EiC8jUV+ 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

%(3tVQy!x*7hy=;Jb3ZH{HrMi*t)87gNl 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%0A>lXd^Uq(lcy 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 O1_n=8KbLh*2~7Z=1f?SY literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/performance/images/iosmetal.png b/tensorflow/lite/g3doc/performance/images/iosmetal.png new file mode 100644 index 0000000000000000000000000000000000000000..5e2b8bde8c1dac18ff66920f4f2a3f369f81bb3a GIT binary patch literal 36053 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 B#-soM literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/performance/images/iosrelease.png b/tensorflow/lite/g3doc/performance/images/iosrelease.png new file mode 100644 index 0000000000000000000000000000000000000000..a160c6700e60726d8d9775c4a1c28b3e34b1e930 GIT binary patch literal 75374 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

X-X9Wt`p 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

G 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 literal 0 HcmV?d00001 -- GitLab From 5d7d5a76a339a7ee947aa2e7df812787346e01a5 Mon Sep 17 00:00:00 2001 From: "Wen-Heng (Jack) Chung" Date: Tue, 15 Jan 2019 22:18:50 +0000 Subject: [PATCH 0749/2345] [CUDA] Fix invalid nvcc configuration --- third_party/gpus/cuda_configure.bzl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/third_party/gpus/cuda_configure.bzl b/third_party/gpus/cuda_configure.bzl index af8876ba17..f8f3e90708 100644 --- a/third_party/gpus/cuda_configure.bzl +++ b/third_party/gpus/cuda_configure.bzl @@ -1409,9 +1409,11 @@ def _create_local_cuda_repository(repository_ctx): # bazel's header check failing. cuda_defines["%{extra_no_canonical_prefixes_flags}"] = ( "flag: \"-fno-canonical-system-headers\"") - nvcc_path = "cuda/bin/nvcc" - if _is_windows(repository_ctx): - nvcc_path += ".exe" + nvcc_path = str( + repository_ctx.path("%s/bin/nvcc%s" % ( + cuda_config.cuda_toolkit_path, + ".exe" if _is_windows(repository_ctx) else "", + ))) _tpl( repository_ctx, "crosstool:BUILD", -- GitLab From b4aed7c931041d9d8406ad45fed49e56d7f394e9 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Tue, 15 Jan 2019 14:12:28 -0800 Subject: [PATCH 0750/2345] Make XlaCompiledCpuFunction constructor explicit; NFC PiperOrigin-RevId: 229438156 --- tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h b/tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h index e1da19cf1d..de2e485a47 100644 --- a/tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h +++ b/tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h @@ -112,7 +112,7 @@ class XlaCompiledCpuFunction { RESULTS_PROFILES_AND_TEMPS_ONLY, }; - XlaCompiledCpuFunction( + explicit XlaCompiledCpuFunction( const StaticData& static_data, AllocMode alloc_mode = AllocMode::ARGS_RESULTS_PROFILES_AND_TEMPS); virtual ~XlaCompiledCpuFunction(); -- GitLab From d25d54029fe407932155ad0d527510ebd9ec8e07 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Tue, 15 Jan 2019 14:30:57 -0800 Subject: [PATCH 0751/2345] [TF:XLA] Bump open source llvm revision to r351210 PiperOrigin-RevId: 229441372 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 9a699ae243..81de0fb45b 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -502,11 +502,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 = "066d58d687ea0a97d36135d4aecf6ea31b900cfc74582b2349cadf0c7e4a04fd", - strip_prefix = "llvm-06a887ac8a688a23aa09252f44e9bb95797d057f", + sha256 = "c6e6de0b5cd524acf276188cb6aae427568070cd1b9cb9c0e3aab8044f3ac278", + strip_prefix = "llvm-a42cde684126a3797f3eda9c894c379e7a8c66c1", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/06a887ac8a688a23aa09252f44e9bb95797d057f.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/06a887ac8a688a23aa09252f44e9bb95797d057f.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/a42cde684126a3797f3eda9c894c379e7a8c66c1.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/a42cde684126a3797f3eda9c894c379e7a8c66c1.tar.gz", ], ) -- GitLab From 73b1e8ebbee9246a85acaebcce9b9a8672c22216 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 14:31:41 -0800 Subject: [PATCH 0752/2345] Use default value if the explicit_padding attribute is not specified for Conv2D PiperOrigin-RevId: 229441517 --- tensorflow/core/framework/common_shape_fns.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/framework/common_shape_fns.cc b/tensorflow/core/framework/common_shape_fns.cc index 3d3711ffc5..876ac188ac 100644 --- a/tensorflow/core/framework/common_shape_fns.cc +++ b/tensorflow/core/framework/common_shape_fns.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/framework/common_shape_fns.h" #include "tensorflow/core/framework/attr_value.pb.h" +#include "tensorflow/core/lib/core/errors.h" namespace tensorflow { @@ -498,7 +499,12 @@ Status Conv2DShapeImpl(shape_inference::InferenceContext* c, std::vector explicit_paddings; if (supports_explicit_padding) { - TF_RETURN_IF_ERROR(c->GetAttr("explicit_paddings", &explicit_paddings)); + Status s = c->GetAttr("explicit_paddings", &explicit_paddings); + // Use the default value, which is an empty list, if the attribute is not + // found. Otherwise return the error to the caller. + if (!s.ok() && !errors::IsNotFound(s)) { + return s; + } TF_RETURN_IF_ERROR(CheckValidPadding(padding, explicit_paddings, /*num_dims=*/4, data_format)); } else { -- GitLab From 750b30d2aa9e41802028a1c6693f73fa6ec6be1b Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Tue, 15 Jan 2019 15:09:35 -0800 Subject: [PATCH 0753/2345] Make layer JSON config saved in checkpoints optional for restore In general if a Python string is in the checkpoint but not used directly in the saving program, assert_consumed will pass even if the attribute is totally absent on restore. This should fix checkpoint compatibility with saved_model.load() even without proper Keras revived types. There's no reason it should fail if those aren't available for some reason. PiperOrigin-RevId: 229448783 --- .../checkpointable_object_graph.proto | 4 +++ .../python/training/checkpointable/base.py | 14 ++++++++-- .../training/checkpointable/base_test.py | 28 +++++++++++++++++++ .../python/training/checkpointable/util.py | 11 +++++++- .../python/training/saving/saveable_object.py | 5 ++++ 5 files changed, 58 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/protobuf/checkpointable_object_graph.proto b/tensorflow/core/protobuf/checkpointable_object_graph.proto index 651f692f6d..f2956404b5 100644 --- a/tensorflow/core/protobuf/checkpointable_object_graph.proto +++ b/tensorflow/core/protobuf/checkpointable_object_graph.proto @@ -30,6 +30,10 @@ message CheckpointableObjectGraph { string full_name = 2; // The generated name of the Tensor in the checkpoint. string checkpoint_key = 3; + // Whether checkpoints should be considered as matching even without this + // value restored. Used for non-critical values which don't affect the + // TensorFlow graph, such as layer configurations. + bool optional_restore = 4; } message SlotVariableReference { diff --git a/tensorflow/python/training/checkpointable/base.py b/tensorflow/python/training/checkpointable/base.py index 1aaa85258c..f2797bc5ff 100644 --- a/tensorflow/python/training/checkpointable/base.py +++ b/tensorflow/python/training/checkpointable/base.py @@ -142,8 +142,10 @@ class PythonStringStateSaveable(PythonStateSaveable): state_callback: A function taking no arguments which returns a string. This function is run every time a checkpoint is written. restore_callback: A function taking a Python string, used to restore - state. Optional; defaults to doing nothing. + state. Optional; defaults to doing nothing, in which case it is ignored + by status assertions such as assert_consumed(). """ + self._has_trivial_state_callback = (restore_callback is None) def _state_callback_wrapper(): with ops.init_scope(): return state_callback() @@ -156,6 +158,11 @@ class PythonStringStateSaveable(PythonStateSaveable): super(PythonStringStateSaveable, self).__init__( self._save_string, [spec], name) + @property + def optional_restore(self): + """For values with no restore, relaxes assert_consumed().""" + return self._has_trivial_state_callback + def feed_dict_additions(self): """When running a graph, indicates fresh state to feed.""" return {self._save_string: self._state_callback()} @@ -351,8 +358,9 @@ class _CheckpointPosition(object): # added or deleted. Stores unused attributes so an exception can be # raised if the user decides to check that everything in the # checkpoint was loaded. - self._checkpoint.unused_attributes.setdefault( - self.checkpointable, []).append(serialized_tensor.name) + if not serialized_tensor.optional_restore: + self._checkpoint.unused_attributes.setdefault( + self.checkpointable, []).append(serialized_tensor.name) continue if callable(saveable_factory): saveable = saveable_factory(name=serialized_tensor.checkpoint_key) diff --git a/tensorflow/python/training/checkpointable/base_test.py b/tensorflow/python/training/checkpointable/base_test.py index fd935ac559..736cbee488 100644 --- a/tensorflow/python/training/checkpointable/base_test.py +++ b/tensorflow/python/training/checkpointable/base_test.py @@ -16,6 +16,9 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import functools +import os + from tensorflow.python.framework import ops from tensorflow.python.ops import variable_scope from tensorflow.python.platform import test @@ -57,5 +60,30 @@ class InterfaceTests(test.TestCase): name="v", shape=[], overwrite=False, getter=variable_scope.get_variable) + def testAssertConsumedWithUnusedPythonState(self): + has_config = base.CheckpointableBase() + has_config.get_config = lambda: {} + saved = util.Checkpoint(obj=has_config) + save_path = saved.save(os.path.join(self.get_temp_dir(), "ckpt")) + restored = util.Checkpoint(obj=base.CheckpointableBase()) + restored.restore(save_path).assert_consumed() + + def testAssertConsumedFailsWithUsedPythonState(self): + has_config = base.CheckpointableBase() + attributes = { + "foo_attr": functools.partial( + base.PythonStringStateSaveable, + state_callback=lambda: "", + restore_callback=lambda x: None)} + has_config._gather_saveables_for_checkpoint = lambda: attributes + saved = util.Checkpoint(obj=has_config) + save_path = saved.save(os.path.join(self.get_temp_dir(), "ckpt")) + restored = util.Checkpoint(obj=base.CheckpointableBase()) + status = restored.restore(save_path) + with self.assertRaisesRegexp(AssertionError, "foo_attr"): + status.assert_consumed() + + if __name__ == "__main__": + ops.enable_eager_execution() test.main() diff --git a/tensorflow/python/training/checkpointable/util.py b/tensorflow/python/training/checkpointable/util.py index a45263f5c6..1ff0cd9f25 100644 --- a/tensorflow/python/training/checkpointable/util.py +++ b/tensorflow/python/training/checkpointable/util.py @@ -666,7 +666,13 @@ def _add_attributes_to_object_graph( if cached_attributes is not None: cached_attributes[name] = saveables + optional_restore = None for saveable in saveables: + if optional_restore is None: + optional_restore = saveable.optional_restore + else: + optional_restore = optional_restore and saveable.optional_restore + if hasattr(saveable, "full_name"): attribute.full_name = saveable.full_name if isinstance(saveable, base.PythonStateSaveable): @@ -688,6 +694,9 @@ def _add_attributes_to_object_graph( % (checkpointable, new_feed_key)) feed_additions.update(saveable_feed_dict) named_saveable_objects.append(saveable) + if optional_restore is None: + optional_restore = False + attribute.optional_restore = optional_restore return named_saveable_objects, feed_additions @@ -1020,7 +1029,7 @@ class CheckpointLoadStatus(_LoadStatus): raise AssertionError( ("Unused attributes in these objects (the attributes exist in the " "checkpoint but not in the objects): %s") % ( - self._checkpoint.unused_attributes.items(),)) + list(self._checkpoint.unused_attributes.items()),)) return self def assert_existing_objects_matched(self): diff --git a/tensorflow/python/training/saving/saveable_object.py b/tensorflow/python/training/saving/saveable_object.py index 4b19294b65..981d4580fc 100644 --- a/tensorflow/python/training/saving/saveable_object.py +++ b/tensorflow/python/training/saving/saveable_object.py @@ -66,6 +66,11 @@ class SaveableObject(object): self.name = name self._device = None + @property + def optional_restore(self): + """A hint to restore assertions that this object is optional.""" + return False # Default to required + @property def device(self): """The device for SaveSpec Tensors.""" -- GitLab From ee64c06de75664e937d3f6e7791dac9a9e6731ac Mon Sep 17 00:00:00 2001 From: Yanan Cao Date: Tue, 15 Jan 2019 15:15:19 -0800 Subject: [PATCH 0754/2345] Support nested output structure in xla.compile and tpu.rewrite PiperOrigin-RevId: 229449848 --- tensorflow/contrib/compiler/xla.py | 227 +++++++++++++++------ tensorflow/contrib/tpu/python/tpu/tpu.py | 239 ++++++++++++++++------- 2 files changed, 335 insertions(+), 131 deletions(-) diff --git a/tensorflow/contrib/compiler/xla.py b/tensorflow/contrib/compiler/xla.py index 2aa5d77b10..0f1be500f4 100644 --- a/tensorflow/contrib/compiler/xla.py +++ b/tensorflow/contrib/compiler/xla.py @@ -85,7 +85,14 @@ def compile(computation, inputs=None): # pylint: disable=redefined-builtin with `tf.convert_to_tensor`. Returns: - A list of output tensors. + 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. """ # pylint: disable=protected-access return _compile_internal(computation, inputs) @@ -251,13 +258,21 @@ def _compile_internal(computation, inputs=None): Args: computation: A Python function that builds the computation to compile and execute. - inputs: A list of input tensors or `None` (equivalent to `[]`). Its order - should match ordering of computation arguments. + inputs: A list of inputs 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-dimension 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`. + Returns: - A list of output tensors from computation. + Same data structure as if computation(*inputs) is called directly with some + exceptions for correctness. Exceptions include: 1) None output 2) Single + value output 3) Operation-only outputs Raises: ValueError: If any element in computation outputs is neither an operations or a value that can be converted to tensor. + ValueError: If computation outputs is non-flat and contains any Operations. TypeError: If `inputs` is not a list or tuple. """ if inputs is None: @@ -300,66 +315,166 @@ def _compile_internal(computation, inputs=None): # Restore variable scope after computation. vscope.set_use_resource(saved_use_resource) - # If the computation returns `None`, make it an empty tuple. - if outputs is None: - outputs = tuple() - # If the computation only returned one value, make it a tuple. - if not isinstance(outputs, collections.Sequence): - outputs = (outputs,) - - # Append `no_op` here so that return value of this function always contains - # at least one op that can trigger XlaLaunch node. - outputs += (control_flow_ops.no_op(),) - try: - outputs = [ - o if isinstance(o, ops.Operation) else ops.convert_to_tensor(o) - for o in outputs - ] - except Exception as e: - raise ValueError( - 'XLA computation function return values must all either be Operations' - ' or convertible to Tensors. Got error: "%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( - 'XLA computation function must return zero or more Tensor values ' - 'followed by zero or more Operations.') - output_arity = len(output_tensors) - - new_output_tensors = [] - for t in output_tensors: - with ops.device(t.device if t.device else ''): - new_output_tensors.append(array_ops.identity(t)) + outputs_is_flat = 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) - output_tensors = new_output_tensors context.ExitResult(output_tensors) finally: context.report_unsupported_operations() context.Exit() - outputs = [ - xla_ops.xla_cluster_output(output_tensors[i], name='output{}'.format(i)) - for i in xrange(output_arity) + # When XLA computation returns only operations and no tensors, a NoOp + # dependent on the operations in outputs is returned. Otherwise final + # outputs would be empty and there is no way to trigger returned + # operations. + if not output_tensors: + return control_flow_ops.group(control_deps, name='output_0') + + output_tensors = [ + xla_ops.xla_cluster_output(o, name='output{}'.format(i)) + for i, o in enumerate(output_tensors) ] - with ops.control_dependencies(output_operations): - if output_arity == 0: - # When XLA computation returns only operations and no tensors, a NoOp - # dependent on the operations in outputs is returned. Otherwise final - # outputs would be empty and there is no way to trigger returned - # operations. - return control_flow_ops.no_op(name='output_0') - else: - # Wraps the outputs in identity operators that carries control - # dependencies. - return [ - array_ops.identity(outputs[i], name='output_%d' % i) - for i in xrange(output_arity) - ] + with ops.control_dependencies(control_deps): + # Wraps the outputs in identity operators that carries control + # dependencies. + output_tensors = [ + array_ops.identity(o, name='output_%d' % i) + for i, o in enumerate(output_tensors) + ] + + # If `computation` returned non-flat output structure, pack output tensors + # back into same structure. + if not outputs_is_flat: + output_tensors = nest.pack_sequence_as( + structure=outputs, flat_sequence=output_tensors) + + return output_tensors + + +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 _postprocess_flat_outputs(outputs): + """Validates flat outputs and adds back device assignments. + + Args: + outputs: Output from `computation` inside `xla.compile`. + + 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, make it a tuple. + if not isinstance(outputs, collections.Sequence): + outputs = (outputs,) + + # Append `no_op` here so that return value of this function always contains + # at least one op that can trigger XlaLaunch node. + outputs += (control_flow_ops.no_op(),) + try: + outputs = [ + o if isinstance(o, ops.Operation) else ops.convert_to_tensor(o) + for o in outputs + ] + except Exception as e: + raise ValueError( + 'XLA computation function return values must all either be Operations' + ' or convertible to Tensors. Got error: "%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( + 'XLA computation function must return zero or more Tensor values ' + 'followed by zero or more Operations.') + + new_output_tensors = [] + for t in output_tensors: + with ops.device(t.device if t.device else ''): + new_output_tensors.append(array_ops.identity(t)) + + return new_output_tensors, output_operations + + +def _postprocess_non_flat_outputs(outputs): + """Validates non-flat outputs and adds back device assignments. + + Args: + outputs: Output from `computation` inside `xla.compile`. + + Returns: + Tensors extracted from outputs and an empty list because Operations are not + allowed in non-flat outputs.. + """ + # Convert all non-Operation outputs to Tensors. + new_output_tensors = [] + for o in nest.flatten(outputs): + if isinstance(o, ops.Operation): + raise ValueError( + 'xla.compile 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( + 'XLA computation function return values must all either be ' + 'Operations or convertible to Tensors. Got error: "%s"' % str(e)) + + # Makes sure even pass-through inputs/outputs are touched in compile + # context by creating an Identity node inside compile context. + with ops.device(o.device if o.device else ''): + new_output_tensors.append(array_ops.identity(o)) + + return new_output_tensors, [] @contextlib.contextmanager diff --git a/tensorflow/contrib/tpu/python/tpu/tpu.py b/tensorflow/contrib/tpu/python/tpu/tpu.py index a267dd435c..b04baebfe6 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu.py @@ -19,6 +19,7 @@ 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 @@ -503,7 +504,17 @@ def replicate(computation, replicas is equal to the number of cores in the TPU system. name: (Deprecated) Does nothing. Returns: - A list of lists of output tensors, indexed by `[replica_num][output_num]`. + 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 @@ -712,51 +723,12 @@ def split_compile_and_replicate(computation, vscope.set_use_resource(saved_use_resource) vscope.set_custom_getter(saved_custom_getter) - # 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, (list, tuple)): - 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.") - output_arity = len(output_tensors) + 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) - # 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) - output_tensors = new_output_tensors context.ExitResult(output_tensors) finally: context.report_unsupported_operations() @@ -768,11 +740,6 @@ def split_compile_and_replicate(computation, 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 - # Fan-out: Builds a TPUReplicatedOutput node for each output. - outputs = [tpu_ops.tpu_replicated_output(output_tensors[i], num_replicas, - name="output{}".format(i)) - for i in xrange(output_arity)] - with ops.control_dependencies([metadata]): if use_tpu: compile_status = tpu_ops.tpu_compilation_result() @@ -782,28 +749,146 @@ def split_compile_and_replicate(computation, else: compile_status = control_flow_ops.no_op(name="compilation_status") - with ops.control_dependencies(output_operations): - if output_arity == 0: - # Returns a list of NoOps dependent on the replication Op, indexed by - # [replica_num]. - return [ - compile_status, [ - control_flow_ops.no_op(name="shard_%d" % i) - for i in range(num_replicas) - ] - ] - else: - # Wraps the outputs in identity operators so the names of any possible - # `fetch` nodes are preserved by the replication rewrite. - return [ - compile_status, [[ - array_ops.identity( - outputs[out][replica], - name="output_%d_shard_%d" % (out, replica)) - for out in xrange(output_arity) - ] - for replica in xrange(num_replicas)] + 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, @@ -1126,11 +1211,15 @@ def rewrite(computation, case the core attached to task 0, TPU device 0 is used. name: (Deprecated) Does nothing. Returns: - A list of output tensors. + 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. """ - if inputs is not None and not isinstance(inputs, (list, tuple)): - raise TypeError("tpu.rewrite() inputs must be a list or tuple") - # TODO(b/36647078) remove disable when pylint bug is fixed. # pylint: disable=indexing-exception return replicate( -- GitLab From bea8a4a3a1fb0282b0c59a687d25c6ae160316fc Mon Sep 17 00:00:00 2001 From: Guangda Lai <31743510+aaroey@users.noreply.github.com> Date: Tue, 15 Jan 2019 15:46:33 -0800 Subject: [PATCH 0755/2345] Fix formatting issues and broken build --- tensorflow/contrib/tensorrt/BUILD | 2 +- .../tensorrt/python/trt_convert_test.py | 8 ++-- .../tensorrt/resources/trt_lru_cache.h | 38 ++++++++++----- tensorflow/contrib/tensorrt/test/base_test.py | 14 +++--- .../tensorrt/test/batch_matmul_test.py | 2 +- .../tensorrt/test/biasadd_matmul_test.py | 2 +- .../binary_tensor_weight_broadcast_test.py | 2 +- .../tensorrt/test/concatenation_test.py | 2 +- .../tensorrt/test/const_broadcast_test.py | 2 +- .../contrib/tensorrt/test/conv2d_test.py | 12 ++--- .../test/dynamic_input_shapes_test.py | 31 ++++++------ .../tensorrt/test/identity_output_test.py | 4 +- .../contrib/tensorrt/test/int32_test.py | 4 +- .../contrib/tensorrt/test/lru_cache_test.py | 48 ++++++++++++------- .../tensorrt/test/memory_alignment_test.py | 2 +- .../multi_connection_neighbor_engine_test.py | 2 +- .../tensorrt/test/neighboring_engine_test.py | 2 +- .../tensorrt/test/quantization_test.py | 2 +- .../contrib/tensorrt/test/rank_two_test.py | 2 +- .../tensorrt/test/reshape_transpose_test.py | 4 +- .../test/tf_trt_integration_test_base.py | 46 +++++++++--------- tensorflow/contrib/tensorrt/test/topk_test.py | 6 +-- .../contrib/tensorrt/test/unary_test.py | 2 +- .../tensorrt/test/vgg_block_nchw_test.py | 2 +- .../contrib/tensorrt/test/vgg_block_test.py | 2 +- 25 files changed, 138 insertions(+), 105 deletions(-) diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 58c188b314..7ae3caaaa2 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -567,8 +567,8 @@ cc_library( hdrs = ["convert/utils.h"], copts = tf_copts(), deps = [ - "//tensorflow/core:lib", "//tensorflow/core:framework", + "//tensorflow/core:lib", ], ) diff --git a/tensorflow/contrib/tensorrt/python/trt_convert_test.py b/tensorflow/contrib/tensorrt/python/trt_convert_test.py index 60c31580db..14ab4772c4 100644 --- a/tensorflow/contrib/tensorrt/python/trt_convert_test.py +++ b/tensorflow/contrib/tensorrt/python/trt_convert_test.py @@ -239,8 +239,8 @@ class TrtConvertTest(test_util.TensorFlowTestCase): # Run with batch size 2, a new engine is created and cached. self._TestRun(sess, 2, True) # Run with batch size 3, since the number of cached engines has reached - # the max, it should fall back to TF function. - self._TestRun(sess, 3, False) + # the max, it should evict an old engine and create a new one. + self._TestRun(sess, 3, True) # Test the output SavedModel with ops.Graph().as_default(): @@ -251,8 +251,8 @@ class TrtConvertTest(test_util.TensorFlowTestCase): # Run with batch size 2, a new engine is created and cached. self._TestRun(sess, 2, True) # Run with batch size 3, since the number of cached engines has reached - # the max, it should fall back to TF function. - self._TestRun(sess, 3, False) + # the max, it should evict an old engine and create a new one. + self._TestRun(sess, 3, True) def testCreateInferenceGraph_StaticOp(self): if not trt_convert.is_tensorrt_enabled(): diff --git a/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h b/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h index ba455b9b35..afd1b83e74 100644 --- a/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h +++ b/tensorflow/contrib/tensorrt/resources/trt_lru_cache.h @@ -1,5 +1,20 @@ -#ifndef TENSORFLOW_CONTRIB_TENSORRT_TRT_LRU_CACHE_H_ -#define TENSORFLOW_CONTRIB_TENSORRT_TRT_LRU_CACHE_H_ +/* 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_CONTRIB_TENSORRT_RESOURCES_TRT_LRU_CACHE_H_ +#define TENSORFLOW_CONTRIB_TENSORRT_RESOURCES_TRT_LRU_CACHE_H_ #include #include @@ -9,9 +24,9 @@ #include "tensorflow/core/framework/resource_mgr.h" #include "tensorflow/core/lib/core/errors.h" -#if GOOGLE_CUDA -#if GOOGLE_TENSORRT +#if GOOGLE_CUDA && GOOGLE_TENSORRT #include "tensorrt/include/NvInfer.h" +#endif // GOOGLE_CUDA && GOOGLE_TENSORRT namespace tensorflow { namespace tensorrt { @@ -40,9 +55,7 @@ class LRUCache { size_t count(const key_type& key) const { return objects_.count(key); } - value_type& at(const key_type& key) { - return Touch(key); - } + value_type& at(const key_type& key) { return Touch(key); } const_iterator begin() const { return objects_.begin(); } const_iterator end() const { return objects_.end(); } @@ -111,6 +124,9 @@ struct VectorTensorShapeHasher { } }; +#if GOOGLE_CUDA +#if GOOGLE_TENSORRT + struct EngineContext { EngineContext() {} // Creates an empty context. EngineContext( @@ -167,10 +183,10 @@ class TRTEngineCacheResource : public tensorflow::ResourceBase { cache_; }; -} // namespace tensorrt -} // namespace tensorflow - #endif // GOOGLE_TENSORRT #endif // GOOGLE_CUDA -#endif // TENSORFLOW_CONTRIB_TENSORRT_TRT_LRU_CACHE_H_ +} // namespace tensorrt +} // namespace tensorflow + +#endif // TENSORFLOW_CONTRIB_TENSORRT_RESOURCES_TRT_LRU_CACHE_H_ diff --git a/tensorflow/contrib/tensorrt/test/base_test.py b/tensorflow/contrib/tensorrt/test/base_test.py index 0f6293b166..17e0b6f4d2 100644 --- a/tensorflow/contrib/tensorrt/test/base_test.py +++ b/tensorflow/contrib/tensorrt/test/base_test.py @@ -70,7 +70,7 @@ class SimpleSingleEngineTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(100, 6, 6, 6)]]) + expected_output_dims=[[[100, 6, 6, 6]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -127,7 +127,7 @@ class SimpleMultiEnginesTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(100, 12, 12, 6)]]) + expected_output_dims=[[[100, 12, 12, 6]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -185,7 +185,7 @@ class PartiallyConvertedTestA(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[tuple(input_dims)]]) + expected_output_dims=[[input_dims]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -255,7 +255,7 @@ class ConstInputTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[tuple(input_dims)]]) + expected_output_dims=[[input_dims]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -288,7 +288,7 @@ class ConstDataInputSingleEngineTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[tuple(input_dims)]]) + expected_output_dims=[[input_dims]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -322,7 +322,7 @@ class ConstDataInputMultipleEnginesTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[tuple(input_dims)]]) + expected_output_dims=[[input_dims]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -371,7 +371,7 @@ class ControlDependencyTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[tuple(input_dims)]]) + expected_output_dims=[[input_dims]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/batch_matmul_test.py b/tensorflow/contrib/tensorrt/test/batch_matmul_test.py index 1b015dd369..46e3407d96 100644 --- a/tensorflow/contrib/tensorrt/test/batch_matmul_test.py +++ b/tensorflow/contrib/tensorrt/test/batch_matmul_test.py @@ -73,7 +73,7 @@ class BatchMatMulTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name, w1_name, w2_name], input_dims=[[input_dims, w1_dims, w2_dims]], output_names=[output_name], - expected_output_dims=[[(12, 5, 8, 7)]]) + expected_output_dims=[[[12, 5, 8, 7]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/biasadd_matmul_test.py b/tensorflow/contrib/tensorrt/test/biasadd_matmul_test.py index 8a5f8ef5d5..ca23629efa 100644 --- a/tensorflow/contrib/tensorrt/test/biasadd_matmul_test.py +++ b/tensorflow/contrib/tensorrt/test/biasadd_matmul_test.py @@ -113,7 +113,7 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(4, 6680)]]) + expected_output_dims=[[[4, 6680]]]) def GetConversionParams(self, run_params): """Return a ConversionParams for test.""" diff --git a/tensorflow/contrib/tensorrt/test/binary_tensor_weight_broadcast_test.py b/tensorflow/contrib/tensorrt/test/binary_tensor_weight_broadcast_test.py index 0083695a50..846fd009db 100644 --- a/tensorflow/contrib/tensorrt/test/binary_tensor_weight_broadcast_test.py +++ b/tensorflow/contrib/tensorrt/test/binary_tensor_weight_broadcast_test.py @@ -65,7 +65,7 @@ class BinaryTensorWeightBroadcastTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(5, 23040)]]) + expected_output_dims=[[[5, 23040]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/concatenation_test.py b/tensorflow/contrib/tensorrt/test/concatenation_test.py index e6afe4e3f6..5d8742ae35 100644 --- a/tensorflow/contrib/tensorrt/test/concatenation_test.py +++ b/tensorflow/contrib/tensorrt/test/concatenation_test.py @@ -75,7 +75,7 @@ class ConcatenationTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(2, 126)]]) + expected_output_dims=[[[2, 126]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/const_broadcast_test.py b/tensorflow/contrib/tensorrt/test/const_broadcast_test.py index 56a297afb5..9137d0072f 100644 --- a/tensorflow/contrib/tensorrt/test/const_broadcast_test.py +++ b/tensorflow/contrib/tensorrt/test/const_broadcast_test.py @@ -60,7 +60,7 @@ class ConstBroadcastTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(5, 12, 12, 1)]]) + expected_output_dims=[[[5, 12, 12, 1]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/conv2d_test.py b/tensorflow/contrib/tensorrt/test/conv2d_test.py index d4cee3cce0..e7993b4620 100644 --- a/tensorflow/contrib/tensorrt/test/conv2d_test.py +++ b/tensorflow/contrib/tensorrt/test/conv2d_test.py @@ -103,9 +103,9 @@ class Conv2DNCHWTest(trt_test.TfTrtIntegrationTestBase): return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), input_names=["input"], - input_dims=[input_dims], + input_dims=[[input_dims]], output_names=["output"], - expected_output_dims=[(13, 5, 7, 11)]) + expected_output_dims=[[[13, 5, 7, 11]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -128,9 +128,9 @@ class Conv2DNHWCTest(trt_test.TfTrtIntegrationTestBase): return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), input_names=["input"], - input_dims=[input_dims], + input_dims=[[input_dims]], output_names=["output"], - expected_output_dims=[(13, 7, 11, 5)]) + expected_output_dims=[[[13, 7, 11, 5]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -178,9 +178,9 @@ class Conv2DStridedNCHWTest(trt_test.TfTrtIntegrationTestBase): return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), input_names=[input_name], - input_dims=[input_dims], + input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[(n, num_filters, h, w)]) + expected_output_dims=[[[n, num_filters, h, w]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py b/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py index 1c6d411d11..90addecb14 100644 --- a/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py +++ b/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py @@ -18,17 +18,18 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.tensorrt.test import tf_trt_integration_test_base as trt_test +import numpy as np + # pylint: disable=unused-import from tensorflow.contrib.tensorrt.python.ops import trt_engine_op # pylint: enable=unused-import +from tensorflow.contrib.tensorrt.test import tf_trt_integration_test_base as trt_test +from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops -from tensorflow.python.framework import constant_op from tensorflow.python.ops import array_ops from tensorflow.python.ops import nn from tensorflow.python.platform import test -import numpy as np class DynamicInputShapesTest(trt_test.TfTrtIntegrationTestBase): @@ -47,12 +48,10 @@ class DynamicInputShapesTest(trt_test.TfTrtIntegrationTestBase): g = ops.Graph() with g.as_default(): - x = array_ops.placeholder(shape=(None, None, None, 1), - dtype=dtypes.float32, - name='input') - conv_filter1 = constant_op.constant(np.ones([3, 3, 1, 8]), - name="weights1", - dtype=dtypes.float32) + x = array_ops.placeholder( + shape=(None, None, None, 1), dtype=dtypes.float32, name="input") + conv_filter1 = constant_op.constant( + np.ones([3, 3, 1, 8]), name="weights1", dtype=dtypes.float32) bias1 = constant_op.constant(np.random.randn(8), dtype=dtypes.float32) x = nn.conv2d( input=x, @@ -62,9 +61,8 @@ class DynamicInputShapesTest(trt_test.TfTrtIntegrationTestBase): name="conv") x = nn.bias_add(x, bias1) x = nn.relu(x) - conv_filter2 = constant_op.constant(np.ones([3, 3, 8, 1]), - name="weights2", - dtype=dtypes.float32) + conv_filter2 = constant_op.constant( + np.ones([3, 3, 8, 1]), name="weights2", dtype=dtypes.float32) bias2 = constant_op.constant(np.random.randn(1), dtype=dtypes.float32) x = nn.conv2d( input=x, @@ -73,13 +71,13 @@ class DynamicInputShapesTest(trt_test.TfTrtIntegrationTestBase): padding="SAME", name="conv") x = nn.bias_add(x, bias2) - x = array_ops.identity(x, name='output') + x = array_ops.identity(x, name="output") return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), - input_names=['input'], + input_names=["input"], input_dims=input_dims, - output_names=['output'], + output_names=["output"], expected_output_dims=expected_output_dims) def GetConversionParams(self, run_params): @@ -107,5 +105,6 @@ class DynamicInputShapesTest(trt_test.TfTrtIntegrationTestBase): """The relative tolerance to compare floating point results.""" return 1.e-03 if run_params.precision_mode == "FP32" else 1.e-01 -if __name__ == '__main__': + +if __name__ == "__main__": test.main() diff --git a/tensorflow/contrib/tensorrt/test/identity_output_test.py b/tensorflow/contrib/tensorrt/test/identity_output_test.py index 196ccdbe9d..b568eeda94 100644 --- a/tensorflow/contrib/tensorrt/test/identity_output_test.py +++ b/tensorflow/contrib/tensorrt/test/identity_output_test.py @@ -61,9 +61,9 @@ class IdentityTest(trt_test.TfTrtIntegrationTestBase): return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), input_names=[input_name], - input_dims=[input_dims], + input_dims=[[input_dims]], output_names=['output1', 'output2', 'output3'], - expected_output_dims=[(100, 4), (100, 4), (100, 4)]) + expected_output_dims=[[[100, 4]] * 3]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/int32_test.py b/tensorflow/contrib/tensorrt/test/int32_test.py index 181229c8a7..8cf5387038 100644 --- a/tensorflow/contrib/tensorrt/test/int32_test.py +++ b/tensorflow/contrib/tensorrt/test/int32_test.py @@ -52,9 +52,9 @@ class ExcludeUnsupportedInt32Test(trt_test.TfTrtIntegrationTestBase): return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), input_names=[input_name], - input_dims=[input_dims], + input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[(100, 10)]) + expected_output_dims=[[[100, 10]]]) def GetConversionParams(self, run_params): """Return a ConversionParams for test.""" diff --git a/tensorflow/contrib/tensorrt/test/lru_cache_test.py b/tensorflow/contrib/tensorrt/test/lru_cache_test.py index 74b51932ac..7702413e6c 100644 --- a/tensorflow/contrib/tensorrt/test/lru_cache_test.py +++ b/tensorflow/contrib/tensorrt/test/lru_cache_test.py @@ -1,3 +1,19 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Test LRUCache by running different input batch sizes on same network.""" + from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -17,29 +33,28 @@ from tensorflow.python.platform import test class LRUCacheTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): - """Test for running network multiple times with different batch size to - check that lru_cache works correctly""" dtype = dtypes.float32 input_name = "input" input_dims = [[[1, 10, 10, 2]], [[2, 10, 10, 2]], [[4, 10, 10, 2]], [[2, 10, 10, 2]]] - expected_output_dims = [[(1, 10, 10, 1)], [(2, 10, 10, 1)], - [(4, 10, 10, 1)], [(2, 10, 10, 1)]] + expected_output_dims = [[[1, 10, 10, 1]], [[2, 10, 10, 1]], [[4, 10, 10, + 1]], + [[2, 10, 10, 1]]] output_name = "output" g = ops.Graph() with g.as_default(): - x = array_ops.placeholder(dtype=dtype, - shape=[None, 10, 10, 2], - name=input_name) - conv_filter = constant_op.constant(np.random.randn(3, 3, 2, 1), - dtype=dtypes.float32) - x = nn.conv2d(input=x, - filter=conv_filter, - strides=[1, 1, 1, 1], - padding="SAME", - name="conv") - bias = constant_op.constant(np.random.randn(1, 10, 10, 1), - dtype=dtypes.float32) + x = array_ops.placeholder( + dtype=dtype, shape=[None, 10, 10, 2], name=input_name) + conv_filter = constant_op.constant( + np.random.randn(3, 3, 2, 1), dtype=dtypes.float32) + x = nn.conv2d( + input=x, + filter=conv_filter, + strides=[1, 1, 1, 1], + padding="SAME", + name="conv") + bias = constant_op.constant( + np.random.randn(1, 10, 10, 1), dtype=dtypes.float32) x = math_ops.add(x, bias) x = nn.relu(x) x = array_ops.identity(x, name="output") @@ -58,5 +73,6 @@ class LRUCacheTest(trt_test.TfTrtIntegrationTestBase): return (run_params.dynamic_engine and not trt_test.IsQuantizationMode(run_params.precision_mode)) + if __name__ == "__main__": test.main() diff --git a/tensorflow/contrib/tensorrt/test/memory_alignment_test.py b/tensorflow/contrib/tensorrt/test/memory_alignment_test.py index 6142e6d8e3..cc64329bbd 100644 --- a/tensorflow/contrib/tensorrt/test/memory_alignment_test.py +++ b/tensorflow/contrib/tensorrt/test/memory_alignment_test.py @@ -64,7 +64,7 @@ class MemoryAlignmentTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(2, 15, 15, 10)]]) + expected_output_dims=[[[2, 15, 15, 10]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/multi_connection_neighbor_engine_test.py b/tensorflow/contrib/tensorrt/test/multi_connection_neighbor_engine_test.py index fcf89d3a74..a14bb0396e 100644 --- a/tensorflow/contrib/tensorrt/test/multi_connection_neighbor_engine_test.py +++ b/tensorflow/contrib/tensorrt/test/multi_connection_neighbor_engine_test.py @@ -77,7 +77,7 @@ class MultiConnectionNeighborEngineTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(2, 4, 5, 4)]]) + expected_output_dims=[[[2, 4, 5, 4]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/neighboring_engine_test.py b/tensorflow/contrib/tensorrt/test/neighboring_engine_test.py index 840344aeba..06a86bbb8d 100644 --- a/tensorflow/contrib/tensorrt/test/neighboring_engine_test.py +++ b/tensorflow/contrib/tensorrt/test/neighboring_engine_test.py @@ -61,7 +61,7 @@ class NeighboringEngineTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(2, 4, 5, 4)]]) + expected_output_dims=[[[2, 4, 5, 4]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/quantization_test.py b/tensorflow/contrib/tensorrt/test/quantization_test.py index fb09980552..ce1b25ebf3 100644 --- a/tensorflow/contrib/tensorrt/test/quantization_test.py +++ b/tensorflow/contrib/tensorrt/test/quantization_test.py @@ -62,7 +62,7 @@ def _GetParams(add_quantization_nodes, dtype=dtypes.float32): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(8, 1)]]) + expected_output_dims=[[[8, 1]]]) class QuantizationMissingAllRangesTest(trt_test.TfTrtIntegrationTestBase): diff --git a/tensorflow/contrib/tensorrt/test/rank_two_test.py b/tensorflow/contrib/tensorrt/test/rank_two_test.py index 85ce89e151..97159bb008 100644 --- a/tensorflow/contrib/tensorrt/test/rank_two_test.py +++ b/tensorflow/contrib/tensorrt/test/rank_two_test.py @@ -65,7 +65,7 @@ class RankTwoTest(trt_test.TfTrtIntegrationTestBase): input_names=input_names, input_dims=[input_dims], output_names=[output_name], - expected_output_dims=[[tuple(input_dims[1])]]) + expected_output_dims=[[input_dims[1]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py index 530c4992db..7fb2cbde07 100644 --- a/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py +++ b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py @@ -74,7 +74,7 @@ class ReshapeTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[tuple(input_dims)]]) + expected_output_dims=[[input_dims]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" @@ -131,7 +131,7 @@ class TransposeTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(24, 100, 2, 24)]]) + expected_output_dims=[[[24, 100, 2, 24]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py index db6be3828c..f13c615cb1 100644 --- a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py +++ b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py @@ -39,17 +39,19 @@ from tensorflow.python.framework import test_util from tensorflow.python.ops import math_ops from tensorflow.python.platform import tf_logging as logging -TfTrtIntegrationTestParams = namedtuple("TfTrtIntegrationTestParams", [ - "gdef", - # A list of names of the input placeholder nodes. - "input_names", - # A list of list of output shapes of the input placeholder nodes. - "input_dims", - # A list of names of the output identity nodes. - "output_names", - # A list of list of expected output shapes of the output identity nodes. - "expected_output_dims" -]) +TfTrtIntegrationTestParams = namedtuple( + "TfTrtIntegrationTestParams", + [ + "gdef", + # A list of names of the input placeholder nodes. + "input_names", + # A list of list of output shapes of the input placeholder nodes. + "input_dims", + # A list of names of the output identity nodes. + "output_names", + # A list of list of expected output shapes of the output identity nodes. + "expected_output_dims" + ]) RunParams = namedtuple("RunParams", [ "use_optimizer", "precision_mode", "dynamic_engine", "test_name", @@ -167,10 +169,9 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): def GetConversionParams(self, run_params): """Return a ConversionParams for test.""" - batch_list + batch_list = [] for dims_list in self._GetParamsCached().input_dims: - if not len(dims_list): - continue + assert dims_list # Each list of shapes should have same batch size. input_batches = [dims[0] for dims in dims_list] assert max(input_batches) == min(input_batches) @@ -323,19 +324,20 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): with self.test_session( graph=g, config=config, use_gpu=True, force_gpu=True) as sess: # Run for each input(s) shape - for ind in range(len(inputs_data)): + for shape_index in range(len(inputs_data)): val = None # Defaults to 2 runs to verify result across multiple runs is same. for _ in range(num_runs): self._PrepareRun(graph_state) - new_val = sess.run( - outputs, - {inputs[i]: inputs_data[ind][i] for i in range(len(inputs))}) - output_len = len(params.expected_output_dims[ind]) + new_val = sess.run(outputs, { + inputs[i]: inputs_data[shape_index][i] + for i in range(len(inputs)) + }) + output_len = len(params.expected_output_dims[shape_index]) self.assertEqual(output_len, len(new_val)) for i in range(output_len): self.assertEqual( - list(params.expected_output_dims[ind][i]), + list(params.expected_output_dims[shape_index][i]), list(new_val[i].shape)) if val is not None: self.assertAllClose(val, new_val, atol=1.e-06, rtol=1.e-06) @@ -506,8 +508,8 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): current_input_data = [] for i in range(len(params.input_names)): dtype = input_dtypes[params.input_names[i]] - # Multiply the input by some constant to avoid all zeros input for integer - # types. + # Multiply the input by some constant to avoid all zeros input for + # integer types. scale = 10.0 if np.issubdtype(dtype, np.integer) else 1.0 dims = inp[i] # TODO(laigd): add debug options. E.g. we can set the input data to be diff --git a/tensorflow/contrib/tensorrt/test/topk_test.py b/tensorflow/contrib/tensorrt/test/topk_test.py index 263ea0cfa1..633a51982b 100644 --- a/tensorflow/contrib/tensorrt/test/topk_test.py +++ b/tensorflow/contrib/tensorrt/test/topk_test.py @@ -38,16 +38,16 @@ class TopKTest(trt_test.TfTrtIntegrationTestBase): g = ops.Graph() with g.as_default(): x = array_ops.placeholder(dtype=dtype, shape=input_dims, name=input_name) - k_tensor = constant_op.constant(k, dtype=tf.int32, name="Const") + k_tensor = constant_op.constant(k, dtype=dtypes.int32, name="Const") values, indices = nn_ops.top_k(x, k_tensor, name="TopK") values = array_ops.identity(values, name="output_values") indices = array_ops.identity(indices, name="output_indices") return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), input_names=[input_name], - input_dims=[input_dims], + input_dims=[[input_dims]], output_names=["output_values", "output_indices"], - expected_output_dims=[(100, k), (100, k)]) + expected_output_dims=[[[100, k], [100, k]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/unary_test.py b/tensorflow/contrib/tensorrt/test/unary_test.py index f630d695cf..497ea2848a 100644 --- a/tensorflow/contrib/tensorrt/test/unary_test.py +++ b/tensorflow/contrib/tensorrt/test/unary_test.py @@ -102,7 +102,7 @@ class UnaryTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name, input2_name], input_dims=[[input_dims, input2_dims]], output_names=[output_name], - expected_output_dims=[[(12, 5, 8, 12)]]) + expected_output_dims=[[[12, 5, 8, 12]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/vgg_block_nchw_test.py b/tensorflow/contrib/tensorrt/test/vgg_block_nchw_test.py index 1a4746e8d0..b5fed73d2d 100644 --- a/tensorflow/contrib/tensorrt/test/vgg_block_nchw_test.py +++ b/tensorflow/contrib/tensorrt/test/vgg_block_nchw_test.py @@ -72,7 +72,7 @@ class VGGBlockNCHWTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(5, 6, 2, 2)]]) + expected_output_dims=[[[5, 6, 2, 2]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/contrib/tensorrt/test/vgg_block_test.py b/tensorflow/contrib/tensorrt/test/vgg_block_test.py index 051ced055a..307128f1a8 100644 --- a/tensorflow/contrib/tensorrt/test/vgg_block_test.py +++ b/tensorflow/contrib/tensorrt/test/vgg_block_test.py @@ -63,7 +63,7 @@ class VGGBlockTest(trt_test.TfTrtIntegrationTestBase): input_names=[input_name], input_dims=[[input_dims]], output_names=[output_name], - expected_output_dims=[[(5, 2, 2, 6)]]) + expected_output_dims=[[[5, 2, 2, 6]]]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" -- GitLab From f9b9cf52eb36a5d5e8bbf96fc1f2b6f584ce7867 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Tue, 15 Jan 2019 16:08:22 -0800 Subject: [PATCH 0756/2345] Checkpointable->AutoCheckpointable CheckpointableBase->Checkpointable In preparation for adding public symbols. PiperOrigin-RevId: 229459684 --- tensorflow/contrib/checkpoint/__init__.py | 4 +- .../contrib/checkpoint/python/containers.py | 2 +- .../checkpoint/python/containers_test.py | 4 +- .../contrib/checkpoint/python/python_state.py | 4 +- .../checkpoint/python/split_dependency.py | 2 +- .../python/split_dependency_test.py | 6 +- .../cudnn_rnn/python/ops/cudnn_rnn_ops.py | 2 +- .../distribute/python/checkpointing_test.py | 2 +- .../contrib/eager/python/metrics_impl.py | 2 +- tensorflow/contrib/eager/python/tfe.py | 2 +- .../optimizer_v2/checkpointable_utils_test.py | 10 +- tensorflow/python/data/ops/iterator_ops.py | 4 +- tensorflow/python/distribute/values.py | 8 +- .../feature_column/feature_column_v2.py | 2 +- tensorflow/python/keras/engine/base_layer.py | 2 +- tensorflow/python/keras/engine/network.py | 2 +- tensorflow/python/keras/engine/training.py | 2 +- tensorflow/python/keras/layers/recurrent.py | 2 +- .../python/keras/optimizer_v2/optimizer_v2.py | 2 +- tensorflow/python/keras/optimizers.py | 2 +- tensorflow/python/layers/base.py | 2 +- tensorflow/python/ops/lookup_ops.py | 4 +- tensorflow/python/ops/rnn_cell_impl.py | 8 +- tensorflow/python/ops/stateful_random_ops.py | 2 +- tensorflow/python/ops/template.py | 2 +- tensorflow/python/ops/variables.py | 2 +- tensorflow/python/saved_model/load.py | 3 +- tensorflow/python/saved_model/load_test.py | 58 +++++----- .../python/saved_model/revived_types_test.py | 4 +- tensorflow/python/saved_model/save.py | 2 +- tensorflow/python/saved_model/save_test.py | 18 ++-- .../python/training/checkpointable/base.py | 12 +-- .../training/checkpointable/base_test.py | 16 +-- .../checkpointable/data_structures.py | 18 ++-- .../checkpointable/data_structures_test.py | 38 +++---- .../training/checkpointable/tracking.py | 8 +- .../training/checkpointable/tracking_test.py | 46 ++++---- .../python/training/checkpointable/util.py | 4 +- .../training/checkpointable/util_test.py | 100 +++++++++--------- .../util_with_v1_optimizers_test.py | 14 +-- .../training/checkpointable/util_xla_test.py | 2 +- tensorflow/python/training/optimizer.py | 2 +- tensorflow/python/training/saver_test.py | 6 +- .../training/saving/saveable_object_util.py | 4 +- .../api/golden/v1/tensorflow.-variable.pbtxt | 2 +- .../golden/v1/tensorflow.data.-iterator.pbtxt | 2 +- .../golden/v1/tensorflow.keras.-model.pbtxt | 2 +- .../v1/tensorflow.keras.-sequential.pbtxt | 2 +- ....experimental.-peephole-l-s-t-m-cell.pbtxt | 2 +- .../tensorflow.keras.layers.-activation.pbtxt | 2 +- ...eras.layers.-activity-regularization.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-add.pbtxt | 2 +- ...nsorflow.keras.layers.-alpha-dropout.pbtxt | 2 +- ...low.keras.layers.-average-pooling1-d.pbtxt | 2 +- ...low.keras.layers.-average-pooling2-d.pbtxt | 2 +- ...low.keras.layers.-average-pooling3-d.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-average.pbtxt | 2 +- ...tensorflow.keras.layers.-avg-pool1-d.pbtxt | 2 +- ...tensorflow.keras.layers.-avg-pool2-d.pbtxt | 2 +- ...tensorflow.keras.layers.-avg-pool3-d.pbtxt | 2 +- ...ow.keras.layers.-batch-normalization.pbtxt | 2 +- ...nsorflow.keras.layers.-bidirectional.pbtxt | 2 +- ...tensorflow.keras.layers.-concatenate.pbtxt | 2 +- ...orflow.keras.layers.-conv-l-s-t-m2-d.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-conv1-d.pbtxt | 2 +- ...flow.keras.layers.-conv2-d-transpose.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-conv2-d.pbtxt | 2 +- ...flow.keras.layers.-conv3-d-transpose.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-conv3-d.pbtxt | 2 +- ...sorflow.keras.layers.-convolution1-d.pbtxt | 2 +- ...ras.layers.-convolution2-d-transpose.pbtxt | 2 +- ...sorflow.keras.layers.-convolution2-d.pbtxt | 2 +- ...ras.layers.-convolution3-d-transpose.pbtxt | 2 +- ...sorflow.keras.layers.-convolution3-d.pbtxt | 2 +- ...tensorflow.keras.layers.-cropping1-d.pbtxt | 2 +- ...tensorflow.keras.layers.-cropping2-d.pbtxt | 2 +- ...tensorflow.keras.layers.-cropping3-d.pbtxt | 2 +- ...sorflow.keras.layers.-cu-d-n-n-g-r-u.pbtxt | 2 +- ...rflow.keras.layers.-cu-d-n-n-l-s-t-m.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-dense.pbtxt | 2 +- ...flow.keras.layers.-depthwise-conv2-d.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-dot.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-dropout.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-e-l-u.pbtxt | 2 +- .../tensorflow.keras.layers.-embedding.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-flatten.pbtxt | 2 +- .../tensorflow.keras.layers.-g-r-u-cell.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-g-r-u.pbtxt | 2 +- ...rflow.keras.layers.-gaussian-dropout.pbtxt | 2 +- ...sorflow.keras.layers.-gaussian-noise.pbtxt | 2 +- ...as.layers.-global-average-pooling1-d.pbtxt | 2 +- ...as.layers.-global-average-pooling2-d.pbtxt | 2 +- ...as.layers.-global-average-pooling3-d.pbtxt | 2 +- ...low.keras.layers.-global-avg-pool1-d.pbtxt | 2 +- ...low.keras.layers.-global-avg-pool2-d.pbtxt | 2 +- ...low.keras.layers.-global-avg-pool3-d.pbtxt | 2 +- ...low.keras.layers.-global-max-pool1-d.pbtxt | 2 +- ...low.keras.layers.-global-max-pool2-d.pbtxt | 2 +- ...low.keras.layers.-global-max-pool3-d.pbtxt | 2 +- ....keras.layers.-global-max-pooling1-d.pbtxt | 2 +- ....keras.layers.-global-max-pooling2-d.pbtxt | 2 +- ....keras.layers.-global-max-pooling3-d.pbtxt | 2 +- ...tensorflow.keras.layers.-input-layer.pbtxt | 2 +- ...ensorflow.keras.layers.-l-s-t-m-cell.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-l-s-t-m.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-lambda.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-layer.pbtxt | 2 +- ...ensorflow.keras.layers.-leaky-re-l-u.pbtxt | 2 +- ...w.keras.layers.-locally-connected1-d.pbtxt | 2 +- ...w.keras.layers.-locally-connected2-d.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-masking.pbtxt | 2 +- ...tensorflow.keras.layers.-max-pool1-d.pbtxt | 2 +- ...tensorflow.keras.layers.-max-pool2-d.pbtxt | 2 +- ...tensorflow.keras.layers.-max-pool3-d.pbtxt | 2 +- ...sorflow.keras.layers.-max-pooling1-d.pbtxt | 2 +- ...sorflow.keras.layers.-max-pooling2-d.pbtxt | 2 +- ...sorflow.keras.layers.-max-pooling3-d.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-maximum.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-minimum.pbtxt | 2 +- .../tensorflow.keras.layers.-multiply.pbtxt | 2 +- .../tensorflow.keras.layers.-p-re-l-u.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-permute.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-r-n-n.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-re-l-u.pbtxt | 2 +- ...nsorflow.keras.layers.-repeat-vector.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-reshape.pbtxt | 2 +- ...flow.keras.layers.-separable-conv1-d.pbtxt | 2 +- ...flow.keras.layers.-separable-conv2-d.pbtxt | 2 +- ...ras.layers.-separable-convolution1-d.pbtxt | 2 +- ...ras.layers.-separable-convolution2-d.pbtxt | 2 +- ...flow.keras.layers.-simple-r-n-n-cell.pbtxt | 2 +- ...ensorflow.keras.layers.-simple-r-n-n.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-softmax.pbtxt | 2 +- ...low.keras.layers.-spatial-dropout1-d.pbtxt | 2 +- ...low.keras.layers.-spatial-dropout2-d.pbtxt | 2 +- ...low.keras.layers.-spatial-dropout3-d.pbtxt | 2 +- ...ow.keras.layers.-stacked-r-n-n-cells.pbtxt | 2 +- .../tensorflow.keras.layers.-subtract.pbtxt | 2 +- ...low.keras.layers.-thresholded-re-l-u.pbtxt | 2 +- ...rflow.keras.layers.-time-distributed.pbtxt | 2 +- ...sorflow.keras.layers.-up-sampling1-d.pbtxt | 2 +- ...sorflow.keras.layers.-up-sampling2-d.pbtxt | 2 +- ...sorflow.keras.layers.-up-sampling3-d.pbtxt | 2 +- .../v1/tensorflow.keras.layers.-wrapper.pbtxt | 2 +- ...orflow.keras.layers.-zero-padding1-d.pbtxt | 2 +- ...orflow.keras.layers.-zero-padding2-d.pbtxt | 2 +- ...orflow.keras.layers.-zero-padding3-d.pbtxt | 2 +- .../tensorflow.keras.metrics.-accuracy.pbtxt | 2 +- ...rflow.keras.metrics.-binary-accuracy.pbtxt | 2 +- ....keras.metrics.-categorical-accuracy.pbtxt | 2 +- ...low.keras.metrics.-categorical-hinge.pbtxt | 2 +- ...flow.keras.metrics.-cosine-proximity.pbtxt | 2 +- ...rflow.keras.metrics.-false-negatives.pbtxt | 2 +- ...rflow.keras.metrics.-false-positives.pbtxt | 2 +- .../v1/tensorflow.keras.metrics.-hinge.pbtxt | 2 +- ...w.keras.metrics.-mean-absolute-error.pbtxt | 2 +- ...rics.-mean-absolute-percentage-error.pbtxt | 2 +- ...ow.keras.metrics.-mean-squared-error.pbtxt | 2 +- ...rics.-mean-squared-logarithmic-error.pbtxt | 2 +- .../v1/tensorflow.keras.metrics.-mean.pbtxt | 2 +- .../tensorflow.keras.metrics.-precision.pbtxt | 2 +- .../v1/tensorflow.keras.metrics.-recall.pbtxt | 2 +- ....metrics.-sensitivity-at-specificity.pbtxt | 2 +- ...metrics.-sparse-categorical-accuracy.pbtxt | 2 +- ....metrics.-specificity-at-sensitivity.pbtxt | 2 +- ...sorflow.keras.metrics.-squared-hinge.pbtxt | 2 +- ...orflow.keras.metrics.-true-negatives.pbtxt | 2 +- ...orflow.keras.metrics.-true-positives.pbtxt | 2 +- .../v1/tensorflow.keras.models.-model.pbtxt | 2 +- .../tensorflow.keras.models.-sequential.pbtxt | 2 +- ...ensorflow.keras.optimizers.-adadelta.pbtxt | 2 +- ...tensorflow.keras.optimizers.-adagrad.pbtxt | 2 +- .../tensorflow.keras.optimizers.-adam.pbtxt | 2 +- .../tensorflow.keras.optimizers.-adamax.pbtxt | 2 +- .../tensorflow.keras.optimizers.-nadam.pbtxt | 2 +- ...nsorflow.keras.optimizers.-optimizer.pbtxt | 2 +- ...nsorflow.keras.optimizers.-r-m-sprop.pbtxt | 2 +- .../tensorflow.keras.optimizers.-s-g-d.pbtxt | 2 +- ...ensorflow.layers.-average-pooling1-d.pbtxt | 2 +- ...ensorflow.layers.-average-pooling2-d.pbtxt | 2 +- ...ensorflow.layers.-average-pooling3-d.pbtxt | 2 +- ...nsorflow.layers.-batch-normalization.pbtxt | 2 +- .../v1/tensorflow.layers.-conv1-d.pbtxt | 2 +- ...tensorflow.layers.-conv2-d-transpose.pbtxt | 2 +- .../v1/tensorflow.layers.-conv2-d.pbtxt | 2 +- ...tensorflow.layers.-conv3-d-transpose.pbtxt | 2 +- .../v1/tensorflow.layers.-conv3-d.pbtxt | 2 +- .../golden/v1/tensorflow.layers.-dense.pbtxt | 2 +- .../v1/tensorflow.layers.-dropout.pbtxt | 2 +- .../v1/tensorflow.layers.-flatten.pbtxt | 2 +- .../golden/v1/tensorflow.layers.-layer.pbtxt | 2 +- .../tensorflow.layers.-max-pooling1-d.pbtxt | 2 +- .../tensorflow.layers.-max-pooling2-d.pbtxt | 2 +- .../tensorflow.layers.-max-pooling3-d.pbtxt | 2 +- ...tensorflow.layers.-separable-conv1-d.pbtxt | 2 +- ...tensorflow.layers.-separable-conv2-d.pbtxt | 2 +- ...flow.nn.rnn_cell.-basic-l-s-t-m-cell.pbtxt | 2 +- ...orflow.nn.rnn_cell.-basic-r-n-n-cell.pbtxt | 2 +- ...nsorflow.nn.rnn_cell.-device-wrapper.pbtxt | 2 +- ...sorflow.nn.rnn_cell.-dropout-wrapper.pbtxt | 2 +- .../tensorflow.nn.rnn_cell.-g-r-u-cell.pbtxt | 2 +- ...tensorflow.nn.rnn_cell.-l-s-t-m-cell.pbtxt | 2 +- ...orflow.nn.rnn_cell.-multi-r-n-n-cell.pbtxt | 2 +- .../tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt | 2 +- ...orflow.nn.rnn_cell.-residual-wrapper.pbtxt | 2 +- ...tensorflow.train.-adadelta-optimizer.pbtxt | 2 +- ...sorflow.train.-adagrad-d-a-optimizer.pbtxt | 2 +- .../tensorflow.train.-adagrad-optimizer.pbtxt | 2 +- .../v1/tensorflow.train.-adam-optimizer.pbtxt | 2 +- .../v1/tensorflow.train.-checkpoint.pbtxt | 4 +- .../v1/tensorflow.train.-ftrl-optimizer.pbtxt | 2 +- ...ow.train.-gradient-descent-optimizer.pbtxt | 2 +- ...tensorflow.train.-momentum-optimizer.pbtxt | 2 +- .../v1/tensorflow.train.-optimizer.pbtxt | 2 +- ...ow.train.-proximal-adagrad-optimizer.pbtxt | 2 +- ...-proximal-gradient-descent-optimizer.pbtxt | 2 +- ...nsorflow.train.-r-m-s-prop-optimizer.pbtxt | 2 +- ...rflow.train.-sync-replicas-optimizer.pbtxt | 2 +- .../api/golden/v2/tensorflow.-variable.pbtxt | 2 +- .../golden/v2/tensorflow.keras.-model.pbtxt | 2 +- .../v2/tensorflow.keras.-sequential.pbtxt | 2 +- ....experimental.-peephole-l-s-t-m-cell.pbtxt | 2 +- .../tensorflow.keras.layers.-activation.pbtxt | 2 +- ...eras.layers.-activity-regularization.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-add.pbtxt | 2 +- ...nsorflow.keras.layers.-alpha-dropout.pbtxt | 2 +- ...low.keras.layers.-average-pooling1-d.pbtxt | 2 +- ...low.keras.layers.-average-pooling2-d.pbtxt | 2 +- ...low.keras.layers.-average-pooling3-d.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-average.pbtxt | 2 +- ...tensorflow.keras.layers.-avg-pool1-d.pbtxt | 2 +- ...tensorflow.keras.layers.-avg-pool2-d.pbtxt | 2 +- ...tensorflow.keras.layers.-avg-pool3-d.pbtxt | 2 +- ...ow.keras.layers.-batch-normalization.pbtxt | 2 +- ...nsorflow.keras.layers.-bidirectional.pbtxt | 2 +- ...tensorflow.keras.layers.-concatenate.pbtxt | 2 +- ...orflow.keras.layers.-conv-l-s-t-m2-d.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-conv1-d.pbtxt | 2 +- ...flow.keras.layers.-conv2-d-transpose.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-conv2-d.pbtxt | 2 +- ...flow.keras.layers.-conv3-d-transpose.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-conv3-d.pbtxt | 2 +- ...sorflow.keras.layers.-convolution1-d.pbtxt | 2 +- ...ras.layers.-convolution2-d-transpose.pbtxt | 2 +- ...sorflow.keras.layers.-convolution2-d.pbtxt | 2 +- ...ras.layers.-convolution3-d-transpose.pbtxt | 2 +- ...sorflow.keras.layers.-convolution3-d.pbtxt | 2 +- ...tensorflow.keras.layers.-cropping1-d.pbtxt | 2 +- ...tensorflow.keras.layers.-cropping2-d.pbtxt | 2 +- ...tensorflow.keras.layers.-cropping3-d.pbtxt | 2 +- ...sorflow.keras.layers.-dense-features.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-dense.pbtxt | 2 +- ...flow.keras.layers.-depthwise-conv2-d.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-dot.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-dropout.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-e-l-u.pbtxt | 2 +- .../tensorflow.keras.layers.-embedding.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-flatten.pbtxt | 2 +- .../tensorflow.keras.layers.-g-r-u-cell.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-g-r-u.pbtxt | 2 +- ...rflow.keras.layers.-gaussian-dropout.pbtxt | 2 +- ...sorflow.keras.layers.-gaussian-noise.pbtxt | 2 +- ...as.layers.-global-average-pooling1-d.pbtxt | 2 +- ...as.layers.-global-average-pooling2-d.pbtxt | 2 +- ...as.layers.-global-average-pooling3-d.pbtxt | 2 +- ...low.keras.layers.-global-avg-pool1-d.pbtxt | 2 +- ...low.keras.layers.-global-avg-pool2-d.pbtxt | 2 +- ...low.keras.layers.-global-avg-pool3-d.pbtxt | 2 +- ...low.keras.layers.-global-max-pool1-d.pbtxt | 2 +- ...low.keras.layers.-global-max-pool2-d.pbtxt | 2 +- ...low.keras.layers.-global-max-pool3-d.pbtxt | 2 +- ....keras.layers.-global-max-pooling1-d.pbtxt | 2 +- ....keras.layers.-global-max-pooling2-d.pbtxt | 2 +- ....keras.layers.-global-max-pooling3-d.pbtxt | 2 +- ...tensorflow.keras.layers.-input-layer.pbtxt | 2 +- ...ensorflow.keras.layers.-l-s-t-m-cell.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-l-s-t-m.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-lambda.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-layer.pbtxt | 2 +- ...ensorflow.keras.layers.-leaky-re-l-u.pbtxt | 2 +- ...ensorflow.keras.layers.-linear-model.pbtxt | 2 +- ...w.keras.layers.-locally-connected1-d.pbtxt | 2 +- ...w.keras.layers.-locally-connected2-d.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-masking.pbtxt | 2 +- ...tensorflow.keras.layers.-max-pool1-d.pbtxt | 2 +- ...tensorflow.keras.layers.-max-pool2-d.pbtxt | 2 +- ...tensorflow.keras.layers.-max-pool3-d.pbtxt | 2 +- ...sorflow.keras.layers.-max-pooling1-d.pbtxt | 2 +- ...sorflow.keras.layers.-max-pooling2-d.pbtxt | 2 +- ...sorflow.keras.layers.-max-pooling3-d.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-maximum.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-minimum.pbtxt | 2 +- .../tensorflow.keras.layers.-multiply.pbtxt | 2 +- .../tensorflow.keras.layers.-p-re-l-u.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-permute.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-r-n-n.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-re-l-u.pbtxt | 2 +- ...nsorflow.keras.layers.-repeat-vector.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-reshape.pbtxt | 2 +- ...flow.keras.layers.-separable-conv1-d.pbtxt | 2 +- ...flow.keras.layers.-separable-conv2-d.pbtxt | 2 +- ...ras.layers.-separable-convolution1-d.pbtxt | 2 +- ...ras.layers.-separable-convolution2-d.pbtxt | 2 +- ...flow.keras.layers.-simple-r-n-n-cell.pbtxt | 2 +- ...ensorflow.keras.layers.-simple-r-n-n.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-softmax.pbtxt | 2 +- ...low.keras.layers.-spatial-dropout1-d.pbtxt | 2 +- ...low.keras.layers.-spatial-dropout2-d.pbtxt | 2 +- ...low.keras.layers.-spatial-dropout3-d.pbtxt | 2 +- ...ow.keras.layers.-stacked-r-n-n-cells.pbtxt | 2 +- .../tensorflow.keras.layers.-subtract.pbtxt | 2 +- ...low.keras.layers.-thresholded-re-l-u.pbtxt | 2 +- ...rflow.keras.layers.-time-distributed.pbtxt | 2 +- ...sorflow.keras.layers.-up-sampling1-d.pbtxt | 2 +- ...sorflow.keras.layers.-up-sampling2-d.pbtxt | 2 +- ...sorflow.keras.layers.-up-sampling3-d.pbtxt | 2 +- .../v2/tensorflow.keras.layers.-wrapper.pbtxt | 2 +- ...orflow.keras.layers.-zero-padding1-d.pbtxt | 2 +- ...orflow.keras.layers.-zero-padding2-d.pbtxt | 2 +- ...orflow.keras.layers.-zero-padding3-d.pbtxt | 2 +- .../tensorflow.keras.metrics.-accuracy.pbtxt | 2 +- ...rflow.keras.metrics.-binary-accuracy.pbtxt | 2 +- ....keras.metrics.-categorical-accuracy.pbtxt | 2 +- ...low.keras.metrics.-categorical-hinge.pbtxt | 2 +- ...flow.keras.metrics.-cosine-proximity.pbtxt | 2 +- ...rflow.keras.metrics.-false-negatives.pbtxt | 2 +- ...rflow.keras.metrics.-false-positives.pbtxt | 2 +- .../v2/tensorflow.keras.metrics.-hinge.pbtxt | 2 +- ...w.keras.metrics.-mean-absolute-error.pbtxt | 2 +- ...rics.-mean-absolute-percentage-error.pbtxt | 2 +- ...ow.keras.metrics.-mean-squared-error.pbtxt | 2 +- ...rics.-mean-squared-logarithmic-error.pbtxt | 2 +- .../v2/tensorflow.keras.metrics.-mean.pbtxt | 2 +- .../tensorflow.keras.metrics.-precision.pbtxt | 2 +- .../v2/tensorflow.keras.metrics.-recall.pbtxt | 2 +- ....metrics.-sensitivity-at-specificity.pbtxt | 2 +- ...metrics.-sparse-categorical-accuracy.pbtxt | 2 +- ....metrics.-specificity-at-sensitivity.pbtxt | 2 +- ...sorflow.keras.metrics.-squared-hinge.pbtxt | 2 +- ...orflow.keras.metrics.-true-negatives.pbtxt | 2 +- ...orflow.keras.metrics.-true-positives.pbtxt | 2 +- .../v2/tensorflow.keras.models.-model.pbtxt | 2 +- .../tensorflow.keras.models.-sequential.pbtxt | 2 +- ...ensorflow.keras.optimizers.-adadelta.pbtxt | 2 +- ...tensorflow.keras.optimizers.-adagrad.pbtxt | 2 +- .../tensorflow.keras.optimizers.-adam.pbtxt | 2 +- .../tensorflow.keras.optimizers.-adamax.pbtxt | 2 +- .../tensorflow.keras.optimizers.-nadam.pbtxt | 2 +- ...nsorflow.keras.optimizers.-optimizer.pbtxt | 2 +- ...nsorflow.keras.optimizers.-r-m-sprop.pbtxt | 2 +- .../tensorflow.keras.optimizers.-s-g-d.pbtxt | 2 +- ...nsorflow.nn.rnn_cell.-device-wrapper.pbtxt | 2 +- .../tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt | 2 +- ...orflow.nn.rnn_cell.-residual-wrapper.pbtxt | 2 +- .../v2/tensorflow.rnn.-dropout-wrapper.pbtxt | 2 +- .../v2/tensorflow.train.-checkpoint.pbtxt | 4 +- ...-proximal-gradient-descent-optimizer.pbtxt | 2 +- 357 files changed, 535 insertions(+), 536 deletions(-) diff --git a/tensorflow/contrib/checkpoint/__init__.py b/tensorflow/contrib/checkpoint/__init__.py index 94b7f4f867..99ed4959fa 100644 --- a/tensorflow/contrib/checkpoint/__init__.py +++ b/tensorflow/contrib/checkpoint/__init__.py @@ -51,11 +51,11 @@ from tensorflow.contrib.checkpoint.python.split_dependency import split_dependen from tensorflow.contrib.checkpoint.python.visualize import dot_graph_from_checkpoint from tensorflow.core.protobuf.checkpointable_object_graph_pb2 import CheckpointableObjectGraph from tensorflow.python.training.checkpoint_management import CheckpointManager -from tensorflow.python.training.checkpointable.base import CheckpointableBase +from tensorflow.python.training.checkpointable.base import Checkpointable as CheckpointableBase from tensorflow.python.training.checkpointable.data_structures import List from tensorflow.python.training.checkpointable.data_structures import Mapping from tensorflow.python.training.checkpointable.data_structures import NoDependency -from tensorflow.python.training.checkpointable.tracking import Checkpointable +from tensorflow.python.training.checkpointable.tracking import AutoCheckpointable as Checkpointable from tensorflow.python.training.checkpointable.util import capture_dependencies from tensorflow.python.training.checkpointable.util import list_objects from tensorflow.python.training.checkpointable.util import object_metadata diff --git a/tensorflow/contrib/checkpoint/python/containers.py b/tensorflow/contrib/checkpoint/python/containers.py index 5418e2605b..97936d9e9d 100644 --- a/tensorflow/contrib/checkpoint/python/containers.py +++ b/tensorflow/contrib/checkpoint/python/containers.py @@ -63,7 +63,7 @@ class UniqueNameTracker(data_structures.CheckpointableDataStructure): ValueError: If `checkpointable` is not a checkpointable object. """ - if not isinstance(checkpointable, checkpointable_lib.CheckpointableBase): + if not isinstance(checkpointable, checkpointable_lib.Checkpointable): raise ValueError( ("Expected a checkpointable value, got %s which does not inherit " "from CheckpointableBase.") % (checkpointable,)) diff --git a/tensorflow/contrib/checkpoint/python/containers_test.py b/tensorflow/contrib/checkpoint/python/containers_test.py index ac85c7be80..a2d453ec6e 100644 --- a/tensorflow/contrib/checkpoint/python/containers_test.py +++ b/tensorflow/contrib/checkpoint/python/containers_test.py @@ -52,7 +52,7 @@ class UniqueNameTrackerTests(test.TestCase): save_root = util.Checkpoint(slots=slots) save_path = save_root.save(checkpoint_prefix) - restore_slots = tracking.Checkpointable() + restore_slots = tracking.AutoCheckpointable() restore_root = util.Checkpoint( slots=restore_slots) status = restore_root.restore(save_path) @@ -68,7 +68,7 @@ class UniqueNameTrackerTests(test.TestCase): @test_util.run_in_graph_and_eager_modes def testExample(self): - class SlotManager(tracking.Checkpointable): + class SlotManager(tracking.AutoCheckpointable): def __init__(self): self.slotdeps = containers.UniqueNameTracker() diff --git a/tensorflow/contrib/checkpoint/python/python_state.py b/tensorflow/contrib/checkpoint/python/python_state.py index 302d5cfb79..969c90c788 100644 --- a/tensorflow/contrib/checkpoint/python/python_state.py +++ b/tensorflow/contrib/checkpoint/python/python_state.py @@ -34,7 +34,7 @@ except ImportError: # pylint: enable=g-import-not-at-top -class NumpyState(base.CheckpointableBase): +class NumpyState(base.Checkpointable): """A checkpointable object whose NumPy array attributes are saved/restored. Example usage: @@ -130,7 +130,7 @@ class NumpyState(base.CheckpointableBase): @six.add_metaclass(abc.ABCMeta) -class PythonStateWrapper(base.CheckpointableBase): +class PythonStateWrapper(base.Checkpointable): """Wraps a Python object for storage in an object-based checkpoint.""" @abc.abstractmethod diff --git a/tensorflow/contrib/checkpoint/python/split_dependency.py b/tensorflow/contrib/checkpoint/python/split_dependency.py index 7e77453f3d..3e9700ad74 100644 --- a/tensorflow/contrib/checkpoint/python/split_dependency.py +++ b/tensorflow/contrib/checkpoint/python/split_dependency.py @@ -43,7 +43,7 @@ class _CallbackSaveable(saver_lib.BaseSaverBuilder.SaveableObject): return self._restore_callback(tensor) -class _SplitDependency(checkpointable.CheckpointableBase): +class _SplitDependency(checkpointable.Checkpointable): """Looks like a regular variable while synchronizing save/restores.""" def __init__(self, save_buffer, restore_buffer, name, dtype, num_components, diff --git a/tensorflow/contrib/checkpoint/python/split_dependency_test.py b/tensorflow/contrib/checkpoint/python/split_dependency_test.py index 00a805af25..664a4e76ab 100644 --- a/tensorflow/contrib/checkpoint/python/split_dependency_test.py +++ b/tensorflow/contrib/checkpoint/python/split_dependency_test.py @@ -44,7 +44,7 @@ def _combine_variable_closure(variable): return _consume_restore_buffer_fn -class SaveTensorSlicesAsDeps(base.CheckpointableBase): +class SaveTensorSlicesAsDeps(base.Checkpointable): def __init__(self): self.combined = resource_variable_ops.ResourceVariable([0., 0., 0., 0.]) @@ -59,14 +59,14 @@ class SaveTensorSlicesAsDeps(base.CheckpointableBase): self._track_checkpointable(dep, name=name) -class HasRegularDeps(tracking.Checkpointable): +class HasRegularDeps(tracking.AutoCheckpointable): def __init__(self): self.first_half = resource_variable_ops.ResourceVariable([0., 0.]) self.second_half = resource_variable_ops.ResourceVariable([0., 0.]) -class OnlyOneDep(tracking.Checkpointable): +class OnlyOneDep(tracking.AutoCheckpointable): def __init__(self): self.first_half = resource_variable_ops.ResourceVariable([0., 0.]) diff --git a/tensorflow/contrib/cudnn_rnn/python/ops/cudnn_rnn_ops.py b/tensorflow/contrib/cudnn_rnn/python/ops/cudnn_rnn_ops.py index 1facc83972..f36e8d5022 100644 --- a/tensorflow/contrib/cudnn_rnn/python/ops/cudnn_rnn_ops.py +++ b/tensorflow/contrib/cudnn_rnn/python/ops/cudnn_rnn_ops.py @@ -837,7 +837,7 @@ class CudnnLSTMSaveable(CudnnOpaqueParamsSaveable): checkpointable._track_checkpointable(bias, name="bias") # pylint: disable=protected-access assert len(biases) == len(weights) for cell_index, (bias, kernel) in enumerate(zip(biases, weights)): - cell = checkpointable_lib.Checkpointable() + cell = checkpointable_lib.AutoCheckpointable() checkpointable._track_checkpointable(cell, name="cell-%d" % cell_index) # pylint: disable=protected-access cell.bias = bias cell.kernel = kernel diff --git a/tensorflow/contrib/distribute/python/checkpointing_test.py b/tensorflow/contrib/distribute/python/checkpointing_test.py index f37026bc95..aa5b9f57b8 100644 --- a/tensorflow/contrib/distribute/python/checkpointing_test.py +++ b/tensorflow/contrib/distribute/python/checkpointing_test.py @@ -34,7 +34,7 @@ from tensorflow.python.training.checkpointable import tracking from tensorflow.python.training.checkpointable import util as checkpointable_utils -class NonLayerCheckpointable(tracking.Checkpointable): +class NonLayerCheckpointable(tracking.AutoCheckpointable): def __init__(self): super(NonLayerCheckpointable, self).__init__() diff --git a/tensorflow/contrib/eager/python/metrics_impl.py b/tensorflow/contrib/eager/python/metrics_impl.py index 566246de49..c8d9266672 100644 --- a/tensorflow/contrib/eager/python/metrics_impl.py +++ b/tensorflow/contrib/eager/python/metrics_impl.py @@ -37,7 +37,7 @@ from tensorflow.python.training.checkpointable import base as checkpointable _to_replace = re.compile("[^A-Za-z0-9.]") -class Metric(checkpointable.CheckpointableBase): +class Metric(checkpointable.Checkpointable): """A metric holds state for aggregating statistics over an evaluation run. Example use with eager execution: diff --git a/tensorflow/contrib/eager/python/tfe.py b/tensorflow/contrib/eager/python/tfe.py index 31481d7685..b82e1bb71b 100644 --- a/tensorflow/contrib/eager/python/tfe.py +++ b/tensorflow/contrib/eager/python/tfe.py @@ -138,7 +138,7 @@ from tensorflow.python.ops.resource_variable_ops import ResourceVariable as Vari from tensorflow.python.ops.variable_scope import EagerVariableStore from tensorflow.python.ops import script_ops from tensorflow.python.ops import template -from tensorflow.python.training.checkpointable.tracking import Checkpointable +from tensorflow.python.training.checkpointable.tracking import AutoCheckpointable as Checkpointable from tensorflow.python.training.checkpointable.util import CheckpointableSaver from tensorflow.python.training.checkpointable.util import Checkpoint from tensorflow.python.util.all_util import remove_undocumented diff --git a/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py b/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py index 72019b3154..0243927ce4 100644 --- a/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py +++ b/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py @@ -48,7 +48,7 @@ from tensorflow.python.training.checkpointable import tracking from tensorflow.python.training.checkpointable import util -class NonLayerCheckpointable(tracking.Checkpointable): +class NonLayerCheckpointable(tracking.AutoCheckpointable): def __init__(self): super(NonLayerCheckpointable, self).__init__() @@ -440,7 +440,7 @@ class CheckpointingTests(test.TestCase): def testDeferredSlotRestoration(self): checkpoint_directory = self.get_temp_dir() - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.var = util.add_variable( root, name="var", initializer=0.) optimizer = adam.AdamOptimizer(0.1) @@ -463,7 +463,7 @@ class CheckpointingTests(test.TestCase): 14.)) slots_path = util.CheckpointableSaver(root).save( os.path.join(checkpoint_directory, "with_slots")) - new_root = tracking.Checkpointable() + new_root = tracking.AutoCheckpointable() # Load the slot-containing checkpoint (deferred), then immediately overwrite # the non-slot variable (also deferred). slot_status = util.CheckpointableSaver( @@ -508,7 +508,7 @@ class CheckpointingTests(test.TestCase): with graph.as_default(), self.session(graph): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - obj = tracking.Checkpointable() + obj = tracking.AutoCheckpointable() obj.var = variable_scope.get_variable(name="v", initializer=0.) obj.opt = adam.AdamOptimizer(0.1) obj.opt.minimize(obj.var.read_value()) @@ -526,7 +526,7 @@ class CheckpointingTests(test.TestCase): with graph.as_default(), self.session(graph): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - obj = tracking.Checkpointable() + obj = tracking.AutoCheckpointable() obj.var = variable_scope.get_variable(name="v", initializer=0.) obj.opt = adam.AdamOptimizer(0.1) obj.opt.minimize(obj.var.read_value()) diff --git a/tensorflow/python/data/ops/iterator_ops.py b/tensorflow/python/data/ops/iterator_ops.py index bfa256f8d7..d6fb73813c 100644 --- a/tensorflow/python/data/ops/iterator_ops.py +++ b/tensorflow/python/data/ops/iterator_ops.py @@ -68,7 +68,7 @@ def _device_stack_is_empty(): @tf_export(v1=["data.Iterator"]) -class Iterator(checkpointable.CheckpointableBase): +class Iterator(checkpointable.Checkpointable): """Represents the state of iterating through a `Dataset`.""" def __init__(self, iterator_resource, initializer, output_types, @@ -491,7 +491,7 @@ def _generate_shared_name(prefix): return "{}{}".format(prefix, uid) -class EagerIterator(checkpointable.CheckpointableBase): +class EagerIterator(checkpointable.Checkpointable): """An iterator producing tf.Tensor objects from a tf.data.Dataset.""" def __init__(self, dataset): diff --git a/tensorflow/python/distribute/values.py b/tensorflow/python/distribute/values.py index ec0ea4bc2d..e7db285be5 100644 --- a/tensorflow/python/distribute/values.py +++ b/tensorflow/python/distribute/values.py @@ -626,7 +626,7 @@ class _MirroredSaveable(saver.BaseSaverBuilder.ResourceVariableSaveable): class MirroredVariable(DistributedVariable, Mirrored, - checkpointable.CheckpointableBase): + checkpointable.Checkpointable): """Holds a map from device to variables whose values are kept in sync.""" def __init__( @@ -748,7 +748,7 @@ def _enclosing_tpu_context(): # tpu.replicate() because it assumes that you're in a device context where you # can operate on a single version of the variable, but a tpu.replicate() # operates on all variables and is replicated during a rewrite pass. -class TPUMirroredVariable(checkpointable.CheckpointableBase): +class TPUMirroredVariable(checkpointable.Checkpointable): """Holds a map from device to TPU variables whose values are kept in sync.""" def __init__( @@ -1201,7 +1201,7 @@ def _assert_replica_context(strategy): class ReplicaLocalVariable(DistributedVariable, PerReplica, - checkpointable.CheckpointableBase): + checkpointable.Checkpointable): """Holds a map from device to variables whose values are reduced on save.""" def __init__( @@ -1432,7 +1432,7 @@ def value_container(val): # TODO(josh11b): Descend from Variable. -class AggregatingVariable(checkpointable.CheckpointableBase): +class AggregatingVariable(checkpointable.Checkpointable): """A wrapper around a variable that aggregates updates across replicas.""" def __init__(self, strategy, v, aggregation): diff --git a/tensorflow/python/feature_column/feature_column_v2.py b/tensorflow/python/feature_column/feature_column_v2.py index 73e3001fc0..5976f647af 100644 --- a/tensorflow/python/feature_column/feature_column_v2.py +++ b/tensorflow/python/feature_column/feature_column_v2.py @@ -3175,7 +3175,7 @@ def _raise_shared_embedding_column_error(): '`DenseFeatures` or `LinearModel` instead.') -class SharedEmbeddingColumnCreator(tracking.Checkpointable): +class SharedEmbeddingColumnCreator(tracking.AutoCheckpointable): def __init__(self, dimension, diff --git a/tensorflow/python/keras/engine/base_layer.py b/tensorflow/python/keras/engine/base_layer.py index 47900e7533..5a3540d12d 100644 --- a/tensorflow/python/keras/engine/base_layer.py +++ b/tensorflow/python/keras/engine/base_layer.py @@ -57,7 +57,7 @@ from tensorflow.tools.docs import doc_controls @keras_export('keras.layers.Layer') -class Layer(checkpointable.CheckpointableBase): +class Layer(checkpointable.Checkpointable): """Base layer class. This is the class from which all layers inherit. diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py index cca9874e8b..41f5f319bc 100644 --- a/tensorflow/python/keras/engine/network.py +++ b/tensorflow/python/keras/engine/network.py @@ -1434,7 +1434,7 @@ class Network(base_layer.Layer): session = backend.get_session() optimizer = getattr(self, 'optimizer', None) if (optimizer - and not isinstance(optimizer, checkpointable.CheckpointableBase)): + and not isinstance(optimizer, checkpointable.Checkpointable)): logging.warning( ('This model was compiled with a Keras optimizer (%s) but is being ' 'saved in TensorFlow format with `save_weights`. The model\'s ' diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index d690df2986..f5a720b292 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -261,7 +261,7 @@ class Model(Network): self.optimizer = optimizer # We've disabled automatic dependency tracking for this method, but do want # to add a checkpoint dependency on the optimizer if it's checkpointable. - if isinstance(self.optimizer, checkpointable.CheckpointableBase): + if isinstance(self.optimizer, checkpointable.Checkpointable): self._track_checkpointable( self.optimizer, name='optimizer', overwrite=True) self.loss = loss diff --git a/tensorflow/python/keras/layers/recurrent.py b/tensorflow/python/keras/layers/recurrent.py index 797278a5e5..9404543ed9 100644 --- a/tensorflow/python/keras/layers/recurrent.py +++ b/tensorflow/python/keras/layers/recurrent.py @@ -468,7 +468,7 @@ class RNN(Layer): self.zero_output_for_mask = kwargs.pop('zero_output_for_mask', False) super(RNN, self).__init__(**kwargs) self.cell = cell - if isinstance(cell, checkpointable.CheckpointableBase): + if isinstance(cell, checkpointable.Checkpointable): self._track_checkpointable(self.cell, name='cell') self.return_sequences = return_sequences self.return_state = return_state diff --git a/tensorflow/python/keras/optimizer_v2/optimizer_v2.py b/tensorflow/python/keras/optimizer_v2/optimizer_v2.py index bc210132b5..6cd6cf0a8d 100644 --- a/tensorflow/python/keras/optimizer_v2/optimizer_v2.py +++ b/tensorflow/python/keras/optimizer_v2/optimizer_v2.py @@ -70,7 +70,7 @@ def _deduplicate_indexed_slices(values, indices): @six.add_metaclass(abc.ABCMeta) @keras_export("keras.optimizers.Optimizer") -class OptimizerV2(checkpointable.CheckpointableBase): +class OptimizerV2(checkpointable.Checkpointable): """Updated base class for optimizers. This class defines the API to add Ops to train a model. You never use this diff --git a/tensorflow/python/keras/optimizers.py b/tensorflow/python/keras/optimizers.py index 8c3bf110d4..b704b885cb 100644 --- a/tensorflow/python/keras/optimizers.py +++ b/tensorflow/python/keras/optimizers.py @@ -710,7 +710,7 @@ class Nadam(Optimizer): return dict(list(base_config.items()) + list(config.items())) -class TFOptimizer(Optimizer, checkpointable.CheckpointableBase): +class TFOptimizer(Optimizer, checkpointable.Checkpointable): """Wrapper class for native TensorFlow optimizers. """ diff --git a/tensorflow/python/layers/base.py b/tensorflow/python/layers/base.py index 5354d437b4..1b84ec1f69 100644 --- a/tensorflow/python/layers/base.py +++ b/tensorflow/python/layers/base.py @@ -554,7 +554,7 @@ class Layer(base_layer.Layer): def __setattr__(self, value, name): # By-pass the automatic dependency tracking performed by the parent Layer. - super(checkpointable.CheckpointableBase, self).__setattr__(value, name) + super(checkpointable.Checkpointable, self).__setattr__(value, name) def _add_elements_to_collection(elements, collection_list): diff --git a/tensorflow/python/ops/lookup_ops.py b/tensorflow/python/ops/lookup_ops.py index 0cc3ea5ec7..6a7057fafd 100644 --- a/tensorflow/python/ops/lookup_ops.py +++ b/tensorflow/python/ops/lookup_ops.py @@ -164,7 +164,7 @@ class InitializableLookupTableBase(LookupInterface): self._default_value = ops.convert_to_tensor( default_value, dtype=self._value_dtype) self._default_value.get_shape().merge_with(tensor_shape.scalar()) - if isinstance(initializer, checkpointable_base.CheckpointableBase): + if isinstance(initializer, checkpointable_base.Checkpointable): self._initializer = self._track_checkpointable( initializer, "_initializer") self._resource_handle = self.create_resource() @@ -315,7 +315,7 @@ class HashTable(InitializableLookupTableBase): return exported_keys, exported_values -class TableInitializerBase(checkpointable_base.CheckpointableBase): +class TableInitializerBase(checkpointable_base.Checkpointable): """Base class for lookup table initializers.""" def __init__(self, key_dtype, value_dtype): diff --git a/tensorflow/python/ops/rnn_cell_impl.py b/tensorflow/python/ops/rnn_cell_impl.py index 2da66cfcd5..40c3771f4e 100644 --- a/tensorflow/python/ops/rnn_cell_impl.py +++ b/tensorflow/python/ops/rnn_cell_impl.py @@ -1183,7 +1183,7 @@ class DropoutWrapper(RNNCell): # Set cell, variational_recurrent, seed before running the code below self._cell = cell - if isinstance(cell, checkpointable.CheckpointableBase): + if isinstance(cell, checkpointable.Checkpointable): self._track_checkpointable(self._cell, name="cell") self._variational_recurrent = variational_recurrent self._seed = seed @@ -1424,7 +1424,7 @@ class ResidualWrapper(RNNCell): """ super(ResidualWrapper, self).__init__() self._cell = cell - if isinstance(cell, checkpointable.CheckpointableBase): + if isinstance(cell, checkpointable.Checkpointable): self._track_checkpointable(self._cell, name="cell") self._residual_fn = residual_fn @@ -1482,7 +1482,7 @@ class DeviceWrapper(RNNCell): """ super(DeviceWrapper, self).__init__() self._cell = cell - if isinstance(cell, checkpointable.CheckpointableBase): + if isinstance(cell, checkpointable.Checkpointable): self._track_checkpointable(self._cell, name="cell") self._device = device @@ -1551,7 +1551,7 @@ class MultiRNNCell(RNNCell): for cell_number, cell in enumerate(self._cells): # Add Checkpointable dependencies on these cells so their variables get # saved with this object when using object-based saving. - if isinstance(cell, checkpointable.CheckpointableBase): + if isinstance(cell, checkpointable.Checkpointable): # TODO(allenl): Track down non-Checkpointable callers. self._track_checkpointable(cell, name="cell-%d" % (cell_number,)) self._state_is_tuple = state_is_tuple diff --git a/tensorflow/python/ops/stateful_random_ops.py b/tensorflow/python/ops/stateful_random_ops.py index c7a14f2015..155ad969f6 100644 --- a/tensorflow/python/ops/stateful_random_ops.py +++ b/tensorflow/python/ops/stateful_random_ops.py @@ -151,7 +151,7 @@ def _shape_tensor(shape): @tf_export("random.experimental.Generator") -class Generator(tracking.Checkpointable): +class Generator(tracking.AutoCheckpointable): """Random-number generator. It uses Variable to manage its internal state. diff --git a/tensorflow/python/ops/template.py b/tensorflow/python/ops/template.py index 4eaa16a22c..e02175d6fe 100644 --- a/tensorflow/python/ops/template.py +++ b/tensorflow/python/ops/template.py @@ -232,7 +232,7 @@ def _skip_common_stack_elements(stacktrace, base_case): return stacktrace[-1:] -class Template(checkpointable.CheckpointableBase): +class Template(checkpointable.Checkpointable): """Wrap a function to aid in variable sharing. Templates are functions that create variables the first time they are called diff --git a/tensorflow/python/ops/variables.py b/tensorflow/python/ops/variables.py index d765936399..7eb2e14b42 100644 --- a/tensorflow/python/ops/variables.py +++ b/tensorflow/python/ops/variables.py @@ -204,7 +204,7 @@ class VariableMetaclass(type): @tf_export("Variable", v1=[]) class Variable(six.with_metaclass(VariableMetaclass, - checkpointable.CheckpointableBase)): + checkpointable.Checkpointable)): """See the [Variables Guide](https://tensorflow.org/guide/variables). A variable maintains state in the graph across calls to `run()`. You add a diff --git a/tensorflow/python/saved_model/load.py b/tensorflow/python/saved_model/load.py index 93a081b62b..134373906d 100644 --- a/tensorflow/python/saved_model/load.py +++ b/tensorflow/python/saved_model/load.py @@ -150,7 +150,7 @@ class _Loader(object): # individually callable by adding a `__call__` method to the classes of # the objects instances that have a `__call__` property. - class _UserObject(tracking.Checkpointable): + class _UserObject(tracking.AutoCheckpointable): pass return _UserObject(), setattr @@ -200,4 +200,3 @@ def load(export_dir): "Currently only SavedModels exported with `tf.saved_model.save` may be " "imported. Other SavedModels may eventually be supported via load().") return root - diff --git a/tensorflow/python/saved_model/load_test.py b/tensorflow/python/saved_model/load_test.py index ff6b72053b..9eea170bf0 100644 --- a/tensorflow/python/saved_model/load_test.py +++ b/tensorflow/python/saved_model/load_test.py @@ -43,17 +43,17 @@ class LoadTest(test.TestCase): return load.load(path) def test_structure_import(self): - root = tracking.Checkpointable() - root.dep_one = tracking.Checkpointable() - root.dep_two = tracking.Checkpointable() - root.dep_two.dep = tracking.Checkpointable() + root = tracking.AutoCheckpointable() + root.dep_one = tracking.AutoCheckpointable() + root.dep_two = tracking.AutoCheckpointable() + root.dep_two.dep = tracking.AutoCheckpointable() root.dep_three = root.dep_two.dep imported = self.cycle(root) self.assertIs(imported.dep_three, imported.dep_two.dep) self.assertIsNot(imported.dep_one, imported.dep_two) def test_variables(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.v1 = variables.Variable(1., trainable=True) root.v2 = variables.Variable(2., trainable=False) imported = self.cycle(root) @@ -63,7 +63,7 @@ class LoadTest(test.TestCase): self.assertFalse(imported.v2.trainable) def test_capture_variables(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.weights = variables.Variable(2.) root.f = def_function.function( lambda x: root.weights * x, @@ -83,7 +83,7 @@ class LoadTest(test.TestCase): file1 = self._make_asset("contents 1") file2 = self._make_asset("contents 2") - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.asset1 = tracking.TrackableAsset(file1) root.asset2 = tracking.TrackableAsset(file2) @@ -102,7 +102,7 @@ class LoadTest(test.TestCase): self.assertEquals("contents 2", f.read()) def test_capture_assets(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.vocab = tracking.TrackableAsset(self._make_asset("contents")) root.f = def_function.function( lambda: root.vocab.asset_path, @@ -116,7 +116,7 @@ class LoadTest(test.TestCase): def test_dedup_assets(self): vocab = self._make_asset("contents") - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.asset1 = tracking.TrackableAsset(vocab) root.asset2 = tracking.TrackableAsset(vocab) imported = self.cycle(root) @@ -128,7 +128,7 @@ class LoadTest(test.TestCase): def func(x): return 2 * x - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = func # Add two traces. @@ -146,7 +146,7 @@ class LoadTest(test.TestCase): def func(x): return 2 * x - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = func imported = self.cycle(root) @@ -157,7 +157,7 @@ class LoadTest(test.TestCase): def func(x): return 2 * x - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = func imported = self.cycle( @@ -173,7 +173,7 @@ class LoadTest(test.TestCase): lambda x: f(x) + 1.0, input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.g = g imported = self.cycle(root) imported.g(constant_op.constant([1.0])) @@ -186,7 +186,7 @@ class LoadTest(test.TestCase): else: return 7 - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function(func) self.assertEqual(20, root.f(constant_op.constant(10), True).numpy()) @@ -208,7 +208,7 @@ class LoadTest(test.TestCase): else: return 7 - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function(func) x = constant_op.constant(10) @@ -240,7 +240,7 @@ class LoadTest(test.TestCase): named_tuple = named_tuple_type(a=input1 + input2, b=input1 * input2) return [named_tuple, input2, {"x": 0.5}] - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function(func) result = root.f(constant_op.constant(2), constant_op.constant(3)) @@ -270,7 +270,7 @@ class LoadTest(test.TestCase): else: return 7 - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function(func) self.assertEqual(20, root.f(constant_op.constant(10), True).numpy()) @@ -285,7 +285,7 @@ class LoadTest(test.TestCase): self.assertEqual(6, imported.f(constant_op.constant(1), defg=7.0).numpy()) def test_member_function(self): - class CheckpointableWithMember(tracking.Checkpointable): + class CheckpointableWithMember(tracking.AutoCheckpointable): def __init__(self): super(CheckpointableWithMember, self).__init__() @@ -310,7 +310,7 @@ class LoadTest(test.TestCase): self.assertEqual(27, imported.f(constant_op.constant(2)).numpy()) def test_side_effect_listing(self): - class M(tracking.Checkpointable): + class M(tracking.AutoCheckpointable): def __init__(self): super(M, self).__init__() @@ -334,7 +334,7 @@ class LoadTest(test.TestCase): lambda x: x*weight + bias, input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.weight = weight root.bias = bias root.g = g @@ -346,16 +346,16 @@ class LoadTest(test.TestCase): self.assertAllClose(grad, [3.5, 1.0]) def test_callable(self): - class M1(tracking.Checkpointable): + class M1(tracking.AutoCheckpointable): @def_function.function( input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) def __call__(self, x): return x - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.m1 = M1() - root.m2 = tracking.Checkpointable() + root.m2 = tracking.AutoCheckpointable() root.m2.__call__ = def_function.function( input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)])( lambda x: x*3.0) @@ -378,9 +378,9 @@ class LoadTest(test.TestCase): func = def_function.function( input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)])( lambda x: x*3.0) - root = tracking.Checkpointable() - root.__call__ = tracking.Checkpointable() - root.__call__.__call__ = tracking.Checkpointable() + root = tracking.AutoCheckpointable() + root.__call__ = tracking.AutoCheckpointable() + root.__call__.__call__ = tracking.AutoCheckpointable() root.__call__.__call__.__call__ = func imported = self.cycle(root) @@ -395,7 +395,7 @@ class LoadTest(test.TestCase): def func(x): return 2 * x - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = func self.assertAllEqual([2], root.f(constant_op.constant([1])).numpy()) @@ -419,7 +419,7 @@ class LoadTest(test.TestCase): imported.f(constant_op.constant([1, 2, 3])).numpy()) def test_dict(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.variables = dict(a=variables.Variable(1.)) root.variables["b"] = variables.Variable(2.) root.variables["c"] = 1 @@ -429,7 +429,7 @@ class LoadTest(test.TestCase): self.assertEqual(set(["a", "b"]), set(imported.variables.keys())) def test_list(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.variables = [variables.Variable(1.)] root.variables.append(1) root.variables.append(variables.Variable(3.)) diff --git a/tensorflow/python/saved_model/revived_types_test.py b/tensorflow/python/saved_model/revived_types_test.py index 98e10cfec0..ede5922b80 100644 --- a/tensorflow/python/saved_model/revived_types_test.py +++ b/tensorflow/python/saved_model/revived_types_test.py @@ -25,7 +25,7 @@ from tensorflow.python.saved_model import saved_object_graph_pb2 from tensorflow.python.training.checkpointable import tracking -class CustomTestClass(tracking.Checkpointable): +class CustomTestClass(tracking.AutoCheckpointable): def __init__(self, version): self.version = version @@ -56,7 +56,7 @@ revived_types.register_revived_type( class RegistrationMatchingTest(test.TestCase): def test_save_typecheck(self): - self.assertIs(revived_types.serialize(tracking.Checkpointable()), None) + self.assertIs(revived_types.serialize(tracking.AutoCheckpointable()), None) def test_load_identifier_not_found(self): nothing_matches = revived_types.deserialize( diff --git a/tensorflow/python/saved_model/save.py b/tensorflow/python/saved_model/save.py index 207dc2cb74..d383bbecfc 100644 --- a/tensorflow/python/saved_model/save.py +++ b/tensorflow/python/saved_model/save.py @@ -796,7 +796,7 @@ def save(obj, export_dir, signatures=None): "tf.enable_eager_execution() must run first when calling it from " "TensorFlow 1.x.") # pylint: enable=line-too-long - if not isinstance(obj, base.CheckpointableBase): + if not isinstance(obj, base.Checkpointable): raise ValueError( "Expected a Checkpointable object for export, got {}.".format(obj)) diff --git a/tensorflow/python/saved_model/save_test.py b/tensorflow/python/saved_model/save_test.py index 533b954190..5f9dbe2c25 100644 --- a/tensorflow/python/saved_model/save_test.py +++ b/tensorflow/python/saved_model/save_test.py @@ -87,7 +87,7 @@ def _import_and_infer( class SaveTest(test.TestCase): def test_method_save_signature(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function( lambda x: 2. * x, input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) @@ -99,7 +99,7 @@ class SaveTest(test.TestCase): _import_and_infer(save_dir, {"x": 1.})) def test_method_save_concrete(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function( lambda z: {"out": 2. * z}) root.f(constant_op.constant(1.)) @@ -115,7 +115,7 @@ class SaveTest(test.TestCase): save_dir, {"z": 1.}, signature_key="non_default_key")) def test_non_concrete_error(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function(lambda x: 2. * x) root.f(constant_op.constant(1.)) save_dir = os.path.join(self.get_temp_dir(), "saved_model") @@ -124,7 +124,7 @@ class SaveTest(test.TestCase): save.save(root, save_dir, root.f) def test_nested_inputs(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function( lambda x: 2. * x[0], input_signature=([tensor_spec.TensorSpec(None, dtypes.float32), @@ -137,7 +137,7 @@ class SaveTest(test.TestCase): root.f.get_concrete_function() def test_nested_outputs(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function(lambda x: (2. * x, (3. * x, 4. * x))) root.f(constant_op.constant(1.)) to_save = root.f.get_concrete_function(constant_op.constant(1.)) @@ -158,7 +158,7 @@ class SaveTest(test.TestCase): save.save(root, save_dir, to_save) def test_variable(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.v1 = variables.Variable(3.) root.v2 = variables.Variable(2.) root.f = def_function.function( @@ -186,7 +186,7 @@ class SaveTest(test.TestCase): def test_trivial_save_exception(self): save_dir = os.path.join(self.get_temp_dir(), "saved_model") with self.assertRaisesRegexp(ValueError, "signature"): - save.save(tracking.Checkpointable(), save_dir) + save.save(tracking.AutoCheckpointable(), save_dir) def test_single_method_default_signature(self): model = _ModelWithOptimizer() @@ -200,7 +200,7 @@ class SaveTest(test.TestCase): {"x": [[3., 4.]], "y": [2.]})) def test_single_function_default_signature(self): - model = tracking.Checkpointable() + model = tracking.AutoCheckpointable() model.f = def_function.function(lambda: 3., input_signature=()) model.f() save_dir = os.path.join(self.get_temp_dir(), "saved_model") @@ -369,7 +369,7 @@ class AssetTests(test.TestCase): _import_and_infer(second_dir, {"keys": ["gamma", "beta"]})) def test_unused_asset(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.f = def_function.function( lambda x: 2. * x, input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)]) diff --git a/tensorflow/python/training/checkpointable/base.py b/tensorflow/python/training/checkpointable/base.py index f2797bc5ff..8257693055 100644 --- a/tensorflow/python/training/checkpointable/base.py +++ b/tensorflow/python/training/checkpointable/base.py @@ -460,16 +460,16 @@ def no_automatic_dependency_tracking(method): target=method, decorator_func=_method_wrapper) -class CheckpointableBase(object): +class Checkpointable(object): """Base class for `Checkpointable` objects without automatic dependencies. This class has no __setattr__ override for performance reasons. Dependencies must be added explicitly. Unless attribute assignment is performance-critical, - use `Checkpointable` instead. Use `CheckpointableBase` for `isinstance` + use `AutoCheckpointable` instead. Use `Checkpointable` for `isinstance` checks. """ - # CheckpointableBase does not do automatic dependency tracking, but uses the + # Checkpointable does not do automatic dependency tracking, but uses the # no_automatic_dependency_tracking decorator so it can avoid adding # dependencies if a subclass is Checkpointable / inherits from Model (both of # which have __setattr__ overrides). @@ -623,7 +623,7 @@ class CheckpointableBase(object): # assign again. It will add this variable to our dependencies, and if there # is a non-trivial restoration queued, it will handle that. This also # handles slot variables. - if not overwrite or isinstance(new_variable, CheckpointableBase): + if not overwrite or isinstance(new_variable, Checkpointable): return self._track_checkpointable(new_variable, name=name, overwrite=overwrite) else: @@ -695,7 +695,7 @@ class CheckpointableBase(object): ValueError: If another object is already tracked by this name. """ self._maybe_initialize_checkpointable() - if not isinstance(checkpointable, CheckpointableBase): + if not isinstance(checkpointable, Checkpointable): raise TypeError( ("Checkpointable._track_checkpointable() passed type %s, not a " "Checkpointable.") % (type(checkpointable),)) @@ -742,7 +742,7 @@ class CheckpointableBase(object): name: The name of the dependency within this object (`self`), used to match `checkpointable` with values saved in a checkpoint. checkpointable: The Checkpointable object to restore (inheriting from - `CheckpointableBase`). + `Checkpointable`). """ self._maybe_initialize_checkpointable() checkpointable._maybe_initialize_checkpointable() # pylint: disable=protected-access diff --git a/tensorflow/python/training/checkpointable/base_test.py b/tensorflow/python/training/checkpointable/base_test.py index 736cbee488..750799f030 100644 --- a/tensorflow/python/training/checkpointable/base_test.py +++ b/tensorflow/python/training/checkpointable/base_test.py @@ -29,13 +29,13 @@ from tensorflow.python.training.checkpointable import util class InterfaceTests(test.TestCase): def testOverwrite(self): - root = base.CheckpointableBase() - leaf = base.CheckpointableBase() + root = base.Checkpointable() + leaf = base.Checkpointable() root._track_checkpointable(leaf, name="leaf") (current_name, current_dependency), = root._checkpoint_dependencies self.assertIs(leaf, current_dependency) self.assertEqual("leaf", current_name) - duplicate_name_dep = base.CheckpointableBase() + duplicate_name_dep = base.Checkpointable() with self.assertRaises(ValueError): root._track_checkpointable(duplicate_name_dep, name="leaf") root._track_checkpointable(duplicate_name_dep, name="leaf", overwrite=True) @@ -44,7 +44,7 @@ class InterfaceTests(test.TestCase): self.assertEqual("leaf", current_name) def testAddVariableOverwrite(self): - root = base.CheckpointableBase() + root = base.Checkpointable() a = root._add_variable_with_custom_getter( name="v", shape=[], getter=variable_scope.get_variable) self.assertEqual([root, a], util.list_objects(root)) @@ -61,15 +61,15 @@ class InterfaceTests(test.TestCase): getter=variable_scope.get_variable) def testAssertConsumedWithUnusedPythonState(self): - has_config = base.CheckpointableBase() + has_config = base.Checkpointable() has_config.get_config = lambda: {} saved = util.Checkpoint(obj=has_config) save_path = saved.save(os.path.join(self.get_temp_dir(), "ckpt")) - restored = util.Checkpoint(obj=base.CheckpointableBase()) + restored = util.Checkpoint(obj=base.Checkpointable()) restored.restore(save_path).assert_consumed() def testAssertConsumedFailsWithUsedPythonState(self): - has_config = base.CheckpointableBase() + has_config = base.Checkpointable() attributes = { "foo_attr": functools.partial( base.PythonStringStateSaveable, @@ -78,7 +78,7 @@ class InterfaceTests(test.TestCase): has_config._gather_saveables_for_checkpoint = lambda: attributes saved = util.Checkpoint(obj=has_config) save_path = saved.save(os.path.join(self.get_temp_dir(), "ckpt")) - restored = util.Checkpoint(obj=base.CheckpointableBase()) + restored = util.Checkpoint(obj=base.Checkpointable()) status = restored.restore(save_path) with self.assertRaisesRegexp(AssertionError, "foo_attr"): status.assert_consumed() diff --git a/tensorflow/python/training/checkpointable/data_structures.py b/tensorflow/python/training/checkpointable/data_structures.py index d1e617ed46..5a5b444a6c 100644 --- a/tensorflow/python/training/checkpointable/data_structures.py +++ b/tensorflow/python/training/checkpointable/data_structures.py @@ -58,7 +58,7 @@ def _wrap_or_unwrap(value): """Wraps basic data structures, unwraps NoDependency objects.""" if isinstance(value, NoDependency): return value.value - if isinstance(value, base.CheckpointableBase): + if isinstance(value, base.Checkpointable): return value # Skip conversion for already checkpointable objects. elif isinstance(value, dict): return _DictWrapper(value) @@ -99,7 +99,7 @@ def sticky_attribute_assignment(checkpointable, name, value): value = _wrap_or_unwrap(value) if not add_dependency: return value - if isinstance(value, base.CheckpointableBase): + if isinstance(value, base.Checkpointable): checkpointable._track_checkpointable( # pylint: disable=protected-access value, name=name, # Allow the user to switch the Checkpointable which is tracked by this @@ -109,7 +109,7 @@ def sticky_attribute_assignment(checkpointable, name, value): return value -class CheckpointableDataStructure(base.CheckpointableBase): +class CheckpointableDataStructure(base.Checkpointable): """Base class for data structures which contain checkpointable objects.""" def __init__(self): @@ -122,11 +122,11 @@ class CheckpointableDataStructure(base.CheckpointableBase): checkpointable=self, value=value, name=name) if isinstance(value, variables.Variable): self._extra_variables.append(value) - if not isinstance(value, base.CheckpointableBase): + if not isinstance(value, base.Checkpointable): raise ValueError( ("Only checkpointable objects (such as Layers or Optimizers) may be " "stored in a List object. Got %s, which does not inherit from " - "CheckpointableBase.") % (value,)) + "Checkpointable.") % (value,)) if hasattr(value, "_use_resource_variables"): # In subclassed models, legacy layers (tf.layers) must always use # resource variables. @@ -410,7 +410,7 @@ class _ListWrapper(List, collections.MutableSequence, def __setitem__(self, key, value): self._check_external_modification() - if isinstance(self._storage[key], base.CheckpointableBase): + if isinstance(self._storage[key], base.Checkpointable): self._non_append_mutation = True self._storage[key] = self._track_value(value, self._name_element(key)) self._update_snapshot() @@ -693,14 +693,14 @@ class _DictWrapper(Mapping, collections.MutableMapping): else: value = _wrap_or_unwrap(value) existing_dependency = None - if not no_dep and isinstance(value, base.CheckpointableBase): + if not no_dep and isinstance(value, base.Checkpointable): # Non-string keys are OK as long as we have no reason to add a # dependency on the value (either because the value is not # checkpointable, or because it was wrapped in a NoDependency object). self._non_string_key = True current_value = self._storage.setdefault(key, value) if current_value is not value: - if ((not no_dep and isinstance(value, base.CheckpointableBase)) + if ((not no_dep and isinstance(value, base.Checkpointable)) # We don't want to just check that the existing object is # checkpointable, since it may have been wrapped in a NoDependency # object. @@ -716,7 +716,7 @@ class _DictWrapper(Mapping, collections.MutableMapping): def __delitem__(self, key): self._check_external_modification() existing_value = self[key] - if isinstance(existing_value, base.CheckpointableBase): + if isinstance(existing_value, base.Checkpointable): # Deleting tracked checkpointable values means restoring is problematic, # so we'll throw an exception on save. self._non_append_mutation = True diff --git a/tensorflow/python/training/checkpointable/data_structures_test.py b/tensorflow/python/training/checkpointable/data_structures_test.py index 79191f3448..53cbd66482 100644 --- a/tensorflow/python/training/checkpointable/data_structures_test.py +++ b/tensorflow/python/training/checkpointable/data_structures_test.py @@ -210,8 +210,8 @@ class ListTests(test.TestCase): def testListWrapperBasic(self): # _ListWrapper, unlike List, compares like the built-in list type (since it # is used to automatically replace lists). - a = tracking.Checkpointable() - b = tracking.Checkpointable() + a = tracking.AutoCheckpointable() + b = tracking.AutoCheckpointable() self.assertEqual([a, a], [a, a]) self.assertEqual(data_structures._ListWrapper([a, a]), @@ -343,7 +343,7 @@ class MappingTests(test.TestCase): def testLayerCollectionWithExternalMutation(self): d = {} - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.wrapper = d self.assertEqual([], root.wrapper.layers) self.assertEqual([], root.wrapper.trainable_weights) @@ -361,7 +361,7 @@ class MappingTests(test.TestCase): self.assertEqual(2, len(has_mappings)) self.assertNotIn(data_structures.Mapping(), has_mappings) # In contrast to Mapping, dict wrappers are not hashable - a = tracking.Checkpointable() + a = tracking.AutoCheckpointable() a.d = {} self.assertEqual({}, a.d) self.assertFalse({} != a.d) # pylint: disable=g-explicit-bool-comparison @@ -370,7 +370,7 @@ class MappingTests(test.TestCase): set([a.d]) def testDictWrapperBadKeys(self): - a = tracking.Checkpointable() + a = tracking.AutoCheckpointable() a.d = {} a.d[1] = data_structures.List() model = training.Model() @@ -380,7 +380,7 @@ class MappingTests(test.TestCase): model.save_weights(save_path) def testDictWrapperNoDependency(self): - a = tracking.Checkpointable() + a = tracking.AutoCheckpointable() a.d = data_structures.NoDependency({}) a.d[1] = [3] self.assertEqual([a], util.list_objects(a)) @@ -391,7 +391,7 @@ class MappingTests(test.TestCase): model.load_weights(save_path) def testNonStringKeyNotCheckpointableValue(self): - a = tracking.Checkpointable() + a = tracking.AutoCheckpointable() a.d = {} a.d["a"] = [3] a.d[1] = data_structures.NoDependency([3]) @@ -405,15 +405,15 @@ class MappingTests(test.TestCase): def testNonAppendNotCheckpointable(self): # Non-append mutations (deleting or overwriting values) are OK when the # values aren't tracked. - a = tracking.Checkpointable() + a = tracking.AutoCheckpointable() a.d = {} a.d["a"] = [3] a.d[1] = 3 a.d[1] = 2 self.assertEqual(2, a.d[1]) del a.d[1] - a.d[2] = data_structures.NoDependency(tracking.Checkpointable()) - second = tracking.Checkpointable() + a.d[2] = data_structures.NoDependency(tracking.AutoCheckpointable()) + second = tracking.AutoCheckpointable() a.d[2] = data_structures.NoDependency(second) self.assertIs(second, a.d[2]) self.assertEqual([a, a.d, a.d["a"]], util.list_objects(a)) @@ -475,7 +475,7 @@ class MappingTests(test.TestCase): self.assertEqual({1: 3}, new_dict) def testListShallowCopy(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() orig_list = [[1.]] root.a = orig_list copied = copy.copy(root.a) @@ -492,7 +492,7 @@ class MappingTests(test.TestCase): util.list_objects(copy.copy(root.a)) def testListDeepCopy(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() orig_list = [[1.]] root.a = orig_list copied = copy.deepcopy(root.a) @@ -509,7 +509,7 @@ class MappingTests(test.TestCase): util.list_objects(copy.deepcopy(root.a)) def testDictShallowCopy(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() orig_dict = {"a": [1.]} root.a = orig_dict copied = copy.copy(root.a) @@ -526,7 +526,7 @@ class MappingTests(test.TestCase): util.list_objects(copy.copy(root.a)) def testDictDeepCopy(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() orig_dict = {"a": [1.]} root.a = orig_dict copied = copy.deepcopy(root.a) @@ -543,8 +543,8 @@ class MappingTests(test.TestCase): util.list_objects(copy.deepcopy(root.a)) def testShallowCopyCheckpointable(self): - original = tracking.Checkpointable() - original_sub = tracking.Checkpointable() + original = tracking.AutoCheckpointable() + original_sub = tracking.AutoCheckpointable() original.a = [[1.]] original.b = {"a": original_sub} shallow_copied = copy.copy(original) @@ -557,15 +557,15 @@ class MappingTests(test.TestCase): self.assertIn(shallow_copied.b["a"], shallow_deps) def testDeepCopyCheckpointable(self): - original = tracking.Checkpointable() - original_sub = tracking.Checkpointable() + original = tracking.AutoCheckpointable() + original_sub = tracking.AutoCheckpointable() original.a = [[1.]] original.b = {"a": original_sub} deep_copied = copy.deepcopy(original) self.assertIsNot(original, deep_copied) self.assertIsNot(original_sub, deep_copied.b["a"]) self.assertEqual([[1.]], deep_copied.a) - self.assertIsInstance(deep_copied.b["a"], tracking.Checkpointable) + self.assertIsInstance(deep_copied.b["a"], tracking.AutoCheckpointable) deps = util.list_objects(deep_copied) self.assertIn(deep_copied.a, deps) self.assertIn(deep_copied.b, deps) diff --git a/tensorflow/python/training/checkpointable/tracking.py b/tensorflow/python/training/checkpointable/tracking.py index 4e96aee0c5..04fd5547e1 100644 --- a/tensorflow/python/training/checkpointable/tracking.py +++ b/tensorflow/python/training/checkpointable/tracking.py @@ -41,7 +41,7 @@ class NotCheckpointable(object): pass -class Checkpointable(base.CheckpointableBase): +class AutoCheckpointable(base.Checkpointable): """Manages dependencies on other objects. `Checkpointable` objects may have dependencies: other `Checkpointable` objects @@ -74,7 +74,7 @@ class Checkpointable(base.CheckpointableBase): if getattr(self, "_setattr_tracking", True): value = data_structures.sticky_attribute_assignment( checkpointable=self, value=value, name=name) - super(Checkpointable, self).__setattr__(name, value) + super(AutoCheckpointable, self).__setattr__(name, value) def _no_dependency(self, value): """Override to allow CheckpointableBase to disable dependency tracking.""" @@ -124,7 +124,7 @@ def resource_tracker_scope(resource_tracker): _RESOURCE_TRACKER_STACK = old -class TrackableResource(base.CheckpointableBase): +class TrackableResource(base.Checkpointable): """Base class for all resources that need to be tracked.""" def __init__(self): @@ -151,7 +151,7 @@ class TrackableResource(base.CheckpointableBase): return self._resource_handle -class TrackableAsset(base.CheckpointableBase): +class TrackableAsset(base.Checkpointable): """Base class for asset files which need to be tracked.""" def __init__(self, path): diff --git a/tensorflow/python/training/checkpointable/tracking_test.py b/tensorflow/python/training/checkpointable/tracking_test.py index 17c5461bc2..eb70919b9c 100644 --- a/tensorflow/python/training/checkpointable/tracking_test.py +++ b/tensorflow/python/training/checkpointable/tracking_test.py @@ -35,10 +35,10 @@ from tensorflow.python.util import nest class InterfaceTests(test.TestCase): def testMultipleAssignment(self): - root = tracking.Checkpointable() - root.leaf = tracking.Checkpointable() + root = tracking.AutoCheckpointable() + root.leaf = tracking.AutoCheckpointable() root.leaf = root.leaf - duplicate_name_dep = tracking.Checkpointable() + duplicate_name_dep = tracking.AutoCheckpointable() with self.assertRaisesRegexp(ValueError, "already declared"): root._track_checkpointable(duplicate_name_dep, name="leaf") # No error; we're overriding __setattr__, so we can't really stop people @@ -50,10 +50,10 @@ class InterfaceTests(test.TestCase): self.assertIs(duplicate_name_dep, dep_object) def testNoDependency(self): - root = tracking.Checkpointable() - hasdep = tracking.Checkpointable() + root = tracking.AutoCheckpointable() + hasdep = tracking.AutoCheckpointable() root.hasdep = hasdep - nodep = tracking.Checkpointable() + nodep = tracking.AutoCheckpointable() root.nodep = data_structures.NoDependency(nodep) self.assertEqual(1, len(root._checkpoint_dependencies)) self.assertIs(root._checkpoint_dependencies[0].ref, root.hasdep) @@ -66,16 +66,16 @@ class InterfaceTests(test.TestCase): def __init__(self): super(NoDependencyModel, self).__init__() self.a = [] - self.b = tracking.Checkpointable() + self.b = tracking.AutoCheckpointable() nodeps = NoDependencyModel() self.assertEqual([nodeps], util.list_objects(nodeps)) def testListBasic(self): - a = tracking.Checkpointable() - b = tracking.Checkpointable() + a = tracking.AutoCheckpointable() + b = tracking.AutoCheckpointable() a.l = [b] - c = tracking.Checkpointable() + c = tracking.AutoCheckpointable() a.l.append(c) a_deps = util.list_objects(a) self.assertIn(b, a_deps) @@ -87,10 +87,10 @@ class InterfaceTests(test.TestCase): @test_util.run_in_graph_and_eager_modes def testMutationDirtiesList(self): - a = tracking.Checkpointable() - b = tracking.Checkpointable() + a = tracking.AutoCheckpointable() + b = tracking.AutoCheckpointable() a.l = [b] - c = tracking.Checkpointable() + c = tracking.AutoCheckpointable() a.l.insert(0, c) checkpoint = util.Checkpoint(a=a) with self.assertRaisesRegexp(ValueError, "A list element was replaced"): @@ -98,11 +98,11 @@ class InterfaceTests(test.TestCase): @test_util.run_in_graph_and_eager_modes def testOutOfBandEditDirtiesList(self): - a = tracking.Checkpointable() - b = tracking.Checkpointable() + a = tracking.AutoCheckpointable() + b = tracking.AutoCheckpointable() held_reference = [b] a.l = held_reference - c = tracking.Checkpointable() + c = tracking.AutoCheckpointable() held_reference.append(c) checkpoint = util.Checkpoint(a=a) with self.assertRaisesRegexp(ValueError, "The wrapped list was modified"): @@ -110,25 +110,25 @@ class InterfaceTests(test.TestCase): @test_util.run_in_graph_and_eager_modes def testNestedLists(self): - a = tracking.Checkpointable() + a = tracking.AutoCheckpointable() a.l = [] - b = tracking.Checkpointable() + b = tracking.AutoCheckpointable() a.l.append([b]) - c = tracking.Checkpointable() + c = tracking.AutoCheckpointable() a.l[0].append(c) a_deps = util.list_objects(a) self.assertIn(b, a_deps) self.assertIn(c, a_deps) a.l[0].append(1) - d = tracking.Checkpointable() + d = tracking.AutoCheckpointable() a.l[0].append(d) a_deps = util.list_objects(a) self.assertIn(d, a_deps) self.assertIn(b, a_deps) self.assertIn(c, a_deps) self.assertNotIn(1, a_deps) - e = tracking.Checkpointable() - f = tracking.Checkpointable() + e = tracking.AutoCheckpointable() + f = tracking.AutoCheckpointable() a.l1 = [[], [e]] a.l1[0].append(f) a_deps = util.list_objects(a) @@ -183,7 +183,7 @@ class InterfaceTests(test.TestCase): @test_util.run_in_graph_and_eager_modes def testAssertions(self): - a = tracking.Checkpointable() + a = tracking.AutoCheckpointable() a.l = {"k": [numpy.zeros([2, 2])]} self.assertAllEqual(nest.flatten({"k": [numpy.zeros([2, 2])]}), nest.flatten(a.l)) diff --git a/tensorflow/python/training/checkpointable/util.py b/tensorflow/python/training/checkpointable/util.py index 1ff0cd9f25..5f96ce0db8 100644 --- a/tensorflow/python/training/checkpointable/util.py +++ b/tensorflow/python/training/checkpointable/util.py @@ -1655,7 +1655,7 @@ def frozen_saver(root_checkpointable): @tf_export("train.Checkpoint") -class Checkpoint(tracking.Checkpointable): +class Checkpoint(tracking.AutoCheckpointable): """Groups checkpointable objects, saving and restoring them. `Checkpoint`'s constructor accepts keyword arguments whose values are types @@ -1757,7 +1757,7 @@ class Checkpoint(tracking.Checkpointable): """ super(Checkpoint, self).__init__() for k, v in sorted(kwargs.items(), key=lambda item: item[0]): - if not isinstance(v, (base.CheckpointableBase, + if not isinstance(v, (base.Checkpointable, def_function.PolymorphicFunction)): raise ValueError( ("`Checkpoint` was expecting a checkpointable object (an object " diff --git a/tensorflow/python/training/checkpointable/util_test.py b/tensorflow/python/training/checkpointable/util_test.py index 78a8282f25..cef1075e93 100644 --- a/tensorflow/python/training/checkpointable/util_test.py +++ b/tensorflow/python/training/checkpointable/util_test.py @@ -51,7 +51,7 @@ from tensorflow.python.training.checkpointable import tracking from tensorflow.python.training.checkpointable import util as checkpointable_utils -class NonLayerCheckpointable(tracking.Checkpointable): +class NonLayerCheckpointable(tracking.AutoCheckpointable): def __init__(self): super(NonLayerCheckpointable, self).__init__() @@ -139,7 +139,7 @@ class InterfaceTests(test.TestCase): def testInitNotCalled(self): - class NoInit(tracking.Checkpointable): + class NoInit(tracking.AutoCheckpointable): def __init__(self): pass @@ -148,7 +148,7 @@ class InterfaceTests(test.TestCase): checkpointable_utils.add_variable(NoInit(), "var", shape=[]) def testShapeDtype(self): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() v1 = checkpointable_utils.add_variable( root, name="v1", initializer=3., dtype=dtypes.float64) self.assertEqual(dtypes.float64, v1.dtype) @@ -180,7 +180,7 @@ class InterfaceTests(test.TestCase): def testNotCheckpointable(self): class CallsFunctionalStuff( - tracking.NotCheckpointable, tracking.Checkpointable): + tracking.NotCheckpointable, tracking.AutoCheckpointable): pass test_dir = self.get_temp_dir() @@ -190,7 +190,7 @@ class InterfaceTests(test.TestCase): checkpoint.save(prefix) class CallsFunctionalStuffOtherMRO( - tracking.Checkpointable, tracking.NotCheckpointable): + tracking.AutoCheckpointable, tracking.NotCheckpointable): pass checkpoint_reversed = checkpointable_utils.Checkpoint( @@ -220,7 +220,7 @@ class _MirroringSaveable(saver_lib.BaseSaverBuilder.SaveableObject): self._mirrored_variable.assign(tensor)) -class _OwnsMirroredVariables(base.CheckpointableBase): +class _OwnsMirroredVariables(base.Checkpointable): """A Checkpointable object which returns a more complex SaveableObject.""" def __init__(self): @@ -653,7 +653,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): # pylint: enable=cell-var-from-loop def _get_checkpoint_name(self, name): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() checkpointable_utils.add_variable( root, name=name, shape=[1, 2], dtype=dtypes.float64) (named_variable,), _, _ = checkpointable_utils._serialize_object_graph( @@ -674,8 +674,8 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): @test_util.run_in_graph_and_eager_modes(assert_no_eager_garbage=True) def testNumberedPath(self): - root = tracking.Checkpointable() - leaf = tracking.Checkpointable() + root = tracking.AutoCheckpointable() + leaf = tracking.AutoCheckpointable() root.leaf = leaf checkpointable_utils.add_variable(leaf, name="v", shape=[]) (named_variable,), _, _ = checkpointable_utils._serialize_object_graph( @@ -684,8 +684,8 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): @test_util.run_in_graph_and_eager_modes def testLocalNameValidation(self): - root = tracking.Checkpointable() - leaf = tracking.Checkpointable() + root = tracking.AutoCheckpointable() + leaf = tracking.AutoCheckpointable() # Dots are escaped, which avoids conflicts with reserved names. root._track_checkpointable(leaf, name=".ATTRIBUTES") checkpointable_utils.add_variable(checkpointable=leaf, name="a", shape=[]) @@ -726,13 +726,13 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): @test_util.run_in_graph_and_eager_modes def testLateDependencyTracking(self): - class Dependency(tracking.Checkpointable): + class Dependency(tracking.AutoCheckpointable): def build(self): self.var = checkpointable_utils.add_variable( self, "var", initializer=0.) - class LateDependencies(tracking.Checkpointable): + class LateDependencies(tracking.AutoCheckpointable): def add_dep(self): self.dep = Dependency() @@ -759,13 +759,13 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): @test_util.run_in_graph_and_eager_modes def testDepAfterVar(self): - class Dependency(tracking.Checkpointable): + class Dependency(tracking.AutoCheckpointable): def build(self): self.var = checkpointable_utils.add_variable( self, "var", initializer=0.) - class DepAfterVar(tracking.Checkpointable): + class DepAfterVar(tracking.AutoCheckpointable): def add_dep(self): dep = Dependency() @@ -792,7 +792,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): def testDeferredSlotRestoration(self): checkpoint_directory = self.get_temp_dir() - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.var = checkpointable_utils.add_variable( root, name="var", initializer=0.) optimizer = adam.Adam(0.1) @@ -815,7 +815,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): 14.)) slots_path = checkpointable_utils.CheckpointableSaver(root).save( os.path.join(checkpoint_directory, "with_slots")) - new_root = tracking.Checkpointable() + new_root = tracking.AutoCheckpointable() # Load the slot-containing checkpoint (deferred), then immediately overwrite # the non-slot variable (also deferred). slot_status = checkpointable_utils.CheckpointableSaver( @@ -861,8 +861,8 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): @test_util.run_in_graph_and_eager_modes def testOverlappingRestores(self): checkpoint_directory = self.get_temp_dir() - save_root = tracking.Checkpointable() - save_root.dep = tracking.Checkpointable() + save_root = tracking.AutoCheckpointable() + save_root.dep = tracking.AutoCheckpointable() save_root.dep.var = checkpointable_utils.add_variable( save_root.dep, name="var", initializer=0.) self.evaluate(state_ops.assign(save_root.dep.var, 12.)) @@ -871,13 +871,13 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): self.evaluate(state_ops.assign(save_root.dep.var, 13.)) second_path = saver.save(os.path.join(checkpoint_directory, "second")) - first_root = tracking.Checkpointable() - second_root = tracking.Checkpointable() + first_root = tracking.AutoCheckpointable() + second_root = tracking.AutoCheckpointable() first_status = checkpointable_utils.CheckpointableSaver( first_root).restore(first_path) second_status = checkpointable_utils.CheckpointableSaver( second_root).restore(second_path) - load_dep = tracking.Checkpointable() + load_dep = tracking.AutoCheckpointable() load_dep.var = checkpointable_utils.add_variable( load_dep, name="var", shape=[]) first_root.dep = load_dep @@ -891,13 +891,13 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): # Try again with the order of the restore() reversed. The last restore # determines the final value. - first_root = tracking.Checkpointable() - second_root = tracking.Checkpointable() + first_root = tracking.AutoCheckpointable() + second_root = tracking.AutoCheckpointable() second_status = checkpointable_utils.CheckpointableSaver( second_root).restore(second_path) first_status = checkpointable_utils.CheckpointableSaver( first_root).restore(first_path) - load_dep = tracking.Checkpointable() + load_dep = tracking.AutoCheckpointable() load_dep.var = checkpointable_utils.add_variable( load_dep, name="var", shape=[]) first_root.dep = load_dep @@ -913,23 +913,23 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): def testAmbiguousLoad(self): # Not OK to split one checkpoint object into two checkpoint_directory = self.get_temp_dir() - save_root = tracking.Checkpointable() - save_root.dep_one = tracking.Checkpointable() - save_root.dep_two = tracking.Checkpointable() - dep_three = tracking.Checkpointable() + save_root = tracking.AutoCheckpointable() + save_root.dep_one = tracking.AutoCheckpointable() + save_root.dep_two = tracking.AutoCheckpointable() + dep_three = tracking.AutoCheckpointable() save_root.dep_one.dep_three = dep_three save_root.dep_two.dep_three = dep_three checkpointable_utils.add_variable(dep_three, name="var", initializer=0.) self.evaluate(checkpointable_utils.gather_initializers(save_root)) save_path = checkpointable_utils.CheckpointableSaver(save_root).save( os.path.join(checkpoint_directory, "ckpt")) - load_root = tracking.Checkpointable() + load_root = tracking.AutoCheckpointable() status = checkpointable_utils.CheckpointableSaver(load_root).restore( save_path) - load_root.dep_one = tracking.Checkpointable() - load_root.dep_two = tracking.Checkpointable() - load_root.dep_one.dep_three = tracking.Checkpointable() - load_root.dep_two.dep_three = tracking.Checkpointable() + load_root.dep_one = tracking.AutoCheckpointable() + load_root.dep_two = tracking.AutoCheckpointable() + load_root.dep_one.dep_three = tracking.AutoCheckpointable() + load_root.dep_two.dep_three = tracking.AutoCheckpointable() checkpointable_utils.add_variable( load_root.dep_one.dep_three, name="var", initializer=0.) with self.assertRaises(AssertionError): @@ -941,9 +941,9 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): def testObjectsCombined(self): # Currently fine to load two checkpoint objects into one Python object checkpoint_directory = self.get_temp_dir() - save_root = tracking.Checkpointable() - save_root.dep_one = tracking.Checkpointable() - save_root.dep_two = tracking.Checkpointable() + save_root = tracking.AutoCheckpointable() + save_root.dep_one = tracking.AutoCheckpointable() + save_root.dep_two = tracking.AutoCheckpointable() checkpointable_utils.add_variable( save_root.dep_one, name="var1", initializer=32., dtype=dtypes.float64) checkpointable_utils.add_variable( @@ -951,8 +951,8 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): self.evaluate(checkpointable_utils.gather_initializers(save_root)) save_path = checkpointable_utils.CheckpointableSaver(save_root).save( os.path.join(checkpoint_directory, "ckpt")) - load_root = tracking.Checkpointable() - load_root.dep_one = tracking.Checkpointable() + load_root = tracking.AutoCheckpointable() + load_root.dep_one = tracking.AutoCheckpointable() load_root.dep_two = load_root.dep_one v1 = checkpointable_utils.add_variable( load_root.dep_one, name="var1", shape=[], dtype=dtypes.float64) @@ -968,8 +968,8 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): def testDependencyLoop(self): # Note: this test creates garbage during eager execution because it # purposefully creates a reference cycle. - first = tracking.Checkpointable() - second = tracking.Checkpointable() + first = tracking.AutoCheckpointable() + second = tracking.AutoCheckpointable() first.second = second second.first = first first.v = checkpointable_utils.add_variable( @@ -982,10 +982,10 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): os.path.join(checkpoint_directory, "ckpt")) # Test deferred loading - first_load = tracking.Checkpointable() + first_load = tracking.AutoCheckpointable() status = checkpointable_utils.CheckpointableSaver( first_load).restore(save_path) - second_load = tracking.Checkpointable() + second_load = tracking.AutoCheckpointable() first_load.second = second_load second_load.first = first_load with self.assertRaises(AssertionError): @@ -1014,7 +1014,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): def testRestoreOnAssign(self): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - first = tracking.Checkpointable() + first = tracking.AutoCheckpointable() first.var1 = variables_lib.Variable(0., name="outside_var") first.var2 = variables_lib.Variable(0., name="blah") self.evaluate(first.var1.assign(4.)) @@ -1022,7 +1022,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): save_path = checkpointable_utils.CheckpointableSaver(first).save( checkpoint_prefix) - second = tracking.Checkpointable() + second = tracking.AutoCheckpointable() second.var2 = variables_lib.Variable(0., name="blah") status = checkpointable_utils.CheckpointableSaver( second).restore(save_path) @@ -1042,7 +1042,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): with graph.as_default(), self.session(graph): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - obj = tracking.Checkpointable() + obj = tracking.AutoCheckpointable() obj.var = variables_lib.Variable(0., name="v") obj.opt = adam.Adam(0.1) variables = [obj.var] @@ -1059,7 +1059,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): # No checkpoints are deleted by default checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - obj = tracking.Checkpointable() + obj = tracking.AutoCheckpointable() obj.var = variable_scope.get_variable(name="v", initializer=0.) self.evaluate(checkpointable_utils.gather_initializers(obj)) saver = checkpointable_utils.Checkpoint(obj=obj) @@ -1079,7 +1079,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): def testCheckpointStateChangingVarList(self): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - obj = tracking.Checkpointable() + obj = tracking.AutoCheckpointable() obj.var = variable_scope.get_variable(name="v", initializer=0.) self.evaluate(checkpointable_utils.gather_initializers(obj)) checkpoint = checkpointable_utils.Checkpoint(obj=obj) @@ -1132,7 +1132,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): with graph.as_default(), self.session(graph): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - obj = tracking.Checkpointable() + obj = tracking.AutoCheckpointable() obj.var = variables_lib.Variable(0., name="v") obj.opt = adam.Adam(0.1) variables = [obj.var] @@ -1286,7 +1286,7 @@ class CheckpointingTests(parameterized.TestCase, test.TestCase): load_status.assert_existing_objects_matched().run_restore_ops() -class _ManualScope(tracking.Checkpointable): +class _ManualScope(tracking.AutoCheckpointable): def __call__(self): with variable_scope.variable_scope("ManualScope") as vs: diff --git a/tensorflow/python/training/checkpointable/util_with_v1_optimizers_test.py b/tensorflow/python/training/checkpointable/util_with_v1_optimizers_test.py index bb6bb8bd1d..bd80fa60f0 100644 --- a/tensorflow/python/training/checkpointable/util_with_v1_optimizers_test.py +++ b/tensorflow/python/training/checkpointable/util_with_v1_optimizers_test.py @@ -47,7 +47,7 @@ from tensorflow.python.training.checkpointable import tracking from tensorflow.python.training.checkpointable import util as checkpointable_utils -class NonLayerCheckpointable(tracking.Checkpointable): +class NonLayerCheckpointable(tracking.AutoCheckpointable): def __init__(self): super(NonLayerCheckpointable, self).__init__() @@ -461,7 +461,7 @@ class CheckpointingTests(test.TestCase): # pylint: enable=cell-var-from-loop def _get_checkpoint_name(self, name): - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() checkpointable_utils.add_variable( root, name=name, shape=[1, 2], dtype=dtypes.float64) (named_variable,), _, _ = checkpointable_utils._serialize_object_graph( @@ -503,7 +503,7 @@ class CheckpointingTests(test.TestCase): def testDeferredSlotRestoration(self): checkpoint_directory = self.get_temp_dir() - root = tracking.Checkpointable() + root = tracking.AutoCheckpointable() root.var = checkpointable_utils.add_variable( root, name="var", initializer=0.) optimizer = adam.AdamOptimizer(0.1) @@ -526,7 +526,7 @@ class CheckpointingTests(test.TestCase): 14.)) slots_path = checkpointable_utils.CheckpointableSaver(root).save( os.path.join(checkpoint_directory, "with_slots")) - new_root = tracking.Checkpointable() + new_root = tracking.AutoCheckpointable() # Load the slot-containing checkpoint (deferred), then immediately overwrite # the non-slot variable (also deferred). slot_status = checkpointable_utils.CheckpointableSaver( @@ -572,7 +572,7 @@ class CheckpointingTests(test.TestCase): with graph.as_default(), self.session(graph): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - obj = tracking.Checkpointable() + obj = tracking.AutoCheckpointable() obj.var = variable_scope.get_variable(name="v", initializer=0.) obj.opt = adam.AdamOptimizer(0.1) obj.opt.minimize(obj.var.read_value()) @@ -590,7 +590,7 @@ class CheckpointingTests(test.TestCase): with graph.as_default(), self.session(graph): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - obj = tracking.Checkpointable() + obj = tracking.AutoCheckpointable() obj.var = variable_scope.get_variable(name="v", initializer=0.) obj.opt = adam.AdamOptimizer(0.1) obj.opt.minimize(obj.var.read_value()) @@ -739,7 +739,7 @@ class CheckpointingTests(test.TestCase): self.assertEqual(42., self.evaluate(optimizer.variables()[0])) -class _ManualScope(tracking.Checkpointable): +class _ManualScope(tracking.AutoCheckpointable): def __call__(self): with variable_scope.variable_scope("ManualScope") as vs: diff --git a/tensorflow/python/training/checkpointable/util_xla_test.py b/tensorflow/python/training/checkpointable/util_xla_test.py index d80ac003b4..4e96a7514a 100644 --- a/tensorflow/python/training/checkpointable/util_xla_test.py +++ b/tensorflow/python/training/checkpointable/util_xla_test.py @@ -29,7 +29,7 @@ from tensorflow.python.training.checkpointable import tracking from tensorflow.python.training.checkpointable import util as checkpointable_utils -class NonLayerCheckpointable(tracking.Checkpointable): +class NonLayerCheckpointable(tracking.AutoCheckpointable): def __init__(self): super(NonLayerCheckpointable, self).__init__() diff --git a/tensorflow/python/training/optimizer.py b/tensorflow/python/training/optimizer.py index a591dc9fec..3742ebb807 100644 --- a/tensorflow/python/training/optimizer.py +++ b/tensorflow/python/training/optimizer.py @@ -218,7 +218,7 @@ class Optimizer( # Optimizers inherit from CheckpointableBase rather than Checkpointable # since they do most of their dependency management themselves (slot # variables are special-cased, and non-slot variables are keyed to graphs). - checkpointable.CheckpointableBase): + checkpointable.Checkpointable): """Base class for optimizers. This class defines the API to add Ops to train a model. You never use this diff --git a/tensorflow/python/training/saver_test.py b/tensorflow/python/training/saver_test.py index d1b51adaa4..dec23c50e8 100644 --- a/tensorflow/python/training/saver_test.py +++ b/tensorflow/python/training/saver_test.py @@ -2775,7 +2775,7 @@ class ScopedGraphTest(test.TestCase): self.assertEqual(2.0, self.evaluate(var_dict2["variable2:0"])) -class _OwnsAVariableSimple(checkpointable_base.CheckpointableBase): +class _OwnsAVariableSimple(checkpointable_base.Checkpointable): """A Checkpointable object which can be saved using a tf.train.Saver.""" def __init__(self): @@ -2808,7 +2808,7 @@ class _MirroringSaveable( self._mirrored_variable.assign(tensor)) -class _OwnsMirroredVariables(checkpointable_base.CheckpointableBase): +class _OwnsMirroredVariables(checkpointable_base.Checkpointable): """A Checkpointable object which returns a more complex SaveableObject.""" def __init__(self): @@ -2831,7 +2831,7 @@ class _OwnsMirroredVariables(checkpointable_base.CheckpointableBase): return self.non_dep_variable.name -class NonLayerCheckpointable(checkpointable_tracking.Checkpointable): +class NonLayerCheckpointable(checkpointable_tracking.AutoCheckpointable): def __init__(self): super(NonLayerCheckpointable, self).__init__() diff --git a/tensorflow/python/training/saving/saveable_object_util.py b/tensorflow/python/training/saving/saveable_object_util.py index fa88d2c6eb..b8cc66249b 100644 --- a/tensorflow/python/training/saving/saveable_object_util.py +++ b/tensorflow/python/training/saving/saveable_object_util.py @@ -165,7 +165,7 @@ def saveable_objects_for_op(op, name): yield ResourceVariableSaveable( variable, variable._save_slice_info.spec, name) # pylint: enable=protected-access - elif isinstance(op, checkpointable.CheckpointableBase) and not isinstance( + elif isinstance(op, checkpointable.Checkpointable) and not isinstance( op, variables.Variable): # pylint: disable=protected-access for attr, factory in op._gather_saveables_for_checkpoint().items(): @@ -250,7 +250,7 @@ def op_list_to_dict(op_list, convert_variable_to_tensor=True): names_to_saveables[name].append(var) else: names_to_saveables[name] = [var] - elif (isinstance(var, checkpointable.CheckpointableBase) + elif (isinstance(var, checkpointable.Checkpointable) and not isinstance(var, variables.Variable)): checkpointable_saveables = [ (factory() if callable(factory) else factory) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.-variable.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.-variable.pbtxt index 048f666305..f042097acc 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.-variable.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.-variable.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.Variable" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "SaveSliceInfo" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.-iterator.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.-iterator.pbtxt index 4f0147a523..682a2b91b6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.-iterator.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.-iterator.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.data.Iterator" tf_class { is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "initializer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.-model.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.-model.pbtxt index eced2e1cb0..0a81c3a663 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.-model.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.-model.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.-sequential.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.-sequential.pbtxt index 2acb90173f..2a9ceec30e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.-sequential.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.-sequential.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.experimental.-peephole-l-s-t-m-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.experimental.-peephole-l-s-t-m-cell.pbtxt index b2ab5006dc..1bfd51cdcc 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.experimental.-peephole-l-s-t-m-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.experimental.-peephole-l-s-t-m-cell.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-activation.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-activation.pbtxt index da212382c1..8a0b8eb46f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-activation.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-activation.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Activation" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-activity-regularization.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-activity-regularization.pbtxt index c910db027e..abb3c23694 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-activity-regularization.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-activity-regularization.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ActivityRegularization" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-add.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-add.pbtxt index 8b7b33e98c..b27db4e7f2 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-add.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-add.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-alpha-dropout.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-alpha-dropout.pbtxt index 5e3e41ba20..50998ac9d6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-alpha-dropout.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-alpha-dropout.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.AlphaDropout" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling1-d.pbtxt index e160b10153..be17aeafb5 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling2-d.pbtxt index b6b71358c8..7f21b444bc 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling3-d.pbtxt index 5c5ab1580e..2ac86f152f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average-pooling3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average.pbtxt index 489de2e4d3..f6b1dd2f7e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-average.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool1-d.pbtxt index 30fec249b8..3da1f43a92 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool2-d.pbtxt index 0e983c9234..a7be5ac818 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool3-d.pbtxt index ec50db7127..c5c29bead3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-avg-pool3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-batch-normalization.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-batch-normalization.pbtxt index cbbb000e25..3af3c2a501 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-batch-normalization.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-batch-normalization.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-bidirectional.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-bidirectional.pbtxt index 23153d4284..880d18e1aa 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-bidirectional.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-bidirectional.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-concatenate.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-concatenate.pbtxt index 766c3f267f..1eb0cf1a18 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-concatenate.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-concatenate.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv-l-s-t-m2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv-l-s-t-m2-d.pbtxt index 8980982271..d9394e60f5 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv-l-s-t-m2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv-l-s-t-m2-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activation" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv1-d.pbtxt index a74b8d2950..a0f6dc8097 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv2-d-transpose.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv2-d-transpose.pbtxt index b093f8ead9..037b92f861 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv2-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv2-d-transpose.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv2-d.pbtxt index 0ce9f6fd59..6a0d027d47 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv3-d-transpose.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv3-d-transpose.pbtxt index c1f5bfae0d..66b5bd75fc 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv3-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv3-d-transpose.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv3-d.pbtxt index 4aa872c4f0..e73133ff07 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-conv3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution1-d.pbtxt index 6e01f7c70c..7af6b2b3c3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution2-d-transpose.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution2-d-transpose.pbtxt index c002042d77..baff492dfb 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution2-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution2-d-transpose.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution2-d.pbtxt index f5e5446d2b..63d30a6185 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution3-d-transpose.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution3-d-transpose.pbtxt index d5f36f4bc3..7a29cbbec3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution3-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution3-d-transpose.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution3-d.pbtxt index 346fec6056..87c75c0224 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-convolution3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping1-d.pbtxt index 0f8fe9f05e..f69104ddfe 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping1-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Cropping1D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping2-d.pbtxt index 68fb7382a7..aa05471933 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping2-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Cropping2D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping3-d.pbtxt index deda82f9b3..d61f1ddc1d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cropping3-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Cropping3D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cu-d-n-n-g-r-u.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cu-d-n-n-g-r-u.pbtxt index 2eba3fb954..e2d05f8298 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cu-d-n-n-g-r-u.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cu-d-n-n-g-r-u.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cu-d-n-n-l-s-t-m.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cu-d-n-n-l-s-t-m.pbtxt index 6ed13d37f2..f650f48423 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cu-d-n-n-l-s-t-m.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-cu-d-n-n-l-s-t-m.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dense.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dense.pbtxt index 919aed5723..06e8b6b314 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dense.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dense.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Dense" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-depthwise-conv2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-depthwise-conv2-d.pbtxt index f590ce1ef7..9fdf6f66d1 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-depthwise-conv2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-depthwise-conv2-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dot.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dot.pbtxt index db4261fadc..cbe1020650 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dot.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dot.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dropout.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dropout.pbtxt index 7369552b3b..0efba09b27 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dropout.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-dropout.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Dropout" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-e-l-u.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-e-l-u.pbtxt index f643ef9de2..b34c499eb2 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-e-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-e-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ELU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-embedding.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-embedding.pbtxt index ce053ae8c4..51dd853127 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-embedding.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-embedding.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Embedding" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-flatten.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-flatten.pbtxt index db95043077..dcd18a9ced 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-flatten.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-flatten.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Flatten" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-g-r-u-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-g-r-u-cell.pbtxt index a6edba6b7e..f029907ee8 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-g-r-u-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-g-r-u-cell.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.GRUCell" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-g-r-u.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-g-r-u.pbtxt index f8c0dbb273..278ae06cba 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-g-r-u.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-g-r-u.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activation" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-gaussian-dropout.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-gaussian-dropout.pbtxt index ac4bbe7d19..15cbcfe8ed 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-gaussian-dropout.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-gaussian-dropout.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.GaussianDropout" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-gaussian-noise.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-gaussian-noise.pbtxt index 947e3170ae..865b898c4c 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-gaussian-noise.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-gaussian-noise.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.GaussianNoise" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling1-d.pbtxt index 17e202c581..3e17aca17c 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling2-d.pbtxt index 9772c5df9b..b160687a2a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling3-d.pbtxt index cd65075591..70e8d51a5a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-average-pooling3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool1-d.pbtxt index 0423de7a24..809dc8554b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool2-d.pbtxt index 4471cba245..3fbce8cb71 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool3-d.pbtxt index c0e7fae456..70e4103ea1 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-avg-pool3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool1-d.pbtxt index 6975a6e88d..000bf54c45 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool2-d.pbtxt index 56bd70db7e..8ffbf07f9b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool3-d.pbtxt index 656319920e..3803d2b0a8 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pool3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling1-d.pbtxt index f815e66911..28668224e0 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling2-d.pbtxt index f61f0e521b..b83ed67723 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling3-d.pbtxt index c58c8ce63f..e689d69140 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-global-max-pooling3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-input-layer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-input-layer.pbtxt index 0efe9a4297..bb6eddae71 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-input-layer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-input-layer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.InputLayer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-l-s-t-m-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-l-s-t-m-cell.pbtxt index 5caa02e71a..5fb3f9dd3a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-l-s-t-m-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-l-s-t-m-cell.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.LSTMCell" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-l-s-t-m.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-l-s-t-m.pbtxt index f21c7e5b21..8eb6dd9f4a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-l-s-t-m.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-l-s-t-m.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activation" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-lambda.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-lambda.pbtxt index 381d5660b9..376bec0814 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-lambda.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-lambda.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Lambda" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-layer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-layer.pbtxt index 36b0a86628..c5f91a6338 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-layer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-layer.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.keras.layers.Layer" tf_class { is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-leaky-re-l-u.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-leaky-re-l-u.pbtxt index b41662e63a..bde8887359 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-leaky-re-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-leaky-re-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.LeakyReLU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-locally-connected1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-locally-connected1-d.pbtxt index e4abfca913..16945f2c12 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-locally-connected1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-locally-connected1-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.LocallyConnected1D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-locally-connected2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-locally-connected2-d.pbtxt index cfcb92e293..f05741ffce 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-locally-connected2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-locally-connected2-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.LocallyConnected2D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-masking.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-masking.pbtxt index e0721353d1..7885db4ed2 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-masking.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-masking.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Masking" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool1-d.pbtxt index 0618fbeead..9380d26cf4 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool2-d.pbtxt index 4af52ffec8..8eb8218df3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool3-d.pbtxt index db9311ee58..0c96f86ed3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pool3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling1-d.pbtxt index bfb15cb447..0c6b230eb7 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling2-d.pbtxt index 1db962dbb8..eb7ca52fba 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling3-d.pbtxt index f80d5267e7..e724e9088f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-max-pooling3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-maximum.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-maximum.pbtxt index cd772d4ac7..dafbd09ee2 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-maximum.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-maximum.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-minimum.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-minimum.pbtxt index 2bb6b3073a..3122fbec1c 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-minimum.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-minimum.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-multiply.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-multiply.pbtxt index e1a1f07355..0527cda1f0 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-multiply.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-multiply.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-p-re-l-u.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-p-re-l-u.pbtxt index 66c4446572..814e5a5d54 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-p-re-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-p-re-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.PReLU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-permute.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-permute.pbtxt index 0839554f43..aa1731afb8 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-permute.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-permute.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Permute" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-r-n-n.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-r-n-n.pbtxt index b10695f6f7..9d7dd85fe0 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-r-n-n.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-r-n-n.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.RNN" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-re-l-u.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-re-l-u.pbtxt index b96500f710..e9bba298bb 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-re-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-re-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ReLU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-repeat-vector.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-repeat-vector.pbtxt index a27d93ec62..3c783eb512 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-repeat-vector.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-repeat-vector.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.RepeatVector" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-reshape.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-reshape.pbtxt index 6dda24d3d2..b8e0882541 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-reshape.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-reshape.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Reshape" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-conv1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-conv1-d.pbtxt index 8a4ae8aaa7..310f369ed6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-conv1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-conv1-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-conv2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-conv2-d.pbtxt index a083c1da2e..df19d781c2 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-conv2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-conv2-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-convolution1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-convolution1-d.pbtxt index 5d5b361f82..bf909509bd 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-convolution1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-convolution1-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-convolution2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-convolution2-d.pbtxt index 392c338d73..5d66bc6fb6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-convolution2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-separable-convolution2-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-simple-r-n-n-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-simple-r-n-n-cell.pbtxt index 1143604903..88e9300de9 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-simple-r-n-n-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-simple-r-n-n-cell.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.SimpleRNNCell" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-simple-r-n-n.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-simple-r-n-n.pbtxt index 5a15f1a55f..9d81c6d4bc 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-simple-r-n-n.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-simple-r-n-n.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activation" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-softmax.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-softmax.pbtxt index c470d9c8e8..712eb0c6ec 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-softmax.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-softmax.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Softmax" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout1-d.pbtxt index d17d6495c0..dfc4ca2705 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout2-d.pbtxt index 2d538b4734..5e4f727f71 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout3-d.pbtxt index b70923601a..9d893cb30a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-spatial-dropout3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-stacked-r-n-n-cells.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-stacked-r-n-n-cells.pbtxt index f453ddd50e..a2ed954e4c 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-stacked-r-n-n-cells.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-stacked-r-n-n-cells.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.StackedRNNCells" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-subtract.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-subtract.pbtxt index 5759169e07..8a0818e78a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-subtract.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-subtract.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-thresholded-re-l-u.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-thresholded-re-l-u.pbtxt index bfde1c35f6..b5591b4826 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-thresholded-re-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-thresholded-re-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ThresholdedReLU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-time-distributed.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-time-distributed.pbtxt index e7f59a9cc5..210e4fd4e6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-time-distributed.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-time-distributed.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling1-d.pbtxt index 0354149d4f..da2213a84f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling1-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.UpSampling1D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling2-d.pbtxt index fff0e26bc1..e2c303d506 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling2-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.UpSampling2D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling3-d.pbtxt index c49fa5663d..396e774c8a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-up-sampling3-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.UpSampling3D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-wrapper.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-wrapper.pbtxt index c961699053..8b6418d514 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-wrapper.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-wrapper.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Wrapper" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding1-d.pbtxt index 1911e128eb..e8fda4c71a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding1-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ZeroPadding1D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding2-d.pbtxt index 88be914347..50c52d270b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding2-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ZeroPadding2D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding3-d.pbtxt index 2bbb71ece2..84c6b78a2b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-zero-padding3-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ZeroPadding3D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-accuracy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-accuracy.pbtxt index e3e8dc00fe..6756beca1d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-accuracy.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-binary-accuracy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-binary-accuracy.pbtxt index 60b4f992c0..bec0b20aa5 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-binary-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-binary-accuracy.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-accuracy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-accuracy.pbtxt index deab5a7a70..71dc294dc3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-accuracy.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-hinge.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-hinge.pbtxt index 10477e6662..43024e738a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-categorical-hinge.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt index cdb3e1a79a..1e39385f81 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-negatives.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-negatives.pbtxt index f4d067243d..5432f7f400 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-negatives.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-negatives.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-positives.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-positives.pbtxt index f898e00eea..75541bf285 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-positives.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-false-positives.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-hinge.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-hinge.pbtxt index 393ba964c4..f8d47f3771 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-hinge.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-error.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-error.pbtxt index a86b26e618..bf7fc7cfc5 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-error.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-error.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt index 5e8f321a06..59bb767d35 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-error.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-error.pbtxt index a2eb10c5f6..91f4712312 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-error.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-error.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt index 7df45a8caf..205e15d439 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean.pbtxt index 899502cfad..eec26ffce7 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-mean.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-precision.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-precision.pbtxt index 3bdf3a4167..9aeaa5627a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-precision.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-precision.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-recall.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-recall.pbtxt index 1ac2aacca0..748cec0866 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-recall.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-recall.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt index 5c4f26929f..97aeb680be 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt index 48ac5c2fdc..571c2bf9d3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt index 702f711722..85f80b062e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-squared-hinge.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-squared-hinge.pbtxt index 6c3a9748c7..71c047e973 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-squared-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-squared-hinge.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-negatives.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-negatives.pbtxt index 3f1a99b83b..4bc9383f6f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-negatives.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-negatives.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-positives.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-positives.pbtxt index e5a4207a32..2eae4df0ae 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-positives.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-true-positives.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-model.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-model.pbtxt index 5885cd21c1..55bd285612 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-model.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-model.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-sequential.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-sequential.pbtxt index 935fa32f8c..a82424167e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-sequential.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-sequential.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adadelta.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adadelta.pbtxt index 5426269793..0a56293e80 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adadelta.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adadelta.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Adadelta" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adagrad.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adagrad.pbtxt index c39fe6ba4f..14d0894e56 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adagrad.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adagrad.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Adagrad" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adam.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adam.pbtxt index 05d46d380b..fdb1ea838c 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adam.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adam.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Adam" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adamax.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adamax.pbtxt index 88fd96c71d..ece63ec168 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adamax.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-adamax.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Adamax" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-nadam.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-nadam.pbtxt index 7b824e7f0b..f952f88b6d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-nadam.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-nadam.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Nadam" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-optimizer.pbtxt index 58b7f27491..27bae902b0 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-optimizer.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.keras.optimizers.Optimizer" tf_class { is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-r-m-sprop.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-r-m-sprop.pbtxt index 8de796edde..e523443a00 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-r-m-sprop.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-r-m-sprop.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.RMSprop" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-s-g-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-s-g-d.pbtxt index 393eeb3d6c..d2721f8e92 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-s-g-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.optimizers.-s-g-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.SGD" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling1-d.pbtxt index 85764cc8dc..6d826a8f8e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling1-d.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling2-d.pbtxt index 259da2ad3e..9505c90aac 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling2-d.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling3-d.pbtxt index ffda9334cf..5b1b8f78dc 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-average-pooling3-d.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-batch-normalization.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-batch-normalization.pbtxt index 56a3fc3de7..ef4c57b694 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-batch-normalization.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-batch-normalization.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv1-d.pbtxt index d72f24b3d5..b5ee2e7302 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv1-d.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv2-d-transpose.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv2-d-transpose.pbtxt index 72a7339368..57f6d7c7c0 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv2-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv2-d-transpose.pbtxt @@ -6,7 +6,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv2-d.pbtxt index 38a63df42d..88c616bd17 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv2-d.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv3-d-transpose.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv3-d-transpose.pbtxt index 29620561f7..b70a907907 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv3-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv3-d-transpose.pbtxt @@ -6,7 +6,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv3-d.pbtxt index f1a2bcbb72..33e8765ce6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-conv3-d.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-dense.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-dense.pbtxt index d1e2d57570..1ac13b5791 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-dense.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-dense.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-dropout.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-dropout.pbtxt index 92e40f6d96..77faa3c2b9 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-dropout.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-dropout.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-flatten.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-flatten.pbtxt index 087601a3c1..0b2631491b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-flatten.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-flatten.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-layer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-layer.pbtxt index b052c6bb0a..0a3414d20c 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-layer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-layer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.layers.Layer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling1-d.pbtxt index 9444a1bc76..ffc5cf1c8b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling1-d.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling2-d.pbtxt index 83dcb5e4e7..ff2cf2ba90 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling2-d.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling3-d.pbtxt index eb26e2220b..09c8a31a7b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-max-pooling3-d.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-separable-conv1-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-separable-conv1-d.pbtxt index 38d75e8bd5..549e13a7ac 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-separable-conv1-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-separable-conv1-d.pbtxt @@ -6,7 +6,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.layers.-separable-conv2-d.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.layers.-separable-conv2-d.pbtxt index 90fc61cdfa..169ecdece5 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.layers.-separable-conv2-d.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.layers.-separable-conv2-d.pbtxt @@ -6,7 +6,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-basic-l-s-t-m-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-basic-l-s-t-m-cell.pbtxt index adffc55227..4251206cda 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-basic-l-s-t-m-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-basic-l-s-t-m-cell.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-basic-r-n-n-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-basic-r-n-n-cell.pbtxt index 95746cc49c..20af24633a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-basic-r-n-n-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-basic-r-n-n-cell.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-device-wrapper.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-device-wrapper.pbtxt index 3547b66d19..3205c6a4dc 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-device-wrapper.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-device-wrapper.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-dropout-wrapper.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-dropout-wrapper.pbtxt index 7582fd52b6..14cf5ce456 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-dropout-wrapper.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-dropout-wrapper.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-g-r-u-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-g-r-u-cell.pbtxt index 7ec61661fd..e43547b154 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-g-r-u-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-g-r-u-cell.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-l-s-t-m-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-l-s-t-m-cell.pbtxt index 9617d07568..99381cd7e1 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-l-s-t-m-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-l-s-t-m-cell.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-multi-r-n-n-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-multi-r-n-n-cell.pbtxt index b31886f736..1fbde9df17 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-multi-r-n-n-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-multi-r-n-n-cell.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt index c36ecaa4b2..8ba92fcc8d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-residual-wrapper.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-residual-wrapper.pbtxt index 42128ebd17..9de73076b1 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-residual-wrapper.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.nn.rnn_cell.-residual-wrapper.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-adadelta-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-adadelta-optimizer.pbtxt index 1f1d8b6f9e..65a2b605d5 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-adadelta-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-adadelta-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.AdadeltaOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-adagrad-d-a-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-adagrad-d-a-optimizer.pbtxt index a7c05d4849..179272d8a8 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-adagrad-d-a-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-adagrad-d-a-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.AdagradDAOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-adagrad-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-adagrad-optimizer.pbtxt index bc8b92389c..15c2ef46c1 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-adagrad-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-adagrad-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.AdagradOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-adam-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-adam-optimizer.pbtxt index 5d17be9378..9c902e582f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-adam-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-adam-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.AdamOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-checkpoint.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-checkpoint.pbtxt index 5be37200f3..42dcdac9e7 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-checkpoint.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-checkpoint.pbtxt @@ -1,8 +1,8 @@ path: "tensorflow.train.Checkpoint" tf_class { is_instance: "" - is_instance: "" - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" member { name: "save_counter" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-ftrl-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-ftrl-optimizer.pbtxt index d265fdeb01..f41d9f12d9 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-ftrl-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-ftrl-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.FtrlOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-gradient-descent-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-gradient-descent-optimizer.pbtxt index c673e29cd4..7399750385 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-gradient-descent-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-gradient-descent-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.GradientDescentOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-momentum-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-momentum-optimizer.pbtxt index 8199f63b9b..9bbaa14a6f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-momentum-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-momentum-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.MomentumOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-optimizer.pbtxt index 876bb35e39..448e17a448 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-optimizer.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.train.Optimizer" tf_class { is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-proximal-adagrad-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-proximal-adagrad-optimizer.pbtxt index 14349a74ef..eb1782e9ca 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-proximal-adagrad-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-proximal-adagrad-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.ProximalAdagradOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-proximal-gradient-descent-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-proximal-gradient-descent-optimizer.pbtxt index 7d982dc51f..eb9a86183e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-proximal-gradient-descent-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-proximal-gradient-descent-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.ProximalGradientDescentOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-r-m-s-prop-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-r-m-s-prop-optimizer.pbtxt index 906384a287..2cf4c2e7ea 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-r-m-s-prop-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-r-m-s-prop-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.RMSPropOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-sync-replicas-optimizer.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-sync-replicas-optimizer.pbtxt index 2c0fda3c72..ecce08220d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-sync-replicas-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-sync-replicas-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.SyncReplicasOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt index ee16ffa505..2206f0f8d8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.Variable" tf_class { is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "SaveSliceInfo" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.-model.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.-model.pbtxt index eced2e1cb0..0a81c3a663 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.-model.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.-model.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.-sequential.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.-sequential.pbtxt index 2acb90173f..2a9ceec30e 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.-sequential.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.-sequential.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.experimental.-peephole-l-s-t-m-cell.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.experimental.-peephole-l-s-t-m-cell.pbtxt index b2ab5006dc..1bfd51cdcc 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.experimental.-peephole-l-s-t-m-cell.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.experimental.-peephole-l-s-t-m-cell.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-activation.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-activation.pbtxt index da212382c1..8a0b8eb46f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-activation.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-activation.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Activation" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-activity-regularization.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-activity-regularization.pbtxt index c910db027e..abb3c23694 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-activity-regularization.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-activity-regularization.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ActivityRegularization" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-add.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-add.pbtxt index 8b7b33e98c..b27db4e7f2 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-add.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-add.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-alpha-dropout.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-alpha-dropout.pbtxt index 5e3e41ba20..50998ac9d6 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-alpha-dropout.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-alpha-dropout.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.AlphaDropout" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling1-d.pbtxt index e160b10153..be17aeafb5 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling2-d.pbtxt index b6b71358c8..7f21b444bc 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling3-d.pbtxt index 5c5ab1580e..2ac86f152f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average-pooling3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average.pbtxt index 489de2e4d3..f6b1dd2f7e 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-average.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool1-d.pbtxt index 30fec249b8..3da1f43a92 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool2-d.pbtxt index 0e983c9234..a7be5ac818 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool3-d.pbtxt index ec50db7127..c5c29bead3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-avg-pool3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-batch-normalization.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-batch-normalization.pbtxt index 36ea9d5851..b13f963a6f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-batch-normalization.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-batch-normalization.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.BatchNormalization" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-bidirectional.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-bidirectional.pbtxt index 23153d4284..880d18e1aa 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-bidirectional.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-bidirectional.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-concatenate.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-concatenate.pbtxt index 766c3f267f..1eb0cf1a18 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-concatenate.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-concatenate.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv-l-s-t-m2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv-l-s-t-m2-d.pbtxt index 8980982271..d9394e60f5 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv-l-s-t-m2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv-l-s-t-m2-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activation" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv1-d.pbtxt index a74b8d2950..a0f6dc8097 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv2-d-transpose.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv2-d-transpose.pbtxt index b093f8ead9..037b92f861 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv2-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv2-d-transpose.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv2-d.pbtxt index 0ce9f6fd59..6a0d027d47 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv3-d-transpose.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv3-d-transpose.pbtxt index c1f5bfae0d..66b5bd75fc 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv3-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv3-d-transpose.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv3-d.pbtxt index 4aa872c4f0..e73133ff07 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-conv3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution1-d.pbtxt index 6e01f7c70c..7af6b2b3c3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution2-d-transpose.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution2-d-transpose.pbtxt index c002042d77..baff492dfb 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution2-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution2-d-transpose.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution2-d.pbtxt index f5e5446d2b..63d30a6185 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution3-d-transpose.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution3-d-transpose.pbtxt index d5f36f4bc3..7a29cbbec3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution3-d-transpose.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution3-d-transpose.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution3-d.pbtxt index 346fec6056..87c75c0224 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-convolution3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping1-d.pbtxt index 0f8fe9f05e..f69104ddfe 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping1-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Cropping1D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping2-d.pbtxt index 68fb7382a7..aa05471933 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping2-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Cropping2D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping3-d.pbtxt index deda82f9b3..d61f1ddc1d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-cropping3-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Cropping3D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dense-features.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dense-features.pbtxt index ff00ca1bb2..28c1926bd7 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dense-features.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dense-features.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.DenseFeatures" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dense.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dense.pbtxt index 919aed5723..06e8b6b314 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dense.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dense.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Dense" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-depthwise-conv2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-depthwise-conv2-d.pbtxt index f590ce1ef7..9fdf6f66d1 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-depthwise-conv2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-depthwise-conv2-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dot.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dot.pbtxt index db4261fadc..cbe1020650 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dot.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dot.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dropout.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dropout.pbtxt index 7369552b3b..0efba09b27 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dropout.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-dropout.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Dropout" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-e-l-u.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-e-l-u.pbtxt index f643ef9de2..b34c499eb2 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-e-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-e-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ELU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-embedding.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-embedding.pbtxt index ce053ae8c4..51dd853127 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-embedding.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-embedding.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Embedding" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-flatten.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-flatten.pbtxt index db95043077..dcd18a9ced 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-flatten.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-flatten.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Flatten" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-g-r-u-cell.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-g-r-u-cell.pbtxt index a6edba6b7e..f029907ee8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-g-r-u-cell.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-g-r-u-cell.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.GRUCell" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-g-r-u.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-g-r-u.pbtxt index df2ea3fbe9..ac2d8c9aa3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-g-r-u.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-g-r-u.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activation" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-gaussian-dropout.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-gaussian-dropout.pbtxt index ac4bbe7d19..15cbcfe8ed 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-gaussian-dropout.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-gaussian-dropout.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.GaussianDropout" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-gaussian-noise.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-gaussian-noise.pbtxt index 947e3170ae..865b898c4c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-gaussian-noise.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-gaussian-noise.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.GaussianNoise" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling1-d.pbtxt index 17e202c581..3e17aca17c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling2-d.pbtxt index 9772c5df9b..b160687a2a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling3-d.pbtxt index cd65075591..70e8d51a5a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-average-pooling3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool1-d.pbtxt index 0423de7a24..809dc8554b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool2-d.pbtxt index 4471cba245..3fbce8cb71 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool3-d.pbtxt index c0e7fae456..70e4103ea1 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-avg-pool3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool1-d.pbtxt index 6975a6e88d..000bf54c45 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool2-d.pbtxt index 56bd70db7e..8ffbf07f9b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool3-d.pbtxt index 656319920e..3803d2b0a8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pool3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling1-d.pbtxt index f815e66911..28668224e0 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling2-d.pbtxt index f61f0e521b..b83ed67723 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling3-d.pbtxt index c58c8ce63f..e689d69140 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-global-max-pooling3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-input-layer.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-input-layer.pbtxt index 0efe9a4297..bb6eddae71 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-input-layer.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-input-layer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.InputLayer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-l-s-t-m-cell.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-l-s-t-m-cell.pbtxt index 5caa02e71a..5fb3f9dd3a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-l-s-t-m-cell.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-l-s-t-m-cell.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.LSTMCell" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-l-s-t-m.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-l-s-t-m.pbtxt index 33082a6f06..89dfc2a256 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-l-s-t-m.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-l-s-t-m.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activation" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-lambda.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-lambda.pbtxt index 381d5660b9..376bec0814 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-lambda.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-lambda.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Lambda" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-layer.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-layer.pbtxt index 36b0a86628..c5f91a6338 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-layer.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-layer.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.keras.layers.Layer" tf_class { is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-leaky-re-l-u.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-leaky-re-l-u.pbtxt index b41662e63a..bde8887359 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-leaky-re-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-leaky-re-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.LeakyReLU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-linear-model.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-linear-model.pbtxt index 5766528b31..d40a999bd8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-linear-model.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-linear-model.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-locally-connected1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-locally-connected1-d.pbtxt index e4abfca913..16945f2c12 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-locally-connected1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-locally-connected1-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.LocallyConnected1D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-locally-connected2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-locally-connected2-d.pbtxt index cfcb92e293..f05741ffce 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-locally-connected2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-locally-connected2-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.LocallyConnected2D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-masking.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-masking.pbtxt index e0721353d1..7885db4ed2 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-masking.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-masking.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Masking" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool1-d.pbtxt index 0618fbeead..9380d26cf4 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool2-d.pbtxt index 4af52ffec8..8eb8218df3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool3-d.pbtxt index db9311ee58..0c96f86ed3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pool3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling1-d.pbtxt index bfb15cb447..0c6b230eb7 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling2-d.pbtxt index 1db962dbb8..eb7ca52fba 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling3-d.pbtxt index f80d5267e7..e724e9088f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-max-pooling3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-maximum.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-maximum.pbtxt index cd772d4ac7..dafbd09ee2 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-maximum.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-maximum.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-minimum.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-minimum.pbtxt index 2bb6b3073a..3122fbec1c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-minimum.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-minimum.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-multiply.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-multiply.pbtxt index e1a1f07355..0527cda1f0 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-multiply.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-multiply.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-p-re-l-u.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-p-re-l-u.pbtxt index 66c4446572..814e5a5d54 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-p-re-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-p-re-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.PReLU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-permute.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-permute.pbtxt index 0839554f43..aa1731afb8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-permute.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-permute.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Permute" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-r-n-n.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-r-n-n.pbtxt index b10695f6f7..9d7dd85fe0 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-r-n-n.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-r-n-n.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.RNN" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-re-l-u.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-re-l-u.pbtxt index b96500f710..e9bba298bb 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-re-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-re-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ReLU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-repeat-vector.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-repeat-vector.pbtxt index a27d93ec62..3c783eb512 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-repeat-vector.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-repeat-vector.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.RepeatVector" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-reshape.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-reshape.pbtxt index 6dda24d3d2..b8e0882541 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-reshape.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-reshape.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Reshape" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-conv1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-conv1-d.pbtxt index 8a4ae8aaa7..310f369ed6 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-conv1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-conv1-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-conv2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-conv2-d.pbtxt index a083c1da2e..df19d781c2 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-conv2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-conv2-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-convolution1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-convolution1-d.pbtxt index 5d5b361f82..bf909509bd 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-convolution1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-convolution1-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-convolution2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-convolution2-d.pbtxt index 392c338d73..5d66bc6fb6 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-convolution2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-separable-convolution2-d.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-simple-r-n-n-cell.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-simple-r-n-n-cell.pbtxt index 1143604903..88e9300de9 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-simple-r-n-n-cell.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-simple-r-n-n-cell.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.SimpleRNNCell" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-simple-r-n-n.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-simple-r-n-n.pbtxt index 5a15f1a55f..9d81c6d4bc 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-simple-r-n-n.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-simple-r-n-n.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activation" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-softmax.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-softmax.pbtxt index c470d9c8e8..712eb0c6ec 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-softmax.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-softmax.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Softmax" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout1-d.pbtxt index d17d6495c0..dfc4ca2705 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout1-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout2-d.pbtxt index 2d538b4734..5e4f727f71 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout2-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout3-d.pbtxt index b70923601a..9d893cb30a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-spatial-dropout3-d.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-stacked-r-n-n-cells.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-stacked-r-n-n-cells.pbtxt index f453ddd50e..a2ed954e4c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-stacked-r-n-n-cells.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-stacked-r-n-n-cells.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.StackedRNNCells" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-subtract.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-subtract.pbtxt index 5759169e07..8a0818e78a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-subtract.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-subtract.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-thresholded-re-l-u.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-thresholded-re-l-u.pbtxt index bfde1c35f6..b5591b4826 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-thresholded-re-l-u.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-thresholded-re-l-u.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ThresholdedReLU" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-time-distributed.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-time-distributed.pbtxt index e7f59a9cc5..210e4fd4e6 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-time-distributed.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-time-distributed.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling1-d.pbtxt index 0354149d4f..da2213a84f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling1-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.UpSampling1D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling2-d.pbtxt index fff0e26bc1..e2c303d506 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling2-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.UpSampling2D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling3-d.pbtxt index c49fa5663d..396e774c8a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-up-sampling3-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.UpSampling3D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-wrapper.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-wrapper.pbtxt index c961699053..8b6418d514 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-wrapper.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-wrapper.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.Wrapper" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding1-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding1-d.pbtxt index 1911e128eb..e8fda4c71a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding1-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding1-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ZeroPadding1D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding2-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding2-d.pbtxt index 88be914347..50c52d270b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding2-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding2-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ZeroPadding2D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding3-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding3-d.pbtxt index 2bbb71ece2..84c6b78a2b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding3-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-zero-padding3-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.layers.ZeroPadding3D" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-accuracy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-accuracy.pbtxt index e3e8dc00fe..6756beca1d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-accuracy.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-binary-accuracy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-binary-accuracy.pbtxt index 60b4f992c0..bec0b20aa5 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-binary-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-binary-accuracy.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-accuracy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-accuracy.pbtxt index deab5a7a70..71dc294dc3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-accuracy.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-hinge.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-hinge.pbtxt index 10477e6662..43024e738a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-categorical-hinge.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt index cdb3e1a79a..1e39385f81 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-negatives.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-negatives.pbtxt index f4d067243d..5432f7f400 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-negatives.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-negatives.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-positives.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-positives.pbtxt index f898e00eea..75541bf285 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-positives.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-false-positives.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-hinge.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-hinge.pbtxt index 393ba964c4..f8d47f3771 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-hinge.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-error.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-error.pbtxt index a86b26e618..bf7fc7cfc5 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-error.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-error.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt index 5e8f321a06..59bb767d35 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-absolute-percentage-error.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-error.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-error.pbtxt index a2eb10c5f6..91f4712312 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-error.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-error.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt index 7df45a8caf..205e15d439 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean-squared-logarithmic-error.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean.pbtxt index 899502cfad..eec26ffce7 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-mean.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-precision.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-precision.pbtxt index 3bdf3a4167..9aeaa5627a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-precision.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-precision.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-recall.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-recall.pbtxt index 1ac2aacca0..748cec0866 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-recall.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-recall.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt index 5c4f26929f..97aeb680be 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sensitivity-at-specificity.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt index 48ac5c2fdc..571c2bf9d3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-sparse-categorical-accuracy.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt index 702f711722..85f80b062e 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-specificity-at-sensitivity.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-squared-hinge.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-squared-hinge.pbtxt index 6c3a9748c7..71c047e973 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-squared-hinge.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-squared-hinge.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-negatives.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-negatives.pbtxt index 3f1a99b83b..4bc9383f6f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-negatives.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-negatives.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-positives.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-positives.pbtxt index e5a4207a32..2eae4df0ae 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-positives.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-true-positives.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-model.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-model.pbtxt index 5885cd21c1..55bd285612 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-model.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-model.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-sequential.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-sequential.pbtxt index 935fa32f8c..a82424167e 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-sequential.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-sequential.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adadelta.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adadelta.pbtxt index 5426269793..0a56293e80 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adadelta.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adadelta.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Adadelta" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adagrad.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adagrad.pbtxt index c39fe6ba4f..14d0894e56 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adagrad.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adagrad.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Adagrad" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adam.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adam.pbtxt index 05d46d380b..fdb1ea838c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adam.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adam.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Adam" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adamax.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adamax.pbtxt index 88fd96c71d..ece63ec168 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adamax.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-adamax.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Adamax" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-nadam.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-nadam.pbtxt index 7b824e7f0b..f952f88b6d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-nadam.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-nadam.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.Nadam" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-optimizer.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-optimizer.pbtxt index 58b7f27491..27bae902b0 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-optimizer.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.keras.optimizers.Optimizer" tf_class { is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-r-m-sprop.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-r-m-sprop.pbtxt index 8de796edde..e523443a00 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-r-m-sprop.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-r-m-sprop.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.RMSprop" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-s-g-d.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-s-g-d.pbtxt index 393eeb3d6c..d2721f8e92 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-s-g-d.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.optimizers.-s-g-d.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.keras.optimizers.SGD" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "iterations" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-device-wrapper.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-device-wrapper.pbtxt index 3547b66d19..3205c6a4dc 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-device-wrapper.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-device-wrapper.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt index c36ecaa4b2..8ba92fcc8d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt @@ -3,7 +3,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-residual-wrapper.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-residual-wrapper.pbtxt index 42128ebd17..9de73076b1 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-residual-wrapper.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-residual-wrapper.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.rnn.-dropout-wrapper.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.rnn.-dropout-wrapper.pbtxt index 7721eed65b..7781337c82 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.rnn.-dropout-wrapper.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.rnn.-dropout-wrapper.pbtxt @@ -6,7 +6,7 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "activity_regularizer" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.train.-checkpoint.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.train.-checkpoint.pbtxt index 5be37200f3..42dcdac9e7 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.train.-checkpoint.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.train.-checkpoint.pbtxt @@ -1,8 +1,8 @@ path: "tensorflow.train.Checkpoint" tf_class { is_instance: "" - is_instance: "" - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" member { name: "save_counter" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.train.-proximal-gradient-descent-optimizer.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.train.-proximal-gradient-descent-optimizer.pbtxt index 7d982dc51f..eb9a86183e 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.train.-proximal-gradient-descent-optimizer.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.train.-proximal-gradient-descent-optimizer.pbtxt @@ -2,7 +2,7 @@ path: "tensorflow.train.ProximalGradientDescentOptimizer" tf_class { is_instance: "" is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" member { name: "GATE_GRAPH" -- GitLab From 5b2fa842fafcffd7fb1251a1e550d8b6ff7a1467 Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Tue, 15 Jan 2019 16:28:44 -0800 Subject: [PATCH 0757/2345] [Grappler] Preserve function execution semantics wrt to Dataset ops. PiperOrigin-RevId: 229463147 --- tensorflow/core/grappler/grappler_item.cc | 16 ++--- tensorflow/core/grappler/grappler_item.h | 24 +++---- tensorflow/core/grappler/op_types.cc | 7 +++ tensorflow/core/grappler/op_types.h | 3 + .../optimizers/arithmetic_optimizer.cc | 2 +- .../grappler/optimizers/function_optimizer.cc | 26 ++++---- .../grappler/optimizers/meta_optimizer.cc | 12 ++-- .../optimizers/meta_optimizer_test.cc | 62 +++++++++---------- tensorflow/core/grappler/utils/functions.cc | 6 +- .../core/grappler/utils/functions_test.cc | 2 +- 10 files changed, 89 insertions(+), 71 deletions(-) diff --git a/tensorflow/core/grappler/grappler_item.cc b/tensorflow/core/grappler/grappler_item.cc index 2d71ac54cc..1323dd9a64 100644 --- a/tensorflow/core/grappler/grappler_item.cc +++ b/tensorflow/core/grappler/grappler_item.cc @@ -43,7 +43,7 @@ GrapplerItem GrapplerItem::WithGraph(GraphDef&& graph_def) const { item.save_restore_loc_tensor = save_restore_loc_tensor; item.queue_runners = queue_runners; item.devices_ = devices_; - item.allowed_optimizations_ = allowed_optimizations_; + item.optimization_options_ = optimization_options_; item.graph.Swap(&graph_def); return item; } @@ -115,9 +115,11 @@ std::unordered_set GrapplerItem::NodesToPreserve() const { } } - if (!allowed_optimizations_.prune_ops_with_side_effects) { + // Tensorflow functions do not prune side effects, or dataset-output ops from + // the function body (see PruneFunctionBody in common_runtime/function.cc). + if (optimization_options_.is_function_instantiation) { for (const NodeDef& node : graph.node()) { - if (!IsFreeOfSideEffect(node)) { + if (!IsFreeOfSideEffect(node) || IsDataset(node)) { result.insert(node.name()); } } @@ -175,13 +177,13 @@ Status GrapplerItem::InferDevicesFromGraph() { void GrapplerItem::ClearDevices() { devices_.clear(); } -const GrapplerItem::AllowedOptimizations& GrapplerItem::allowed_optimizations() +const GrapplerItem::OptimizationOptions& GrapplerItem::optimization_options() const { - return allowed_optimizations_; + return optimization_options_; } -GrapplerItem::AllowedOptimizations& GrapplerItem::allowed_optimizations() { - return allowed_optimizations_; +GrapplerItem::OptimizationOptions& GrapplerItem::optimization_options() { + return optimization_options_; } std::vector ComputeTransitiveFanin( diff --git a/tensorflow/core/grappler/grappler_item.h b/tensorflow/core/grappler/grappler_item.h index 1ae551f5ac..75712e9f92 100644 --- a/tensorflow/core/grappler/grappler_item.h +++ b/tensorflow/core/grappler/grappler_item.h @@ -81,17 +81,17 @@ struct GrapplerItem { // fetch nodes, keep_ops, init_ops. std::unordered_set NodesToPreserve() const; - // Restrict types of optimizations that are allowed for this GrapplerItem. - struct AllowedOptimizations { + struct OptimizationOptions { // Is it allowed to add nodes to the graph that do not have registered // gradient function. - bool non_differentiable_rewrites = true; - - // By default we are allowed to prune ops with side-effects from the main - // graph if they are not in transitive fanin of the fetch nodes. If we are - // optimizing a graph that was instantiated by a function definition, we - // must keep all side effects intact. - bool prune_ops_with_side_effects = true; + bool allow_non_differentiable_rewrites = true; + + // Tensorflow function execution semantics is slightly different from the + // main Tensorflow graph, and we need to make sure that we do not change it + // by running Grappler optimizer passes. One main difference is that + // functions do not prune ops with side-effects and dataset-output ops (see + // PruneFunctionBody in common_runtime/function.cc). + bool is_function_instantiation = false; }; const std::unordered_set& devices() const; @@ -108,8 +108,8 @@ struct GrapplerItem { // Clears a set of available devices. void ClearDevices(); - const AllowedOptimizations& allowed_optimizations() const; - AllowedOptimizations& allowed_optimizations(); + const OptimizationOptions& optimization_options() const; + OptimizationOptions& optimization_options(); private: // TODO(ezhulenev) Make GrapplerItem a class and hide all public data members. @@ -120,7 +120,7 @@ struct GrapplerItem { // Example of a fully defined name: "/job:work/replica:1/task:1/device:CPU:0" std::unordered_set devices_; - AllowedOptimizations allowed_optimizations_; + OptimizationOptions optimization_options_; }; // Return the transitive fanin of a set of terminal nodes. diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index b201c3a717..e06580cebf 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -561,6 +561,13 @@ bool MaybeHasRefInput(const NodeDef& node) { return false; } +bool IsDataset(const NodeDef& node) { + const string& op = node.op(); + // See `GetNodeClassForOp` in core/graph/graph.cc. + return op == "IteratorGetNext" || op == "IteratorGetNextSync" || + op == "DatasetToSingleElement" || op == "ReduceDataset"; +} + bool IsFreeOfSideEffect(const NodeDef& node, const OpRegistryInterface* op_registry) { // Placeholders must be preserved to keep the graph feedable. diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h index cb7781ec6e..092748f4ff 100644 --- a/tensorflow/core/grappler/op_types.h +++ b/tensorflow/core/grappler/op_types.h @@ -183,6 +183,9 @@ bool IsCommutative(const NodeDef& node); // value. bool IsPersistent(const NodeDef& node); +// Returns true if the node belongs to the NC_DATASET class (see graph/graph.h). +bool IsDataset(const NodeDef& node); + bool IsFreeOfSideEffect(const NodeDef& node, const OpRegistryInterface* op_registry); bool IsFreeOfSideEffect(const NodeDef& node); // use OpRegistry::Global() diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index e85e0473b3..2168dbd623 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -3576,7 +3576,7 @@ Status ArithmeticOptimizer::Optimize(Cluster* /*cluster*/, // Disable restricted graph rewrites. options_.unary_ops_composition &= - item.allowed_optimizations().non_differentiable_rewrites; + item.optimization_options().allow_non_differentiable_rewrites; if (options_.dedup_computations) { DedupComputations(); diff --git a/tensorflow/core/grappler/optimizers/function_optimizer.cc b/tensorflow/core/grappler/optimizers/function_optimizer.cc index 1394aa5047..794e87dc38 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer.cc @@ -260,7 +260,7 @@ class FunctionOptimizerContext { : grappler_item_id_(item.id), graph_version_(item.graph.versions().producer()), opt_level_(opt_level), - allowed_optimizations_(item.allowed_optimizations()), + optimization_options_(item.optimization_options()), function_library_(OpRegistry::Global(), item.graph.library()), available_device_names_(item.devices().begin(), item.devices().end()), graph_view_(&item.graph) { @@ -272,8 +272,8 @@ class FunctionOptimizerContext { const RewriterConfig::Toggle opt_level() const { return opt_level_; } - const GrapplerItem::AllowedOptimizations& allowed_optimizations() const { - return allowed_optimizations_; + const GrapplerItem::OptimizationOptions& optimization_options() const { + return optimization_options_; } const FunctionLibraryDefinition& function_library() const { @@ -409,7 +409,7 @@ class FunctionOptimizerContext { const string grappler_item_id_; const int graph_version_; const RewriterConfig::Toggle opt_level_; - const GrapplerItem::AllowedOptimizations allowed_optimizations_; + const GrapplerItem::OptimizationOptions optimization_options_; FunctionLibraryDefinition function_library_; // These fields initialized lazily only if needed. @@ -1620,21 +1620,25 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // edges from inlined side-effectful ops. std::vector side_effectful_nodes; - // We have to make sure that all side-effectful nodes inside a function body - // will be executed after function inlining. + // We have to make sure that all side-effectful and dataset-output nodes + // inside a function body will be executed after function inlining. for (NodeDef& func_body_node : *placed_graph_def.mutable_node()) { - if (!IsFreeOfSideEffect(func_body_node, &ctx->function_library())) { + const bool node_must_execute = + !IsFreeOfSideEffect(func_body_node, &ctx->function_library()) || + IsDataset(func_body_node); + + if (node_must_execute) { int num_fanouts = placed_graph_view.NumFanouts( func_body_node, /*include_controlled_nodes=*/true); // If the node doesn't have any outgoing edges and we do not have any // nodes in the `happens_after` set, we can't inline a function and - // guarantee that side-effects will be executed. The only exception if we - // do function library optimization, and the GrapplerItem was constructed - // for the function body, because functions have strict semantics. + // guarantee that it will be executed. The only exception if we do + // function library optimization, and the GrapplerItem was instantiated + // for the function body, because functions do not prune these ops. if (num_fanouts == 0 && happens_after.empty() && - ctx->allowed_optimizations().prune_ops_with_side_effects) { + !ctx->optimization_options().is_function_instantiation) { return errors::Internal( "Can't inline a function with a side-effectful op with empty " "fanouts and empty output control edge set. Function body node: ", diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer.cc b/tensorflow/core/grappler/optimizers/meta_optimizer.cc index a84bb1d62f..c200b7db07 100644 --- a/tensorflow/core/grappler/optimizers/meta_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/meta_optimizer.cc @@ -527,7 +527,8 @@ Status MetaOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, // can't perform non-differentiable rewrites. if (differentiable_functions.find(func_name) != differentiable_functions.end()) { - func_item.allowed_optimizations().non_differentiable_rewrites = false; + func_item.optimization_options().allow_non_differentiable_rewrites = + false; } // Function item is allowed to use all devices from the main graph. @@ -536,10 +537,11 @@ Status MetaOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, VLOG(3) << added_devices.error_message(); } - // We are not allowed to prune side effects from the graph instantiated - // by the function definition, because we must guarantee function - // execution semantics wrt side effects (see function_optimizer.cc). - func_item.allowed_optimizations().prune_ops_with_side_effects = false; + // We are not allowed to prune certain types of ops from the graph + // instantiated by the function definition, because we must guarantee + // function execution semantics wrt side effects (see + // function_optimizer.cc). + func_item.optimization_options().is_function_instantiation = true; // Optimize function body graph. GraphDef optimized_func_graph; diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc b/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc index a061f6194a..b1b72075ca 100644 --- a/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc @@ -87,12 +87,12 @@ REGISTER_GRAPH_OPTIMIZER(TestOptimizerWithParams); // Record various properties of the GrapplerItems passed for optimization. class GrapplerItemPropertiesAccumulator : public CustomGraphOptimizer { public: - static void SetAllowedOptimizations( - gtl::FlatMap* - allowed_optimizations) { - allowed_optimizations_ = allowed_optimizations; + static void SetOptimizationOptions( + gtl::FlatMap* + optimization_options) { + optimization_options_ = optimization_options; } - static void ResetAllowedOptimizations() { allowed_optimizations_ = nullptr; } + static void ResetOptimizationOptions() { optimization_options_ = nullptr; } GrapplerItemPropertiesAccumulator() {} string name() const override { @@ -107,8 +107,8 @@ class GrapplerItemPropertiesAccumulator : public CustomGraphOptimizer { Status Optimize(Cluster* cluster, const GrapplerItem& item, GraphDef* optimized_graph) override { *optimized_graph = item.graph; - if (allowed_optimizations_) { - allowed_optimizations_->insert({item.id, item.allowed_optimizations()}); + if (optimization_options_) { + optimization_options_->insert({item.id, item.optimization_options()}); } return Status::OK(); } @@ -117,12 +117,12 @@ class GrapplerItemPropertiesAccumulator : public CustomGraphOptimizer { const GraphDef& optimized_graph, double result) override {} private: - static gtl::FlatMap* - allowed_optimizations_; + static gtl::FlatMap* + optimization_options_; }; -gtl::FlatMap* - GrapplerItemPropertiesAccumulator::allowed_optimizations_; +gtl::FlatMap* + GrapplerItemPropertiesAccumulator::optimization_options_; REGISTER_GRAPH_OPTIMIZER(GrapplerItemPropertiesAccumulator); @@ -602,10 +602,9 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibraryWithRestrictions) { // We will record what type of optimizations meta optimizer allows for each // GrapplerItem (main graph and graphs for each function). - gtl::FlatMap - allowed_optimizations; - GrapplerItemPropertiesAccumulator::SetAllowedOptimizations( - &allowed_optimizations); + gtl::FlatMap optimization_options; + GrapplerItemPropertiesAccumulator::SetOptimizationOptions( + &optimization_options); // Just record properties of optimized Grappler items. ConfigProto config_proto; @@ -664,22 +663,23 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibraryWithRestrictions) { // Our custom optimizer must be called for the main graph and for the two // functions. - ASSERT_EQ(allowed_optimizations.size(), 3); - - auto allowed_optimizations_main = - gtl::FindOrNull(allowed_optimizations, "main"); - ASSERT_NE(allowed_optimizations_main, nullptr); - EXPECT_TRUE(allowed_optimizations_main->non_differentiable_rewrites); - - auto allowed_optimizations_my_mul_1 = - gtl::FindOrNull(allowed_optimizations, "MyMul1"); - ASSERT_NE(allowed_optimizations_my_mul_1, nullptr); - EXPECT_TRUE(allowed_optimizations_my_mul_1->non_differentiable_rewrites); - - auto allowed_optimizations_my_mul_2 = - gtl::FindOrNull(allowed_optimizations, "MyMul2"); - ASSERT_NE(allowed_optimizations_my_mul_2, nullptr); - EXPECT_FALSE(allowed_optimizations_my_mul_2->non_differentiable_rewrites); + ASSERT_EQ(optimization_options.size(), 3); + + auto optimization_options_main = + gtl::FindOrNull(optimization_options, "main"); + ASSERT_NE(optimization_options_main, nullptr); + EXPECT_TRUE(optimization_options_main->allow_non_differentiable_rewrites); + + auto optimization_options_my_mul_1 = + gtl::FindOrNull(optimization_options, "MyMul1"); + ASSERT_NE(optimization_options_my_mul_1, nullptr); + EXPECT_TRUE(optimization_options_my_mul_1->allow_non_differentiable_rewrites); + + auto optimization_options_my_mul_2 = + gtl::FindOrNull(optimization_options, "MyMul2"); + ASSERT_NE(optimization_options_my_mul_2, nullptr); + EXPECT_FALSE( + optimization_options_my_mul_2->allow_non_differentiable_rewrites); } class SleepingOptimizer : public CustomGraphOptimizer { diff --git a/tensorflow/core/grappler/utils/functions.cc b/tensorflow/core/grappler/utils/functions.cc index 150728d030..357a0b3b47 100644 --- a/tensorflow/core/grappler/utils/functions.cc +++ b/tensorflow/core/grappler/utils/functions.cc @@ -337,9 +337,9 @@ GrapplerFunctionItem::GrapplerFunctionItem( } } - // It's unsafe to prune side-effectful ops from the graph instantiated from a - // function definition (see inlining in function_optimizer.cc). - allowed_optimizations().prune_ops_with_side_effects = false; + // Tensorflow functions execution semantics is different from the main graph, + // and we need to preserve it when we do graph optimizations. + optimization_options().is_function_instantiation = true; } const string& GrapplerFunctionItem::description() const { return description_; } diff --git a/tensorflow/core/grappler/utils/functions_test.cc b/tensorflow/core/grappler/utils/functions_test.cc index c49920c79c..7720888828 100644 --- a/tensorflow/core/grappler/utils/functions_test.cc +++ b/tensorflow/core/grappler/utils/functions_test.cc @@ -641,7 +641,7 @@ TEST_F(FunctionsTest, FromFunctionDefWithSideEffectfulOps) { EXPECT_EQ(3, item.function_body().node_size()); EXPECT_EQ(1, item.input_size()); EXPECT_EQ(0, item.output_size()); - EXPECT_EQ(false, item.allowed_optimizations().prune_ops_with_side_effects); + EXPECT_EQ(true, item.optimization_options().is_function_instantiation); } TEST_F(FunctionsTest, MakeFunctionDef) { -- GitLab From 7e090f6a5412fd5f22b5b75ca7ae7844c3d025e9 Mon Sep 17 00:00:00 2001 From: Nick Hamatake Date: Tue, 15 Jan 2019 16:34:32 -0800 Subject: [PATCH 0758/2345] Force ICU to look for its data statically. This is required for Windows 10 builds. Patch suggested by github.com/cielavenir. Fixes #23655 PiperOrigin-RevId: 229464102 --- tensorflow/core/kernels/unicode_script_op.cc | 3 +++ third_party/icu/BUILD.bazel | 1 + 2 files changed, 4 insertions(+) diff --git a/tensorflow/core/kernels/unicode_script_op.cc b/tensorflow/core/kernels/unicode_script_op.cc index 085e397eba..f01bb52293 100644 --- a/tensorflow/core/kernels/unicode_script_op.cc +++ b/tensorflow/core/kernels/unicode_script_op.cc @@ -17,6 +17,9 @@ limitations under the License. #include "unicode/uscript.h" // TF:icu #include "tensorflow/core/framework/op_kernel.h" +// ICU codemap data is linked statically. +#define U_STATIC_IMPLEMENTATION + namespace tensorflow { class UnicodeScriptOp : public OpKernel { diff --git a/third_party/icu/BUILD.bazel b/third_party/icu/BUILD.bazel index 36d6b9006b..399962d249 100644 --- a/third_party/icu/BUILD.bazel +++ b/third_party/icu/BUILD.bazel @@ -44,6 +44,7 @@ cc_library( ]), copts = [ "-DU_COMMON_IMPLEMENTATION", + "-DU_STATIC_IMPLEMENTATION", "-DU_HAVE_STD_ATOMICS", ] + select({ ":android": [ -- GitLab From bc246800912b450cf5ee7a485a95a0b77a3d9f53 Mon Sep 17 00:00:00 2001 From: Francois Chollet Date: Tue, 15 Jan 2019 17:40:10 -0800 Subject: [PATCH 0759/2345] Reduce flakyness of integration test. Tested by trying a range of random seeds. PiperOrigin-RevId: 229473610 --- tensorflow/python/keras/integration_test.py | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tensorflow/python/keras/integration_test.py b/tensorflow/python/keras/integration_test.py index febee44cd4..dc8d1deddb 100644 --- a/tensorflow/python/keras/integration_test.py +++ b/tensorflow/python/keras/integration_test.py @@ -35,7 +35,7 @@ class VectorClassificationIntegrationTest(keras_parameterized.TestCase): def test_vector_classification(self): np.random.seed(1337) (x_train, y_train), _ = testing_utils.get_test_data( - train_samples=150, + train_samples=100, test_samples=0, input_shape=(10,), num_classes=2) @@ -47,10 +47,10 @@ class VectorClassificationIntegrationTest(keras_parameterized.TestCase): keras.layers.Dense(y_train.shape[-1], activation='softmax')], input_shape=x_train.shape[1:]) model.compile(loss='categorical_crossentropy', - optimizer=keras.optimizer_v2.adam.Adam(0.01), + optimizer=keras.optimizer_v2.adam.Adam(0.005), metrics=['accuracy'], run_eagerly=testing_utils.should_run_eagerly()) - history = model.fit(x_train, y_train, epochs=10, batch_size=16, + history = model.fit(x_train, y_train, epochs=10, batch_size=10, validation_data=(x_train, y_train), verbose=2) self.assertGreater(history.history['val_acc'][-1], 0.7) @@ -64,7 +64,7 @@ class VectorClassificationIntegrationTest(keras_parameterized.TestCase): # and internal losses can be shared. np.random.seed(1337) (x_train, y_train), _ = testing_utils.get_test_data( - train_samples=150, + train_samples=100, test_samples=0, input_shape=(10,), num_classes=2) @@ -82,13 +82,13 @@ class VectorClassificationIntegrationTest(keras_parameterized.TestCase): y = keras.layers.Dense(y_train.shape[-1], activation='softmax')(y) model = keras.models.Model(x, y) model.compile(loss='categorical_crossentropy', - optimizer=keras.optimizer_v2.adam.Adam(0.01), + optimizer=keras.optimizer_v2.adam.Adam(0.005), metrics=['accuracy'], run_eagerly=testing_utils.should_run_eagerly()) if not testing_utils.should_run_eagerly(): self.assertEqual(len(model.losses), 2) self.assertEqual(len(model.updates), 2) - history = model.fit(x_train, y_train, epochs=10, batch_size=16, + history = model.fit(x_train, y_train, epochs=10, batch_size=10, validation_data=(x_train, y_train), verbose=2) self.assertGreater(history.history['val_acc'][-1], 0.7) @@ -106,7 +106,7 @@ class TimeseriesClassificationIntegrationTest(keras_parameterized.TestCase): def test_timeseries_classification(self): np.random.seed(1337) (x_train, y_train), _ = testing_utils.get_test_data( - train_samples=250, + train_samples=100, test_samples=0, input_shape=(4, 10), num_classes=2) @@ -119,22 +119,22 @@ class TimeseriesClassificationIntegrationTest(keras_parameterized.TestCase): model = testing_utils.get_model_from_layers( layers, input_shape=x_train.shape[1:]) model.compile(loss='categorical_crossentropy', - optimizer=keras.optimizer_v2.adam.Adam(0.01), + optimizer=keras.optimizer_v2.adam.Adam(0.005), metrics=['accuracy'], run_eagerly=testing_utils.should_run_eagerly()) - history = model.fit(x_train, y_train, epochs=15, batch_size=16, + history = model.fit(x_train, y_train, epochs=15, batch_size=10, validation_data=(x_train, y_train), verbose=2) self.assertGreater(history.history['val_acc'][-1], 0.7) _, val_acc = model.evaluate(x_train, y_train) self.assertAlmostEqual(history.history['val_acc'][-1], val_acc) predictions = model.predict(x_train) - self.assertEqual(predictions.shape, (250, 2)) + self.assertEqual(predictions.shape, (x_train.shape[0], 2)) def test_timeseries_classification_sequential_tf_rnn(self): np.random.seed(1337) (x_train, y_train), _ = testing_utils.get_test_data( - train_samples=150, + train_samples=100, test_samples=0, input_shape=(4, 10), num_classes=2) @@ -147,17 +147,17 @@ class TimeseriesClassificationIntegrationTest(keras_parameterized.TestCase): activation='softmax', dtype=dtypes.float32))) model.compile(loss='categorical_crossentropy', - optimizer=keras.optimizer_v2.adam.Adam(0.01), + optimizer=keras.optimizer_v2.adam.Adam(0.005), metrics=['accuracy'], run_eagerly=testing_utils.should_run_eagerly()) - history = model.fit(x_train, y_train, epochs=15, batch_size=16, + history = model.fit(x_train, y_train, epochs=15, batch_size=10, validation_data=(x_train, y_train), verbose=2) self.assertGreater(history.history['val_acc'][-1], 0.7) _, val_acc = model.evaluate(x_train, y_train) self.assertAlmostEqual(history.history['val_acc'][-1], val_acc) predictions = model.predict(x_train) - self.assertEqual(predictions.shape, (150, 2)) + self.assertEqual(predictions.shape, (x_train.shape[0], 2)) @keras_parameterized.run_with_all_model_types @@ -167,7 +167,7 @@ class ImageClassificationIntegrationTest(keras_parameterized.TestCase): def test_image_classification(self): np.random.seed(1337) (x_train, y_train), _ = testing_utils.get_test_data( - train_samples=250, + train_samples=100, test_samples=0, input_shape=(10, 10, 3), num_classes=2) @@ -184,17 +184,17 @@ class ImageClassificationIntegrationTest(keras_parameterized.TestCase): model = testing_utils.get_model_from_layers( layers, input_shape=x_train.shape[1:]) model.compile(loss='categorical_crossentropy', - optimizer=keras.optimizer_v2.adam.Adam(0.01), + optimizer=keras.optimizer_v2.adam.Adam(0.005), metrics=['accuracy'], run_eagerly=testing_utils.should_run_eagerly()) - history = model.fit(x_train, y_train, epochs=10, batch_size=16, + history = model.fit(x_train, y_train, epochs=10, batch_size=10, validation_data=(x_train, y_train), verbose=2) self.assertGreater(history.history['val_acc'][-1], 0.7) _, val_acc = model.evaluate(x_train, y_train) self.assertAlmostEqual(history.history['val_acc'][-1], val_acc) predictions = model.predict(x_train) - self.assertEqual(predictions.shape, (250, 2)) + self.assertEqual(predictions.shape, (x_train.shape[0], 2)) if __name__ == '__main__': -- GitLab From 13ecb4c3bcdd93c85a4bf21080b240681c763eda Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Tue, 15 Jan 2019 17:47:32 -0800 Subject: [PATCH 0760/2345] Document the GPU accelerator best practices. PiperOrigin-RevId: 229474455 --- .../lite/g3doc/performance/best_practices.md | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/g3doc/performance/best_practices.md b/tensorflow/lite/g3doc/performance/best_practices.md index b76414cebe..090f19e810 100644 --- a/tensorflow/lite/g3doc/performance/best_practices.md +++ b/tensorflow/lite/g3doc/performance/best_practices.md @@ -37,8 +37,34 @@ If your application is not careful, there can be redundant copies when feeding t Platform specific tools like [Android profiler](https://developer.android.com/studio/profile/android-profiler) and [Instruments](https://help.apple.com/instruments/mac/current/) provide a wealth of profiling information that can be used to debug your app. Sometimes the performance bug may be not in the model but in parts of application code that interact with the model. Make sure to familiarize yourself with platform specific profiling tools and best practices for your platform. ## Evaluate whether your model benefits from using hardware accelerators available on the device -Tensorflow Lite is working on adding support for accelerators like GPU and provides acceleration through [Neural Networks API](https://developer.android.com/ndk/guides/neuralnetworks/) on Android. -You can utilize these hardware accelerator backends to improve the speed and efficiency of your model. To enable Neural Networks API call [UseNNAPI](https://github.com/tensorflow/tensorflow/blob/6305a6d83552ba6a472cd72398b60d9241467f1f/tensorflow/lite/interpreter.h#L334) on the interpreter instance. + +TensorFlow Lite has added been new ways to accelerate models with faster +hardware like GPUs, DSPs, and neural accelerators. Typically, these accelerators +are exposed through *delegate* submodules that take over parts of the +interpreter execution. TensorFlow Lite can use delegates by: + +* Using Android's + [Neural Networks API](https://developer.android.com/ndk/guides/neuralnetworks/). + You can utilize these hardware accelerator backends to improve the speed and + efficiency of your model. To enable the Neural Networks API, call + [UseNNAPI](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/interpreter.h#L330) + on the interpreter instance. +* A binary-only GPU delegate has been released for Android and iOS—using + OpenGL and Metal, respectively. To try them out, see the + [GPU delegate tutorial](gpu.md) and [documentation](gpu_advanced.md). +* It is possible to create your own delegate if you have access to + non-standard hardware. View the NN API delegate in the source code as an + example. + +Be aware that some accelerators work better for different types of models. It is +important to benchmark each delegate to see if it is a good choice for your +application. For example, if you have a very small model, it may not be worth +delegating the model to either the NN API or the GPU. Conversely, accelerators +are a great choice for large models that have high arithmetic intensity. ## Need more help -The Tensorflow team is happy to help diagnose and address specific performance issues you may be facing. Please file an issue on [GitHub](https://github.com/tensorflow/tensorflow/issues) with details of the issue. + +The Tensorflow team is happy to help diagnose and address specific performance +issues you may be facing. Please file an issue on +[GitHub](https://github.com/tensorflow/tensorflow/issues) with details of the +issue. -- GitLab From a3fa57c6d2f4678597c0b76bb448c37b66c45376 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Tue, 15 Jan 2019 17:57:55 -0800 Subject: [PATCH 0761/2345] Add new distribution strategy test rule PiperOrigin-RevId: 229475641 --- tensorflow/contrib/distribute/python/BUILD | 10 +++-- .../core/platform/default/distribute.bzl | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 tensorflow/core/platform/default/distribute.bzl diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 1a54cd167e..384fe53923 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -1,6 +1,7 @@ # Implementation of a prototype TF distributed computation library. load("//tensorflow/compiler/tests:build_defs.bzl", "tf_xla_py_test") +load("//tensorflow/core:platform/default/distribute.bzl", "distribute_py_test") load("//tensorflow:tensorflow.bzl", "py_test") load("//tensorflow:tensorflow.bzl", "cuda_py_test") @@ -621,12 +622,10 @@ py_library( ], ) -cuda_py_test( +distribute_py_test( name = "keras_test", srcs = ["keras_test.py"], - additional_deps = [ - ":keras_test_lib", - ], + main = "keras_test.py", shard_count = 16, tags = [ "multi_and_single_gpu", @@ -635,6 +634,9 @@ cuda_py_test( "no_windows_gpu", "notsan", ], + deps = [ + ":keras_test_lib", + ], ) # TODO(b/121200287): Remove this in 2.0 diff --git a/tensorflow/core/platform/default/distribute.bzl b/tensorflow/core/platform/default/distribute.bzl new file mode 100644 index 0000000000..a3afc37f03 --- /dev/null +++ b/tensorflow/core/platform/default/distribute.bzl @@ -0,0 +1,39 @@ +"""Build rules for tf.distritbute testing.""" + +load("//tensorflow:tensorflow.bzl", "cuda_py_test") + +def distribute_py_test( + name, + srcs = [], + deps = [], + tags = [], + data = [], + main = None, + args = [], + shard_count = 1, + **kwargs): + """Generates py_test targets for CPU and GPU. + + Args: + name: test target name to generate suffixed with `test`. + srcs: source files for the tests. + deps: additional dependencies for the test targets. + tags: tags to be assigned to the different test targets. + data: data files that need to be associated with the target files. + main: optional main script. + args: arguments to the tests. + shard_count: number of shards to split the tests across. + **kwargs: extra keyword arguments to the test. + """ + + cuda_py_test( + name = name, + srcs = srcs, + data = data, + main = main, + additional_deps = deps, + shard_count = shard_count, + tags = tags, + args = args, + **kwargs + ) -- GitLab From 6750ba5da025f71ea989ea7f9d924a5e9b22aedc Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 18:06:59 -0800 Subject: [PATCH 0762/2345] Update ops-related pbtxt files. PiperOrigin-RevId: 229476985 --- .../core/ops/compat/ops_history.v1.pbtxt | 128 ++++++++++++++++++ tensorflow/core/ops/ops.pbtxt | 95 ++++++++++++- 2 files changed, 217 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/ops/compat/ops_history.v1.pbtxt b/tensorflow/core/ops/compat/ops_history.v1.pbtxt index 8705492c66..9990b8629c 100644 --- a/tensorflow/core/ops/compat/ops_history.v1.pbtxt +++ b/tensorflow/core/ops/compat/ops_history.v1.pbtxt @@ -1298,6 +1298,34 @@ op { type: DT_FLOAT } } +op { + name: "AdjustContrastv2" + input_arg { + name: "images" + type_attr: "T" + } + input_arg { + name: "contrast_factor" + type: DT_FLOAT + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + default_value { + type: DT_FLOAT + } + allowed_values { + list { + type: DT_HALF + type: DT_FLOAT + } + } + } +} op { name: "AdjustHue" input_arg { @@ -1313,6 +1341,34 @@ op { type: DT_FLOAT } } +op { + name: "AdjustHue" + input_arg { + name: "images" + type_attr: "T" + } + input_arg { + name: "delta" + type: DT_FLOAT + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + default_value { + type: DT_FLOAT + } + allowed_values { + list { + type: DT_HALF + type: DT_FLOAT + } + } + } +} op { name: "AdjustSaturation" input_arg { @@ -1328,6 +1384,34 @@ op { type: DT_FLOAT } } +op { + name: "AdjustSaturation" + input_arg { + name: "images" + type_attr: "T" + } + input_arg { + name: "scale" + type: DT_FLOAT + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + default_value { + type: DT_FLOAT + } + allowed_values { + list { + type: DT_HALF + type: DT_FLOAT + } + } + } +} op { name: "All" input_arg { @@ -73373,6 +73457,50 @@ op { } is_stateful: true } +op { + name: "StatefulStandardNormal" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "shape" + type_attr: "shape_dtype" + } + output_arg { + name: "output" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + default_value { + type: DT_FLOAT + } + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "shape_dtype" + type: "type" + default_value { + type: DT_INT64 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} op { name: "StatelessIf" input_arg { diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt index 012c03a834..8212282623 100644 --- a/tensorflow/core/ops/ops.pbtxt +++ b/tensorflow/core/ops/ops.pbtxt @@ -486,7 +486,7 @@ op { name: "AdjustContrastv2" input_arg { name: "images" - type: DT_FLOAT + type_attr: "T" } input_arg { name: "contrast_factor" @@ -494,14 +494,27 @@ op { } output_arg { name: "output" - type: DT_FLOAT + type_attr: "T" + } + attr { + name: "T" + type: "type" + default_value { + type: DT_FLOAT + } + allowed_values { + list { + type: DT_HALF + type: DT_FLOAT + } + } } } op { name: "AdjustHue" input_arg { name: "images" - type: DT_FLOAT + type_attr: "T" } input_arg { name: "delta" @@ -509,14 +522,27 @@ op { } output_arg { name: "output" - type: DT_FLOAT + type_attr: "T" + } + attr { + name: "T" + type: "type" + default_value { + type: DT_FLOAT + } + allowed_values { + list { + type: DT_HALF + type: DT_FLOAT + } + } } } op { name: "AdjustSaturation" input_arg { name: "images" - type: DT_FLOAT + type_attr: "T" } input_arg { name: "scale" @@ -524,7 +550,20 @@ op { } output_arg { name: "output" - type: DT_FLOAT + type_attr: "T" + } + attr { + name: "T" + type: "type" + default_value { + type: DT_FLOAT + } + allowed_values { + list { + type: DT_HALF + type: DT_FLOAT + } + } } } op { @@ -34365,6 +34404,50 @@ op { } is_stateful: true } +op { + name: "StatefulStandardNormal" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "shape" + type_attr: "shape_dtype" + } + output_arg { + name: "output" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + default_value { + type: DT_FLOAT + } + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "shape_dtype" + type: "type" + default_value { + type: DT_INT64 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} op { name: "StatelessIf" input_arg { -- GitLab From d62f6223fc0a1e4d81dffd92c61ada3e9ec17fb7 Mon Sep 17 00:00:00 2001 From: Shashi Shekhar Date: Tue, 15 Jan 2019 18:23:21 -0800 Subject: [PATCH 0763/2345] Move to new quantization parameters for subgraph quantizer. PiperOrigin-RevId: 229478990 --- .../lite/tools/optimize/subgraph_quantizer.cc | 37 ++++---- .../tools/optimize/subgraph_quantizer_test.cc | 76 +++++++--------- .../optimize/symmetric_per_channel_params.cc | 86 ------------------- .../optimize/symmetric_per_channel_params.h | 58 ------------- .../symmetric_per_channel_params_test.cc | 51 ----------- 5 files changed, 47 insertions(+), 261 deletions(-) delete mode 100644 tensorflow/lite/tools/optimize/symmetric_per_channel_params.cc delete mode 100644 tensorflow/lite/tools/optimize/symmetric_per_channel_params.h delete mode 100644 tensorflow/lite/tools/optimize/symmetric_per_channel_params_test.cc diff --git a/tensorflow/lite/tools/optimize/subgraph_quantizer.cc b/tensorflow/lite/tools/optimize/subgraph_quantizer.cc index 56f2e6cf76..30320304ec 100644 --- a/tensorflow/lite/tools/optimize/subgraph_quantizer.cc +++ b/tensorflow/lite/tools/optimize/subgraph_quantizer.cc @@ -27,7 +27,6 @@ limitations under the License. #include "tensorflow/lite/model.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/tools/optimize/quantization_utils.h" -#include "tensorflow/lite/tools/optimize/symmetric_per_channel_params.h" namespace tflite { namespace optimize { @@ -37,11 +36,14 @@ namespace { const int8_t kMinQuantizedValue = -127; const int8_t kMaxQuantizedValue = 127; -TfLiteStatus AddQuantizationParams(const SymmetricPerChannelParams& params, +TfLiteStatus AddQuantizationParams(const std::vector& scales, + int quantized_dimension, const uint8_t* buffer_data, size_t buffer_size, TensorType output_type, ModelT* model, TensorT* tensor) { - params.AddToTensor(tensor); + tensor->quantization = absl::make_unique(); + tensor->quantization->scale.assign(scales.begin(), scales.end()); + tensor->quantization->quantized_dimension = quantized_dimension; model->buffers[tensor->buffer]->data.assign(buffer_data, buffer_data + buffer_size); // Update the tensor type. @@ -95,10 +97,6 @@ TfLiteStatus SymmetricPerChannelQuantizeTensor(ModelT* model, TensorT* tensor, TF_LITE_ENSURE_STATUS(utils::NumElements(*tensor, &num_elements)); const uint64_t num_elements_per_channel = num_elements / channel_dim_size; - if (tensor->quantization == nullptr) { - tensor->quantization = absl::make_unique(); - } - std::vector min_vals(channel_dim_size); std::vector max_vals(channel_dim_size); std::vector has_min_max_value(channel_dim_size, false); @@ -170,9 +168,8 @@ TfLiteStatus SymmetricPerChannelQuantizeTensor(ModelT* model, TensorT* tensor, // Set the buffers and output type. uint8_t* uint8_buffer = reinterpret_cast(final_buffer.data()); size_t buffer_size = num_elements * sizeof(int8_t); - SymmetricPerChannelParams symmetric_params(scales, channel_dim_index); - return AddQuantizationParams(symmetric_params, uint8_buffer, buffer_size, - TensorType_INT8, model, tensor); + return AddQuantizationParams(scales, channel_dim_index, uint8_buffer, + buffer_size, TensorType_INT8, model, tensor); } // Symmetrically quantizes the bias for ops like Conv and DepthwiseConv. @@ -204,10 +201,8 @@ TfLiteStatus SymmetricPerChannelBiasQuantize(const TensorT* input_tensor, error_reporter->Report("Input tensor missing quantization information"); return kTfLiteError; } - std::unique_ptr weight_params; - TF_LITE_ENSURE_STATUS(SymmetricPerChannelParams::ReadFromTensor( - *weight_tensor, &weight_params)); - const auto& weight_scales = weight_params->scales(); + TF_LITE_ENSURE(error_reporter, weight_tensor->quantization); + const std::vector& weight_scales = weight_tensor->quantization->scale; if (weight_scales.size() != channel_dim_size) { error_reporter->Report("Mismatch weight scale dimension: %d", @@ -226,9 +221,6 @@ TfLiteStatus SymmetricPerChannelBiasQuantize(const TensorT* input_tensor, uint64_t num_elements; TF_LITE_ENSURE_STATUS(utils::NumElements(*tensor, &num_elements)); - if (tensor->quantization == nullptr) { - tensor->quantization = absl::make_unique(); - } std::vector final_buffer(num_elements); const int32_t kScale = std::numeric_limits::max(); @@ -244,9 +236,14 @@ TfLiteStatus SymmetricPerChannelBiasQuantize(const TensorT* input_tensor, // Set the buffers and output type. uint8_t* uint8_buffer = reinterpret_cast(final_buffer.data()); size_t buffer_size = num_elements * sizeof(int32_t); - SymmetricPerChannelParams symmetric_params(scales, channel_dim_index); - return AddQuantizationParams(symmetric_params, uint8_buffer, buffer_size, - TensorType_INT32, model, tensor); + // For Bias we only set the quantized values, the scale and quantized + // dimension is implicit. + tensor->quantization = nullptr; + model->buffers[tensor->buffer]->data.assign(uint8_buffer, + uint8_buffer + buffer_size); + + tensor->type = TensorType_INT32; + return kTfLiteOk; } } // namespace diff --git a/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc b/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc index 96ba20d4d9..085b66e90f 100644 --- a/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc +++ b/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc @@ -21,7 +21,6 @@ limitations under the License. #include "tensorflow/core/util/command_line_flags.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/tools/optimize/subgraph_quantizer.h" -#include "tensorflow/lite/tools/optimize/symmetric_per_channel_params.h" #include "tensorflow/lite/tools/optimize/test_util.h" namespace { @@ -91,31 +90,22 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantizationWithUnitScale) { ASSERT_TRUE(weights_tensor->quantization); const int out_channel_size = weights_tensor->shape[0]; - std::unique_ptr bias_params; - std::unique_ptr weights_params; - ASSERT_EQ(kTfLiteOk, SymmetricPerChannelParams::ReadFromTensor( - *weights_tensor, &weights_params)); - ASSERT_EQ(kTfLiteOk, SymmetricPerChannelParams::ReadFromTensor(*bias_tensor, - &bias_params)); - - ASSERT_EQ(bias_params->scales().size(), out_channel_size); - ASSERT_EQ(weights_params->scales().size(), out_channel_size); + + // Bias tensor doesn't contain quantization info. + ASSERT_FALSE(bias_tensor->quantization); + + const std::vector& weights_scales = + weights_tensor->quantization->scale; + + ASSERT_EQ(weights_scales.size(), out_channel_size); ASSERT_EQ(input_tensor->quantization->scale.size(), 1); ASSERT_EQ(output_tensor->quantization->scale.size(), 1); - const float eps = 1e-7; - // Bias scale should be input * per_channel_weight_scale. - for (size_t i = 0; i < out_channel_size; i++) { - EXPECT_NEAR( - bias_params->scales()[i], - input_tensor->quantization->scale[0] * weights_params->scales()[i], - eps); - } for (size_t i = 0; i < out_channel_size; i++) { - EXPECT_EQ(weights_params->scales()[i], 1); - EXPECT_EQ(bias_params->scales()[i], 1); + EXPECT_EQ(weights_scales[i], 1); } + EXPECT_EQ(input_tensor->quantization->scale[0], 1); EXPECT_EQ(output_tensor->quantization->scale[0], 1); @@ -128,8 +118,11 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantizationWithUnitScale) { const float* bias_float_buffer = reinterpret_cast(original_bias_buffer->data()->data()); + const float eps = 1e-7; for (size_t i = 0; i < bias_tensor->shape[0]; i++) { - auto dequantized_value = bias_values[i] * bias_params->scales()[i]; + const float bias_scale = + input_tensor->quantization->scale[0] * weights_scales[i]; + auto dequantized_value = bias_values[i] * bias_scale; EXPECT_NEAR(dequantized_value, bias_float_buffer[i], eps); } @@ -147,7 +140,7 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantizationWithUnitScale) { for (size_t j = 0; j < num_values_in_channel; j++) { size_t element_idx = channel_idx * out_channel_size + j; auto dequantized_value = - weight_values[element_idx] * weights_params->scales()[channel_idx]; + weight_values[element_idx] * weights_scales[channel_idx]; EXPECT_NEAR(dequantized_value, weights_float_buffer[element_idx], eps); } } @@ -190,27 +183,17 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantization) { ASSERT_TRUE(weights_tensor->quantization); const int out_channel_size = weights_tensor->shape[0]; - std::unique_ptr bias_params; - std::unique_ptr weights_params; - ASSERT_EQ(kTfLiteOk, SymmetricPerChannelParams::ReadFromTensor( - *weights_tensor, &weights_params)); - ASSERT_EQ(kTfLiteOk, SymmetricPerChannelParams::ReadFromTensor(*bias_tensor, - &bias_params)); - - ASSERT_EQ(bias_params->scales().size(), out_channel_size); - ASSERT_EQ(weights_params->scales().size(), out_channel_size); - ASSERT_EQ(input_tensor->quantization->scale.size(), 1); - ASSERT_EQ(output_tensor->quantization->scale.size(), 1); - const float eps = 1e-7; + // Bias tensor doesn't contain quantization info. + ASSERT_FALSE(bias_tensor->quantization); - // Bias scale should be input * per_channel_weight_scale. - for (size_t i = 0; i < out_channel_size; i++) { - EXPECT_NEAR( - bias_params->scales()[i], - input_tensor->quantization->scale[0] * weights_params->scales()[i], - eps); - } + ASSERT_TRUE(weights_tensor->quantization); + const std::vector& weights_scales = + weights_tensor->quantization->scale; + + ASSERT_EQ(weights_scales.size(), out_channel_size); + ASSERT_EQ(input_tensor->quantization->scale.size(), 1); + ASSERT_EQ(output_tensor->quantization->scale.size(), 1); const auto bias_buffer = model.buffers[bias_tensor->buffer].get(); ASSERT_EQ(bias_buffer->data.size(), sizeof(int32_t) * bias_tensor->shape[0]); @@ -221,10 +204,11 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantization) { const float* bias_float_buffer = reinterpret_cast(original_bias_buffer->data()->data()); - for (size_t i = 0; i < bias_tensor->shape[0]; i++) { - auto scale = bias_params->scales()[i]; - auto dequantized_value = bias_values[i] * scale; - EXPECT_NEAR(dequantized_value, bias_float_buffer[i], scale / 2); + for (size_t i = 0; i < out_channel_size; i++) { + const float bias_scale = + input_tensor->quantization->scale[0] * weights_scales[i]; + auto dequantized_value = bias_values[i] * bias_scale; + EXPECT_NEAR(dequantized_value, bias_float_buffer[i], bias_scale / 2); } const auto weights_buffer = model.buffers[weights_tensor->buffer].get(); @@ -240,7 +224,7 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantization) { for (size_t channel_idx = 0; channel_idx < out_channel_size; channel_idx++) { for (size_t j = 0; j < num_values_in_channel; j++) { size_t element_idx = channel_idx * out_channel_size + j; - auto scale = weights_params->scales()[channel_idx]; + auto scale = weights_scales[channel_idx]; auto dequantized_value = weight_values[element_idx] * scale; EXPECT_NEAR(dequantized_value, weights_float_buffer[element_idx], scale / 2); diff --git a/tensorflow/lite/tools/optimize/symmetric_per_channel_params.cc b/tensorflow/lite/tools/optimize/symmetric_per_channel_params.cc deleted file mode 100644 index ff7e5657f1..0000000000 --- a/tensorflow/lite/tools/optimize/symmetric_per_channel_params.cc +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#include "tensorflow/lite/tools/optimize/symmetric_per_channel_params.h" - -#include "flatbuffers/flexbuffers.h" -#include "absl/memory/memory.h" -#include "tensorflow/lite/schema/schema_generated.h" - -namespace tflite { -namespace optimize { -namespace internal { - -namespace { -const char* kCustomQuantizationScales = "scales"; -const char* kCustomQuantizationChannelDimIndex = "channel_dim_index"; - -// Writes scales and dimensions to custom quantization. -std::vector SerializeToFlexBuffer(const std::vector& scales, - int channel_dim_index) { - flexbuffers::Builder fbb; - fbb.Map([&]() { - fbb.Vector(kCustomQuantizationScales, [&]() { - for (float scale : scales) { - fbb.Float(scale); - } - }); - fbb.Int(kCustomQuantizationChannelDimIndex, channel_dim_index); - }); - fbb.Finish(); - return fbb.GetBuffer(); -} - -} // namespace - -/*static*/ TfLiteStatus SymmetricPerChannelParams::ReadFromTensor( - const TensorT& tensor, std::unique_ptr* params) { - const auto* q_params = tensor.quantization.get(); - if (!q_params) return kTfLiteError; - auto custom_quantization = q_params->details.AsCustomQuantization(); - - auto buffer = flexbuffers::GetRoot(custom_quantization->custom.data(), - custom_quantization->custom.size()); - if (!buffer.IsMap()) { - return kTfLiteError; - } - - auto custom_quantization_map = buffer.AsMap(); - auto quant_scales = - custom_quantization_map[kCustomQuantizationScales].AsVector(); - auto channel_dim_index = - custom_quantization_map[kCustomQuantizationChannelDimIndex].AsInt32(); - std::vector scales; - scales.reserve(quant_scales.size()); - for (int i = 0; i < quant_scales.size(); i++) { - scales.push_back(quant_scales[i].AsFloat()); - } - *params = - absl::make_unique(scales, channel_dim_index); - return kTfLiteOk; -} - -TfLiteStatus SymmetricPerChannelParams::AddToTensor(TensorT* tensor) const { - flatbuffers::FlatBufferBuilder fbb; - CustomQuantizationT* custom_quantization = new CustomQuantizationT; - custom_quantization->custom = - SerializeToFlexBuffer(scales_, channel_dim_index_); - tensor->quantization = absl::make_unique(); - tensor->quantization->details.type = QuantizationDetails_CustomQuantization; - tensor->quantization->details.value = custom_quantization; - return kTfLiteOk; -} -} // namespace internal -} // namespace optimize -} // namespace tflite diff --git a/tensorflow/lite/tools/optimize/symmetric_per_channel_params.h b/tensorflow/lite/tools/optimize/symmetric_per_channel_params.h deleted file mode 100644 index 2ec3ea373a..0000000000 --- a/tensorflow/lite/tools/optimize/symmetric_per_channel_params.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_SYMMETRIC_PER_CHANNEL_PARAMS_H_ -#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_SYMMETRIC_PER_CHANNEL_PARAMS_H_ - -#include -#include - -#include "tensorflow/lite/context.h" -#include "tensorflow/lite/model.h" -#include "tensorflow/lite/schema/schema_generated.h" - -namespace tflite { -namespace optimize { -namespace internal { - -// Helper to read/write symmetric per channel parameters to tensors. -// -// TODO(shashishekhar): This maybe useful for kernels as well, refactor to -// kernel utils. -class SymmetricPerChannelParams { - public: - SymmetricPerChannelParams(const std::vector scales, - int channel_dim_index) - : scales_(scales), channel_dim_index_(channel_dim_index) {} - - // Creates a new instance by reading the information from existing tensor. - static TfLiteStatus ReadFromTensor( - const TensorT& tensor, - std::unique_ptr* params); - - // Add the symmetric per channel info to the tensor. - TfLiteStatus AddToTensor(TensorT* tensor) const; - - const std::vector& scales() const { return scales_; } - - int channel_dim_index() const { return channel_dim_index_; } - - private: - std::vector scales_; - int channel_dim_index_; -}; -} // namespace internal -} // namespace optimize -} // namespace tflite -#endif // TENSORFLOW_LITE_TOOLS_OPTIMIZE_SYMMETRIC_PER_CHANNEL_PARAMS_H_ diff --git a/tensorflow/lite/tools/optimize/symmetric_per_channel_params_test.cc b/tensorflow/lite/tools/optimize/symmetric_per_channel_params_test.cc deleted file mode 100644 index 4d3cbe6f49..0000000000 --- a/tensorflow/lite/tools/optimize/symmetric_per_channel_params_test.cc +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#include "tensorflow/lite/tools/optimize/symmetric_per_channel_params.h" -#include -#include -#include "flatbuffers/flexbuffers.h" // TF:flatbuffers -#include "tensorflow/lite/schema/schema_generated.h" - -namespace tflite { -namespace optimize { -namespace internal { -namespace { - -TEST(SymmetricPerChannelParamsTest, TestReadWrite) { - TensorT tensor; - std::unique_ptr read_back_params; - const std::vector scales = {3.0, 4.0, 5.0}; - const int channel_dim_index = 42; - - SymmetricPerChannelParams params(scales, channel_dim_index); - params.AddToTensor(&tensor); - auto status = - SymmetricPerChannelParams::ReadFromTensor(tensor, &read_back_params); - EXPECT_EQ(kTfLiteOk, status); - ASSERT_TRUE(read_back_params); - - EXPECT_EQ(channel_dim_index, read_back_params->channel_dim_index()); - ASSERT_EQ(read_back_params->scales(), scales); -} - -} // namespace -} // namespace internal -} // namespace optimize -} // namespace tflite - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} -- GitLab From dc91b313e86e6bcb8c88a4be2e098296ba6c3174 Mon Sep 17 00:00:00 2001 From: Billy Lamberta Date: Tue, 15 Jan 2019 18:25:10 -0800 Subject: [PATCH 0764/2345] * Cleaned up prose for clarity. * Refactored options example to highlight preferred usage. PiperOrigin-RevId: 229479188 --- tensorflow/lite/g3doc/_book.yaml | 4 + .../lite/g3doc/performance/gpu_advanced.md | 196 ++++++++++-------- 2 files changed, 114 insertions(+), 86 deletions(-) diff --git a/tensorflow/lite/g3doc/_book.yaml b/tensorflow/lite/g3doc/_book.yaml index 0c79e79fdd..9c48e1e54d 100644 --- a/tensorflow/lite/g3doc/_book.yaml +++ b/tensorflow/lite/g3doc/_book.yaml @@ -59,6 +59,10 @@ upper_tabs: - title: Post-training quantization example path: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tutorials/post_training_quant.ipynb status: external + - title: GPU delegate + path: /lite/performance/gpu + - title: Advanced GPU + path: /lite/performance/gpu_advanced - title: TF Mobile style: accordion diff --git a/tensorflow/lite/g3doc/performance/gpu_advanced.md b/tensorflow/lite/g3doc/performance/gpu_advanced.md index d0ae123613..6274948040 100644 --- a/tensorflow/lite/g3doc/performance/gpu_advanced.md +++ b/tensorflow/lite/g3doc/performance/gpu_advanced.md @@ -5,24 +5,32 @@ hardware accelerators. This document describes how to use the GPU backend using the TensorFlow Lite delegate APIs on Android (requires OpenGL ES 3.1 or higher) and iOS (requires iOS 8 or later). +## Benefits of GPU Acceleration + +### Speed + GPUs are designed to have high throughput for massively parallelizable -workloads. Thus, they are well-suited for deep neural nets which consists of a +workloads. Thus, they are well-suited for deep neural nets, which consist of a huge number of operators, each working on some input tensor(s) that can be -easily divided into smaller workloads and carried out in parallel, typically -resulting in lower latency. In the best scenario, inference on the GPU may now -run fast enough and now become suitable for real-time applications if it was not -before. - -GPUs do their computation with 16-bit or 32-bit floating point numbers and do -not require quantization for optimal performance unlike the CPUs. If -quantization of your neural network was not an option due to lower accuracy -caused by lost precision, such concern can be discarded when running deep neural -net models on the GPU. - -Another benefit that comes with GPU inference is its power efficiency. GPUs -carry out the computations in a very efficient and optimized way, so that they -consume less power and generate less heat than when the same task is run on the -CPUs. +easily divided into smaller workloads and carried out in parallel. This +parallelism typically results in lower latency. In the best scenario, inference +on the GPU may run fast enough to become suitable for real-time applications +that were not previously possible. + +### Accuracy + +GPUs do their computation with 16-bit or 32-bit floating point numbers and +(unlike the CPUs) do not require quantization for optimal performance. If +decreased accuracy made quantization untenable for your models, running your +neural network on a GPU may eliminate this concern. + +### Energy Efficiency + +Another benefit that comes with GPU inference is its power efficiency. A GPU +carries out computations in a very efficient and optimized way, consuming less +power and generating less heat than the same task run on a CPU. + +## Supported Ops TensorFlow Lite on GPU supports the following ops in 16-bit and 32-bit float precision: @@ -51,8 +59,8 @@ precision: ### Android -Using TensorFlow Lite on GPU is achieved via `TfLiteDelegate`. In Java, you can -specify the GpuDelegate through `Interpreter.Options`. +Run TensorFlow Lite on GPU with `TfLiteDelegate`. In Java, you can specify the +GpuDelegate through `Interpreter.Options`. ```java // NEW: Prepare GPU delegate. @@ -73,10 +81,9 @@ delegate.close(); ### iOS -Using TensorFlow Lite on GPU is as simple as getting the GPU delegate via -`NewGpuDelegate()` and then passing it to -`Interpreter::ModifyGraphWithDelegate()` instead of calling -`Interpreter::AllocateTensors()`: +To use TensorFlow Lite on GPU, get the GPU delegate via `NewGpuDelegate()` and +then pass it to `Interpreter::ModifyGraphWithDelegate()` (instead of calling +`Interpreter::AllocateTensors()`). ```c++ // Set up interpreter. @@ -87,7 +94,13 @@ std::unique_ptr interpreter; InterpreterBuilder(*model, op_resolver)(&interpreter); // NEW: Prepare GPU delegate. -auto* delegate = NewGpuDelegate(nullptr); // default config + +const GpuDelegateOptions options = { + .allow_precision_loss = false, + .wait_type = kGpuDelegateOptions::WaitType::Passive, +}; + +auto* delegate = NewGpuDelegate(options); if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false; // Run inference. @@ -99,17 +112,19 @@ ReadFromOutputTensor(interpreter->typed_output_tensor(0)); DeleteGpuDelegate(delegate); ``` -*IMPORTANT:* When calling `Interpreter::ModifyGraphWithDelegate()` or -`Interpreter::Invoke()`, the caller must have a `EGLContext` in the current -thread and `Interpreter::Invoke()` must be called from the same `EGLContext`. -If such `EGLContext` does not exist, the delegate will internally create one, -but then the developer must ensure that `Interpreter::Invoke()` is always called -from the same thread `Interpreter::ModifyGraphWithDelegate()` was called. +Note: When calling `Interpreter::ModifyGraphWithDelegate()` or +`Interpreter::Invoke()`, the caller must have an `EGLContext` in the current +thread and `Interpreter::Invoke()` must be called from the same `EGLContext`. If +an `EGLContext` does not exist, the delegate will internally create one, but +then the developer must ensure that `Interpreter::Invoke()` is always called +from the same thread in which `Interpreter::ModifyGraphWithDelegate()` was +called. -## Advanced: Delegate Options for iOS +## Advanced Usage -There are a couple of GPU options that can be set and passed on to -`NewGpuDelegate()`: +### Delegate Options for iOS + +`NewGpuDelegate()` accepts a `struct` of options. ```c++ struct GpuDelegateOptions { @@ -130,45 +145,53 @@ struct GpuDelegateOptions { }; ``` -When option is set to `nullptr` as shown in the Basic Usage, it translates to: +Passing `nullptr` into `NewGpuDelegate()` sets the default options (which are +explicated in the Basic Usage example above). ```c++ + +// THIS: const GpuDelegateOptions options = { .allow_precision_loss = false, .wait_type = kGpuDelegateOptions::WaitType::Passive, }; + +auto* delegate = NewGpuDelegate(options); + +// IS THE SAME AS THIS: +auto* delegate = NewGpuDelegate(nullptr); + ``` -While it is convenient to just supply `nullptr`, it is recommended to explicitly -set the options to avoid any unexpected artifacts in case default values are -changed. +While it is convenient to use `nullptr`, we recommend that you explicitly set +the options, to avoid any unexpected behavior if default values are changed in +the future. -## Advanced: Input/Output Buffers +### Input/Output Buffers -To do computation on the GPU, data must be made available to the GPU which often -translates to performing a memory copy. It is desirable not to cross the -CPU/GPU memory boundary if possible, as this can take up a significant amount of -time. Usually, such crossing is inevitable, but in some special cases, one or -the other can be omitted. +To do computation on the GPU, data must be made available to the GPU. This often +requires performing a memory copy. It is desirable not to cross the CPU/GPU +memory boundary if possible, as this can take up a significant amount of time. +Usually, such crossing is inevitable, but in some special cases, one or the +other can be omitted. -If the network's input is an image already loaded in the GPU memory, e.g. a GPU -texture containing the camera feed, it can stay in the GPU memory without ever -entering the CPU memory. Similarly, if the network's output is in the form of a -renderable image, e.g. -[image style transfer](https://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/Gatys_Image_Style_Transfer_CVPR_2016_paper.pdf), +If the network's input is an image already loaded in the GPU memory (for +example, a GPU texture containing the camera feed) it can stay in the GPU memory +without ever entering the CPU memory. Similarly, if the network's output is in +the form of a renderable image (for example, +[image style transfer](https://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/Gatys_Image_Style_Transfer_CVPR_2016_paper.pdf)_) it can be directly displayed on the screen. -To let users achieve best performance, TensorFlow Lite makes it possible for -them to directly read from/write to the TensorFlow hardware buffer and bypass +To achieve best performance, TensorFlow Lite makes it possible for users to +directly read from and write to the TensorFlow hardware buffer and bypass avoidable memory copies. -### Android +#### Android -Assuming the image input is in the GPU memory, it must be first converted to a -OpenGL Shader Storage Buffer Object (SSBO). One can associate a TfLiteTensor -with user-prepared SSBO with `Interpreter.bindGlBufferToTensor()`. - -*IMPORTANT:* `Interpreter.bindGlBufferToTensor()` must be called before +Assuming the image input is in the GPU memory, it must first be converted to an +OpenGL Shader Storage Buffer Object (SSBO). You can associate a TfLiteTensor to +a user-prepared SSBO with `Interpreter.bindGlBufferToTensor()`. Note that +`Interpreter.bindGlBufferToTensor()` must be called before `Interpreter.modifyGraphWithDelegate()`. ```java @@ -176,7 +199,7 @@ with user-prepared SSBO with `Interpreter.bindGlBufferToTensor()`. EGLContext eglContext = eglGetCurrentContext(); if (eglContext.equals(EGL_NO_CONTEXT)) return false; -// Create a SSBO. +// Create an SSBO. int[] id = new int[1]; glGenBuffers(id.length, id, 0); glBindBuffer(GL_SHADER_STORAGE_BUFFER, id[0]); @@ -198,9 +221,10 @@ float[] outputArray = new float[outputSize]; interpreter.runInference(null, outputArray); ``` -Similar approach can be applied to the output tensor. In that case, +A similar approach can be applied to the output tensor. In that case, `Interpreter.Options.setAllowBufferHandleOutput(true)` should be passed on, to -disable the default copying the network's output from GPU memory to CPU memory. +disable the default copying of the network's output from GPU memory to CPU +memory. ```java // Ensure a valid EGL rendering context. @@ -230,21 +254,16 @@ interpreter.runInference(input, null); renderOutputSsbo(outputSsboId); ``` -### iOS - -Assuming the image input is in the GPU memory, it must be first converted to a -`MTLBuffer` object for Metal. One can associate a TfLiteTensor with a -user-prepared `MTLBuffer` with `BindMetalBufferToTensor()`. - -*IMPORTANT:* `BindMetalBufferToTensor()` must be called before -`Interpreter::ModifyGraphWithDelegate()`. +#### iOS -*IMPORTANT:* By default, the inference output is copied from GPU memory to CPU -memory implicitly by the framework. This behavior can be turned off by calling -`Interpreter::SetAllowBufferHandleOutput(true)` during initialization. To copy -the inference output from GPU memory to CPU memory, explicit -`Interpreter::EnsureTensorDataIsReadable()` calls are required for each output -tensor. +Assuming the image input is in GPU memory, it must first be converted to a +`MTLBuffer` object for Metal. You can associate a TfLiteTensor to a +user-prepared `MTLBuffer` with `BindMetalBufferToTensor()`. Note that +`BindMetalBufferToTensor()` must be called before +`Interpreter::ModifyGraphWithDelegate()`. Additionally, the inference output is, +by default, copied from GPU memory to CPU memory. This behavior can be turned +off by calling `Interpreter::SetAllowBufferHandleOutput(true)` during +initialization. ```c++ // Prepare GPU delegate. @@ -258,22 +277,27 @@ if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false; if (interpreter->Invoke() != kTfLiteOk) return false; ``` +Note: Once the default behavior is turned off, copying the inference output from +GPU memory to CPU memory requires an explicit call to +`Interpreter::EnsureTensorDataIsReadable()` for each output tensor. + ## Tips and Tricks -* Some operations that are trivial on CPU side may be high cost in GPU land. - One class of such operation is various forms of reshape operations (including - `BATCH_TO_SPACE`, `SPACE_TO_BATCH`, `SPACE_TO_DEPTH`, etc.). If those ops - are inserted into the network just for the network architect's logical - thinking, it is worth removing them for performance. +* Some operations that are trivial on the CPU may be high cost on a GPU. One + class of such operation includes various forms of reshape operations + (including `BATCH_TO_SPACE`, `SPACE_TO_BATCH`, `SPACE_TO_DEPTH`, and similar + operation). If these operations are not required (for example, they were + inserted to help the network architect reason about the system but do not + otherwise affect output), it is worth removing them for performance. -* On GPU, tensor data is sliced into 4-channels. Thus, a computation on a - tensor of shape `[B, H, W, 5]` will perform about the same on a tensor of - shape `[B, H, W, 8]`, but significantly worse than `[B, H, W, 4]`. +* On a GPU, tensor data is sliced into 4-channels. Thus, a computation on a + tensor of shape `[B, H, W, 5]` will perform about the same on a tensor of + shape `[B, H, W, 8]`, but significantly worse than `[B, H, W, 4]`. -* In that sense, if the camera hardware supports image frames in RGBA, feeding - that 4-channel input is significantly faster as a memory copy (from 3-channel - RGB to 4-channel RGBX) can be avoided. + * For example, if the camera hardware supports image frames in RGBA, + feeding that 4-channel input is significantly faster, because a memory + copy (from 3-channel RGB to 4-channel RGBX) can be avoided. -* For best performance, do not hesitate to re-train your classifier with - mobile-optimized network architecture. That is a significant part of - optimization for on-device inference. +* For best performance, do not hesitate to re-train your classifier with + mobile-optimized network architecture. That is a significant part of + optimization for on-device inference. -- GitLab From 8d89bfeaae9540d60e290254a8a82828656d3e23 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 15 Jan 2019 18:25:17 -0800 Subject: [PATCH 0765/2345] Go: Update generated wrapper functions for TensorFlow ops. PiperOrigin-RevId: 229479202 --- tensorflow/go/op/wrappers.go | 511 +++++++++++++++++++---------------- 1 file changed, 276 insertions(+), 235 deletions(-) diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index f8a5b5e7e1..fbabb37c0e 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -6606,6 +6606,27 @@ func DeleteSessionTensor(scope *Scope, handle tf.Output) (o *tf.Operation) { return scope.AddOperation(opspec) } +// Store the input tensor in the state of the current session. +// +// Arguments: +// value: The tensor to be stored. +// +// Returns The handle for the tensor stored in the session state, represented +// as a string. +func GetSessionHandle(scope *Scope, value tf.Output) (handle tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "GetSessionHandle", + Input: []tf.Input{ + value, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Compute the regularized incomplete beta integral \\(I_x(a, b)\\). // // The regularized incomplete beta integral is defined as: @@ -11629,220 +11650,6 @@ func OneHot(scope *Scope, indices tf.Output, depth tf.Output, on_value tf.Output return op.Output(0) } -// Computes exponential of x element-wise. \\(y = e^x\\). -func Exp(scope *Scope, x tf.Output) (y tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "Exp", - Input: []tf.Input{ - x, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// NthElementAttr is an optional argument to NthElement. -type NthElementAttr func(optionalAttr) - -// NthElementReverse sets the optional reverse attribute to value. -// -// value: When set to True, find the nth-largest value in the vector and vice -// versa. -// If not specified, defaults to false -func NthElementReverse(value bool) NthElementAttr { - return func(m optionalAttr) { - m["reverse"] = value - } -} - -// Finds values of the `n`-th order statistic for the last dimension. -// -// If the input is a vector (rank-1), finds the entries which is the nth-smallest -// value in the vector and outputs their values as scalar tensor. -// -// For matrices (resp. higher rank input), computes the entries which is the -// nth-smallest value in each row (resp. vector along the last dimension). Thus, -// -// values.shape = input.shape[:-1] -// -// Arguments: -// input: 1-D or higher with last dimension at least `n+1`. -// n: 0-D. Position of sorted vector to select along the last dimension (along -// each row for matrices). Valid range of n is `[0, input.shape[:-1])` -// -// Returns The `n`-th order statistic along each last dimensional slice. -func NthElement(scope *Scope, input tf.Output, n tf.Output, optional ...NthElementAttr) (values tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "NthElement", - Input: []tf.Input{ - input, n, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Computes the maximum along segments of a tensor. -// -// Read -// [the section on segmentation](https://tensorflow.org/api_guides/python/math_ops#Segmentation) -// for an explanation of segments. -// -// This operator is similar to the unsorted segment sum operator found -// [(here)](../../../api_docs/python/math_ops.md#UnsortedSegmentSum). -// Instead of computing the sum over segments, it computes the maximum such that: -// -// \\(output_i = \max_{j...} data[j...]\\) where max is over tuples `j...` such -// that `segment_ids[j...] == i`. -// -// If the maximum is empty for a given segment ID `i`, it outputs the smallest -// possible value for the specific numeric type, -// `output[i] = numeric_limits::lowest()`. -// -// If the given segment ID `i` is negative, then the corresponding value is -// dropped, and will not be included in the result. -// -//

-// -// Arguments: -// -// segment_ids: A tensor whose shape is a prefix of `data.shape`. -// -// -// Returns Has same shape as data, except for the first `segment_ids.rank` -// dimensions, which are replaced with a single dimension which has size -// `num_segments`. -func UnsortedSegmentMax(scope *Scope, data tf.Output, segment_ids tf.Output, num_segments tf.Output) (output tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "UnsortedSegmentMax", - Input: []tf.Input{ - data, segment_ids, num_segments, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Transforms a vector of brain.Example protos (as strings) into typed tensors. -// -// Arguments: -// serialized: A vector containing a batch of binary serialized Example protos. -// names: A vector containing the names of the serialized protos. -// May contain, for example, table key (descriptive) names for the -// corresponding serialized protos. These are purely useful for debugging -// purposes, and the presence of values here has no effect on the output. -// May also be an empty vector if no names are available. -// If non-empty, this vector must be the same length as "serialized". -// sparse_keys: A list of Nsparse string Tensors (scalars). -// The keys expected in the Examples' features associated with sparse values. -// dense_keys: A list of Ndense string Tensors (scalars). -// The keys expected in the Examples' features associated with dense values. -// dense_defaults: A list of Ndense Tensors (some may be empty). -// dense_defaults[j] provides default values -// when the example's feature_map lacks dense_key[j]. If an empty Tensor is -// provided for dense_defaults[j], then the Feature dense_keys[j] is required. -// The input type is inferred from dense_defaults[j], even when it's empty. -// If dense_defaults[j] is not empty, and dense_shapes[j] is fully defined, -// then the shape of dense_defaults[j] must match that of dense_shapes[j]. -// If dense_shapes[j] has an undefined major dimension (variable strides dense -// feature), dense_defaults[j] must contain a single element: -// the padding element. -// sparse_types: A list of Nsparse types; the data types of data in each Feature -// given in sparse_keys. -// Currently the ParseExample supports DT_FLOAT (FloatList), -// DT_INT64 (Int64List), and DT_STRING (BytesList). -// dense_shapes: A list of Ndense shapes; the shapes of data in each Feature -// given in dense_keys. -// The number of elements in the Feature corresponding to dense_key[j] -// must always equal dense_shapes[j].NumEntries(). -// If dense_shapes[j] == (D0, D1, ..., DN) then the shape of output -// Tensor dense_values[j] will be (|serialized|, D0, D1, ..., DN): -// The dense outputs are just the inputs row-stacked by batch. -// This works for dense_shapes[j] = (-1, D1, ..., DN). In this case -// the shape of the output Tensor dense_values[j] will be -// (|serialized|, M, D1, .., DN), where M is the maximum number of blocks -// of elements of length D1 * .... * DN, across all minibatch entries -// in the input. Any minibatch entry with less than M blocks of elements of -// length D1 * ... * DN will be padded with the corresponding default_value -// scalar element along the second dimension. -func ParseExample(scope *Scope, serialized tf.Output, names tf.Output, sparse_keys []tf.Output, dense_keys []tf.Output, dense_defaults []tf.Output, sparse_types []tf.DataType, dense_shapes []tf.Shape) (sparse_indices []tf.Output, sparse_values []tf.Output, sparse_shapes []tf.Output, dense_values []tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"sparse_types": sparse_types, "dense_shapes": dense_shapes} - opspec := tf.OpSpec{ - Type: "ParseExample", - Input: []tf.Input{ - serialized, names, tf.OutputList(sparse_keys), tf.OutputList(dense_keys), tf.OutputList(dense_defaults), - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - if scope.Err() != nil { - return - } - var idx int - var err error - if sparse_indices, idx, err = makeOutputList(op, idx, "sparse_indices"); err != nil { - scope.UpdateErr("ParseExample", err) - return - } - if sparse_values, idx, err = makeOutputList(op, idx, "sparse_values"); err != nil { - scope.UpdateErr("ParseExample", err) - return - } - if sparse_shapes, idx, err = makeOutputList(op, idx, "sparse_shapes"); err != nil { - scope.UpdateErr("ParseExample", err) - return - } - if dense_values, idx, err = makeOutputList(op, idx, "dense_values"); err != nil { - scope.UpdateErr("ParseExample", err) - return - } - return sparse_indices, sparse_values, sparse_shapes, dense_values -} - -// Compute the pairwise cross product. -// -// `a` and `b` must be the same shape; they can either be simple 3-element vectors, -// or any shape where the innermost dimension is 3. In the latter case, each pair -// of corresponding 3-element vectors is cross-multiplied independently. -// -// Arguments: -// a: A tensor containing 3-element vectors. -// b: Another tensor, of same type and shape as `a`. -// -// Returns Pairwise cross product of the vectors in `a` and `b`. -func Cross(scope *Scope, a tf.Output, b tf.Output) (product tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "Cross", - Input: []tf.Input{ - a, b, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // CudnnRNNAttr is an optional argument to CudnnRNN. type CudnnRNNAttr func(optionalAttr) @@ -12696,6 +12503,261 @@ func ResourceApplyFtrl(scope *Scope, var_ tf.Output, accum tf.Output, linear tf. return scope.AddOperation(opspec) } +// Computes exponential of x element-wise. \\(y = e^x\\). +func Exp(scope *Scope, x tf.Output) (y tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "Exp", + Input: []tf.Input{ + x, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// NthElementAttr is an optional argument to NthElement. +type NthElementAttr func(optionalAttr) + +// NthElementReverse sets the optional reverse attribute to value. +// +// value: When set to True, find the nth-largest value in the vector and vice +// versa. +// If not specified, defaults to false +func NthElementReverse(value bool) NthElementAttr { + return func(m optionalAttr) { + m["reverse"] = value + } +} + +// Finds values of the `n`-th order statistic for the last dimension. +// +// If the input is a vector (rank-1), finds the entries which is the nth-smallest +// value in the vector and outputs their values as scalar tensor. +// +// For matrices (resp. higher rank input), computes the entries which is the +// nth-smallest value in each row (resp. vector along the last dimension). Thus, +// +// values.shape = input.shape[:-1] +// +// Arguments: +// input: 1-D or higher with last dimension at least `n+1`. +// n: 0-D. Position of sorted vector to select along the last dimension (along +// each row for matrices). Valid range of n is `[0, input.shape[:-1])` +// +// Returns The `n`-th order statistic along each last dimensional slice. +func NthElement(scope *Scope, input tf.Output, n tf.Output, optional ...NthElementAttr) (values tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "NthElement", + Input: []tf.Input{ + input, n, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Computes the maximum along segments of a tensor. +// +// Read +// [the section on segmentation](https://tensorflow.org/api_guides/python/math_ops#Segmentation) +// for an explanation of segments. +// +// This operator is similar to the unsorted segment sum operator found +// [(here)](../../../api_docs/python/math_ops.md#UnsortedSegmentSum). +// Instead of computing the sum over segments, it computes the maximum such that: +// +// \\(output_i = \max_{j...} data[j...]\\) where max is over tuples `j...` such +// that `segment_ids[j...] == i`. +// +// If the maximum is empty for a given segment ID `i`, it outputs the smallest +// possible value for the specific numeric type, +// `output[i] = numeric_limits::lowest()`. +// +// If the given segment ID `i` is negative, then the corresponding value is +// dropped, and will not be included in the result. +// +//
+// +//
+// +// Arguments: +// +// segment_ids: A tensor whose shape is a prefix of `data.shape`. +// +// +// Returns Has same shape as data, except for the first `segment_ids.rank` +// dimensions, which are replaced with a single dimension which has size +// `num_segments`. +func UnsortedSegmentMax(scope *Scope, data tf.Output, segment_ids tf.Output, num_segments tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "UnsortedSegmentMax", + Input: []tf.Input{ + data, segment_ids, num_segments, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Transforms a vector of brain.Example protos (as strings) into typed tensors. +// +// Arguments: +// serialized: A vector containing a batch of binary serialized Example protos. +// names: A vector containing the names of the serialized protos. +// May contain, for example, table key (descriptive) names for the +// corresponding serialized protos. These are purely useful for debugging +// purposes, and the presence of values here has no effect on the output. +// May also be an empty vector if no names are available. +// If non-empty, this vector must be the same length as "serialized". +// sparse_keys: A list of Nsparse string Tensors (scalars). +// The keys expected in the Examples' features associated with sparse values. +// dense_keys: A list of Ndense string Tensors (scalars). +// The keys expected in the Examples' features associated with dense values. +// dense_defaults: A list of Ndense Tensors (some may be empty). +// dense_defaults[j] provides default values +// when the example's feature_map lacks dense_key[j]. If an empty Tensor is +// provided for dense_defaults[j], then the Feature dense_keys[j] is required. +// The input type is inferred from dense_defaults[j], even when it's empty. +// If dense_defaults[j] is not empty, and dense_shapes[j] is fully defined, +// then the shape of dense_defaults[j] must match that of dense_shapes[j]. +// If dense_shapes[j] has an undefined major dimension (variable strides dense +// feature), dense_defaults[j] must contain a single element: +// the padding element. +// sparse_types: A list of Nsparse types; the data types of data in each Feature +// given in sparse_keys. +// Currently the ParseExample supports DT_FLOAT (FloatList), +// DT_INT64 (Int64List), and DT_STRING (BytesList). +// dense_shapes: A list of Ndense shapes; the shapes of data in each Feature +// given in dense_keys. +// The number of elements in the Feature corresponding to dense_key[j] +// must always equal dense_shapes[j].NumEntries(). +// If dense_shapes[j] == (D0, D1, ..., DN) then the shape of output +// Tensor dense_values[j] will be (|serialized|, D0, D1, ..., DN): +// The dense outputs are just the inputs row-stacked by batch. +// This works for dense_shapes[j] = (-1, D1, ..., DN). In this case +// the shape of the output Tensor dense_values[j] will be +// (|serialized|, M, D1, .., DN), where M is the maximum number of blocks +// of elements of length D1 * .... * DN, across all minibatch entries +// in the input. Any minibatch entry with less than M blocks of elements of +// length D1 * ... * DN will be padded with the corresponding default_value +// scalar element along the second dimension. +func ParseExample(scope *Scope, serialized tf.Output, names tf.Output, sparse_keys []tf.Output, dense_keys []tf.Output, dense_defaults []tf.Output, sparse_types []tf.DataType, dense_shapes []tf.Shape) (sparse_indices []tf.Output, sparse_values []tf.Output, sparse_shapes []tf.Output, dense_values []tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"sparse_types": sparse_types, "dense_shapes": dense_shapes} + opspec := tf.OpSpec{ + Type: "ParseExample", + Input: []tf.Input{ + serialized, names, tf.OutputList(sparse_keys), tf.OutputList(dense_keys), tf.OutputList(dense_defaults), + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + if scope.Err() != nil { + return + } + var idx int + var err error + if sparse_indices, idx, err = makeOutputList(op, idx, "sparse_indices"); err != nil { + scope.UpdateErr("ParseExample", err) + return + } + if sparse_values, idx, err = makeOutputList(op, idx, "sparse_values"); err != nil { + scope.UpdateErr("ParseExample", err) + return + } + if sparse_shapes, idx, err = makeOutputList(op, idx, "sparse_shapes"); err != nil { + scope.UpdateErr("ParseExample", err) + return + } + if dense_values, idx, err = makeOutputList(op, idx, "dense_values"); err != nil { + scope.UpdateErr("ParseExample", err) + return + } + return sparse_indices, sparse_values, sparse_shapes, dense_values +} + +// Compute the pairwise cross product. +// +// `a` and `b` must be the same shape; they can either be simple 3-element vectors, +// or any shape where the innermost dimension is 3. In the latter case, each pair +// of corresponding 3-element vectors is cross-multiplied independently. +// +// Arguments: +// a: A tensor containing 3-element vectors. +// b: Another tensor, of same type and shape as `a`. +// +// Returns Pairwise cross product of the vectors in `a` and `b`. +func Cross(scope *Scope, a tf.Output, b tf.Output) (product tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "Cross", + Input: []tf.Input{ + a, b, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// StatefulStandardNormalAttr is an optional argument to StatefulStandardNormal. +type StatefulStandardNormalAttr func(optionalAttr) + +// StatefulStandardNormalDtype sets the optional dtype attribute to value. +// +// value: The type of the output. +// If not specified, defaults to DT_FLOAT +func StatefulStandardNormalDtype(value tf.DataType) StatefulStandardNormalAttr { + return func(m optionalAttr) { + m["dtype"] = value + } +} + +// Outputs random values from a normal distribution. +// +// The generated values will have mean 0 and standard deviation 1. +// +// Arguments: +// resource: The handle of the resource variable that stores the state of the RNG. +// shape: The shape of the output tensor. +// +// Returns A tensor of the specified shape filled with random normal values. +func StatefulStandardNormal(scope *Scope, resource tf.Output, shape tf.Output, optional ...StatefulStandardNormalAttr) (output tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "StatefulStandardNormal", + Input: []tf.Input{ + resource, shape, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Locks a mutex resource. The output is the lock. So long as the lock tensor // // is alive, any other request to use `MutexLock` with this mutex will wait. @@ -17519,27 +17581,6 @@ func DecodeBase64(scope *Scope, input tf.Output) (output tf.Output) { return op.Output(0) } -// Store the input tensor in the state of the current session. -// -// Arguments: -// value: The tensor to be stored. -// -// Returns The handle for the tensor stored in the session state, represented -// as a string. -func GetSessionHandle(scope *Scope, value tf.Output) (handle tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "GetSessionHandle", - Input: []tf.Input{ - value, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // ResourceSparseApplyProximalAdagradAttr is an optional argument to ResourceSparseApplyProximalAdagrad. type ResourceSparseApplyProximalAdagradAttr func(optionalAttr) -- GitLab From 492a0e17ae125c17fb5a0d0406e7c255fec835b6 Mon Sep 17 00:00:00 2001 From: tomguluson92 <314913739@qq.com> Date: Wed, 16 Jan 2019 11:07:16 +0800 Subject: [PATCH 0766/2345] fix spell mistake --- tensorflow/python/util/nest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/util/nest.py b/tensorflow/python/util/nest.py index 70e5ebb3b6..dbae97a373 100644 --- a/tensorflow/python/util/nest.py +++ b/tensorflow/python/util/nest.py @@ -149,7 +149,7 @@ def assert_same_structure(nest1, nest2, check_types=True): Note that namedtuples with identical name and fields are always considered to have the same shallow structure (even with `check_types=True`). - For intance, this code will print `True`: + For instance, this code will print `True`: ```python def nt(a, b): -- GitLab From 1f2952ec47015a300184e340d6f53aa42eb6eadf Mon Sep 17 00:00:00 2001 From: Yu-Cheng Ling Date: Tue, 15 Jan 2019 19:23:23 -0800 Subject: [PATCH 0767/2345] Update the MobileNet models used in iOS examples. PiperOrigin-RevId: 229484657 --- .../lite/examples/ios/camera/data/labels.txt | 1001 +++++++++++++++++ .../lite/examples/ios/download_models.sh | 43 +- .../lite/examples/ios/simple/data/labels.txt | 1001 +++++++++++++++++ 3 files changed, 2018 insertions(+), 27 deletions(-) create mode 100644 tensorflow/lite/examples/ios/camera/data/labels.txt create mode 100644 tensorflow/lite/examples/ios/simple/data/labels.txt diff --git a/tensorflow/lite/examples/ios/camera/data/labels.txt b/tensorflow/lite/examples/ios/camera/data/labels.txt new file mode 100644 index 0000000000..572eccf900 --- /dev/null +++ b/tensorflow/lite/examples/ios/camera/data/labels.txt @@ -0,0 +1,1001 @@ +dummy +tench +goldfish +great white shark +tiger shark +hammerhead +electric ray +stingray +cock +hen +ostrich +brambling +goldfinch +house finch +junco +indigo bunting +robin +bulbul +jay +magpie +chickadee +water ouzel +kite +bald eagle +vulture +great grey owl +European fire salamander +common newt +eft +spotted salamander +axolotl +bullfrog +tree frog +tailed frog +loggerhead +leatherback turtle +mud turtle +terrapin +box turtle +banded gecko +common iguana +American chameleon +whiptail +agama +frilled lizard +alligator lizard +Gila monster +green lizard +African chameleon +Komodo dragon +African crocodile +American alligator +triceratops +thunder snake +ringneck snake +hognose snake +green snake +king snake +garter snake +water snake +vine snake +night snake +boa constrictor +rock python +Indian cobra +green mamba +sea snake +horned viper +diamondback +sidewinder +trilobite +harvestman +scorpion +black and gold garden spider +barn spider +garden spider +black widow +tarantula +wolf spider +tick +centipede +black grouse +ptarmigan +ruffed grouse +prairie chicken +peacock +quail +partridge +African grey +macaw +sulphur-crested cockatoo +lorikeet +coucal +bee eater +hornbill +hummingbird +jacamar +toucan +drake +red-breasted merganser +goose +black swan +tusker +echidna +platypus +wallaby +koala +wombat +jellyfish +sea anemone +brain coral +flatworm +nematode +conch +snail +slug +sea slug +chiton +chambered nautilus +Dungeness crab +rock crab +fiddler crab +king crab +American lobster +spiny lobster +crayfish +hermit crab +isopod +white stork +black stork +spoonbill +flamingo +little blue heron +American egret +bittern +crane +limpkin +European gallinule +American coot +bustard +ruddy turnstone +red-backed sandpiper +redshank +dowitcher +oystercatcher +pelican +king penguin +albatross +grey whale +killer whale +dugong +sea lion +Chihuahua +Japanese spaniel +Maltese dog +Pekinese +Shih-Tzu +Blenheim spaniel +papillon +toy terrier +Rhodesian ridgeback +Afghan hound +basset +beagle +bloodhound +bluetick +black-and-tan coonhound +Walker hound +English foxhound +redbone +borzoi +Irish wolfhound +Italian greyhound +whippet +Ibizan hound +Norwegian elkhound +otterhound +Saluki +Scottish deerhound +Weimaraner +Staffordshire bullterrier +American Staffordshire terrier +Bedlington terrier +Border terrier +Kerry blue terrier +Irish terrier +Norfolk terrier +Norwich terrier +Yorkshire terrier +wire-haired fox terrier +Lakeland terrier +Sealyham terrier +Airedale +cairn +Australian terrier +Dandie Dinmont +Boston bull +miniature schnauzer +giant schnauzer +standard schnauzer +Scotch terrier +Tibetan terrier +silky terrier +soft-coated wheaten terrier +West Highland white terrier +Lhasa +flat-coated retriever +curly-coated retriever +golden retriever +Labrador retriever +Chesapeake Bay retriever +German short-haired pointer +vizsla +English setter +Irish setter +Gordon setter +Brittany spaniel +clumber +English springer +Welsh springer spaniel +cocker spaniel +Sussex spaniel +Irish water spaniel +kuvasz +schipperke +groenendael +malinois +briard +kelpie +komondor +Old English sheepdog +Shetland sheepdog +collie +Border collie +Bouvier des Flandres +Rottweiler +German shepherd +Doberman +miniature pinscher +Greater Swiss Mountain dog +Bernese mountain dog +Appenzeller +EntleBucher +boxer +bull mastiff +Tibetan mastiff +French bulldog +Great Dane +Saint Bernard +Eskimo dog +malamute +Siberian husky +dalmatian +affenpinscher +basenji +pug +Leonberg +Newfoundland +Great Pyrenees +Samoyed +Pomeranian +chow +keeshond +Brabancon griffon +Pembroke +Cardigan +toy poodle +miniature poodle +standard poodle +Mexican hairless +timber wolf +white wolf +red wolf +coyote +dingo +dhole +African hunting dog +hyena +red fox +kit fox +Arctic fox +grey fox +tabby +tiger cat +Persian cat +Siamese cat +Egyptian cat +cougar +lynx +leopard +snow leopard +jaguar +lion +tiger +cheetah +brown bear +American black bear +ice bear +sloth bear +mongoose +meerkat +tiger beetle +ladybug +ground beetle +long-horned beetle +leaf beetle +dung beetle +rhinoceros beetle +weevil +fly +bee +ant +grasshopper +cricket +walking stick +cockroach +mantis +cicada +leafhopper +lacewing +dragonfly +damselfly +admiral +ringlet +monarch +cabbage butterfly +sulphur butterfly +lycaenid +starfish +sea urchin +sea cucumber +wood rabbit +hare +Angora +hamster +porcupine +fox squirrel +marmot +beaver +guinea pig +sorrel +zebra +hog +wild boar +warthog +hippopotamus +ox +water buffalo +bison +ram +bighorn +ibex +hartebeest +impala +gazelle +Arabian camel +llama +weasel +mink +polecat +black-footed ferret +otter +skunk +badger +armadillo +three-toed sloth +orangutan +gorilla +chimpanzee +gibbon +siamang +guenon +patas +baboon +macaque +langur +colobus +proboscis monkey +marmoset +capuchin +howler monkey +titi +spider monkey +squirrel monkey +Madagascar cat +indri +Indian elephant +African elephant +lesser panda +giant panda +barracouta +eel +coho +rock beauty +anemone fish +sturgeon +gar +lionfish +puffer +abacus +abaya +academic gown +accordion +acoustic guitar +aircraft carrier +airliner +airship +altar +ambulance +amphibian +analog clock +apiary +apron +ashcan +assault rifle +backpack +bakery +balance beam +balloon +ballpoint +Band Aid +banjo +bannister +barbell +barber chair +barbershop +barn +barometer +barrel +barrow +baseball +basketball +bassinet +bassoon +bathing cap +bath towel +bathtub +beach wagon +beacon +beaker +bearskin +beer bottle +beer glass +bell cote +bib +bicycle-built-for-two +bikini +binder +binoculars +birdhouse +boathouse +bobsled +bolo tie +bonnet +bookcase +bookshop +bottlecap +bow +bow tie +brass +brassiere +breakwater +breastplate +broom +bucket +buckle +bulletproof vest +bullet train +butcher shop +cab +caldron +candle +cannon +canoe +can opener +cardigan +car mirror +carousel +carpenter's kit +carton +car wheel +cash machine +cassette +cassette player +castle +catamaran +CD player +cello +cellular telephone +chain +chainlink fence +chain mail +chain saw +chest +chiffonier +chime +china cabinet +Christmas stocking +church +cinema +cleaver +cliff dwelling +cloak +clog +cocktail shaker +coffee mug +coffeepot +coil +combination lock +computer keyboard +confectionery +container ship +convertible +corkscrew +cornet +cowboy boot +cowboy hat +cradle +crane +crash helmet +crate +crib +Crock Pot +croquet ball +crutch +cuirass +dam +desk +desktop computer +dial telephone +diaper +digital clock +digital watch +dining table +dishrag +dishwasher +disk brake +dock +dogsled +dome +doormat +drilling platform +drum +drumstick +dumbbell +Dutch oven +electric fan +electric guitar +electric locomotive +entertainment center +envelope +espresso maker +face powder +feather boa +file +fireboat +fire engine +fire screen +flagpole +flute +folding chair +football helmet +forklift +fountain +fountain pen +four-poster +freight car +French horn +frying pan +fur coat +garbage truck +gasmask +gas pump +goblet +go-kart +golf ball +golfcart +gondola +gong +gown +grand piano +greenhouse +grille +grocery store +guillotine +hair slide +hair spray +half track +hammer +hamper +hand blower +hand-held computer +handkerchief +hard disc +harmonica +harp +harvester +hatchet +holster +home theater +honeycomb +hook +hoopskirt +horizontal bar +horse cart +hourglass +iPod +iron +jack-o'-lantern +jean +jeep +jersey +jigsaw puzzle +jinrikisha +joystick +kimono +knee pad +knot +lab coat +ladle +lampshade +laptop +lawn mower +lens cap +letter opener +library +lifeboat +lighter +limousine +liner +lipstick +Loafer +lotion +loudspeaker +loupe +lumbermill +magnetic compass +mailbag +mailbox +maillot +maillot +manhole cover +maraca +marimba +mask +matchstick +maypole +maze +measuring cup +medicine chest +megalith +microphone +microwave +military uniform +milk can +minibus +miniskirt +minivan +missile +mitten +mixing bowl +mobile home +Model T +modem +monastery +monitor +moped +mortar +mortarboard +mosque +mosquito net +motor scooter +mountain bike +mountain tent +mouse +mousetrap +moving van +muzzle +nail +neck brace +necklace +nipple +notebook +obelisk +oboe +ocarina +odometer +oil filter +organ +oscilloscope +overskirt +oxcart +oxygen mask +packet +paddle +paddlewheel +padlock +paintbrush +pajama +palace +panpipe +paper towel +parachute +parallel bars +park bench +parking meter +passenger car +patio +pay-phone +pedestal +pencil box +pencil sharpener +perfume +Petri dish +photocopier +pick +pickelhaube +picket fence +pickup +pier +piggy bank +pill bottle +pillow +ping-pong ball +pinwheel +pirate +pitcher +plane +planetarium +plastic bag +plate rack +plow +plunger +Polaroid camera +pole +police van +poncho +pool table +pop bottle +pot +potter's wheel +power drill +prayer rug +printer +prison +projectile +projector +puck +punching bag +purse +quill +quilt +racer +racket +radiator +radio +radio telescope +rain barrel +recreational vehicle +reel +reflex camera +refrigerator +remote control +restaurant +revolver +rifle +rocking chair +rotisserie +rubber eraser +rugby ball +rule +running shoe +safe +safety pin +saltshaker +sandal +sarong +sax +scabbard +scale +school bus +schooner +scoreboard +screen +screw +screwdriver +seat belt +sewing machine +shield +shoe shop +shoji +shopping basket +shopping cart +shovel +shower cap +shower curtain +ski +ski mask +sleeping bag +slide rule +sliding door +slot +snorkel +snowmobile +snowplow +soap dispenser +soccer ball +sock +solar dish +sombrero +soup bowl +space bar +space heater +space shuttle +spatula +speedboat +spider web +spindle +sports car +spotlight +stage +steam locomotive +steel arch bridge +steel drum +stethoscope +stole +stone wall +stopwatch +stove +strainer +streetcar +stretcher +studio couch +stupa +submarine +suit +sundial +sunglass +sunglasses +sunscreen +suspension bridge +swab +sweatshirt +swimming trunks +swing +switch +syringe +table lamp +tank +tape player +teapot +teddy +television +tennis ball +thatch +theater curtain +thimble +thresher +throne +tile roof +toaster +tobacco shop +toilet seat +torch +totem pole +tow truck +toyshop +tractor +trailer truck +tray +trench coat +tricycle +trimaran +tripod +triumphal arch +trolleybus +trombone +tub +turnstile +typewriter keyboard +umbrella +unicycle +upright +vacuum +vase +vault +velvet +vending machine +vestment +viaduct +violin +volleyball +waffle iron +wall clock +wallet +wardrobe +warplane +washbasin +washer +water bottle +water jug +water tower +whiskey jug +whistle +wig +window screen +window shade +Windsor tie +wine bottle +wing +wok +wooden spoon +wool +worm fence +wreck +yawl +yurt +web site +comic book +crossword puzzle +street sign +traffic light +book jacket +menu +plate +guacamole +consomme +hot pot +trifle +ice cream +ice lolly +French loaf +bagel +pretzel +cheeseburger +hotdog +mashed potato +head cabbage +broccoli +cauliflower +zucchini +spaghetti squash +acorn squash +butternut squash +cucumber +artichoke +bell pepper +cardoon +mushroom +Granny Smith +strawberry +orange +lemon +fig +pineapple +banana +jackfruit +custard apple +pomegranate +hay +carbonara +chocolate sauce +dough +meat loaf +pizza +potpie +burrito +red wine +espresso +cup +eggnog +alp +bubble +cliff +coral reef +geyser +lakeside +promontory +sandbar +seashore +valley +volcano +ballplayer +groom +scuba diver +rapeseed +daisy +yellow lady's slipper +corn +acorn +hip +buckeye +coral fungus +agaric +gyromitra +stinkhorn +earthstar +hen-of-the-woods +bolete +ear +toilet tissue diff --git a/tensorflow/lite/examples/ios/download_models.sh b/tensorflow/lite/examples/ios/download_models.sh index 4828617d95..a450aba042 100755 --- a/tensorflow/lite/examples/ios/download_models.sh +++ b/tensorflow/lite/examples/ios/download_models.sh @@ -17,42 +17,31 @@ set -ex SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -MODELS_URL="https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_1.0_224_ios_lite_float_2017_11_08.zip" -QUANTIZED_MODELS_URL="https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip" +FLOAT_MODEL_URL="http://download.tensorflow.org/models/mobilenet_v1_2018_02_22/mobilenet_v1_1.0_224.tgz" +QUANTIZED_MODEL_URL="http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_224_quant.tgz" DOWNLOADS_DIR=$(mktemp -d) -cd $SCRIPT_DIR +cd "$SCRIPT_DIR" download_and_extract() { - local usage="Usage: download_and_extract URL DIR" - local url="${1:?${usage}}" - local dir="${2:?${usage}}" + local url="$1" + local dir="$2" echo "downloading ${url}" >&2 mkdir -p "${dir}" tempdir=$(mktemp -d) - tempdir2=$(mktemp -d) - curl -L ${url} > ${tempdir}/zipped.zip - unzip ${tempdir}/zipped.zip -d ${tempdir2} - - # If the zip file contains nested directories, extract the files from the - # inner directory. - if ls ${tempdir2}/*/* 1> /dev/null 2>&1; then - # unzip has no strip components, so unzip to a temp dir, and move the - # files we want from the tempdir to destination. - cp -R ${tempdir2}/*/* ${dir}/ - else - cp -R ${tempdir2}/* ${dir}/ - fi - rm -rf ${tempdir2} ${tempdir} + curl -L ${url} > ${tempdir}/archive.tgz + cd ${dir} + tar zxvf ${tempdir}/archive.tgz + rm -rf ${tempdir} } -download_and_extract "${MODELS_URL}" "${DOWNLOADS_DIR}/models" -download_and_extract "${QUANTIZED_MODELS_URL}" "${DOWNLOADS_DIR}/quantized_models" - -file ${DOWNLOADS_DIR}/models +download_and_extract "${FLOAT_MODEL_URL}" "${DOWNLOADS_DIR}/float_model" +download_and_extract "${QUANTIZED_MODEL_URL}" "${DOWNLOADS_DIR}/quantized_model" -cp ${DOWNLOADS_DIR}/models/models/* simple/data/ -cp ${DOWNLOADS_DIR}/models/models/* camera/data/ -cp "${DOWNLOADS_DIR}/quantized_models/mobilenet_quant_v1_224.tflite" \ +cd "$SCRIPT_DIR" +cp "${DOWNLOADS_DIR}/float_model/mobilenet_v1_1.0_224.tflite" "simple/data/mobilenet_v1_1.0_224.tflite" +cp "${DOWNLOADS_DIR}/float_model/mobilenet_v1_1.0_224.tflite" "camera/data/mobilenet_v1_1.0_224.tflite" +cp "${DOWNLOADS_DIR}/quantized_model/mobilenet_v1_1.0_224_quant.tflite" \ 'camera/data/mobilenet_quant_v1_224.tflite' +echo "Done" diff --git a/tensorflow/lite/examples/ios/simple/data/labels.txt b/tensorflow/lite/examples/ios/simple/data/labels.txt new file mode 100644 index 0000000000..572eccf900 --- /dev/null +++ b/tensorflow/lite/examples/ios/simple/data/labels.txt @@ -0,0 +1,1001 @@ +dummy +tench +goldfish +great white shark +tiger shark +hammerhead +electric ray +stingray +cock +hen +ostrich +brambling +goldfinch +house finch +junco +indigo bunting +robin +bulbul +jay +magpie +chickadee +water ouzel +kite +bald eagle +vulture +great grey owl +European fire salamander +common newt +eft +spotted salamander +axolotl +bullfrog +tree frog +tailed frog +loggerhead +leatherback turtle +mud turtle +terrapin +box turtle +banded gecko +common iguana +American chameleon +whiptail +agama +frilled lizard +alligator lizard +Gila monster +green lizard +African chameleon +Komodo dragon +African crocodile +American alligator +triceratops +thunder snake +ringneck snake +hognose snake +green snake +king snake +garter snake +water snake +vine snake +night snake +boa constrictor +rock python +Indian cobra +green mamba +sea snake +horned viper +diamondback +sidewinder +trilobite +harvestman +scorpion +black and gold garden spider +barn spider +garden spider +black widow +tarantula +wolf spider +tick +centipede +black grouse +ptarmigan +ruffed grouse +prairie chicken +peacock +quail +partridge +African grey +macaw +sulphur-crested cockatoo +lorikeet +coucal +bee eater +hornbill +hummingbird +jacamar +toucan +drake +red-breasted merganser +goose +black swan +tusker +echidna +platypus +wallaby +koala +wombat +jellyfish +sea anemone +brain coral +flatworm +nematode +conch +snail +slug +sea slug +chiton +chambered nautilus +Dungeness crab +rock crab +fiddler crab +king crab +American lobster +spiny lobster +crayfish +hermit crab +isopod +white stork +black stork +spoonbill +flamingo +little blue heron +American egret +bittern +crane +limpkin +European gallinule +American coot +bustard +ruddy turnstone +red-backed sandpiper +redshank +dowitcher +oystercatcher +pelican +king penguin +albatross +grey whale +killer whale +dugong +sea lion +Chihuahua +Japanese spaniel +Maltese dog +Pekinese +Shih-Tzu +Blenheim spaniel +papillon +toy terrier +Rhodesian ridgeback +Afghan hound +basset +beagle +bloodhound +bluetick +black-and-tan coonhound +Walker hound +English foxhound +redbone +borzoi +Irish wolfhound +Italian greyhound +whippet +Ibizan hound +Norwegian elkhound +otterhound +Saluki +Scottish deerhound +Weimaraner +Staffordshire bullterrier +American Staffordshire terrier +Bedlington terrier +Border terrier +Kerry blue terrier +Irish terrier +Norfolk terrier +Norwich terrier +Yorkshire terrier +wire-haired fox terrier +Lakeland terrier +Sealyham terrier +Airedale +cairn +Australian terrier +Dandie Dinmont +Boston bull +miniature schnauzer +giant schnauzer +standard schnauzer +Scotch terrier +Tibetan terrier +silky terrier +soft-coated wheaten terrier +West Highland white terrier +Lhasa +flat-coated retriever +curly-coated retriever +golden retriever +Labrador retriever +Chesapeake Bay retriever +German short-haired pointer +vizsla +English setter +Irish setter +Gordon setter +Brittany spaniel +clumber +English springer +Welsh springer spaniel +cocker spaniel +Sussex spaniel +Irish water spaniel +kuvasz +schipperke +groenendael +malinois +briard +kelpie +komondor +Old English sheepdog +Shetland sheepdog +collie +Border collie +Bouvier des Flandres +Rottweiler +German shepherd +Doberman +miniature pinscher +Greater Swiss Mountain dog +Bernese mountain dog +Appenzeller +EntleBucher +boxer +bull mastiff +Tibetan mastiff +French bulldog +Great Dane +Saint Bernard +Eskimo dog +malamute +Siberian husky +dalmatian +affenpinscher +basenji +pug +Leonberg +Newfoundland +Great Pyrenees +Samoyed +Pomeranian +chow +keeshond +Brabancon griffon +Pembroke +Cardigan +toy poodle +miniature poodle +standard poodle +Mexican hairless +timber wolf +white wolf +red wolf +coyote +dingo +dhole +African hunting dog +hyena +red fox +kit fox +Arctic fox +grey fox +tabby +tiger cat +Persian cat +Siamese cat +Egyptian cat +cougar +lynx +leopard +snow leopard +jaguar +lion +tiger +cheetah +brown bear +American black bear +ice bear +sloth bear +mongoose +meerkat +tiger beetle +ladybug +ground beetle +long-horned beetle +leaf beetle +dung beetle +rhinoceros beetle +weevil +fly +bee +ant +grasshopper +cricket +walking stick +cockroach +mantis +cicada +leafhopper +lacewing +dragonfly +damselfly +admiral +ringlet +monarch +cabbage butterfly +sulphur butterfly +lycaenid +starfish +sea urchin +sea cucumber +wood rabbit +hare +Angora +hamster +porcupine +fox squirrel +marmot +beaver +guinea pig +sorrel +zebra +hog +wild boar +warthog +hippopotamus +ox +water buffalo +bison +ram +bighorn +ibex +hartebeest +impala +gazelle +Arabian camel +llama +weasel +mink +polecat +black-footed ferret +otter +skunk +badger +armadillo +three-toed sloth +orangutan +gorilla +chimpanzee +gibbon +siamang +guenon +patas +baboon +macaque +langur +colobus +proboscis monkey +marmoset +capuchin +howler monkey +titi +spider monkey +squirrel monkey +Madagascar cat +indri +Indian elephant +African elephant +lesser panda +giant panda +barracouta +eel +coho +rock beauty +anemone fish +sturgeon +gar +lionfish +puffer +abacus +abaya +academic gown +accordion +acoustic guitar +aircraft carrier +airliner +airship +altar +ambulance +amphibian +analog clock +apiary +apron +ashcan +assault rifle +backpack +bakery +balance beam +balloon +ballpoint +Band Aid +banjo +bannister +barbell +barber chair +barbershop +barn +barometer +barrel +barrow +baseball +basketball +bassinet +bassoon +bathing cap +bath towel +bathtub +beach wagon +beacon +beaker +bearskin +beer bottle +beer glass +bell cote +bib +bicycle-built-for-two +bikini +binder +binoculars +birdhouse +boathouse +bobsled +bolo tie +bonnet +bookcase +bookshop +bottlecap +bow +bow tie +brass +brassiere +breakwater +breastplate +broom +bucket +buckle +bulletproof vest +bullet train +butcher shop +cab +caldron +candle +cannon +canoe +can opener +cardigan +car mirror +carousel +carpenter's kit +carton +car wheel +cash machine +cassette +cassette player +castle +catamaran +CD player +cello +cellular telephone +chain +chainlink fence +chain mail +chain saw +chest +chiffonier +chime +china cabinet +Christmas stocking +church +cinema +cleaver +cliff dwelling +cloak +clog +cocktail shaker +coffee mug +coffeepot +coil +combination lock +computer keyboard +confectionery +container ship +convertible +corkscrew +cornet +cowboy boot +cowboy hat +cradle +crane +crash helmet +crate +crib +Crock Pot +croquet ball +crutch +cuirass +dam +desk +desktop computer +dial telephone +diaper +digital clock +digital watch +dining table +dishrag +dishwasher +disk brake +dock +dogsled +dome +doormat +drilling platform +drum +drumstick +dumbbell +Dutch oven +electric fan +electric guitar +electric locomotive +entertainment center +envelope +espresso maker +face powder +feather boa +file +fireboat +fire engine +fire screen +flagpole +flute +folding chair +football helmet +forklift +fountain +fountain pen +four-poster +freight car +French horn +frying pan +fur coat +garbage truck +gasmask +gas pump +goblet +go-kart +golf ball +golfcart +gondola +gong +gown +grand piano +greenhouse +grille +grocery store +guillotine +hair slide +hair spray +half track +hammer +hamper +hand blower +hand-held computer +handkerchief +hard disc +harmonica +harp +harvester +hatchet +holster +home theater +honeycomb +hook +hoopskirt +horizontal bar +horse cart +hourglass +iPod +iron +jack-o'-lantern +jean +jeep +jersey +jigsaw puzzle +jinrikisha +joystick +kimono +knee pad +knot +lab coat +ladle +lampshade +laptop +lawn mower +lens cap +letter opener +library +lifeboat +lighter +limousine +liner +lipstick +Loafer +lotion +loudspeaker +loupe +lumbermill +magnetic compass +mailbag +mailbox +maillot +maillot +manhole cover +maraca +marimba +mask +matchstick +maypole +maze +measuring cup +medicine chest +megalith +microphone +microwave +military uniform +milk can +minibus +miniskirt +minivan +missile +mitten +mixing bowl +mobile home +Model T +modem +monastery +monitor +moped +mortar +mortarboard +mosque +mosquito net +motor scooter +mountain bike +mountain tent +mouse +mousetrap +moving van +muzzle +nail +neck brace +necklace +nipple +notebook +obelisk +oboe +ocarina +odometer +oil filter +organ +oscilloscope +overskirt +oxcart +oxygen mask +packet +paddle +paddlewheel +padlock +paintbrush +pajama +palace +panpipe +paper towel +parachute +parallel bars +park bench +parking meter +passenger car +patio +pay-phone +pedestal +pencil box +pencil sharpener +perfume +Petri dish +photocopier +pick +pickelhaube +picket fence +pickup +pier +piggy bank +pill bottle +pillow +ping-pong ball +pinwheel +pirate +pitcher +plane +planetarium +plastic bag +plate rack +plow +plunger +Polaroid camera +pole +police van +poncho +pool table +pop bottle +pot +potter's wheel +power drill +prayer rug +printer +prison +projectile +projector +puck +punching bag +purse +quill +quilt +racer +racket +radiator +radio +radio telescope +rain barrel +recreational vehicle +reel +reflex camera +refrigerator +remote control +restaurant +revolver +rifle +rocking chair +rotisserie +rubber eraser +rugby ball +rule +running shoe +safe +safety pin +saltshaker +sandal +sarong +sax +scabbard +scale +school bus +schooner +scoreboard +screen +screw +screwdriver +seat belt +sewing machine +shield +shoe shop +shoji +shopping basket +shopping cart +shovel +shower cap +shower curtain +ski +ski mask +sleeping bag +slide rule +sliding door +slot +snorkel +snowmobile +snowplow +soap dispenser +soccer ball +sock +solar dish +sombrero +soup bowl +space bar +space heater +space shuttle +spatula +speedboat +spider web +spindle +sports car +spotlight +stage +steam locomotive +steel arch bridge +steel drum +stethoscope +stole +stone wall +stopwatch +stove +strainer +streetcar +stretcher +studio couch +stupa +submarine +suit +sundial +sunglass +sunglasses +sunscreen +suspension bridge +swab +sweatshirt +swimming trunks +swing +switch +syringe +table lamp +tank +tape player +teapot +teddy +television +tennis ball +thatch +theater curtain +thimble +thresher +throne +tile roof +toaster +tobacco shop +toilet seat +torch +totem pole +tow truck +toyshop +tractor +trailer truck +tray +trench coat +tricycle +trimaran +tripod +triumphal arch +trolleybus +trombone +tub +turnstile +typewriter keyboard +umbrella +unicycle +upright +vacuum +vase +vault +velvet +vending machine +vestment +viaduct +violin +volleyball +waffle iron +wall clock +wallet +wardrobe +warplane +washbasin +washer +water bottle +water jug +water tower +whiskey jug +whistle +wig +window screen +window shade +Windsor tie +wine bottle +wing +wok +wooden spoon +wool +worm fence +wreck +yawl +yurt +web site +comic book +crossword puzzle +street sign +traffic light +book jacket +menu +plate +guacamole +consomme +hot pot +trifle +ice cream +ice lolly +French loaf +bagel +pretzel +cheeseburger +hotdog +mashed potato +head cabbage +broccoli +cauliflower +zucchini +spaghetti squash +acorn squash +butternut squash +cucumber +artichoke +bell pepper +cardoon +mushroom +Granny Smith +strawberry +orange +lemon +fig +pineapple +banana +jackfruit +custard apple +pomegranate +hay +carbonara +chocolate sauce +dough +meat loaf +pizza +potpie +burrito +red wine +espresso +cup +eggnog +alp +bubble +cliff +coral reef +geyser +lakeside +promontory +sandbar +seashore +valley +volcano +ballplayer +groom +scuba diver +rapeseed +daisy +yellow lady's slipper +corn +acorn +hip +buckeye +coral fungus +agaric +gyromitra +stinkhorn +earthstar +hen-of-the-woods +bolete +ear +toilet tissue -- GitLab From bc4a279ca963a901576a9a7a35b189dfae7f5c85 Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Tue, 15 Jan 2019 19:58:24 -0800 Subject: [PATCH 0768/2345] Unbreak non-CUDA builds, since the testing bot tests all targets in the directory "//tensorflow/...". PiperOrigin-RevId: 229487682 --- tensorflow/stream_executor/cuda/BUILD | 134 +++++++++++++------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/tensorflow/stream_executor/cuda/BUILD b/tensorflow/stream_executor/cuda/BUILD index 87c8eae416..fcfda1337e 100644 --- a/tensorflow/stream_executor/cuda/BUILD +++ b/tensorflow/stream_executor/cuda/BUILD @@ -42,10 +42,10 @@ cc_library( cc_library( name = "cuda_platform", - srcs = ["cuda_platform.cc"], - hdrs = ["cuda_platform.h"], + srcs = if_cuda_is_configured(["cuda_platform.cc"]), + hdrs = if_cuda_is_configured(["cuda_platform.h"]), visibility = ["//visibility:public"], - deps = [ + deps = if_cuda_is_configured([ ":cuda_driver", ":cuda_gpu_executor", ":cuda_platform_id", @@ -55,27 +55,27 @@ cc_library( "//tensorflow/stream_executor:stream_executor_pimpl_header", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", - ] + tf_additional_cuda_platform_deps(), + ] + tf_additional_cuda_platform_deps()), alwayslink = True, # Registers itself with the MultiPlatformManager. ) cc_library( name = "cuda_diagnostics", - srcs = ["cuda_diagnostics.cc"], - hdrs = ["cuda_diagnostics.h"], - deps = [ - "//tensorflow/stream_executor/lib", - "//tensorflow/stream_executor/platform", + srcs = if_cuda_is_configured(["cuda_diagnostics.cc"]), + hdrs = if_cuda_is_configured(["cuda_diagnostics.h"]), + deps = if_cuda_is_configured([ "@com_google_absl//absl/container:inlined_vector", "@com_google_absl//absl/strings", - ], + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + ]), ) cc_library( name = "cuda_driver", - srcs = ["cuda_driver.cc"], - hdrs = ["cuda_driver.h"], - deps = [ + srcs = if_cuda_is_configured(["cuda_driver.cc"]), + hdrs = if_cuda_is_configured(["cuda_driver.h"]), + deps = if_cuda_is_configured([ ":cuda_diagnostics", "@com_google_absl//absl/base", "@com_google_absl//absl/container:inlined_vector", @@ -84,49 +84,49 @@ cc_library( "//tensorflow/stream_executor:device_options", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", - ] + tf_additional_cuda_driver_deps(), + ] + tf_additional_cuda_driver_deps()), ) # The activation library is tightly coupled to the executor library. # TODO(leary) split up cuda_gpu_executor.cc so that this can stand alone. cc_library( name = "cuda_activation_header", - hdrs = ["cuda_activation.h"], + hdrs = if_cuda_is_configured(["cuda_activation.h"]), visibility = ["//visibility:public"], - deps = ["//tensorflow/stream_executor/platform"], + deps = if_cuda_is_configured(["//tensorflow/stream_executor/platform"]), ) cc_library( name = "cuda_activation", - srcs = ["cuda_activation.cc"], - hdrs = ["cuda_activation.h"], - deps = [ + srcs = if_cuda_is_configured(["cuda_activation.cc"]), + hdrs = if_cuda_is_configured(["cuda_activation.h"]), + deps = if_cuda_is_configured([ ":cuda_driver", + "@local_config_cuda//cuda:cuda_headers", "//tensorflow/stream_executor", "//tensorflow/stream_executor:stream_executor_internal", "//tensorflow/stream_executor/platform", - "@local_config_cuda//cuda:cuda_headers", - ], + ]), ) cc_library( name = "cuda_gpu_executor_header", - textual_hdrs = ["cuda_gpu_executor.h"], + textual_hdrs = if_cuda_is_configured(["cuda_gpu_executor.h"]), visibility = ["//visibility:public"], - deps = [ + deps = if_cuda_is_configured([ ":cuda_kernel", "//tensorflow/stream_executor:event", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", - ], + ]), ) cc_library( name = "cublas_plugin", - srcs = ["cuda_blas.cc"], - hdrs = ["cuda_blas.h"], + srcs = if_cuda_is_configured(["cuda_blas.cc"]), + hdrs = if_cuda_is_configured(["cuda_blas.h"]), visibility = ["//visibility:public"], - deps = [ + deps = if_cuda_is_configured([ ":cuda_activation", ":cuda_gpu_executor", ":cuda_helpers", @@ -146,16 +146,16 @@ cc_library( "//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"])), alwayslink = True, ) cc_library( name = "cufft_plugin", - srcs = ["cuda_fft.cc"], - hdrs = ["cuda_fft.h"], + srcs = if_cuda_is_configured(["cuda_fft.cc"]), + hdrs = if_cuda_is_configured(["cuda_fft.h"]), visibility = ["//visibility:public"], - deps = [ + deps = if_cuda_is_configured([ ":cuda_activation_header", ":cuda_gpu_executor_header", ":cuda_helpers", @@ -169,21 +169,21 @@ 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"])), alwayslink = True, ) cc_library( name = "cudnn_plugin", - srcs = ["cuda_dnn.cc"], - hdrs = ["cuda_dnn.h"], + 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 = [ + deps = if_cuda_is_configured([ ":cuda_activation", ":cuda_diagnostics", ":cuda_driver", @@ -208,15 +208,15 @@ cc_library( "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", "//tensorflow/stream_executor/platform:dso_loader", - ] + tf_additional_cudnn_plugin_deps() + if_static(["@local_config_cuda//cuda:cudnn"]), + ] + tf_additional_cudnn_plugin_deps() + if_static(["@local_config_cuda//cuda:cudnn"])), alwayslink = True, ) cc_library( name = "curand_plugin", - srcs = ["cuda_rng.cc"], - hdrs = ["cuda_rng.h"], - deps = [ + srcs = if_cuda_is_configured(["cuda_rng.cc"]), + hdrs = if_cuda_is_configured(["cuda_rng.h"]), + deps = if_cuda_is_configured([ ":cuda_activation", ":cuda_gpu_executor", ":cuda_helpers", @@ -229,75 +229,75 @@ 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"])), alwayslink = True, ) cc_library( name = "cuda_kernel", - hdrs = ["cuda_kernel.h"], - deps = [ + hdrs = if_cuda_is_configured(["cuda_kernel.h"]), + deps = if_cuda_is_configured([ ":cuda_driver", + "@local_config_cuda//cuda:cuda_headers", "//tensorflow/stream_executor:event", "//tensorflow/stream_executor:stream_executor_pimpl_header", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", - "@local_config_cuda//cuda:cuda_headers", - ], + ]), ) # TODO(leary) we likely need to canonicalize/eliminate this. cc_library( name = "cuda_helpers", - textual_hdrs = ["cuda_helpers.h"], + textual_hdrs = if_cuda_is_configured(["cuda_helpers.h"]), ) cc_library( name = "cuda_event", - srcs = ["cuda_event.cc"], - hdrs = ["cuda_event.h"], - deps = [ + srcs = if_cuda_is_configured(["cuda_event.cc"]), + hdrs = if_cuda_is_configured(["cuda_event.h"]), + deps = if_cuda_is_configured([ ":cuda_driver", ":cuda_gpu_executor_header", ":cuda_stream", "//tensorflow/stream_executor:stream_executor_headers", "//tensorflow/stream_executor/lib", - ], + ]), ) cc_library( name = "cuda_stream", - srcs = ["cuda_stream.cc"], - hdrs = ["cuda_stream.h"], - deps = [ + srcs = if_cuda_is_configured(["cuda_stream.cc"]), + hdrs = if_cuda_is_configured(["cuda_stream.h"]), + deps = if_cuda_is_configured([ ":cuda_driver", ":cuda_gpu_executor_header", "//tensorflow/stream_executor:stream_executor_headers", "//tensorflow/stream_executor:stream_header", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", - ], + ]), ) cc_library( name = "cuda_timer", - srcs = ["cuda_timer.cc"], - hdrs = ["cuda_timer.h"], - deps = [ + srcs = if_cuda_is_configured(["cuda_timer.cc"]), + hdrs = if_cuda_is_configured(["cuda_timer.h"]), + deps = if_cuda_is_configured([ ":cuda_driver", ":cuda_gpu_executor_header", ":cuda_stream", "//tensorflow/stream_executor:stream_executor_headers", "//tensorflow/stream_executor/lib", - ], + ]), ) # It implements :cuda_gpu_executor_header cc_library( name = "cuda_gpu_executor", - srcs = ["cuda_gpu_executor.cc"], - hdrs = ["cuda_gpu_executor.h"], - deps = [ + srcs = if_cuda_is_configured(["cuda_gpu_executor.cc"]), + hdrs = if_cuda_is_configured(["cuda_gpu_executor.h"]), + deps = if_cuda_is_configured([ ":cuda_activation", ":cuda_diagnostics", ":cuda_driver", @@ -306,6 +306,7 @@ cc_library( ":cuda_platform_id", ":cuda_stream", ":cuda_timer", + "@com_google_absl//absl/strings", "//tensorflow/stream_executor:event", "//tensorflow/stream_executor:plugin_registry", "//tensorflow/stream_executor:stream_executor_internal", @@ -313,8 +314,7 @@ cc_library( "//tensorflow/stream_executor:timer", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", - "@com_google_absl//absl/strings", - ], + ]), alwayslink = True, ) @@ -341,13 +341,13 @@ cc_library( name = "all_runtime", copts = tf_copts(), visibility = ["//visibility:public"], - deps = if_cuda_is_configured([ - ":cudnn_plugin", - ":cufft_plugin", + deps = [ ":cublas_plugin", - ":curand_plugin", ":cuda_driver", ":cuda_platform", - ]), + ":cudnn_plugin", + ":cufft_plugin", + ":curand_plugin", + ], alwayslink = 1, ) -- GitLab From 742bd415306658a329be3f6581eab74b68c74601 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Tue, 15 Jan 2019 10:09:28 +0530 Subject: [PATCH 0769/2345] Typo error correction context.py --- tensorflow/python/eager/context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/eager/context.py b/tensorflow/python/eager/context.py index faaf742c26..6c5b191cf9 100644 --- a/tensorflow/python/eager/context.py +++ b/tensorflow/python/eager/context.py @@ -180,8 +180,8 @@ class _ContextSwitchStack(threading.local): def push(self, is_building_function, enter_context_fn): """Push metadata about a context switch onto the stack. - A context switch can take one of two forms: installing a graph as the - default graph, or entering the eager context. For each context switch, + A context switch can take any one of the two forms: installing a graph as + the default graph, or entering the eager context. For each context switch, we record whether or not the entered context is building a function. Args: -- GitLab From e2cc09ed38a7ab1b3620aab93b77c4b1d7720677 Mon Sep 17 00:00:00 2001 From: "Li, Guizi" Date: Wed, 16 Jan 2019 13:55:52 +0800 Subject: [PATCH 0770/2345] [Intel MKL] enable depthwise convolution backward side code --- tensorflow/core/graph/mkl_layout_pass.cc | 19 ++++ tensorflow/core/graph/mkl_layout_pass_test.cc | 72 +++++++++++++ .../core/kernels/mkl_conv_grad_filter_ops.cc | 100 +++++++++++------- .../core/kernels/mkl_conv_grad_input_ops.cc | 51 +++++---- tensorflow/core/kernels/mkl_conv_ops.h | 53 ++++++---- tensorflow/core/ops/mkl_nn_ops.cc | 44 ++++++++ 6 files changed, 257 insertions(+), 82 deletions(-) diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc index 0f7a81110c..6f7d49c2ea 100644 --- a/tensorflow/core/graph/mkl_layout_pass.cc +++ b/tensorflow/core/graph/mkl_layout_pass.cc @@ -259,6 +259,9 @@ class MklLayoutRewritePass : public GraphOptimizationPass { csinfo_.conv3d_grad_input = "Conv3DBackpropInputV2"; csinfo_.conv3d_grad_filter = "Conv3DBackpropFilterV2"; csinfo_.depthwise_conv2d = "DepthwiseConv2dNative"; + csinfo_.depthwise_conv2d_grad_input = "DepthwiseConv2dNativeBackpropInput"; + csinfo_.depthwise_conv2d_grad_filter = + "DepthwiseConv2dNativeBackpropFilter"; csinfo_.fused_batch_norm = "FusedBatchNorm"; csinfo_.fused_batch_norm_grad = "FusedBatchNormGrad"; csinfo_.fused_conv2d = "_FusedConv2D"; @@ -278,6 +281,10 @@ class MklLayoutRewritePass : public GraphOptimizationPass { csinfo_.mkl_conv2d_with_bias = "_MklConv2DWithBias"; csinfo_.mkl_conv2d_grad_filter_with_bias = "_MklConv2DBackpropFilterWithBias"; + csinfo_.mkl_depthwise_conv2d_grad_input = + "_MklDepthwiseConv2dNativeBackpropInput"; + csinfo_.mkl_depthwise_conv2d_grad_filter = + "_MklDepthwiseConv2dNativeBackpropFilter"; csinfo_.mkl_fused_conv2d = "_MklFusedConv2D"; csinfo_.mkl_pad_with_conv2d = "_MklPadWithConv2D"; csinfo_.pad = "Pad"; @@ -381,6 +388,14 @@ class MklLayoutRewritePass : public GraphOptimizationPass { rinfo_.push_back({csinfo_.depthwise_conv2d, mkl_op_registry::GetMklOpName(csinfo_.depthwise_conv2d), CopyAttrsConv2DDepthwise, AlwaysRewrite}); + rinfo_.push_back( + {csinfo_.depthwise_conv2d_grad_input, + mkl_op_registry::GetMklOpName(csinfo_.depthwise_conv2d_grad_input), + CopyAttrsConv2DDepthwise, AlwaysRewrite}); + rinfo_.push_back( + {csinfo_.depthwise_conv2d_grad_filter, + mkl_op_registry::GetMklOpName(csinfo_.depthwise_conv2d_grad_filter), + CopyAttrsConv2DDepthwise, AlwaysRewrite}); rinfo_.push_back({csinfo_.fused_batch_norm, mkl_op_registry::GetMklOpName(csinfo_.fused_batch_norm), CopyAttrsFusedBatchNorm, AlwaysRewrite}); @@ -680,6 +695,8 @@ class MklLayoutRewritePass : public GraphOptimizationPass { string conv3d_grad_input; string conv3d_grad_filter; string depthwise_conv2d; + string depthwise_conv2d_grad_input; + string depthwise_conv2d_grad_filter; string fused_batch_norm; string fused_batch_norm_grad; string fused_conv2d; @@ -699,6 +716,8 @@ class MklLayoutRewritePass : public GraphOptimizationPass { string mkl_conv2d_grad_filter; string mkl_conv2d_grad_filter_with_bias; string mkl_conv2d_with_bias; + string mkl_depthwise_conv2d_grad_input; + string mkl_depthwise_conv2d_grad_filter; string mkl_fused_conv2d; string mkl_pad_with_conv2d; string mul; diff --git a/tensorflow/core/graph/mkl_layout_pass_test.cc b/tensorflow/core/graph/mkl_layout_pass_test.cc index 6e73ed1b9f..8e6df142e6 100644 --- a/tensorflow/core/graph/mkl_layout_pass_test.cc +++ b/tensorflow/core/graph/mkl_layout_pass_test.cc @@ -1289,6 +1289,55 @@ TEST_F(MklLayoutPassTest, NodeRewrite_Conv2DGradInput_Positive) { "D->E:1;DMT/_0->D:3;DMT/_1->D:4;DMT/_2->D:5"); } +TEST_F(MklLayoutPassTest, + NodeRewrite_DepthwiseConv2dNativeGradFilter_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Input'}" + "node { name: 'D' op: 'DepthwiseConv2dNativeBackpropFilter'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['A', 'B', 'C']}" + "node { name: 'E' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'D'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Int32Input);C(Input);D(_" + "MklDepthwiseConv2dNativeBackpropFilter);" + "DMT/_0(Const);DMT/_1(Const);DMT/_2(Const);E(Zeta)|" + "A->D;A->E;A:control->DMT/_0:control;A:control->DMT/_1:control;" + "A:control->DMT/_2:control;B->D:1;C->D:2;D->E:1;DMT/_0->D:3;" + "DMT/_1->D:4;DMT/_2->D:5"); +} + +TEST_F(MklLayoutPassTest, NodeRewrite_DepthwiseConv2dNativeGradInput_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Input'}" + "node { name: 'D' op: 'DepthwiseConv2dNativeBackpropInput'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['B', 'A', 'C']}" + "node { name: 'E' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'D'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Int32Input);C(Input);D(_" + "MklDepthwiseConv2dNativeBackpropInput);" + "DMT/_0(Const);DMT/_1(Const);DMT/_2(Const);E(Zeta)|" + "A->D:1;A->E;B->D;B:control->DMT/_0:control;" + "B:control->DMT/_1:control;B:control->DMT/_2:control;C->D:2;" + "D->E:1;DMT/_0->D:3;DMT/_1->D:4;DMT/_2->D:5"); +} + // Check that we never rewrite BiasAddGrad. TEST_F(MklLayoutPassTest, NodeRewrite_BiasAddGrad_Positive) { InitGraph( @@ -2322,6 +2371,29 @@ TEST_F(MklLayoutPassTest, NodeRewrite_Conv2DGradFilter_DeviceTest) { "A->D;A->E;B->D:1;C->D:2;D->E:1"); } +TEST_F(MklLayoutPassTest, + NodeRewrite_DepthwiseConv2dNativeGradFilter_DeviceTest) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Int32Input'}" + "node { name: 'C' op: 'Input'}" + "node { name: 'D' op: 'DepthwiseConv2dNativeBackpropFilter'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " attr { key: 'data_format' value { s: 'NCHW' } }" + " attr { key: 'use_cudnn_on_gpu' value { b: false } }" + " attr { key: 'strides' value { list: {i: 1, i:1, i:1, i:1} } }" + " attr { key: 'padding' value { s: 'SAME' } }" + " attr { key: 'dilations' value { list: {i: 1, i:1, i:1, i:1} } }" + " input: ['A', 'B', 'C']}" + "node { name: 'E' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'D'] }", + kGPUDevice); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Int32Input);C(Input);D(" + "DepthwiseConv2dNativeBackpropFilter);E(Zeta)|" + "A->D;A->E;B->D:1;C->D:2;D->E:1"); +} + TEST_F(MklLayoutPassTest, NodeRewrite_Relu_DeviceTest) { InitGraph( "node { name: 'A' op: 'Input'}" diff --git a/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc b/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc index bc9d45abe6..8faa711975 100644 --- a/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc @@ -357,12 +357,12 @@ class MklConvBwdFilterPrimitiveFactory : public MklPrimitiveFactory { } }; -template +template class MklConvCustomBackpropFilterOp - : public MklConvBackpropCommonOp { + : public MklConvBackpropCommonOp { public: explicit MklConvCustomBackpropFilterOp(OpKernelConstruction* context) - : MklConvBackpropCommonOp(context) {} + : MklConvBackpropCommonOp(context) {} ~MklConvCustomBackpropFilterOp() {} @@ -432,7 +432,7 @@ class MklConvCustomBackpropFilterOp conv_utl.GetConvFwdSizesInMklOrder( src_tf_shape, filter_tf_shape, &fwd_src_dims, &fwd_filter_dims, &strides, &dilations, &fwd_dst_dims_tf_order, &fwd_dst_dims, - &padding_left, &padding_right, false); + &padding_left, &padding_right, false, is_depthwise); if (!context->status().ok()) return; auto tf_fmt = is_conv2d @@ -485,13 +485,27 @@ class MklConvCustomBackpropFilterOp diff_filter_mkl_shape.SetMklTensor(false); if (is_conv2d) { - // Conv2D: output_dims_mkl_order is in OIHW format. - TensorShape diff_filter_tf_shape({bwd_output_dims[MklDnnDims::Dim_H], - bwd_output_dims[MklDnnDims::Dim_W], - bwd_output_dims[MklDnnDims::Dim_I], - bwd_output_dims[MklDnnDims::Dim_O]}); - AllocateOutputSetMklShape(context, 0, &diff_filter_tensor, - diff_filter_tf_shape, diff_filter_mkl_shape); + if (!is_depthwise) { + // Conv2D: output_dims_mkl_order is in OIHW format. + TensorShape diff_filter_tf_shape( + {bwd_output_dims[MklDnnDims::Dim_H], + bwd_output_dims[MklDnnDims::Dim_W], + bwd_output_dims[MklDnnDims::Dim_I], + bwd_output_dims[MklDnnDims::Dim_O]}); + AllocateOutputSetMklShape(context, 0, &diff_filter_tensor, + diff_filter_tf_shape, + diff_filter_mkl_shape); + } else { + // Depthwise Conv2d: bwd_output_dims is GOIHW format + TensorShape diff_filter_tf_shape( + {bwd_output_dims[MklDnnFilterGroupDims::MKL_GROUP_FILTER_DIM_H], + bwd_output_dims[MklDnnFilterGroupDims::MKL_GROUP_FILTER_DIM_W], + bwd_output_dims[MklDnnFilterGroupDims::MKL_GROUP_FILTER_DIM_G], + bwd_output_dims[MklDnnFilterGroupDims::MKL_GROUP_FILTER_DIM_O]}); + AllocateOutputSetMklShape(context, 0, &diff_filter_tensor, + diff_filter_tf_shape, + diff_filter_mkl_shape); + } } else { // Conv3D: output_dims_mkl_order is in OIDHW format. TensorShape diff_filter_tf_shape( @@ -568,9 +582,9 @@ class MklConvCustomBackpropFilterOp // delete primitive since it is not cached. if (do_not_cache) delete conv_bwd_filter; } catch (mkldnn::error& e) { - string error_msg = "Status: " + std::to_string(e.status) + - ", message: " + string(e.message) + ", in file " + - string(__FILE__) + ":" + std::to_string(__LINE__); + string error_msg = "Status: " + std::to_string(e.status) + ", message: " + + string(e.message) + ", in file " + string(__FILE__) + + ":" + std::to_string(__LINE__); OP_REQUIRES_OK( context, errors::Aborted("Operation received an exception:", error_msg)); @@ -628,10 +642,11 @@ class MklConvCustomBackpropFilterOp } // Output layout is Tensorflow's filter layout - // Conv2D: HWIO; Conv3D: DHWIO + // Conv2D: HWIO; Conv3D: DHWIO; Depthwise Conv: HWIGO memory::format GetOutputFormat(const memory::format data_format) { - return (this->strides_.size() == 4) ? memory::format::hwio - : memory::format::dhwio; + return is_depthwise ? memory::format::hwigo : ((this->strides_.size() == 4) + ? memory::format::hwio + : memory::format::dhwio); } // Allocate output tensor. @@ -673,27 +688,36 @@ class MklConvCustomBackpropFilterOp } }; -#define REGISTER_MKL_FILTER_KERNELS(T) \ - REGISTER_KERNEL_BUILDER(Name("_MklConv2DBackpropFilter") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConvCustomBackpropFilterOp); \ - REGISTER_KERNEL_BUILDER(Name("_MklConv2DBackpropFilterWithBias") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConvCustomBackpropFilterOp); \ - REGISTER_KERNEL_BUILDER(Name("__MklDummyConv2DBackpropFilterWithBias") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklDummyOp); \ - REGISTER_KERNEL_BUILDER(Name("_MklConv3DBackpropFilterV2") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConvCustomBackpropFilterOp); +#define REGISTER_MKL_FILTER_KERNELS(T) \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklConv2DBackpropFilter") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvCustomBackpropFilterOp); \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklConv2DBackpropFilterWithBias") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvCustomBackpropFilterOp); \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklDepthwiseConv2dNativeBackpropFilter") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvCustomBackpropFilterOp); \ + REGISTER_KERNEL_BUILDER(Name("__MklDummyConv2DBackpropFilterWithBias") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklDummyOp); \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklConv3DBackpropFilterV2") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvCustomBackpropFilterOp); TF_CALL_float(REGISTER_MKL_FILTER_KERNELS); #undef REGISTER_MKL_FILTER_KERNELS diff --git a/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc b/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc index b5be87ec55..1799a44a45 100644 --- a/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc @@ -295,11 +295,12 @@ class MklConvBwdInputPrimitiveFactory : public MklPrimitiveFactory { } }; -template -class MklConvCustomBackpropInputOp : public MklConvBackpropCommonOp { +template +class MklConvCustomBackpropInputOp + : public MklConvBackpropCommonOp { public: explicit MklConvCustomBackpropInputOp(OpKernelConstruction* context) - : MklConvBackpropCommonOp(context) {} + : MklConvBackpropCommonOp(context) {} ~MklConvCustomBackpropInputOp() {} @@ -367,7 +368,7 @@ class MklConvCustomBackpropInputOp : public MklConvBackpropCommonOp { conv_utl.GetConvFwdSizesInMklOrder( src_tf_shape, filter_tf_shape, &fwd_src_dims, &fwd_filter_dims, &strides, &dilations, &fwd_output_dims_tf_order, &fwd_output_dims, - &padding_left, &padding_right, false); + &padding_left, &padding_right, false, is_depthwise); if (!context->status().ok()) return; // Create Convolution forward descriptor since Convolution backward @@ -383,9 +384,11 @@ class MklConvCustomBackpropInputOp : public MklConvBackpropCommonOp { auto fwd_filter_md = filter_mkl_shape.IsMklTensor() ? filter_mkl_shape.GetMklLayout() - : memory::desc( - fwd_filter_dims, MklDnnType(), - is_conv2d ? memory::format::hwio : memory::format::dhwio); + : memory::desc(fwd_filter_dims, MklDnnType(), + is_depthwise + ? memory::hwigo + : (is_conv2d ? memory::format::hwio + : memory::format::dhwio)); conv_utl.GetInputSizeInMklOrder(diff_dst_tf_shape, &diff_dst_dims); if (!context->status().ok()) return; @@ -462,9 +465,9 @@ class MklConvCustomBackpropInputOp : public MklConvBackpropCommonOp { delete conv_bwd_input; } } catch (mkldnn::error& e) { - string error_msg = "Status: " + std::to_string(e.status) + - ", message: " + string(e.message) + ", in file " + - string(__FILE__) + ":" + std::to_string(__LINE__); + string error_msg = "Status: " + std::to_string(e.status) + ", message: " + + string(e.message) + ", in file " + string(__FILE__) + + ":" + std::to_string(__LINE__); OP_REQUIRES_OK( context, errors::Aborted("Operation received an exception:", error_msg)); @@ -554,18 +557,22 @@ class MklConvCustomBackpropInputOp : public MklConvBackpropCommonOp { } }; -#define REGISTER_MKL_CPU_KERNELS(T) \ - REGISTER_KERNEL_BUILDER(Name("_MklConv2DBackpropInput") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConvCustomBackpropInputOp); \ - REGISTER_KERNEL_BUILDER(Name("_MklConv3DBackpropInputV2") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConvCustomBackpropInputOp); - +#define REGISTER_MKL_CPU_KERNELS(T) \ + REGISTER_KERNEL_BUILDER(Name("_MklConv2DBackpropInput") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvCustomBackpropInputOp); \ + REGISTER_KERNEL_BUILDER(Name("_MklConv3DBackpropInputV2") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvCustomBackpropInputOp); \ + REGISTER_KERNEL_BUILDER(Name("_MklDepthwiseConv2dNativeBackpropInput") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvCustomBackpropInputOp); TF_CALL_float(REGISTER_MKL_CPU_KERNELS); #undef REGISTER_MKL_CPU_KERNELS diff --git a/tensorflow/core/kernels/mkl_conv_ops.h b/tensorflow/core/kernels/mkl_conv_ops.h index fd279ead9d..c49650c1db 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.h +++ b/tensorflow/core/kernels/mkl_conv_ops.h @@ -58,7 +58,7 @@ class MklDnnConvUtil { public: MklDnnConvUtil(OpKernelContext* context, const std::vector& strides, Padding pad, TensorFormat fm, - const std::vector& dilations) + const std::vector& dilations, bool is_depthwise = false) : context_(context), strides_(strides), dilations_(dilations), @@ -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")); } @@ -550,7 +549,7 @@ class MklDnnConvUtil { /// Common class that implements ConvBackpropFilter and Input ///////////////////////////////////////////////////////////////////// -template +template class MklConvBackpropCommonOp : public OpKernel { public: ~MklConvBackpropCommonOp() {} @@ -563,28 +562,38 @@ class MklConvBackpropCommonOp : public OpKernel { OP_REQUIRES_OK(context, context->GetAttr("strides", &strides_)); int stride_n = GetTensorDim(strides_, data_format_, 'N'); int stride_c = GetTensorDim(strides_, data_format_, 'C'); + const int64 stride_h = GetTensorDim(strides_, data_format_, 'H'); + const int64 stride_w = GetTensorDim(strides_, data_format_, 'W'); OP_REQUIRES( context, (stride_n == 1 && stride_c == 1), errors::InvalidArgument("Current implementation does not yet support " "strides in the batch and depth dimensions.")); - OP_REQUIRES_OK(context, context->GetAttr("dilations", &dilations_)); - if (strides_.size() == 4) { - // Check Conv2D dilations - OP_REQUIRES(context, dilations_.size() == 4, - errors::InvalidArgument("Sliding window dilations field must " - "specify 4 dimensions")); - int dilation_n = GetTensorDim(dilations_, data_format_, 'N'); - int dilation_c = GetTensorDim(dilations_, data_format_, 'C'); - int dilation_h = GetTensorDim(dilations_, data_format_, 'H'); - int dilation_w = GetTensorDim(dilations_, data_format_, 'W'); - OP_REQUIRES(context, (dilation_n == 1 && dilation_c == 1), - errors::InvalidArgument( - "Current implementation does not yet support " - "dilations in the batch and depth dimensions.")); - OP_REQUIRES( - context, dilation_h > 0 && dilation_w > 0, - errors::InvalidArgument("Dilated rates should be larger than 0.")); + // Depthwise Convolution doesn't have dilation parameter + if (!is_depthwise) { + OP_REQUIRES_OK(context, context->GetAttr("dilations", &dilations_)); + if (strides_.size() == 4) { + // Check Conv2D dilations + OP_REQUIRES( + context, dilations_.size() == 4, + errors::InvalidArgument("Sliding window dilations field must " + "specify 4 dimensions")); + int dilation_n = GetTensorDim(dilations_, data_format_, 'N'); + int dilation_c = GetTensorDim(dilations_, data_format_, 'C'); + int dilation_h = GetTensorDim(dilations_, data_format_, 'H'); + int dilation_w = GetTensorDim(dilations_, data_format_, 'W'); + OP_REQUIRES(context, (dilation_n == 1 && dilation_c == 1), + errors::InvalidArgument( + "Current implementation does not yet support " + "dilations in the batch and depth dimensions.")); + OP_REQUIRES( + context, dilation_h > 0 && dilation_w > 0, + errors::InvalidArgument("Dilated rates should be larger than 0.")); + } + } else { + // Set dilations as 1 for depthwise conv + // for future support to align with Tensorflow + dilations_ = {1, 1, 1, 1}; } OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_)); diff --git a/tensorflow/core/ops/mkl_nn_ops.cc b/tensorflow/core/ops/mkl_nn_ops.cc index 658afd9901..07d95dd988 100644 --- a/tensorflow/core/ops/mkl_nn_ops.cc +++ b/tensorflow/core/ops/mkl_nn_ops.cc @@ -634,6 +634,50 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasSignedSumAndReluAndRequantize") return Status::OK(); }); +REGISTER_OP("_MklDepthwiseConv2dNativeBackpropInput") + .Input("input_sizes: int32") + .Input("filter: T") + .Input("out_backprop: T") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_out_backprop: uint8") + .Output("output: T") + .Output("mkl_output: uint8") + .Attr("T: {half, bfloat16, float, double}") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr(GetConvnetDataFormatAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + ShapeHandle s; + TF_RETURN_IF_ERROR(c->MakeShapeFromShapeTensor(0, &s)); + TF_RETURN_IF_ERROR(c->WithRank(s, 4, &s)); + c->set_output(0, s); + return Status::OK(); + }); + +REGISTER_OP("_MklDepthwiseConv2dNativeBackpropFilter") + .Input("input: T") + .Input("filter_sizes: int32") + .Input("out_backprop: T") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_out_backprop: uint8") + .Output("output: T") + .Output("mkl_output: uint8") + .Attr("T: {half, bfloat16, float, double}") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr(GetConvnetDataFormatAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + ShapeHandle s; + TF_RETURN_IF_ERROR(c->MakeShapeFromShapeTensor(1, &s)); + TF_RETURN_IF_ERROR(c->WithRank(s, 4, &s)); + c->set_output(0, s); + return Status::OK(); + }); + } // namespace tensorflow #endif // INTEL_MKL -- GitLab From 0bc02af7db9e43c37a89278a2e7c99ecdfe4fb00 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Tue, 15 Jan 2019 23:12:00 -0800 Subject: [PATCH 0771/2345] Fix demo app to handle some cases more properly - Don't crash when threads are changes with a delegate. This is done by now recreating interpreter every time threads is changes. - Don't call showToast() every updateFrame() when classifier is null. This prevents hanging and allows the proper error message to be displayed. PiperOrigin-RevId: 229503428 --- .../Camera2BasicFragment.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tensorflow/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java b/tensorflow/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java index a7b3440536..814d236872 100644 --- a/tensorflow/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java +++ b/tensorflow/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java @@ -190,6 +190,8 @@ public class Camera2BasicFragment extends Fragment int currentModel = -1; + int currentNumThreads = -1; + /** An additional thread for running tasks that shouldn't block the UI. */ private HandlerThread backgroundThread; @@ -323,13 +325,16 @@ public class Camera2BasicFragment extends Fragment // Get UI information before delegating to background final int modelIndex = modelView.getCheckedItemPosition(); final int deviceIndex = deviceView.getCheckedItemPosition(); + final int numThreads = np.getValue(); backgroundHandler.post(() -> { - if (modelIndex == currentModel && deviceIndex == currentDevice) { + if (modelIndex == currentModel && deviceIndex == currentDevice + && numThreads == currentNumThreads) { return; } currentModel = modelIndex; currentDevice = deviceIndex; + currentNumThreads = numThreads; // Disable classifier while updating if (classifier != null) { @@ -357,7 +362,11 @@ public class Camera2BasicFragment extends Fragment classifier = null; } - // Customzie the interpreter to the type of device we want to use. + // Customize the interpreter to the type of device we want to use. + if (classifier == null) { + return; + } + classifier.setNumThreads(numThreads); if (device.equals(cpu)) { } else if (device.equals(gpu)) { if (!GpuDelegateHelper.isGpuDelegateAvailable()) { @@ -437,7 +446,7 @@ public class Camera2BasicFragment extends Fragment new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - backgroundHandler.post(() -> classifier.setNumThreads(newVal)); + updateActiveModel(); } }); @@ -807,7 +816,9 @@ public class Camera2BasicFragment extends Fragment /** Classifies a frame from the preview stream. */ private void classifyFrame() { if (classifier == null || getActivity() == null || cameraDevice == null) { - showToast("Uninitialized Classifier or invalid context."); + // It's important to not call showToast every frame, or else the app will starve and + // hang. updateActiveModel() already puts a error message up with showToast. + // showToast("Uninitialized Classifier or invalid context."); return; } SpannableStringBuilder textToShow = new SpannableStringBuilder(); -- GitLab From 9fd5c9930dc714c089ed137d57e8ba09e0e1a6af Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 01:03:08 -0800 Subject: [PATCH 0772/2345] compat: Update forward compatibility horizon to 2019-01-16 PiperOrigin-RevId: 229513368 --- 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 83f31bd533..773b3a41f9 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, 1, 15) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 16) @tf_export("compat.forward_compatible") -- GitLab From a331eaf593bb91eaafd8953f1bd7d502eaeee53a Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Wed, 16 Jan 2019 02:40:41 -0800 Subject: [PATCH 0773/2345] Move variable ownership test into extended.variable_created_in_scope. We want to invert the relationship here, instead of tf.Variable being aware of strategies, we make strategies keep track of variables created while their scope was active. This change implements that (at least in terms of the public API for variables and dist strat). tf.Variable subclasses created by distribution strategy (e.g. DistributedVariable) still export a public `distribution_strategy` property. PiperOrigin-RevId: 229525171 --- .../python/parameter_server_strategy_test.py | 5 +++- .../python/distribute/distribute_lib.py | 27 +++++++++++++++++++ tensorflow/python/distribute/values.py | 9 ++++--- tensorflow/python/keras/engine/training.py | 5 ++-- .../resource_variable_ops_test.py | 2 +- .../python/kernel_tests/variables_test.py | 2 +- .../python/ops/resource_variable_ops.py | 9 ++----- tensorflow/python/ops/variables.py | 14 ++-------- .../api/golden/v1/tensorflow.-variable.pbtxt | 4 --- ...orflow.distribute.-strategy-extended.pbtxt | 4 +++ .../api/golden/v2/tensorflow.-variable.pbtxt | 4 --- ...orflow.distribute.-strategy-extended.pbtxt | 4 +++ 12 files changed, 53 insertions(+), 36 deletions(-) diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py index c7760a8431..9e7e201519 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py @@ -873,7 +873,10 @@ class ParameterServerStrategyWithChiefTest(ParameterServerStrategyTestBase, id(get_step), get_step.__class__.__name__))) self.assertIs(resource_variable_ops.ResourceVariable, type(created_step)) self.assertIs(resource_variable_ops.ResourceVariable, type(get_step)) - self.assertIs(strategy, created_step.distribute_strategy) + # All variables have an _distribute_strategy parameter. Only variable + # subclasses in distribution strategy expose it publicly. + self.assertFalse(hasattr(strategy, 'distribute_strategy')) + self.assertIs(strategy, created_step._distribute_strategy) @combinations.generate( combinations.combine(mode=['graph'], use_core_strategy=[True, False])) diff --git a/tensorflow/python/distribute/distribute_lib.py b/tensorflow/python/distribute/distribute_lib.py index f112787ba2..32789c821e 100644 --- a/tensorflow/python/distribute/distribute_lib.py +++ b/tensorflow/python/distribute/distribute_lib.py @@ -876,6 +876,30 @@ class DistributionStrategyExtended(object): # Note: should support "colocate_with" argument. raise NotImplementedError("must be implemented in descendants") + def variable_created_in_scope(self, v): + """Tests whether `v` was created while this strategy scope was active. + + Variables created inside the strategy scope are "owned" by it: + + >>> with strategy.scope(): + ... v = tf.Variable(1.) + >>> strategy.variable_created_in_scope(v) + True + + Variables created outside the strategy are not owned by it: + + >>> v = tf.Variable(1.) + >>> strategy.variable_created_in_scope(v) + False + + Args: + v: A `tf.Variable` instance. + + Returns: + True if `v` was created inside the scope, False if not. + """ + return v._distribute_strategy == self._container_strategy_weakref() # pylint: disable=protected-access + def read_var(self, v): """Reads the value of a variable. @@ -1519,6 +1543,9 @@ class _DefaultDistributionExtended(DistributionStrategyExtended): _require_strategy_scope_extended(self) return ops.colocate_with(colocate_with_variable) + def variable_created_in_scope(self, v): + return v._distribute_strategy is None # pylint: disable=protected-access + def _distribute_dataset(self, dataset_fn): return self._call_dataset_fn(dataset_fn) diff --git a/tensorflow/python/distribute/values.py b/tensorflow/python/distribute/values.py index e7db285be5..19967f2c5e 100644 --- a/tensorflow/python/distribute/values.py +++ b/tensorflow/python/distribute/values.py @@ -571,11 +571,12 @@ ops.register_dense_tensor_like_type(DistributedVariable) def _validate_colocate_extended(v, extended): - if v.distribute_strategy.extended is not extended: + variable_strategy = v._distribute_strategy # pylint: disable=protected-access + if variable_strategy.extended is not extended: raise ValueError( "`colocate_vars_with` must only be passed a variable created in this " "tf.distribute.Strategy.scope(), not %s created in scope: %s" % - (v, v.distribute_strategy,)) + (v, variable_strategy)) def validate_colocate_distributed_variable(v, extended): @@ -595,7 +596,7 @@ def validate_colocate_tpu_variable(v, extended): def validate_colocate(v, extended): - if not hasattr(v, "distribute_strategy"): + if not hasattr(v, "_distribute_strategy"): raise ValueError( "`colocate_vars_with` must only be passed a variable created in this " "tf.distribute.Strategy.scope(), not: %r" % (v,)) @@ -1174,7 +1175,7 @@ class _ReplicaLocalSaveable(saver.BaseSaverBuilder.SaveableObject): # 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 + strategy = replica_local_variable._distribute_strategy # pylint: disable=protected-access return strategy.extended.read_var(replica_local_variable) spec = saver.BaseSaverBuilder.SaveSpec( diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index f5a720b292..6b0422e71c 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -547,7 +547,8 @@ class Model(Network): # Validate all variables were correctly created in distribution scope. if self._distribution_strategy and not self._compile_distribution: for v in self.variables: - if v.distribute_strategy is not self._distribution_strategy: + strategy = self._distribution_strategy + if not strategy.extended.variable_created_in_scope(v): raise ValueError( 'Variable (%s) was not created in the distribution strategy ' 'scope of (%s). It is most likely due to not all layers or ' @@ -556,7 +557,7 @@ class Model(Network): 'to the following.\n' 'with strategy.scope():\n' ' model=_create_model()\n' - ' model.compile(...)'% (v, self._distribution_strategy)) + ' model.compile(...)'% (v, strategy)) @property def metrics(self): diff --git a/tensorflow/python/kernel_tests/resource_variable_ops_test.py b/tensorflow/python/kernel_tests/resource_variable_ops_test.py index ec93038f1b..d31652eb89 100644 --- a/tensorflow/python/kernel_tests/resource_variable_ops_test.py +++ b/tensorflow/python/kernel_tests/resource_variable_ops_test.py @@ -1095,7 +1095,7 @@ class _MixedPrecisionVariableTest(test_util.TensorFlowTestCase): @test_util.run_in_graph_and_eager_modes() def testDistributeStrategy(self): v = resource_variable_ops.ResourceVariable(1, dtype=dtypes.int32) - self.assertIsNone(v.distribute_strategy) + self.assertIsNone(v._distribute_strategy) if __name__ == "__main__": diff --git a/tensorflow/python/kernel_tests/variables_test.py b/tensorflow/python/kernel_tests/variables_test.py index 4dc6d0a828..028ef11fc4 100644 --- a/tensorflow/python/kernel_tests/variables_test.py +++ b/tensorflow/python/kernel_tests/variables_test.py @@ -46,7 +46,7 @@ class VariablesTestCase(test.TestCase): @test_util.run_deprecated_v1 def testDistributeStrategy(self): v = variables.VariableV1(0.0) - self.assertIsNone(v.distribute_strategy) + self.assertIsNone(v._distribute_strategy) @test_util.run_v1_only("b/120545219") def testInitialization(self): diff --git a/tensorflow/python/ops/resource_variable_ops.py b/tensorflow/python/ops/resource_variable_ops.py index 2681deb78c..1a895fee87 100644 --- a/tensorflow/python/ops/resource_variable_ops.py +++ b/tensorflow/python/ops/resource_variable_ops.py @@ -593,7 +593,7 @@ class ResourceVariable(variables.VariableV1): constraint=self._constraint, dtype=self._dtype, name=self._shared_name + "_copy", - distribute_strategy=self.distribute_strategy) + distribute_strategy=self._distribute_strategy) memo[self._unique_id] = copied_variable return copied_variable @@ -622,11 +622,6 @@ class ResourceVariable(variables.VariableV1): """The shape of this variable.""" return self._shape - @property - def distribute_strategy(self): - """The `tf.distribute.Strategy` that this variable was created under.""" - return self._distribute_strategy - def _shape_as_list(self): if self.shape.ndims is None: return None @@ -933,7 +928,7 @@ class ResourceVariable(variables.VariableV1): name=self._shared_name, dtype=self.dtype, constraint=self.constraint, - distribute_strategy=self.distribute_strategy), () + distribute_strategy=self._distribute_strategy), () def scatter_sub(self, sparse_delta, use_locking=False, name=None): """Subtracts `IndexedSlices` from this variable. diff --git a/tensorflow/python/ops/variables.py b/tensorflow/python/ops/variables.py index 7eb2e14b42..3392b1aad9 100644 --- a/tensorflow/python/ops/variables.py +++ b/tensorflow/python/ops/variables.py @@ -1004,16 +1004,6 @@ class Variable(six.with_metaclass(VariableMetaclass, """The `Graph` of this variable.""" raise NotImplementedError - @property - def distribute_strategy(self): - """The `tf.distribute.Strategy` that this variable was created under. - - Returns: - A `tf.distribute.Strategy` or `None` if this variable was not created - inside the `scope()` of any strategy. - """ - raise NotImplementedError - @property def shape(self): """The `TensorShape` of this variable. @@ -2206,7 +2196,7 @@ class RefVariable(VariableV1): return self._variable.graph @property - def distribute_strategy(self): + def _distribute_strategy(self): """The `tf.distribute.Strategy` that this variable was created under.""" return None # Ref variables are never created inside a strategy. @@ -2603,7 +2593,7 @@ class PartitionedVariable(object): return self.get_shape() @property - def distribute_strategy(self): + def _distribute_strategy(self): """The `tf.distribute.Strategy` that this variable was created under.""" # NOTE(yuefengz): Today, no partitioned variables in a distribute strategy. return None diff --git a/tensorflow/tools/api/golden/v1/tensorflow.-variable.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.-variable.pbtxt index f042097acc..341ace0766 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.-variable.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.-variable.pbtxt @@ -16,10 +16,6 @@ tf_class { name: "device" mtype: "" } - member { - name: "distribute_strategy" - mtype: "" - } member { name: "dtype" mtype: "" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy-extended.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy-extended.pbtxt index 37b620891f..5c4f090753 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy-extended.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy-extended.pbtxt @@ -82,4 +82,8 @@ tf_class { name: "value_container" argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "variable_created_in_scope" + argspec: "args=[\'self\', \'v\'], varargs=None, keywords=None, defaults=None" + } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt index 2206f0f8d8..a80726d3bb 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt @@ -15,10 +15,6 @@ tf_class { name: "device" mtype: "" } - member { - name: "distribute_strategy" - mtype: "" - } member { name: "dtype" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy-extended.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy-extended.pbtxt index 37b620891f..5c4f090753 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy-extended.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy-extended.pbtxt @@ -82,4 +82,8 @@ tf_class { name: "value_container" argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "variable_created_in_scope" + argspec: "args=[\'self\', \'v\'], varargs=None, keywords=None, defaults=None" + } } -- GitLab From 2225d4d16dcec29f5c4eeda4485618bc3ac4de99 Mon Sep 17 00:00:00 2001 From: Vojtech Bardiovsky Date: Wed, 16 Jan 2019 06:13:58 -0800 Subject: [PATCH 0774/2345] Return the same list of dataset optimizations every time to fix non-determinism. PiperOrigin-RevId: 229545908 --- tensorflow/python/data/experimental/ops/optimization_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/data/experimental/ops/optimization_options.py b/tensorflow/python/data/experimental/ops/optimization_options.py index 9fe1f9499e..02747d3e36 100644 --- a/tensorflow/python/data/experimental/ops/optimization_options.py +++ b/tensorflow/python/data/experimental/ops/optimization_options.py @@ -132,4 +132,4 @@ class OptimizationOptions(options.OptionsBase): for optimization in optimizations_to_disable: if getattr(self, optimization) is not False: result.add(optimization) - return list(result) + return sorted(list(result)) -- GitLab From 2834c69d95a3034c587f3cee80282a47055fa750 Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Wed, 16 Jan 2019 20:53:35 +0530 Subject: [PATCH 0775/2345] Add take_while to v1 golden API --- .../tools/api/golden/v1/tensorflow.data.experimental.pbtxt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt index a262c0f799..abc98a74b6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt @@ -180,6 +180,10 @@ tf_module { name: "shuffle_and_repeat" argspec: "args=[\'buffer_size\', \'count\', \'seed\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " } + member_method { + name: "take_while" + argspec: "args=[\'predicate\'], varargs=None, keywords=None, defaults=None" + } member_method { name: "unbatch" argspec: "args=[], varargs=None, keywords=None, defaults=None" -- GitLab From 625516717dc584267eaff196a3c9a62f0e18658f Mon Sep 17 00:00:00 2001 From: Dheeraj Rajaram Reddy Date: Wed, 16 Jan 2019 21:03:53 +0530 Subject: [PATCH 0776/2345] Add absl_py parameterized testing dependency to take_while_serialization test --- .../python/data/experimental/kernel_tests/serialization/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD index 00b05e4d30..16c8aced6b 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD @@ -681,6 +681,7 @@ py_test( "//tensorflow/python:client_testlib", "//tensorflow/python/data/experimental/ops:take_while_ops", "//tensorflow/python/data/ops:dataset_ops", + "@absl_py//absl/testing:parameterized", ], ) -- GitLab From 1f0e45d698e07834c2e316843403f0742a206d8e Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Wed, 16 Jan 2019 07:42:12 -0800 Subject: [PATCH 0777/2345] [tf.data] Turning map paralellization off by default until an performance regression is resolved. PiperOrigin-RevId: 229555646 --- .../kernel_tests/optimization/optimize_dataset_test.py | 1 - tensorflow/python/data/experimental/ops/optimization_options.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tensorflow/python/data/experimental/kernel_tests/optimization/optimize_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/optimization/optimize_dataset_test.py index a08016ca89..e02f3b1572 100644 --- a/tensorflow/python/data/experimental/kernel_tests/optimization/optimize_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/optimization/optimize_dataset_test.py @@ -268,7 +268,6 @@ class OptimizeDatasetTest(test_base.DatasetTestBase, parameterized.TestCase): options = dataset_ops.Options() expected_optimizations = [ "map_and_batch_fusion", - "map_parallelization", "noop_elimination", "shuffle_and_repeat_fusion", ] diff --git a/tensorflow/python/data/experimental/ops/optimization_options.py b/tensorflow/python/data/experimental/ops/optimization_options.py index 02747d3e36..be1fb4c7ca 100644 --- a/tensorflow/python/data/experimental/ops/optimization_options.py +++ b/tensorflow/python/data/experimental/ops/optimization_options.py @@ -125,7 +125,6 @@ class OptimizationOptions(options.OptionsBase): # user explicitly disables them. optimizations_to_disable = [ "map_and_batch_fusion", - "map_parallelization", "noop_elimination", "shuffle_and_repeat_fusion", ] -- GitLab From 060b6e32adbcee5504377f5bd54a2e4044d78273 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 07:51:23 -0800 Subject: [PATCH 0778/2345] Use Ophint to support unidirectional sequence rnn (static) PiperOrigin-RevId: 229556771 --- .../lite/experimental/examples/lstm/BUILD | 35 ++++ .../experimental/examples/lstm/tflite_rnn.py | 150 ++++++++++++++ .../lstm/unidirectional_sequence_rnn_test.py | 195 ++++++++++++++++++ .../propagate_array_data_types.cc | 6 + .../propagate_fixed_sizes.cc | 44 ++++ tensorflow/lite/toco/import_tensorflow.cc | 22 ++ tensorflow/lite/toco/model.h | 10 +- tensorflow/lite/toco/tflite/operator.cc | 36 ++++ tensorflow/lite/toco/tooling_util.cc | 1 + 9 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 tensorflow/lite/experimental/examples/lstm/tflite_rnn.py create mode 100644 tensorflow/lite/experimental/examples/lstm/unidirectional_sequence_rnn_test.py diff --git a/tensorflow/lite/experimental/examples/lstm/BUILD b/tensorflow/lite/experimental/examples/lstm/BUILD index 0c351ee4ec..f39673c028 100644 --- a/tensorflow/lite/experimental/examples/lstm/BUILD +++ b/tensorflow/lite/experimental/examples/lstm/BUILD @@ -17,6 +17,19 @@ py_library( ], ) +py_library( + name = "tflite_rnn", + srcs = ["tflite_rnn.py"], + srcs_version = "PY2AND3", + visibility = ["//visibility:public"], + deps = [ + "//tensorflow:tensorflow_py", + "//tensorflow/lite/python:lite", + "//tensorflow/python:framework", + "@six_archive//:six", + ], +) + py_test( name = "unidirectional_sequence_lstm_test", size = "large", @@ -38,3 +51,25 @@ py_test( "@six_archive//:six", ], ) + +py_test( + name = "unidirectional_sequence_rnn_test", + size = "large", + srcs = ["unidirectional_sequence_rnn_test.py"], + srcs_version = "PY2AND3", + tags = [ + "no_oss", + "no_pip", + ], + deps = [ + ":tflite_rnn", + "//tensorflow:tensorflow_py", + "//tensorflow/examples/tutorials/mnist:input_data", + "//tensorflow/lite/python:lite", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:platform", + "//tensorflow/python/tools:optimize_for_inference", + "//third_party/py/numpy", + "@six_archive//:six", + ], +) diff --git a/tensorflow/lite/experimental/examples/lstm/tflite_rnn.py b/tensorflow/lite/experimental/examples/lstm/tflite_rnn.py new file mode 100644 index 0000000000..e4aad18367 --- /dev/null +++ b/tensorflow/lite/experimental/examples/lstm/tflite_rnn.py @@ -0,0 +1,150 @@ +# 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. +# ============================================================================== +"""TfLite BasicRnnCell wrapper. + +TODO(renjieliu): Find a better home for this one. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import itertools + +from tensorflow.lite.python import lite +from tensorflow.python.keras import activations +from tensorflow.python.layers import base as base_layer +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import init_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import nn_ops +from tensorflow.python.ops import rnn_cell_impl + + +class TfLiteRNNCell(rnn_cell_impl.LayerRNNCell): + """The most basic RNN cell. + + This is used only for TfLite, it provides hints and it also makes the + variables in the desired for the tflite ops. + """ + + def __init__(self, + num_units, + activation=None, + reuse=None, + name=None, + dtype=None, + **kwargs): + """Initializes the parameters for an RNN cell. + + Args: + num_units: int, The number of units in the RNN cell. + activation: Nonlinearity to use. Default: `tanh`. It could also be string + that is within Keras activation function names. + reuse: (optional) Python boolean describing whether to reuse variables in + an existing scope. Raises an error if not `True` and the existing scope + already has the given variables. + name: String, the name of the layer. Layers with the same name will share + weights, but to avoid mistakes we require reuse=True in such cases. + dtype: Default dtype of the layer (default of `None` means use the type of + the first input). Required when `build` is called before `call`. + **kwargs: Dict, keyword named properties for common layer attributes, like + `trainable` etc when constructing the cell from configs of get_config(). + + Raises: + ValueError: If the existing scope already has the given variables. + """ + super(TfLiteRNNCell, self).__init__( + _reuse=reuse, name=name, dtype=dtype, **kwargs) + + # Inputs must be Rank-2. + self.input_spec = base_layer.InputSpec(ndim=2) + + self._tflite_wrapper = lite.OpHint("UnidirectionalSequenceRnn") + self._num_units = num_units + if activation: + self._activation = activations.get(activation) + else: + self._activation = math_ops.tanh + + @property + def state_size(self): + return self._num_units + + @property + def output_size(self): + return self._num_units + + def build(self, inputs_shape): + """Builds the RNN cell. + + Args: + inputs_shape: Rnn input tensor shape. + + Raises: + ValueError: If last dimension of the input shape is not known. + """ + if inputs_shape[-1] is None: + raise ValueError("Expected inputs.shape[-1] to be known, saw shape: %s" % + (inputs_shape,)) + + input_depth = inputs_shape[-1] + + def add_variable_wrapped(name, shape, initializer, index): + var = self.add_variable(name, shape=shape, initializer=initializer) + return self._tflite_wrapper.add_input( + var, name=name, index_override=index) + + self._input_weights = add_variable_wrapped( + "input_weights", [self._num_units, input_depth], None, 1) + self._recurrent_weights = add_variable_wrapped( + "recurrent_weights", [self._num_units, self._num_units], None, 2) + self._bias = add_variable_wrapped( + "bias", + shape=[self._num_units], + initializer=init_ops.zeros_initializer(dtype=self.dtype), + index=3) + + self.built = True + + def call(self, inputs, state): + """Most basic RNN: output = new_state = act(W * input + U * state + B).""" + inputs = self._tflite_wrapper.add_input( + inputs, tag="input", name="input", aggregate="stack", index_override=0) + state = self._tflite_wrapper.add_input( + state, + tag="hidden_state", + name="hidden_state", + aggregate="first", + index_override=4) + weights = array_ops.transpose( + array_ops.concat([self._input_weights, self._recurrent_weights], 1)) + gate_inputs = math_ops.matmul(array_ops.concat([inputs, state], 1), weights) + gate_inputs = nn_ops.bias_add(gate_inputs, self._bias) + output = self._activation(gate_inputs) + output = self._tflite_wrapper.add_output( + output, + tag="output", + name="output", + index_override=1, + aggregate="stack") + return output, output + + def get_config(self): + config = { + "num_units": self._num_units, + "activation": activations.serialize(self._activation), + "reuse": self._reuse, + } + base_config = super(TfLiteRNNCell, self).get_config() + return dict(itertools.chain(base_config.items(), config.items())) diff --git a/tensorflow/lite/experimental/examples/lstm/unidirectional_sequence_rnn_test.py b/tensorflow/lite/experimental/examples/lstm/unidirectional_sequence_rnn_test.py new file mode 100644 index 0000000000..6f9e2dd949 --- /dev/null +++ b/tensorflow/lite/experimental/examples/lstm/unidirectional_sequence_rnn_test.py @@ -0,0 +1,195 @@ +# 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 tempfile +import numpy as np +import tensorflow as tf + +from tensorflow import flags + +from tensorflow.examples.tutorials.mnist import input_data +from tensorflow.lite.experimental.examples.lstm.tflite_rnn import TfLiteRNNCell +from tensorflow.lite.python.op_hint import convert_op_hints_to_stubs +from tensorflow.python.framework import test_util +from tensorflow.python.platform import test +from tensorflow.python.tools import optimize_for_inference_lib + +FLAGS = flags.FLAGS + +# Number of steps to train model. +TRAIN_STEPS = 1 + +CONFIG = tf.ConfigProto(device_count={"GPU": 0}) + + +class UnidirectionalSequenceRnnTest(test_util.TensorFlowTestCase): + + def __init__(self, *args, **kwargs): + super(UnidirectionalSequenceRnnTest, self).__init__(*args, **kwargs) + # Define constants + # Unrolled through 28 time steps + self.time_steps = 28 + # Rows of 28 pixels + self.n_input = 28 + # Learning rate for Adam optimizer + self.learning_rate = 0.001 + # MNIST is meant to be classified in 10 classes(0-9). + self.n_classes = 10 + # Batch size + self.batch_size = 16 + # Rnn Units. + self.num_units = 16 + + def setUp(self): + super(UnidirectionalSequenceRnnTest, self).setUp() + # Import MNIST dataset + data_dir = tempfile.mkdtemp(dir=FLAGS.test_tmpdir) + self.mnist = input_data.read_data_sets(data_dir, one_hot=True) + + def buildRnnLayer(self): + return tf.nn.rnn_cell.MultiRNNCell([ + TfLiteRNNCell(self.num_units, name="rnn1"), + TfLiteRNNCell(self.num_units, name="rnn2") + ]) + + def buildModel(self, rnn_layer): + # Weights and biases for output softmax layer. + out_weights = tf.Variable( + tf.random_normal([self.num_units, self.n_classes])) + out_bias = tf.Variable(tf.random_normal([self.n_classes])) + + # input image placeholder + x = tf.placeholder( + "float", [None, self.time_steps, self.n_input], name="INPUT_IMAGE") + + # x is shaped [batch_size,time_steps,num_inputs] + rnn_input = tf.unstack(x, self.time_steps, 1) + outputs, _ = tf.nn.static_rnn(rnn_layer, rnn_input, dtype="float32") + + # Compute logits by multiplying outputs[-1] of shape [batch_size,num_units] + # by the softmax layer's out_weight of shape [num_units,n_classes] + # plus out_bias + prediction = tf.matmul(outputs[-1], out_weights) + out_bias + output_class = tf.nn.softmax(prediction, name="OUTPUT_CLASS") + + return x, prediction, output_class + + def trainModel(self, x, prediction, output_class, sess): + # input label placeholder + y = tf.placeholder("float", [None, self.n_classes]) + # Loss function + loss = tf.reduce_mean( + tf.nn.softmax_cross_entropy_with_logits(logits=prediction, labels=y)) + # Optimization + opt = tf.train.AdamOptimizer( + learning_rate=self.learning_rate).minimize(loss) + + # Initialize variables + sess.run(tf.global_variables_initializer()) + for _ in range(TRAIN_STEPS): + batch_x, batch_y = self.mnist.train.next_batch( + batch_size=self.batch_size, shuffle=False) + + batch_x = batch_x.reshape((self.batch_size, self.time_steps, + self.n_input)) + sess.run(opt, feed_dict={x: batch_x, y: batch_y}) + + def saveAndRestoreModel(self, rnn_layer, sess, saver): + """Saves and restores the model to mimic the most common use case. + + Args: + rnn_layer: The rnn layer either a single rnn cell or a multi rnn cell. + sess: Old session. + saver: saver created by tf.train.Saver() + + Returns: + A tuple containing: + + - Input tensor of the restored model. + - Prediction tensor of the restored model. + - Output tensor, which is the softwmax result of the prediction tensor. + - new session of the restored model. + + """ + model_dir = tempfile.mkdtemp(dir=FLAGS.test_tmpdir) + saver.save(sess, model_dir) + + # Reset the graph. + tf.reset_default_graph() + x, prediction, output_class = self.buildModel(rnn_layer) + + new_sess = tf.Session(config=CONFIG) + saver = tf.train.Saver() + saver.restore(new_sess, model_dir) + return x, prediction, output_class, new_sess + + def getInferenceResult(self, x, output_class, sess): + b1, _ = self.mnist.train.next_batch(batch_size=1) + sample_input = np.reshape(b1, (1, self.time_steps, self.n_input)) + + expected_output = sess.run(output_class, feed_dict={x: sample_input}) + frozen_graph = tf.graph_util.convert_variables_to_constants( + sess, sess.graph_def, [output_class.op.name]) + return sample_input, expected_output, frozen_graph + + def tfliteInvoke(self, graph, test_inputs, outputs): + tf.reset_default_graph() + # Turn the input into placeholder of shape 1 + tflite_input = tf.placeholder( + "float", [1, self.time_steps, self.n_input], name="INPUT_IMAGE_LITE") + tf.import_graph_def(graph, name="", input_map={"INPUT_IMAGE": tflite_input}) + with tf.Session() as sess: + curr = sess.graph_def + curr = convert_op_hints_to_stubs(graph_def=curr) + + curr = optimize_for_inference_lib.optimize_for_inference( + curr, ["INPUT_IMAGE_LITE"], ["OUTPUT_CLASS"], + [tf.float32.as_datatype_enum]) + + tflite = tf.lite.toco_convert( + curr, [tflite_input], [outputs], allow_custom_ops=False) + interpreter = tf.lite.Interpreter(model_content=tflite) + interpreter.allocate_tensors() + + input_index = interpreter.get_input_details()[0]["index"] + interpreter.set_tensor(input_index, test_inputs) + interpreter.invoke() + output_index = interpreter.get_output_details()[0]["index"] + result = interpreter.get_tensor(output_index) + # Reset all variables so it will not pollute other inferences. + interpreter.reset_all_variables() + return result + + def testStaticRnnMultiRnnCell(self): + sess = tf.Session(config=CONFIG) + + x, prediction, output_class = self.buildModel(self.buildRnnLayer()) + self.trainModel(x, prediction, output_class, sess) + + saver = tf.train.Saver() + x, prediction, output_class, new_sess = self.saveAndRestoreModel( + self.buildRnnLayer(), sess, saver) + + test_inputs, expected_output, frozen_graph = self.getInferenceResult( + x, output_class, new_sess) + + result = self.tfliteInvoke(frozen_graph, test_inputs, output_class) + self.assertTrue(np.allclose(expected_output, result, rtol=1e-6, atol=1e-2)) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/lite/toco/graph_transformations/propagate_array_data_types.cc b/tensorflow/lite/toco/graph_transformations/propagate_array_data_types.cc index 6d9aad66b6..7aec6728da 100644 --- a/tensorflow/lite/toco/graph_transformations/propagate_array_data_types.cc +++ b/tensorflow/lite/toco/graph_transformations/propagate_array_data_types.cc @@ -252,6 +252,12 @@ void SetDataTypeForAllOutputs(Model* model, Operator* op, SetDataTypeForAllOutputs(model, op, data_type); break; } + case OperatorType::kUnidirectionalSequenceRnn: { + const ArrayDataType data_type = model->GetArray(op->inputs[0]).data_type; + if (data_type != ArrayDataType::kFloat) return ::tensorflow::Status::OK(); + SetDataTypeForAllOutputs(model, op, data_type); + break; + } case OperatorType::kUnique: { CHECK_EQ(op->outputs.size(), 2); const UniqueOperator* unique_op = static_cast(op); diff --git a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc index 5185afd22e..1b1780a73b 100644 --- a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc +++ b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc @@ -1109,6 +1109,46 @@ void ProcessUnidirectionalSequenceLstmOperator( output_shape->ReplaceDims({timestamp, batch_size, output_size}); } +void ProcessUnidirectionalSequenceRnnOperator( + Model* model, UnidirectionalSequenceRnnOperator* op) { + auto& output_array = model->GetArray(op->outputs[0]); + if (output_array.has_shape()) { + // Shape already propagated. + return; + } + + if (output_array.data_type == ArrayDataType::kNone) { + // Yield until the output type has been set by PropagateArrayDataTypes + return; + } + + // TODO(renjieliu): check the inputs, as well as all kinds of weights. + const auto& input_array = model->GetArray(op->inputs[0]); + // Yield until input dims have been resolved. + if (!input_array.has_shape()) { + return; + } + const auto& input_shape = input_array.shape(); + const int batch_size = input_shape.dims(1); + const int timestamp = input_shape.dims(0); + + const auto& bias_array = model->GetArray(op->inputs[3]); + // Yield until input dims have been resolved. + if (!bias_array.has_shape()) { + return; + } + + constexpr int kHiddenStateTensor = 4; + // b(115961645): This is a hack to work around. + model->GetArray(op->inputs[kHiddenStateTensor]).buffer.reset(); + + const auto& bias_shape = bias_array.shape(); + const int output_size = bias_shape.dims(0); + + Shape* output_shape = output_array.mutable_shape(); + output_shape->ReplaceDims({timestamp, batch_size, output_size}); +} + void ProcessSpaceToBatchNDOperator(Model* model, SpaceToBatchNDOperator* op) { const auto& input_array = model->GetArray(op->inputs[0]); // Yield until input dims have been resolved. @@ -2037,6 +2077,10 @@ void ProcessUniqueOperator(Model* model, UniqueOperator* op) { ProcessUnidirectionalSequenceLstmOperator( model, static_cast(op)); break; + case OperatorType::kUnidirectionalSequenceRnn: + ProcessUnidirectionalSequenceRnnOperator( + model, static_cast(op)); + break; case OperatorType::kLstmCell: ProcessLstmCellOperator(model, static_cast(op)); break; diff --git a/tensorflow/lite/toco/import_tensorflow.cc b/tensorflow/lite/toco/import_tensorflow.cc index 86e04b2393..dac106b398 100644 --- a/tensorflow/lite/toco/import_tensorflow.cc +++ b/tensorflow/lite/toco/import_tensorflow.cc @@ -2317,6 +2317,27 @@ tensorflow::Status ConvertLeakyReluOperator( return tensorflow::Status::OK(); } +tensorflow::Status ConvertUnidirectionalSequenceRnn( + const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, + Model* model) { + DCHECK_EQ(node.op(), "UnidirectionalSequenceRnn"); + + auto* op = new UnidirectionalSequenceRnnOperator(); + const auto& indices = GetListAttr(node, "_tflite_input_indices"); + if (indices.i_size() != node.input().size()) { + return tensorflow::errors::InvalidArgument("Input size does not match."); + } + + for (const string& input : node.input()) { + op->inputs.push_back(input); + } + // Only use the last one as input. + op->outputs.push_back(node.name() + ":1"); + model->operators.emplace_back(op); + + return tensorflow::Status::OK(); +} + } // namespace namespace internal { @@ -2454,6 +2475,7 @@ ConverterMapType GetTensorFlowNodeConverterMap() { {"Unpack", ConvertUnpackOperator}, {"ZerosLike", ConvertSimpleOperator}, {"UnidirectionalSequenceLstm", ConvertUnidirectionalSequenceLstm}, + {"UnidirectionalSequenceRnn", ConvertUnidirectionalSequenceRnn}, {"MirrorPad", ConvertMirrorPadOperator}, {"Unique", ConvertSimpleOperator}, }); diff --git a/tensorflow/lite/toco/model.h b/tensorflow/lite/toco/model.h index bfa86c8059..296ed9fc74 100644 --- a/tensorflow/lite/toco/model.h +++ b/tensorflow/lite/toco/model.h @@ -158,7 +158,8 @@ enum class OperatorType : uint8 { kLeakyRelu, kAbs, kMirrorPad, - kUnique + kUnique, + kUnidirectionalSequenceRnn }; // Helper to deal with TensorFlow arrays using a different ordering of @@ -1965,6 +1966,13 @@ struct UniqueOperator : Operator { ArrayDataType idx_out_type = ArrayDataType::kInt32; }; +struct UnidirectionalSequenceRnnOperator : Operator { + UnidirectionalSequenceRnnOperator() + : Operator(OperatorType::kUnidirectionalSequenceRnn) {} + bool time_major; + FusedActivationFunctionType fused_activation_function; +}; + // Alloc's are used for transient arrays only. An Alloc specifies which interval // of the "transient_data" workspace buffer passed to inference functions, is to // be used for the transient array at hand. The 'start' and 'end' values are diff --git a/tensorflow/lite/toco/tflite/operator.cc b/tensorflow/lite/toco/tflite/operator.cc index 8eb4d321ef..d9c051ebf7 100644 --- a/tensorflow/lite/toco/tflite/operator.cc +++ b/tensorflow/lite/toco/tflite/operator.cc @@ -1509,6 +1509,39 @@ class Unique : public BuiltinOperator { + public: + using BuiltinOperator::BuiltinOperator; + flatbuffers::Offset WriteOptions( + const TocoOperator& op, + flatbuffers::FlatBufferBuilder* builder) const override { + return ::tflite::CreateSequenceRNNOptions( + *builder, /*time_major=*/true, + /*fused_activation_function=*/ + ::tflite::ActivationFunctionType_TANH); + } + void ReadOptions(const TfLiteOptions& options, + TocoOperator* op) const override { + // Only support tanh actication, so check that tflite type is tanh. + DCHECK(options.fused_activation_function() == + ::tflite::ActivationFunctionType_TANH); + } + + int GetVersion(const OperatorSignature& op_signature) const override { + return 1; + } + + std::vector GetMutatingInputVariables( + const Operator& op) const override { + std::vector mutating_input_variables(op.inputs.size(), false); + mutating_input_variables[4] = true; + return mutating_input_variables; + } +}; + std::unique_ptr WriteFlexOpOptions( const string& tensorflow_node_def) { auto fbb = absl::make_unique(); @@ -1847,6 +1880,9 @@ std::vector> BuildOperatorList( OperatorType::kMirrorPad)); ops.push_back(MakeUnique(::tflite::BuiltinOperator_UNIQUE, OperatorType::kUnique)); + ops.push_back(MakeUnique( + ::tflite::BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_RNN, + OperatorType::kUnidirectionalSequenceRnn)); // Custom Operators. ops.push_back( diff --git a/tensorflow/lite/toco/tooling_util.cc b/tensorflow/lite/toco/tooling_util.cc index 2396de1a3d..9c0e8067d8 100644 --- a/tensorflow/lite/toco/tooling_util.cc +++ b/tensorflow/lite/toco/tooling_util.cc @@ -417,6 +417,7 @@ const char* OperatorTypeName(OperatorType type) { HANDLE_OPERATORTYPENAME_CASE(SquaredDifference) HANDLE_OPERATORTYPENAME_CASE(MirrorPad) HANDLE_OPERATORTYPENAME_CASE(Unique) + HANDLE_OPERATORTYPENAME_CASE(UnidirectionalSequenceRnn) default: LOG(FATAL) << "Unhandled op type"; #undef HANDLE_OPERATORTYPENAME_CASE -- GitLab From 72c2e3562f7b393d414c57fd87cc32c8a7370b3b Mon Sep 17 00:00:00 2001 From: AG Ramesh Date: Wed, 16 Jan 2019 08:20:12 -0800 Subject: [PATCH 0779/2345] Fix for compilation error when compiling with --config=mkl --- tensorflow/core/kernels/eigen_spatial_convolutions_test.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc b/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc index 03002adec4..14c6903a43 100644 --- a/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc +++ b/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc @@ -1605,6 +1605,7 @@ static void PackLhsHelper(int iters, /*inner_dim_reordered*/ false, // /*Alignment*/ 0>; + using Traits = typename Eigen::internal::gebp_traits; #if defined(TENSORFLOW_USE_MKLDNN_CONTRACTION_KERNEL) using PackLhsImpl = Eigen::internal::mkldnn_gemm_pack; @@ -1697,9 +1698,9 @@ static void PackLhsHelper(int iters, SubMapper sub_mapper = input_mappers[filter_idx].getSubMapper(row_offset, col_offset); - // NOTE: Eigen gemm_pack_lhs accepts contraction depth (k-th dimension) as a - // first argument (aka block cols). MKL-DNN pack is generic for lhs and rhs - // and accepts block rows and cols in the same order for lhs and rhs. +// NOTE: Eigen gemm_pack_lhs accepts contraction depth (k-th dimension) as a +// first argument (aka block cols). MKL-DNN pack is generic for lhs and rhs +// and accepts block rows and cols in the same order for lhs and rhs. #if defined(TENSORFLOW_USE_MKLDNN_CONTRACTION_KERNEL) pack_lhs(packed.data() + packed_offset, sub_mapper, rows, cols); #else -- GitLab From d122a13913a1d058cb872b9c443ecbfef8c8bc8d Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Wed, 16 Jan 2019 08:23:59 -0800 Subject: [PATCH 0780/2345] Fix for ResourceVariables and variants when using while_v2 + GradientTape. PiperOrigin-RevId: 229561249 --- tensorflow/python/kernel_tests/BUILD | 1 + .../python/kernel_tests/while_v2_test.py | 18 ++++++ tensorflow/python/ops/while_v2.py | 59 ++++++++++++++----- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index 7a514875d6..40bf4aedd0 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -3538,6 +3538,7 @@ cuda_py_test( "@absl_py//absl/testing:parameterized", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", + "//tensorflow/python/eager:def_function", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:control_flow_ops", diff --git a/tensorflow/python/kernel_tests/while_v2_test.py b/tensorflow/python/kernel_tests/while_v2_test.py index 1f2c6f94c5..061d787760 100644 --- a/tensorflow/python/kernel_tests/while_v2_test.py +++ b/tensorflow/python/kernel_tests/while_v2_test.py @@ -23,6 +23,8 @@ from absl.testing import parameterized from tensorflow.core.protobuf import config_pb2 from tensorflow.core.protobuf import rewriter_config_pb2 from tensorflow.python.eager import backprop +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 meta_graph @@ -34,6 +36,7 @@ from tensorflow.python.ops import functional_ops from tensorflow.python.ops import gradients_impl from tensorflow.python.ops import list_ops from tensorflow.python.ops import math_ops +from tensorflow.python.ops import variables from tensorflow.python.ops import while_v2 from tensorflow.python.ops.control_flow_ops import while_loop as while_loop_v1 from tensorflow.python.ops.while_v2 import while_loop as while_loop_v2 @@ -65,6 +68,21 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): self.assertEqual(16., eval_result[0]) self.assertSequenceEqual(sess.run(grad), [32.]) + def testGradientTapeResourceVariable(self): + with context.eager_mode(): + v = variables.Variable(1.) + + @def_function.function + def fnWithLoop(): # pylint: disable=invalid-name + with backprop.GradientTape() as tape: + _, x = while_loop_v2( + lambda i, _: i < 2, + lambda i, x: (i + 1, x * v), + [0, 2.]) + return tape.gradient(x, v) + + self.assertAllEqual(fnWithLoop(), 4.0) + @test_util.run_deprecated_v1 def testMultipleLoopVarsBasic(self): x = constant_op.constant(5.) diff --git a/tensorflow/python/ops/while_v2.py b/tensorflow/python/ops/while_v2.py index 50011e34b1..0e427d3c6a 100644 --- a/tensorflow/python/ops/while_v2.py +++ b/tensorflow/python/ops/while_v2.py @@ -38,6 +38,7 @@ from tensorflow.python.ops import control_flow_util from tensorflow.python.ops import control_flow_util_v2 as util from tensorflow.python.ops import custom_gradient from tensorflow.python.ops import gen_functional_ops +from tensorflow.python.ops import gen_resource_variable_ops from tensorflow.python.ops import gradients_impl from tensorflow.python.ops import list_ops from tensorflow.python.ops import math_ops @@ -263,20 +264,9 @@ def _WhileGrad(op, *grads): # pylint: disable=invalid-name assert not _is_in_xla_context() or maximum_iterations is not None maximum_iterations = _validate_and_convert_to_tensor(maximum_iterations) - # Set the incoming gradient of non-trainable inputs to None. It is possible - # that we receive non-None gradients for non-trainable types in nested while - # loops because we accumulate outputs of the inner while as variant tensors - # which are trainable and hence receive zeros_like tensors in the gradient - # pass. The non-trainable tensors then receive the popped zeros tensor from - # this zeros variant. The gradient for the loop vars corresponding to these - # tensors is None or zeros (this happens only if the loop var is accumulated - # as well) in _grad_fn so we reset these. - # TODO(b/118712257): Remove the IsTrainable filter once we can handle None - # output grads in _grad_fn. - grads = [ - None if not _is_trainable(output) else grad - for grad, output in zip(grads, body_graph.outputs) - ] + grads = [_preprocess_grad(grad, body_out, while_out) + for grad, body_out, while_out + in zip(grads, body_graph.outputs, while_op.outputs)] # We compute the gradient for the sub-graph between trainable ys and xs # with non-None incoming gradients. We later pad the None's to the list of @@ -352,6 +342,47 @@ def _WhileGrad(op, *grads): # pylint: disable=invalid-name return none_padded_outputs +def _preprocess_grad(grad, body_graph_output, while_op_output): + """Returns the initial gradient to be used for a given output tensor. + + Args: + grad: the original gradient Tensor passed to the gradient function. + body_graph_output: the corresponding Tensor in the body graph. + while_op_output: the corresponding Tensor output of the While op. + + Returns: + A Tensor or None. + """ + # Set the incoming gradient of non-trainable inputs to None. It is possible + # that we receive non-None gradients for non-trainable types in nested while + # loops because we accumulate outputs of the inner while as variant tensors + # which are trainable and hence receive zeros_like tensors in the gradient + # pass. The non-trainable tensors then receive the popped zeros tensor from + # this zeros variant. The gradient for the loop vars corresponding to these + # tensors is None or zeros (this happens only if the loop var is accumulated + # as well) in _grad_fn so we reset these. + # TODO(b/118712257): Remove once we can handle None output grads in _grad_fn. + if not _is_trainable(body_graph_output): + return None + + # GradientTape initializes resource and variant grads as None instead of + # zeros. Set to zeros so _GradientsHelper computes the gradients instead of + # returning None. + if (while_op_output.dtype in (dtypes.resource, dtypes.variant) + and grad is None): + return _zeros_like(while_op_output) + + return grad + + +def _zeros_like(op_output): + """Like array_ops.zeros_like() but also accepts resource var handles.""" + if op_output.dtype == dtypes.resource: + return array_ops.zeros( + gen_resource_variable_ops.variable_shape(op_output)) + return array_ops.zeros_like(op_output) + + def _is_trainable(tensor): """Returns whether the given tensor is trainable.""" if not gradients_impl.IsTrainable(tensor): -- GitLab From e74388ec6d1e8aeadd7aaa787544f319175dd2a6 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 16 Jan 2019 17:15:17 +0000 Subject: [PATCH 0781/2345] Fix softmax_cross_entropy issue This fix is from 24397/24736 and all credit goes to @felix-schneider This fix fixes 24397. This fix fixes 24736. Signed-off-by: Yong Tang --- tensorflow/python/ops/losses/losses_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/losses/losses_impl.py b/tensorflow/python/ops/losses/losses_impl.py index 7f88ccd879..3393e75335 100644 --- a/tensorflow/python/ops/losses/losses_impl.py +++ b/tensorflow/python/ops/losses/losses_impl.py @@ -794,7 +794,7 @@ def softmax_cross_entropy( if label_smoothing > 0: num_classes = math_ops.cast( - array_ops.shape(onehot_labels)[1], logits.dtype) + array_ops.shape(onehot_labels)[-1], logits.dtype) smooth_positives = 1.0 - label_smoothing smooth_negatives = label_smoothing / num_classes onehot_labels = onehot_labels * smooth_positives + smooth_negatives -- GitLab From 59e9acf78787ee2e46106672533aaff8032f69b0 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Wed, 16 Jan 2019 09:20:44 -0800 Subject: [PATCH 0782/2345] Bump the googletest dependency version for TF PiperOrigin-RevId: 229569726 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 7eb1d8a3c6..354148f034 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -408,11 +408,11 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "com_google_googletest", - sha256 = "61eee610f136c1edc693d979647a4bb2ca253d60e6964724b61af85d32a41251", - strip_prefix = "googletest-6729a1361150131bc5d394d5cd2b4cdf0953ee7b", + sha256 = "ff7a82736e158c077e76188232eac77913a15dac0b22508c390ab3f88e6d6d86", + strip_prefix = "googletest-b6cd405286ed8635ece71c72f118e659f4ade3fb", urls = [ - "https://mirror.bazel.build/github.com/google/googletest/archive/6729a1361150131bc5d394d5cd2b4cdf0953ee7b.zip", - "https://github.com/google/googletest/archive/6729a1361150131bc5d394d5cd2b4cdf0953ee7b.zip", + "https://mirror.bazel.build/github.com/google/googletest/archive/b6cd405286ed8635ece71c72f118e659f4ade3fb.zip", + "https://github.com/google/googletest/archive/b6cd405286ed8635ece71c72f118e659f4ade3fb.zip", ], ) -- GitLab From 899f788738014fe438105fe30100e768b5f7900c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 09:45:13 -0800 Subject: [PATCH 0783/2345] Add a missing explicit dependency between :nn_ops_test (which references the AvgPool op) and :pooling_ops. PiperOrigin-RevId: 229573584 --- tensorflow/core/kernels/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 374aa88d2d..96536e6945 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -4011,6 +4011,7 @@ tf_cuda_cc_test( ":nn", ":ops_testutil", ":ops_util", + ":pooling_ops", "//tensorflow/cc:cc_ops", "//tensorflow/cc:cc_ops_internal", "//tensorflow/core:core_cpu", -- GitLab From 9728b12bf7a2e4cf4010a9baaee5626fd2bd8140 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 09:53:50 -0800 Subject: [PATCH 0784/2345] Update Google Cloud Bigtable C++ Client to the v0.5.0 release. PiperOrigin-RevId: 229574976 --- .../kernels/test_kernels/bigtable_test_client.cc | 11 +++++++++++ .../kernels/test_kernels/bigtable_test_client.h | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/tensorflow/contrib/bigtable/kernels/test_kernels/bigtable_test_client.cc b/tensorflow/contrib/bigtable/kernels/test_kernels/bigtable_test_client.cc index c04d5578ee..e6fda9e617 100644 --- a/tensorflow/contrib/bigtable/kernels/test_kernels/bigtable_test_client.cc +++ b/tensorflow/contrib/bigtable/kernels/test_kernels/bigtable_test_client.cc @@ -410,6 +410,17 @@ BigtableTestClient::AsyncCheckAndMutateRow( return nullptr; } +std::unique_ptr< + grpc::ClientAsyncReaderInterface> +BigtableTestClient::AsyncReadRows( + grpc::ClientContext* context, + const google::bigtable::v2::ReadRowsRequest& request, + grpc::CompletionQueue* cq, void* tag) { + LOG(WARNING) << "Call to InMemoryDataClient::" << __func__ + << "(); this will likely cause a crash!"; + return nullptr; +} + std::shared_ptr BigtableTestClient::Channel() { LOG(WARNING) << "Call to InMemoryDataClient::Channel(); this will likely " "cause a crash!"; diff --git a/tensorflow/contrib/bigtable/kernels/test_kernels/bigtable_test_client.h b/tensorflow/contrib/bigtable/kernels/test_kernels/bigtable_test_client.h index 8570590457..8e1326f2ce 100644 --- a/tensorflow/contrib/bigtable/kernels/test_kernels/bigtable_test_client.h +++ b/tensorflow/contrib/bigtable/kernels/test_kernels/bigtable_test_client.h @@ -87,6 +87,12 @@ class BigtableTestClient : public ::google::cloud::bigtable::DataClient { const google::bigtable::v2::CheckAndMutateRowRequest& request, grpc::CompletionQueue* cq) override; + std::unique_ptr< + grpc::ClientAsyncReaderInterface> + AsyncReadRows(grpc::ClientContext* context, + const google::bigtable::v2::ReadRowsRequest& request, + grpc::CompletionQueue* cq, void* tag) override; + std::shared_ptr Channel() override; private: -- GitLab From 3a2741544dc6aaf0abfe47d8fdd35d710dc7ffb0 Mon Sep 17 00:00:00 2001 From: Billy Lamberta Date: Wed, 16 Jan 2019 10:00:19 -0800 Subject: [PATCH 0785/2345] Publish /lite PiperOrigin-RevId: 229576111 --- tensorflow/lite/g3doc/custom_operators.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/g3doc/custom_operators.md b/tensorflow/lite/g3doc/custom_operators.md index 18202cbfb9..2d80668f37 100644 --- a/tensorflow/lite/g3doc/custom_operators.md +++ b/tensorflow/lite/g3doc/custom_operators.md @@ -137,9 +137,9 @@ operations instead of a single operator. ## Special TF Graph Attributes -When Toco converts a TF graph into TFLite format, it makes some assumption about -custom operations that might be not correct. In this case, the generated graph -can be not executable. +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 graph before it is converted. The following attributes are supported: -- GitLab From ef5d41d827b4130cc3cedf08454e54ce1340f6eb Mon Sep 17 00:00:00 2001 From: Vojtech Bardiovsky Date: Wed, 16 Jan 2019 10:11:41 -0800 Subject: [PATCH 0786/2345] Refactoring: Get signature directly from concrete function. We can do this now, because signature is stored on the function, and it will clean the list_all_concrete_functions return value. PiperOrigin-RevId: 229578645 --- tensorflow/python/eager/def_function.py | 51 ++++++++----------- tensorflow/python/eager/def_function_test.py | 9 +++- .../saved_model/function_serialization.py | 7 ++- tensorflow/python/saved_model/load_test.py | 4 +- tensorflow/python/saved_model/save.py | 2 +- 5 files changed, 36 insertions(+), 37 deletions(-) diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 977f548b61..aef8f9ccda 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -447,50 +447,41 @@ class PolymorphicFunction(object): return initialize_variables.get_concrete_function() def _list_all_concrete_functions_for_serialization(self): - """Returns all of the concrete functions. + """Returns all concrete functions for serialization. Returns: - A list of tuples in the form (signature, concrete_function), where - concrete function is an instance of `Function`. + A list of instances of `Function`. """ - input_signature = self._input_signature - if input_signature is not None: + if self._input_signature is not None: self.get_concrete_function() concrete_functions = [] - for signature in self._cached_input_signatures: - flattened = nest.flatten(signature) - if any( - isinstance(arg, func_graph_module.UnknownArgument) - for arg in flattened): - logging.info("Unsupported signature for serialization: %s.", signature) - continue - concrete_function = self.get_concrete_function(*signature) - concrete_functions.append((signature, concrete_function)) - return concrete_functions - - @property - def _cached_input_signatures(self): - """All input signatures used to call this PolymorphicFunction.""" - seen = list() - # We are using a list so that: - # - the returned collection is deterministic, and - # - we can use a custom equality operator (is_same_structure). - # This is run only at serialization time on likely very small inputs so we - # are not concerned about O(n^2) runtime. # pylint: disable=protected-access - concrete_functions = [] if self._stateful_fn: concrete_functions.extend(self._stateful_fn._function_cache.values()) if self._stateless_fn: concrete_functions.extend(self._stateless_fn._function_cache.values()) + # pylint: enable=protected-access + deduplicated_concrete_functions = list() + seen_signatures = list() + # We are using a list so that: + # - the returned collection is deterministic, and + # - we can use a custom equality operator (is_same_structure). + # This is run only at serialization time on likely very small inputs so we + # are not concerned about O(n^2) runtime. for concrete_function in concrete_functions: signature, _ = concrete_function.structured_input_signature + flattened = nest.flatten(signature) + if any( + isinstance(arg, func_graph_module.UnknownArgument) + for arg in flattened): + logging.info("Unsupported signature for serialization: %s.", signature) + continue equal_to_signature = functools.partial( function_lib.is_same_structure, signature, check_values=True) - if not any(equal_to_signature(s) for s in seen): - yield signature - seen.append(signature) - # pylint: enable=protected-access + if not any(equal_to_signature(s) for s in seen_signatures): + deduplicated_concrete_functions.append(concrete_function) + seen_signatures.append(signature) + return deduplicated_concrete_functions def get_concrete_function(self, *args, **kwargs): """Returns a `Function` object specialized to inputs and execution context. diff --git a/tensorflow/python/eager/def_function_test.py b/tensorflow/python/eager/def_function_test.py index 77cc8ee981..256ef511a3 100644 --- a/tensorflow/python/eager/def_function_test.py +++ b/tensorflow/python/eager/def_function_test.py @@ -247,7 +247,7 @@ class DefFunctionTest(test.TestCase): concrete = compute.get_concrete_function( tensor_spec.TensorSpec(None, dtypes.float32)) self.assertAllClose(4., concrete(constant_op.constant(2.))) - input_signature, = compute._cached_input_signatures + input_signature, _ = concrete.structured_input_signature self.assertEqual( tuple(input_signature), (tensor_spec.TensorSpec(None, dtypes.float32),)) @@ -260,8 +260,13 @@ class DefFunctionTest(test.TestCase): f(constant_op.constant([[3., 4.]]), constant_op.constant([2.])) f(constant_op.constant([[3, 4, 5]]), constant_op.constant([2])) + + concrete_functions = f._list_all_concrete_functions_for_serialization() + signatures_for_serialization = [ + c.structured_input_signature[0] for c in concrete_functions + ] self.assertEqual( - set(f._cached_input_signatures), + set(signatures_for_serialization), set(((tensor_spec.TensorSpec([1, 2], dtypes.float32), tensor_spec.TensorSpec([1], dtypes.float32)), (tensor_spec.TensorSpec([1, 3], dtypes.int32), diff --git a/tensorflow/python/saved_model/function_serialization.py b/tensorflow/python/saved_model/function_serialization.py index 8e8e3bc856..6496b2b2a4 100644 --- a/tensorflow/python/saved_model/function_serialization.py +++ b/tensorflow/python/saved_model/function_serialization.py @@ -48,7 +48,7 @@ def serialize_polymorphic_function(polymorphic_function, node_ids): proto.function_spec.CopyFrom(function_spec_proto) all_concrete_functions = \ polymorphic_function._list_all_concrete_functions_for_serialization() # pylint: disable=protected-access - for signature, concrete_function in all_concrete_functions: + for concrete_function in all_concrete_functions: bound_inputs = [] try: for capture in concrete_function.captured_inputs: @@ -60,10 +60,13 @@ def serialize_polymorphic_function(polymorphic_function, node_ids): "captures tensor %s which is unsupported or not reachable from root.", concrete_function.name, capture) continue + signature_args, signature_kwargs = \ + concrete_function.structured_input_signature + del signature_kwargs function_proto = proto.monomorphic_function.add() function_proto.concrete_function = concrete_function.name function_proto.canonicalized_input_signature.CopyFrom( - coder.encode_structure(signature)) + coder.encode_structure(signature_args)) structured_outputs = func_graph_module.convert_structure_to_signature( concrete_function.structured_outputs) function_proto.output_signature.CopyFrom( diff --git a/tensorflow/python/saved_model/load_test.py b/tensorflow/python/saved_model/load_test.py index 9eea170bf0..c8c8f6e1b3 100644 --- a/tensorflow/python/saved_model/load_test.py +++ b/tensorflow/python/saved_model/load_test.py @@ -401,8 +401,8 @@ class LoadTest(test.TestCase): self.assertAllEqual([2], root.f(constant_op.constant([1])).numpy()) self.assertAllEqual([2, 4], root.f(constant_op.constant([1, 2])).numpy()) - concrete_functions = root.f._list_all_concrete_functions_for_serialization() - self.assertEqual(1, len(concrete_functions)) # pylint: disable=protected-access + concrete_functions = root.f._list_all_concrete_functions_for_serialization() # pylint: disable=protected-access + self.assertEqual(1, len(concrete_functions)) imported = self.cycle(root) diff --git a/tensorflow/python/saved_model/save.py b/tensorflow/python/saved_model/save.py index d383bbecfc..ef3f67bbce 100644 --- a/tensorflow/python/saved_model/save.py +++ b/tensorflow/python/saved_model/save.py @@ -560,7 +560,7 @@ def _fill_meta_graph_def(meta_graph_def, saveable_view, signature_functions, with exported_graph.as_default(): signatures = _generate_signatures(signature_functions, resource_map) - for _, concrete_function in concrete_functions: + for concrete_function in concrete_functions: concrete_function.add_to_graph() saver_def = saver.to_proto() meta_graph_def.saver_def.CopyFrom(saver_def) -- GitLab From 7226c27f4d0161954ee36b0fcb32275a2942d5bc Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Wed, 16 Jan 2019 10:13:48 -0800 Subject: [PATCH 0787/2345] Use new _ property for distribute strategy on resource variable. PiperOrigin-RevId: 229579047 --- .../distribute/python/mirrored_strategy_multigpu_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py index 59d711ae01..6727c7d703 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py @@ -267,7 +267,7 @@ class MirroredStrategyVariableCreationTest(test.TestCase): self.assertIs(strategy, var.distribute_strategy) for d in var.devices: self.assertEqual(d, var.get(d).device) - self.assertIs(strategy, var.get(d).distribute_strategy) + self.assertIs(strategy, var.get(d)._distribute_strategy) # pylint: disable=protected-access def testVariableInFuncGraph(self, distribution): def model_fn(): -- GitLab From 352525990469da6694abaa4e70edfdbb287dd984 Mon Sep 17 00:00:00 2001 From: Billy Lamberta Date: Wed, 16 Jan 2019 10:14:45 -0800 Subject: [PATCH 0788/2345] Add tutorial vid links. PiperOrigin-RevId: 229579217 --- tensorflow/lite/g3doc/performance/gpu.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tensorflow/lite/g3doc/performance/gpu.md b/tensorflow/lite/g3doc/performance/gpu.md index 14ca54a158..c738922612 100644 --- a/tensorflow/lite/g3doc/performance/gpu.md +++ b/tensorflow/lite/g3doc/performance/gpu.md @@ -24,6 +24,9 @@ The easiest way to try out the experimental GPU delegate is to follow the below ### Android (with Android Studio) +For a step-by-step tutorial, watch the +[Experimental GPU Delegate for Android](https://youtu.be/Xkhgre8r5G0) video. + Note: This requires OpenGL ES 3.1 or higher. #### Step 1. Clone the TensorFlow source code and open it in Android Studio @@ -55,6 +58,9 @@ run on the GPU. ### iOS (with XCode) +For a step-by-step tutorial, watch the +[Experimental GPU Delegate for iOS](https://youtu.be/a5H4Zwjp49c) video. + Note: This requires XCode v10.1 or later. #### Step 1. Get the demo source code and make sure it compiles. -- GitLab From ff12131917fb3f9c789063e3a78e1d8a41e49776 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 10:17:33 -0800 Subject: [PATCH 0789/2345] Automated rollback of commit 8154ec0b4a893657e48b838c06e1f988aeac4115 PiperOrigin-RevId: 229579722 --- .../process_function_library_runtime.cc | 10 +- tensorflow/core/grappler/optimizers/BUILD | 1 - .../grappler/optimizers/function_optimizer.cc | 74 +++++------ .../optimizers/function_optimizer_test.cc | 122 ------------------ 4 files changed, 38 insertions(+), 169 deletions(-) diff --git a/tensorflow/core/common_runtime/process_function_library_runtime.cc b/tensorflow/core/common_runtime/process_function_library_runtime.cc index 76c75ad3d2..b236343a0f 100644 --- a/tensorflow/core/common_runtime/process_function_library_runtime.cc +++ b/tensorflow/core/common_runtime/process_function_library_runtime.cc @@ -568,12 +568,15 @@ Status ProcessFunctionLibraryRuntime::InstantiateMultiDevice( DumpGraph("Before running POST_PLACEMENT passes", graph.get()); TF_RETURN_IF_ERROR(OptimizationPassRegistry::Global()->RunGrouping( OptimizationPassRegistry::POST_PLACEMENT, optimization_options)); + DumpGraph("Before running POST_REWRITE_FOR_EXEC passes", graph.get()); + TF_RETURN_IF_ERROR(OptimizationPassRegistry::Global()->RunGrouping( + OptimizationPassRegistry::POST_REWRITE_FOR_EXEC, optimization_options)); + DumpGraph("After all optimization passes", graph.get()); Device* cpu_device; TF_RETURN_IF_ERROR(device_mgr_->LookupDevice("CPU:0", &cpu_device)); if (options.optimize_graph_fn) { - DumpGraph("Before running graph optimization fn", graph.get()); Status status = options.optimize_graph_fn(std::move(ret_node_names), &data->overlay_lib_, device_set, cpu_device, &graph); @@ -584,11 +587,6 @@ Status ProcessFunctionLibraryRuntime::InstantiateMultiDevice( DumpGraph("After optimization", graph.get()); } - DumpGraph("Before running POST_REWRITE_FOR_EXEC passes", graph.get()); - TF_RETURN_IF_ERROR(OptimizationPassRegistry::Global()->RunGrouping( - OptimizationPassRegistry::POST_REWRITE_FOR_EXEC, optimization_options)); - DumpGraph("After all optimization passes", graph.get()); - std::unordered_map> subgraphs; TF_RETURN_IF_ERROR( PartitionFunctionGraph(device_set, std::move(graph), &subgraphs)); diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 70e0f226fc..ee216f80f9 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -181,7 +181,6 @@ tf_cuda_cc_test( "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler:utils", "//tensorflow/core/grappler/utils:grappler_test", - "@com_google_absl//absl/algorithm:container", ], ) diff --git a/tensorflow/core/grappler/optimizers/function_optimizer.cc b/tensorflow/core/grappler/optimizers/function_optimizer.cc index 794e87dc38..bd69405b1a 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer.cc @@ -27,8 +27,6 @@ limitations under the License. #include "tensorflow/core/common_runtime/device_mgr.h" #include "tensorflow/core/common_runtime/device_set.h" #include "tensorflow/core/common_runtime/function.h" -#include "tensorflow/core/common_runtime/lower_if_while.h" -#include "tensorflow/core/common_runtime/optimization_registry.h" #include "tensorflow/core/common_runtime/placer.h" #include "tensorflow/core/common_runtime/process_function_library_runtime.h" #include "tensorflow/core/framework/attr_value_util.h" @@ -1367,6 +1365,20 @@ Status IsInlinableIndirectFunctionCall(const FunctionOptimizerContext& ctx, SummarizeNodeDef(func_node)); } + // TODO(b/120991525, b/120986912): We need to lower `If` and `While` nodes to + // `Switch` nodes after function inlining (one more PRE_PLACEMENT pass?), but + // because of the reason described above we are not sure that it's safe, for + // now just disable inlining functions with functional control flow. + const auto is_functional_ctrl_flow_op = [](const NodeDef& node) { + return IsIf(node) || IsWhile(node); + }; + if (absl::c_any_of(func.node_def(), is_functional_ctrl_flow_op)) { + return errors::FailedPrecondition( + "Can't inline function with `If` or `While` nodes in the function " + "body: ", + SummarizeNodeDef(func_node)); + } + return Status::OK(); } @@ -1476,46 +1488,22 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, const string prefix = strings::StrCat(func_node.name(), "/"); - // ------------------------------------------------------------------------ // - // Grappler receives the graph after PRE_PLACEMENT, Placer, and POST_PLACEMENT - // passes, so each node has a valid device assignment. Also V2 control - // flow ops (functional If and While) lowered to V1 control flow (Switch and - // Merge nodes). To keep graph valid for execution we must assign device to - // every inlined graph node, and also lower the control flow. - - // Control flow lowering and Placer works with a Graph object. - std::unique_ptr func_body_graph = - absl::make_unique(ctx->function_library()); - - GraphConstructorOptions opts; - TF_RETURN_IF_ERROR( - ConvertGraphDefToGraph(opts, item.graph, func_body_graph.get())); - - GraphOptimizationPassOptions opt_options; - opt_options.graph = &func_body_graph; - opt_options.flib_def = ctx->mutable_function_library(); - - // TODO(ezhulenev): Should we run full PRE_PLACEMENT pass here? And - // POST_PLACEMENT after placer? - LowerIfWhilePass pass; - TF_RETURN_IF_ERROR(pass.Run(opt_options)); - // ------------------------------------------------------------------------ // // Before placing the function body nodes we pin input placeholders to the // same device as their corresponding input nodes. - for (Node* func_body_node : func_body_graph->nodes()) { + for (NodeDef& func_body_node : *item.graph.mutable_node()) { const auto input_placeholder_idx = - input_placeholders_idx.find(func_body_node->name()); + input_placeholders_idx.find(func_body_node.name()); if (input_placeholder_idx != input_placeholders_idx.end()) { const int input_idx = input_placeholder_idx->second; const GraphView::OutputPort output_port = ctx->graph_view().GetRegularFanin({&func_node, input_idx}); - VLOG(3) << "Pin inlined function input node '" << func_body_node->name() + VLOG(3) << "Pin inlined function input node '" << func_body_node.name() << "' to the '" << output_port.node->device() << "' device."; - func_body_node->set_requested_device(output_port.node->device()); + func_body_node.set_device(output_port.node->device()); } } @@ -1523,6 +1511,8 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // After placing nodes corresponding to the function inputs, we need to assign // device placements to all other function body nodes. + GraphDef placed_graph_def; + const DeviceSet* devices = ctx->devices(); if (devices->devices().empty()) { @@ -1532,8 +1522,9 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // of a batch job for graph analysis/optimization. VLOG(3) << "Assign function call node device to all function body nodes. " << "Device: " << func_node.device(); - for (Node* func_body_node : func_body_graph->nodes()) { - func_body_node->set_requested_device(func_node.device()); + placed_graph_def = item.mutable_function_body(); + for (NodeDef& node : *placed_graph_def.mutable_node()) { + node.set_device(func_node.device()); } } else { // If we are running in an active runtime session, Grappler will get the @@ -1545,20 +1536,23 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, [](string* out, const Device* d) { out->append(d->name()); }) << "]"; + // Construct a Graph object from the instantiated function body. + GraphConstructorOptions opts; + Graph graph(ctx->function_library()); + TF_RETURN_IF_ERROR( + ConvertGraphDefToGraph(opts, item.function_body(), &graph)); + // Use function caller node device as a default for placer. const Device* default_device = devices->FindDeviceByName(func_node.device()); - Placer placer(func_body_graph.get(), devices, - nullptr /* No session options */, default_device); + Placer placer(&graph, devices, nullptr, /* No session options */ + default_device); TF_RETURN_IF_ERROR(placer.Run()); - } - - // TODO(ezhulenev): Should we run POST_PLACEMENT runtime pass here? - // All following transformations will work with GraphDef. - GraphDef placed_graph_def; - func_body_graph->ToGraphDef(&placed_graph_def); + // Convert Graph back to the GraphDef. + graph.ToGraphDef(&placed_graph_def); + } // ------------------------------------------------------------------------ // // After all nodes placed we need to prepare them for inlining into the diff --git a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc index f4752bd584..827b0658c5 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc @@ -14,8 +14,6 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/grappler/optimizers/function_optimizer.h" - -#include "absl/algorithm/container.h" #include "tensorflow/cc/ops/functional_ops.h" #include "tensorflow/cc/ops/standard_ops.h" #include "tensorflow/core/framework/function_testlib.h" @@ -1184,126 +1182,6 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithMergedDeadTensors) { test::ExpectTensorEqual(tensors[0], tensors_expected[0]); } -TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithFunctionalControlFlow) { - using test::function::NDef; - using FDH = FunctionDefHelper; - - FunctionOptimizer optimizer(RewriterConfig::AGGRESSIVE); - - FunctionDef add_func = FunctionDefHelper::Create( - "MyAdd", {"x:T", "y:T"}, {"z:T"}, {"T: {float, double}"}, - {{{"add"}, "Add", {"x", "y"}, {{"T", "$T"}}}}, - /* Mapping between function returns and function node outputs. */ - {{"z", "add:z:0"}}); - - FunctionDef mul_func = FunctionDefHelper::Create( - "MyMul", {"x:T", "y:T"}, {"z:T"}, {"T: {float, double}"}, - {{{"mul"}, "Mul", {"x", "y"}, {{"T", "$T"}}}}, - /* Mapping between function returns and function node outputs. */ - {{"z", "mul:z:0"}}); - - // Compute: return cond ? a + b : a * b - FunctionDef add_or_mul_func = FunctionDefHelper::Create( - "AddOrMul", {"cond:bool", "x:float", "y:float"}, {"z:float"}, {}, - { - {{"if_node"}, - "If", - {"cond", "x", "y"}, - { - {"Tcond", DT_BOOL}, - {"Tin", DataTypeSlice{DT_FLOAT, DT_FLOAT}}, - {"Tout", DataTypeSlice{DT_FLOAT}}, - {"then_branch", FDH::FunctionRef("MyAdd", {{"T", DT_FLOAT}})}, - {"else_branch", FDH::FunctionRef("MyMul", {{"T", DT_FLOAT}})}, - {"_lower_using_switch_merge", true}, - }}, - }, - /* Mapping between function returns and function node outputs. */ - {{"z", "if_node:output:0"}}); - - // Build a computation graph for: - // is_add: bool - // a: float - // b: float - // c = AddOrMul(is_add, a, b) # is_add ? a + b : a * b - // d = Identity(c) - // return d - - // c = MyMul(a, b) - GrapplerItem item; - item.fetch = {"d"}; - item.graph = test::function::GDef( - {NDef("is_add", "Placeholder", {}, {{"dtype", DT_BOOL}}, kDevice), - NDef("a", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), - NDef("b", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), - - NDef("c", "PartitionedCall", {"is_add", "a", "b"}, - {{"Tin", DataTypeSlice{DT_BOOL, DT_FLOAT, DT_FLOAT}}, - {"Tout", DataTypeSlice{DT_FLOAT}}, - {"f", FDH::FunctionRef("AddOrMul")}}, - kDevice), - - NDef("d", "Identity", {"c"}, {{"T", DT_FLOAT}}, kDevice)}, - // Function library. - {add_or_mul_func, add_func, mul_func}); - - GraphDef optimized_graph; - TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &optimized_graph)); - - const auto count_nodes_with_op = [&](const string& op) { - return absl::c_count_if(optimized_graph.node(), [&](const NodeDef& node) { - return node.op() == op; - }); - }; - - // All `PartitionedCall` nodes in the optimized graph must be inlined, and - // `If` node must be lowered to `Switch` and `Merge` nodes. - EXPECT_EQ(count_nodes_with_op("PartitionedCallOp"), 0); - EXPECT_EQ(count_nodes_with_op("If"), 0); - EXPECT_EQ(count_nodes_with_op("Switch"), 3); - EXPECT_EQ(count_nodes_with_op("Merge"), 1); - - GrapplerItem optimized = item.WithGraph(std::move(optimized_graph)); - - Tensor one = test::AsScalar(1.0); - Tensor two = test::AsScalar(2.0); - Tensor three = test::AsScalar(3.0); - - const auto feed_args = [&](bool is_add) { - std::vector> feed; - feed.emplace_back("a", one); - feed.emplace_back("b", two); - feed.emplace_back("is_add", test::AsScalar(is_add)); - return feed; - }; - - { // Check 'is_add == true': a + b - item.feed = feed_args(true); - optimized.feed = feed_args(true); - - auto tensors_expected = EvaluateFetchNodes(item); - ASSERT_EQ(tensors_expected.size(), 1); - test::ExpectTensorEqual(tensors_expected[0], three); - - auto tensors = EvaluateFetchNodes(optimized); - ASSERT_EQ(tensors.size(), tensors_expected.size()); - test::ExpectTensorEqual(tensors_expected[0], tensors[0]); - } - - { // Check 'is_add == false': a * b - item.feed = feed_args(false); - optimized.feed = feed_args(false); - - auto tensors_expected = EvaluateFetchNodes(item); - ASSERT_EQ(tensors_expected.size(), 1); - test::ExpectTensorEqual(tensors_expected[0], two); - - auto tensors = EvaluateFetchNodes(optimized); - ASSERT_EQ(tensors.size(), tensors_expected.size()); - test::ExpectTensorEqual(tensors_expected[0], tensors[0]); - } -} - TEST_F(FunctionOptimizerTest, SpecializeFunctionXTimesTwo) { using test::function::NDef; -- GitLab From 04181795a31d2b332aa0d0fc5daf9d254432c042 Mon Sep 17 00:00:00 2001 From: Igor Ganichev Date: Wed, 16 Jan 2019 10:24:17 -0800 Subject: [PATCH 0790/2345] Add num_values argument to Tensor::DebugString() Non-CPU Tensors cause a crash when DebugString() tries to access their values. num_values can be used not to access the values or print more than default of 3 during debugging. PiperOrigin-RevId: 229581029 --- tensorflow/compiler/jit/xla_launch_util.cc | 2 +- tensorflow/core/framework/tensor.cc | 9 +++++++-- tensorflow/core/framework/tensor.h | 12 ++++++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/jit/xla_launch_util.cc b/tensorflow/compiler/jit/xla_launch_util.cc index 4e3cb0238f..c64981053f 100644 --- a/tensorflow/compiler/jit/xla_launch_util.cc +++ b/tensorflow/compiler/jit/xla_launch_util.cc @@ -377,7 +377,7 @@ Status XlaComputationLaunchContext::PopulateOutputs( } if (VLOG_IS_ON(3)) { - VLOG(3) << ctx->mutable_output(i)->DebugString(); + VLOG(3) << ctx->mutable_output(i)->DeviceSafeDebugString(); } } diff --git a/tensorflow/core/framework/tensor.cc b/tensorflow/core/framework/tensor.cc index 0c96ec8168..ab492a2189 100644 --- a/tensorflow/core/framework/tensor.cc +++ b/tensorflow/core/framework/tensor.cc @@ -1174,10 +1174,15 @@ bool Tensor::SharesBufferWith(const Tensor& b) const { buf_->root_buffer() == b.buf_->root_buffer(); } -string Tensor::DebugString() const { +string Tensor::DebugString(int num_values) const { return strings::StrCat("Tensor"); + " values: ", SummarizeValue(num_values), ">"); +} + +string Tensor::DeviceSafeDebugString() const { + return strings::StrCat("Tensor"); } void Tensor::FillDescription(TensorDescription* description) const { diff --git a/tensorflow/core/framework/tensor.h b/tensorflow/core/framework/tensor.h index 009dd0846d..44293c5c19 100644 --- a/tensorflow/core/framework/tensor.h +++ b/tensorflow/core/framework/tensor.h @@ -526,7 +526,15 @@ class Tensor { string SummarizeValue(int64 max_entries, bool print_v2 = false) const; /// A human-readable summary of the tensor suitable for debugging. - string DebugString() const; + // `num_values` is the number of actual data values in the tensor + // included in the message. If the tensor might be resident in + // GPU/TPU memory use DeviceSafeDebugString instead. + string DebugString(int num_values = 3) const; + + // Variant of DebugString() that should be used for possibly non-CPU tensors. + // If the tensor is not resident on CPU, we can't read its values as + // DebugString() does. + string DeviceSafeDebugString() const; /// Fill in the `TensorDescription` proto with metadata about the /// tensor that is useful for monitoring and debugging. @@ -594,7 +602,7 @@ class Tensor { OpKernelContext* ctx, Var* var); // For access to RefCountIsOne(). friend Status batch_util::CopyElementToSlice( Tensor element, Tensor* parent, - int64 index); // For access to RefCountIsOne(). + int64 index); // For access to RefCountIsOne(). friend Status batch_util::MaybeMoveSliceToElement( Tensor* parent, Tensor* element, int64 index); // For access to RefCountIsOne(). -- GitLab From 1b2b712b836f1c7e32526f62c797a9d9a46d9561 Mon Sep 17 00:00:00 2001 From: Shivani Agrawal Date: Wed, 16 Jan 2019 10:41:46 -0800 Subject: [PATCH 0791/2345] Fix in evaluate for SparseTensors. PiperOrigin-RevId: 229584367 --- .../bucket_by_sequence_length_test.py | 9 ++--- .../experimental/kernel_tests/scan_test.py | 4 +- .../experimental/kernel_tests/unbatch_test.py | 37 +++++-------------- tensorflow/python/framework/test_util.py | 5 ++- 4 files changed, 17 insertions(+), 38 deletions(-) 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 71ae14b5e1..0bbf0e9a12 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 @@ -77,12 +77,11 @@ def _get_record_shape(sparse): class BucketBySequenceLengthTest(test_base.DatasetTestBase, parameterized.TestCase): - # TODO(b/117581999): add eager coverage. @parameterized.named_parameters( ("WithoutPadding", True), ("WithPadding", False), ) - def testSkipEagerBucketDropReminder(self, param_no_padding): + def testBucketDropReminder(self, param_no_padding): boundaries = [10, 20, 30] batch_sizes = [10, 8, 4, 2] @@ -202,12 +201,11 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase, _test_bucket_by_padding(param_no_padding) - # TODO(b/117581999): add eager coverage. @parameterized.named_parameters( ("WithoutPadding", True), ("WithPadding", False), ) - def testSkipEagerBucket(self, param_no_padding): + def testBucket(self, param_no_padding): boundaries = [10, 20, 30] batch_sizes = [10, 8, 4, 2] @@ -383,12 +381,11 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase, _test_tuple_elements_by_padding(param_no_padding) - # TODO(b/117581999): add eager coverage @parameterized.named_parameters( ("DoDropRemainder", True), ("DoNotDropRemainder", False), ) - def testSkipEagerBucketSparse(self, param_drop_remainder): + def testBucketSparse(self, param_drop_remainder): # pylint: disable=g-doc-args """Tests bucketing of sparse tensors (case where `no_padding` == True). Test runs on following dataset: diff --git a/tensorflow/python/data/experimental/kernel_tests/scan_test.py b/tensorflow/python/data/experimental/kernel_tests/scan_test.py index f5ac0f5007..38e9b1e128 100644 --- a/tensorflow/python/data/experimental/kernel_tests/scan_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/scan_test.py @@ -70,9 +70,7 @@ class ScanTest(test_base.DatasetTestBase): self.assertEqual(5, self.evaluate(next_element())) self.assertEqual(8, self.evaluate(next_element())) - # TODO(b/117581999): Add coverage for eager. - @test_util.run_deprecated_v1 - def testSkipEagerSparseCount(self): + def testSparseCount(self): def _sparse(i): return sparse_tensor.SparseTensorValue( diff --git a/tensorflow/python/data/experimental/kernel_tests/unbatch_test.py b/tensorflow/python/data/experimental/kernel_tests/unbatch_test.py index e4034cc43a..613fe0da6b 100644 --- a/tensorflow/python/data/experimental/kernel_tests/unbatch_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/unbatch_test.py @@ -68,9 +68,7 @@ class UnbatchTest(test_base.DatasetTestBase, parameterized.TestCase): self.assertDatasetProduces( data, [(i, compat.as_bytes(str(i)), i) for i in range(10)]) - # TODO(b/117581999): Add eager coverage. - @test_util.run_deprecated_v1 - def testSkipEagerUnbatchDatasetWithSparseTensor(self): + def testUnbatchDatasetWithSparseTensor(self): st = sparse_tensor.SparseTensorValue( indices=[[i, i] for i in range(10)], values=list(range(10)), @@ -79,20 +77,12 @@ class UnbatchTest(test_base.DatasetTestBase, parameterized.TestCase): data = data.apply(batching.unbatch()) data = data.batch(5) data = data.apply(batching.unbatch()) - iterator = dataset_ops.make_one_shot_iterator(data) - next_element = iterator.get_next() - - for i in range(10): - st_row = self.evaluate(next_element) - self.assertEqual([i], st_row.indices) - self.assertEqual([i], st_row.values) - self.assertEqual([10], st_row.dense_shape) - with self.assertRaises(errors.OutOfRangeError): - self.evaluate(next_element) + expected_output = [ + sparse_tensor.SparseTensorValue([[i]], [i], [10]) for i in range(10) + ] + self.assertDatasetProduces(data, expected_output=expected_output) - # TODO(b/117581999): Add eager coverage. - @test_util.run_deprecated_v1 - def testSkipEagerUnbatchDatasetWithDenseAndSparseTensor(self): + def testUnbatchDatasetWithDenseAndSparseTensor(self): st = sparse_tensor.SparseTensorValue( indices=[[i, i] for i in range(10)], values=list(range(10)), @@ -101,16 +91,9 @@ class UnbatchTest(test_base.DatasetTestBase, parameterized.TestCase): data = data.apply(batching.unbatch()) data = data.batch(5) data = data.apply(batching.unbatch()) - next_element = self.getNext(data) - - for i in range(10): - dense_elem, st_row = self.evaluate(next_element()) - self.assertEqual(i, dense_elem) - self.assertEqual([i], st_row.indices) - self.assertEqual([i], st_row.values) - self.assertEqual([10], st_row.dense_shape) - with self.assertRaises(errors.OutOfRangeError): - self.evaluate(next_element()) + expected_output = [(i, sparse_tensor.SparseTensorValue([[i]], [i], [10])) + for i in range(10)] + self.assertDatasetProduces(data, expected_output=expected_output) def testUnbatchSingleElementTupleDataset(self): data = tuple([(math_ops.range(10),) for _ in range(3)]) @@ -150,7 +133,7 @@ class UnbatchTest(test_base.DatasetTestBase, parameterized.TestCase): with self.assertRaises(ValueError): data.apply(batching.unbatch()) - # TODO(b/117581999): eager mode doesnt capture raised error, debug. + # Note: dynamic shape mismatch is graph specific test. @test_util.run_deprecated_v1 def testSkipEagerUnbatchDynamicShapeMismatch(self): ph1 = array_ops.placeholder(dtypes.int32, shape=[None]) diff --git a/tensorflow/python/framework/test_util.py b/tensorflow/python/framework/test_util.py index e2b906e37c..732fd31389 100644 --- a/tensorflow/python/framework/test_util.py +++ b/tensorflow/python/framework/test_util.py @@ -1658,8 +1658,9 @@ class TensorFlowTestCase(googletest.TestCase): else: try: if sparse_tensor.is_sparse(tensor): - return sparse_tensor.SparseTensorValue(tensor.indices, tensor.values, - tensor.dense_shape) + return sparse_tensor.SparseTensorValue(tensor.indices.numpy(), + tensor.values.numpy(), + tensor.dense_shape.numpy()) return tensor.numpy() except AttributeError as e: six.raise_from(ValueError("Unsupported type %s." % type(tensor)), e) -- GitLab From b59aa4b89356333027bb38f7364f18c4df882835 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 10:44:30 -0800 Subject: [PATCH 0792/2345] Update landscape layout with model, device, and number of thread selectors. PiperOrigin-RevId: 229584823 --- .../layout-land/fragment_camera2_basic.xml | 128 +++++++++++------- 1 file changed, 82 insertions(+), 46 deletions(-) diff --git a/tensorflow/lite/java/demo/app/src/main/res/layout-land/fragment_camera2_basic.xml b/tensorflow/lite/java/demo/app/src/main/res/layout-land/fragment_camera2_basic.xml index ee71ab808f..323b21dbce 100644 --- a/tensorflow/lite/java/demo/app/src/main/res/layout-land/fragment_camera2_basic.xml +++ b/tensorflow/lite/java/demo/app/src/main/res/layout-land/fragment_camera2_basic.xml @@ -16,67 +16,103 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - + > + + + + + + + + -- GitLab From b4ae5ba323c20ad30e3f7559d547a6b76d5f33e6 Mon Sep 17 00:00:00 2001 From: Rachel Lim Date: Wed, 16 Jan 2019 10:51:07 -0800 Subject: [PATCH 0793/2345] [tf.data] Add a ChooseFastestDataset, which will allow our optimizations to dynamically pick between two equivalent input datasets - For the first N iterations, run two threads simultaneously that each pulls from one of the inputs. The threads also measure time taken. Wait for both threads to complete before returning. - After N iterations, decide which dataset is faster and just use that. There are more sophisticated implementations that could be better, e.g. instead of waiting for both threads to complete, return when the faster of them is finished. However, the code here is significantly simpler (I also implemented the other one elsewhere), at the cost of slower iterations for the first N iterations -- the iteration time for the first N iters is max(input_a, input_b). Open to ideas. PiperOrigin-RevId: 229586112 --- ...def_ExperimentalChooseFastestDataset.pbtxt | 4 + .../core/kernels/data/experimental/BUILD | 13 + .../experimental/choose_fastest_dataset_op.cc | 346 ++++++++++++++++++ .../core/ops/experimental_dataset_ops.cc | 7 + .../python/data/experimental/benchmarks/BUILD | 14 + .../benchmarks/choose_fastest_benchmark.py | 105 ++++++ .../kernel_tests/optimization/BUILD | 20 + .../choose_fastest_dataset_test.py | 85 +++++ .../kernel_tests/serialization/BUILD | 18 + ...oose_fastest_dataset_serialization_test.py | 45 +++ .../data/experimental/ops/optimization.py | 45 +++ 11 files changed, 702 insertions(+) create mode 100644 tensorflow/core/api_def/base_api/api_def_ExperimentalChooseFastestDataset.pbtxt create mode 100644 tensorflow/core/kernels/data/experimental/choose_fastest_dataset_op.cc create mode 100644 tensorflow/python/data/experimental/benchmarks/choose_fastest_benchmark.py create mode 100644 tensorflow/python/data/experimental/kernel_tests/optimization/choose_fastest_dataset_test.py create mode 100644 tensorflow/python/data/experimental/kernel_tests/serialization/choose_fastest_dataset_serialization_test.py diff --git a/tensorflow/core/api_def/base_api/api_def_ExperimentalChooseFastestDataset.pbtxt b/tensorflow/core/api_def/base_api/api_def_ExperimentalChooseFastestDataset.pbtxt new file mode 100644 index 0000000000..7aa7a59bb6 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ExperimentalChooseFastestDataset.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ExperimentalChooseFastestDataset" + visibility: HIDDEN +} diff --git a/tensorflow/core/kernels/data/experimental/BUILD b/tensorflow/core/kernels/data/experimental/BUILD index 583fce6d89..29756f0cfd 100644 --- a/tensorflow/core/kernels/data/experimental/BUILD +++ b/tensorflow/core/kernels/data/experimental/BUILD @@ -143,6 +143,18 @@ tf_kernel_library( ], ) +tf_kernel_library( + name = "choose_fastest_dataset_op", + srcs = ["choose_fastest_dataset_op.cc"], + deps = [ + "//tensorflow/core:experimental_dataset_ops_op_lib", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core/kernels/data:dataset", + ], +) + tf_kernel_library( name = "non_serializable_dataset_op", srcs = ["non_serializable_dataset_op.cc"], @@ -343,6 +355,7 @@ tf_kernel_library( name = "dataset_kernels", deps = [ ":assert_next_dataset_op", + ":choose_fastest_dataset_op", ":csv_dataset_op", ":dense_to_sparse_batch_dataset_op", ":directed_interleave_dataset_op", diff --git a/tensorflow/core/kernels/data/experimental/choose_fastest_dataset_op.cc b/tensorflow/core/kernels/data/experimental/choose_fastest_dataset_op.cc new file mode 100644 index 0000000000..f66d5d9955 --- /dev/null +++ b/tensorflow/core/kernels/data/experimental/choose_fastest_dataset_op.cc @@ -0,0 +1,346 @@ +/* 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/common_shape_fns.h" +#include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/lib/histogram/histogram.h" + +namespace tensorflow { +namespace data { +namespace { + +static const double kPercentile = 90.0; + +class ChooseFastestDatasetOp : public DatasetOpKernel { + public: + explicit ChooseFastestDatasetOp(OpKernelConstruction* ctx) + : DatasetOpKernel(ctx) { + OP_REQUIRES_OK(ctx, ctx->GetAttr("num_experiments", &num_experiments_)); + } + + void MakeDataset(OpKernelContext* ctx, DatasetBase** output) override { + OpInputList input_list; + OP_REQUIRES_OK(ctx, ctx->input_list("input_datasets", &input_list)); + OP_REQUIRES( + ctx, input_list.size() > 1, + errors::InvalidArgument( + "ChooseFastestDataset must have at least two input datasets.")); + + std::vector inputs; + inputs.reserve(input_list.size()); + for (const auto& tensor : input_list) { + DatasetBase* input; + OP_REQUIRES_OK(ctx, GetDatasetFromVariantTensor(tensor, &input)); + inputs.push_back(input); + } + + const DataTypeVector& output_types = inputs[0]->output_dtypes(); + for (size_t i = 1, num_inputs = inputs.size(); i < num_inputs; ++i) { + OP_REQUIRES(ctx, inputs[i]->output_dtypes() == output_types, + errors::InvalidArgument( + "All inputs to ChooseFastestDataset " + "must have the same output types. Input ", + i, " has output types: ", + DataTypeVectorString(inputs[i]->output_dtypes()), + ", while all prior inputs have types: ", + DataTypeVectorString(output_types), ".")); + } + + std::vector output_shapes = inputs[0]->output_shapes(); + // Merge the output shapes of all the input datasets, returning an + // error if any of them are incompatible. + for (size_t i = 1, num_inputs = inputs.size(); i < num_inputs; ++i) { + OP_REQUIRES( + ctx, inputs[i]->output_shapes().size() == output_shapes.size(), + errors::InvalidArgument( + "All inputs to ChooseFastestDataset must have compatible outputs." + " Input ", + i, " has ", inputs[i]->output_shapes().size(), + " components, while all prior inputs have ", output_shapes.size(), + " components.")); + for (size_t j = 0, num_components = output_shapes.size(); + j < num_components; ++j) { + PartialTensorShape result; + OP_REQUIRES( + ctx, + output_shapes[j] + .MergeWith(inputs[i]->output_shapes().at(j), &result) + .ok(), + errors::InvalidArgument( + "All inputs to ChooseFastestDataset must have " + "compatible output shapes. Component ", + j, " of input ", i, " has shape: ", + inputs[i]->output_shapes().at(j), ", while components ", j, + " of all prior inputs have shape: ", output_shapes[j], ".")); + output_shapes[j] = std::move(result); + } + } + + int64 cardinality = inputs[0]->Cardinality(); + for (size_t i = 1, num_inputs = inputs.size(); i < num_inputs; ++i) { + if (cardinality == kUnknownCardinality) { + cardinality = inputs[i]->Cardinality(); + } else { + OP_REQUIRES( + ctx, + inputs[i]->Cardinality() == cardinality || + inputs[i]->Cardinality() == kUnknownCardinality, + errors::InvalidArgument( + "All inputs to ChooseFastestDataset must have compatible " + "cardinalities. Input ", + i, " has cardinality: ", inputs[i]->Cardinality(), + ", while all prior inputs have cardinality: ", cardinality, + ".")); + } + } + *output = new Dataset(ctx, std::move(inputs), std::move(output_shapes), + cardinality, num_experiments_); + } + + private: + class Dataset : public DatasetBase { + public: + Dataset(OpKernelContext* ctx, std::vector inputs, + std::vector output_shapes, int64 cardinality, + int64 num_experiments) + : DatasetBase(DatasetContext(ctx)), + inputs_(std::move(inputs)), + output_shapes_(std::move(output_shapes)), + cardinality_(cardinality), + num_experiments_(num_experiments) { + for (auto input : inputs_) { + input->Ref(); + } + } + + ~Dataset() override { + for (auto input : inputs_) { + input->Unref(); + } + } + + std::unique_ptr MakeIteratorInternal( + const string& prefix) const override { + return absl::make_unique( + ChooseFastestIterator::Params{ + this, strings::StrCat(prefix, "::ChooseFastest")}); + } + + const DataTypeVector& output_dtypes() const override { + return inputs_[0]->output_dtypes(); + } + + const std::vector& output_shapes() const override { + return output_shapes_; + } + + string DebugString() const override { + return "ChooseFastestDatasetOp::Dataset"; + } + + int64 Cardinality() const override { return cardinality_; } + + protected: + Status AsGraphDefInternal(SerializationContext* ctx, + DatasetGraphDefBuilder* b, + Node** output) const override { + std::vector input_nodes; + input_nodes.reserve(inputs_.size()); + for (const auto& input : inputs_) { + Node* input_node; + TF_RETURN_IF_ERROR(b->AddInputDataset(ctx, input, &input_node)); + input_nodes.push_back(input_node); + } + AttrValue num_experiments_attr; + b->BuildAttrValue(num_experiments_, &num_experiments_attr); + return b->AddDataset( + this, {}, {std::make_pair(0, input_nodes)}, + {std::make_pair("num_experiments", std::move(num_experiments_attr))}, + output); + } + + private: + class ChooseFastestIterator : public DatasetIterator { + public: + explicit ChooseFastestIterator(const Params& params) + : DatasetIterator(params), + histograms_(dataset()->inputs_.size()) {} + + Status Initialize(IteratorContext* ctx) override { + mutex_lock l(mu_); + input_impls_.resize(dataset()->inputs_.size()); + for (size_t i = 0, num_inputs = dataset()->inputs_.size(); + i < num_inputs; ++i) { + TF_RETURN_IF_ERROR(dataset()->inputs_[i]->MakeIterator( + ctx, strings::StrCat(prefix(), "_", i), &input_impls_[i])); + } + return Status::OK(); + } + + Status GetNextInternal(IteratorContext* ctx, + std::vector* out_tensors, + bool* end_of_sequence) override { + mutex_lock l(mu_); + + // The first num_experiments_ iterations, we fire up a thread for + // each input that calls its GetNext function and records the time + // taken. We only return when all the threads have completed. + if (experiment_counter_ < dataset()->num_experiments_) { + experiment_counter_++; + std::vector threads = StartThreads(ctx); + for (const auto& thread : threads) { + thread.result->notification.WaitForNotification(); + } + + *out_tensors = std::move(threads[0].result->out_tensors); + *end_of_sequence = threads[0].result->end_of_sequence; + + if (experiment_counter_ == dataset()->num_experiments_) { + SelectFastestInputIndex(); + } + return threads[0].result->status; + } + return input_impls_[fastest_index_]->GetNext(ctx, out_tensors, + end_of_sequence); + } + + protected: + std::shared_ptr CreateNode( + IteratorContext* ctx, model::Node::Args args) const override { + return model::MakeKnownRatioNode(std::move(args), /*ratio=*/1); + } + + // TODO(rachelim): Save and restore histogram state as well. Currently, + // if an iterator is saved and restored, the histograms start recording + // from scratch. + Status SaveInternal(IteratorStateWriter* writer) override { + mutex_lock l(mu_); + if (input_impls_.empty()) { + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name("input_impls_empty"), "")); + } else { + for (auto& input_impl : input_impls_) { + TF_RETURN_IF_ERROR(SaveInput(writer, input_impl)); + } + } + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name("experiment_counter"), + experiment_counter_)); + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name("fastest_index"), fastest_index_)); + return Status::OK(); + } + + Status RestoreInternal(IteratorContext* ctx, + IteratorStateReader* reader) override { + mutex_lock l(mu_); + if (reader->Contains(full_name("input_impls_empty"))) { + input_impls_.clear(); + } else { + DCHECK_EQ(input_impls_.size(), dataset()->inputs_.size()); + for (auto& input_impl : input_impls_) { + TF_RETURN_IF_ERROR(RestoreInput(ctx, reader, input_impl)); + } + } + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("experiment_counter"), + &experiment_counter_)); + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name("fastest_index"), &fastest_index_)); + return Status::OK(); + } + + private: + struct InvocationResult { + Notification notification; + Status status; + bool end_of_sequence; + std::vector out_tensors; + }; + + struct ThreadInfo { + std::unique_ptr result; + std::unique_ptr thread; + }; + + std::vector> input_impls_; + // For tracking the time taken for each input's iterations. + std::vector histograms_; + + mutex mu_; + int64 experiment_counter_ GUARDED_BY(mu_) = 0; + int64 fastest_index_ = -1; + + std::vector StartThreads(IteratorContext* ctx) + EXCLUSIVE_LOCKS_REQUIRED(mu_) { + std::vector threads(dataset()->inputs_.size()); + for (size_t i = 0, num_inputs = dataset()->inputs_.size(); + i < num_inputs; ++i) { + threads[i].result = absl::make_unique(); + threads[i].thread.reset(ctx->env()->StartThread( + {}, strings::StrCat("tf_data_merge_", i), + std::bind(&ChooseFastestIterator::RunnerThread, this, ctx, + threads[i].result.get(), i))); + } + return threads; + } + + void RunnerThread(IteratorContext* ctx, InvocationResult* result, int i) { + int64 start = Env::Default()->NowNanos(); + Status s = input_impls_[i]->GetNext(ctx, &result->out_tensors, + &result->end_of_sequence); + histograms_[i].Add( + static_cast(Env::Default()->NowNanos() - start)); + + result->status = s; + result->notification.Notify(); + } + + // Select the fastest input to use based on the histograms of timings + // of the completed threads. The input with the best 90th percentile + // iteration time is selected. + void SelectFastestInputIndex() EXCLUSIVE_LOCKS_REQUIRED(mu_) { + fastest_index_ = 0; + + double best_percentile = histograms_[0].Percentile(kPercentile); + for (size_t i = 1, num_inputs = histograms_.size(); i < num_inputs; + ++i) { + double percentile = histograms_[i].Percentile(kPercentile); + if (percentile <= best_percentile) { + best_percentile = percentile; + fastest_index_ = i; + } + } + } + }; // class Iterator + + const std::vector inputs_; + const std::vector output_shapes_; + const int64 cardinality_; + const int64 num_experiments_; + }; // class Dataset + + int64 num_experiments_; +}; // class ChooseFastestDatasetOp + +// Register the kernel implementation for ChooseFastestDataset. +REGISTER_KERNEL_BUILDER( + Name("ExperimentalChooseFastestDataset").Device(DEVICE_CPU), + ChooseFastestDatasetOp); + +} // namespace +} // namespace data +} // namespace tensorflow diff --git a/tensorflow/core/ops/experimental_dataset_ops.cc b/tensorflow/core/ops/experimental_dataset_ops.cc index f904e2536d..d70b1650ff 100644 --- a/tensorflow/core/ops/experimental_dataset_ops.cc +++ b/tensorflow/core/ops/experimental_dataset_ops.cc @@ -453,6 +453,13 @@ REGISTER_OP("ExperimentalLMDBDataset") // stateful to inhibit constant folding. .SetShapeFn(shape_inference::ScalarShape); +REGISTER_OP("ExperimentalChooseFastestDataset") + .Input("input_datasets: N * variant") + .Output("handle: variant") + .Attr("N: int >= 2") + .Attr("num_experiments: int") + .SetShapeFn(shape_inference::ScalarShape); + REGISTER_OP("ExperimentalIdentityIndexedDataset") .Input("size: uint64") .Output("handle: variant") diff --git a/tensorflow/python/data/experimental/benchmarks/BUILD b/tensorflow/python/data/experimental/benchmarks/BUILD index 2443f7edad..4f2117ec9b 100644 --- a/tensorflow/python/data/experimental/benchmarks/BUILD +++ b/tensorflow/python/data/experimental/benchmarks/BUILD @@ -110,6 +110,20 @@ py_test( ], ) +py_test( + name = "choose_fastest_benchmark", + srcs = ["choose_fastest_benchmark.py"], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:session", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], +) + py_test( name = "optimize_benchmark", srcs = ["optimize_benchmark.py"], diff --git a/tensorflow/python/data/experimental/benchmarks/choose_fastest_benchmark.py b/tensorflow/python/data/experimental/benchmarks/choose_fastest_benchmark.py new file mode 100644 index 0000000000..4a5a264c6f --- /dev/null +++ b/tensorflow/python/data/experimental/benchmarks/choose_fastest_benchmark.py @@ -0,0 +1,105 @@ +# 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. +# ============================================================================== +"""Benchmarks for static optimizations.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import time + +import numpy as np + +from tensorflow.python.client import session +from tensorflow.python.data.experimental.ops import optimization +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.platform import test + + +# TODO(b/119837791): Add eager benchmarks too. +class ChooseFastestBenchmark(test.Benchmark): + """Benchmarks for static optimizations.""" + + def benchmarkChooseFastest(self): + + dataset = dataset_ops.Dataset.range(1000**2).repeat() + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) + map_batch_dataset = dataset.map(lambda x: x + 1).batch(100) + batch_map_dataset = dataset.batch(100).map(lambda x: x + 1) + + merge_dataset = optimization._ChooseFastestDataset( # pylint: disable=protected-access + [batch_map_dataset, map_batch_dataset]) + self._benchmark(map_batch_dataset, "map_batch_dataset") + self._benchmark(batch_map_dataset, "batch_map_dataset") + self._benchmark(merge_dataset, "merge_dataset") + + def benchmarkChooseFastestFirstNIterations(self): + + dataset = dataset_ops.Dataset.range(1000**2).repeat() + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) + map_batch_dataset = dataset.map(lambda x: x + 1).batch(100) + batch_map_dataset = dataset.batch(100).map(lambda x: x + 1) + + merge_dataset = optimization._ChooseFastestDataset( # pylint: disable=protected-access + [batch_map_dataset, map_batch_dataset]) + + self._benchmarkFirstN(map_batch_dataset, "map_batch_dataset") + self._benchmarkFirstN(batch_map_dataset, "batch_map_dataset") + self._benchmarkFirstN(merge_dataset, "merge_dataset") + + def _benchmarkFirstN(self, dataset, name): + n = 10 # The default num_experiments for ChooseFastestDataset + iterator = dataset_ops.make_one_shot_iterator(dataset) + next_element = iterator.get_next() + + deltas = [] + for _ in range(100): + with session.Session() as sess: + start = time.time() + for _ in range(n): + sess.run(next_element.op) + end = time.time() + deltas.append(end - start) + median_wall_time = np.median(deltas) / n + self.report_benchmark( + iters=n, wall_time=median_wall_time, name=name + "_first_%d" % n) + + def _benchmark(self, dataset, name): + iterator = dataset_ops.make_one_shot_iterator(dataset) + next_element = iterator.get_next() + + with session.Session() as sess: + # Run 10 steps to warm up the session caches before taking the first + # measurement. Additionally, 10 is the default num_experiments for + # ChooseFastestDataset. + for _ in range(10): + sess.run(next_element.op) + deltas = [] + for _ in range(50): + start = time.time() + for _ in range(50): + sess.run(next_element.op) + end = time.time() + deltas.append(end - start) + + median_wall_time = np.median(deltas) / 100 + self.report_benchmark(iters=100, wall_time=median_wall_time, name=name) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/python/data/experimental/kernel_tests/optimization/BUILD b/tensorflow/python/data/experimental/kernel_tests/optimization/BUILD index 93a9aca1f2..703d3350db 100644 --- a/tensorflow/python/data/experimental/kernel_tests/optimization/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/optimization/BUILD @@ -242,6 +242,26 @@ py_test( ], ) +py_test( + name = "choose_fastest_dataset_test", + size = "small", + srcs = ["choose_fastest_dataset_test.py"], + srcs_version = "PY2AND3", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:errors", + "//tensorflow/python/data/experimental/ops:optimization", + "//tensorflow/python/data/kernel_tests:test_base", + "//tensorflow/python/data/ops:dataset_ops", + "@absl_py//absl/testing:parameterized", + ], +) + py_test( name = "model_dataset_test", size = "medium", 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 new file mode 100644 index 0000000000..ec7a85ae11 --- /dev/null +++ b/tensorflow/python/data/experimental/kernel_tests/optimization/choose_fastest_dataset_test.py @@ -0,0 +1,85 @@ +# 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.data.experimental._ChooseFastestDataset`.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl.testing import parameterized + +from tensorflow.python.data.experimental.ops import optimization +from tensorflow.python.data.kernel_tests import test_base +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.eager import context +from tensorflow.python.framework import errors +from tensorflow.python.framework import test_util +from tensorflow.python.platform import test + + +@test_util.run_all_in_graph_and_eager_modes +class ChooseFastestDatasetTest(test_base.DatasetTestBase, + parameterized.TestCase): + + def testChooseFastestSimple(self): + dataset = dataset_ops.Dataset.from_tensor_slices([0, 1, 2, 3, 4]) + merge = optimization._ChooseFastestDataset([dataset, dataset]) + self.assertDatasetProduces( + merge, + expected_output=[0, 1, 2, 3, 4], + expected_shapes=dataset.output_shapes) + + def testChooseFastestManyInputs(self): + dataset = dataset_ops.Dataset.from_tensor_slices([0, 1, 2, 3, 4]) + merge = optimization._ChooseFastestDataset([dataset for _ in range(5)]) + self.assertDatasetProduces( + merge, + expected_output=[0, 1, 2, 3, 4], + expected_shapes=dataset.output_shapes) + + def testChooseFastest(self): + dataset = dataset_ops.Dataset.range(600) + f = lambda x: 2 * x + dataset_a = dataset.batch(50).map(f) + dataset_b = dataset.map(f).batch(50) + merge = optimization._ChooseFastestDataset([dataset_a, dataset_b]) + self.assertDatasetProduces( + merge, + expected_output=[ + [i * 2 for i in range(j * 50, (j + 1) * 50)] for j in range(12) + ], + expected_shapes=dataset_a.output_shapes) + + @parameterized.named_parameters( + ("Shapes", [0], [[1, 2, 3]], "must have compatible output shapes."), + ("Types", [0], [0.0], "must have the same output types."), + ("NumComponents", [0], ([0], [1]), "must have the same output types."), + ("Cardinality", [1, 2, 3], [1], "must have compatible cardinalities.")) + def testChooseFastestErrorWithIncompatibleInput(self, slices_a, slices_b, + error_msg): + dataset_a = dataset_ops.Dataset.from_tensor_slices(slices_a) + dataset_b = dataset_ops.Dataset.from_tensor_slices(slices_b) + + # The error is raised at dataset creation time. + if context.executing_eagerly(): + with self.assertRaises(errors.InvalidArgumentError): + merge = optimization._ChooseFastestDataset([dataset_a, dataset_b]) + else: + merge = optimization._ChooseFastestDataset([dataset_a, dataset_b]) + self.assertDatasetProduces( + merge, expected_error=(errors.InvalidArgumentError, error_msg)) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD index 4a2e28f496..3a2ce17228 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD @@ -93,6 +93,24 @@ py_test( ], ) +py_test( + name = "choose_fastest_dataset_serialization_test", + size = "small", + srcs = ["choose_fastest_dataset_serialization_test.py"], + srcs_version = "PY2AND3", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:optimization", + "//tensorflow/python/data/ops:dataset_ops", + ], +) + py_test( name = "concatenate_dataset_serialization_test", size = "small", diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/choose_fastest_dataset_serialization_test.py b/tensorflow/python/data/experimental/kernel_tests/serialization/choose_fastest_dataset_serialization_test.py new file mode 100644 index 0000000000..936dc22214 --- /dev/null +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/choose_fastest_dataset_serialization_test.py @@ -0,0 +1,45 @@ +# 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 the ZipDataset serialization.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.data.experimental.kernel_tests.serialization import dataset_serialization_test_base +from tensorflow.python.data.experimental.ops import optimization +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.platform import test + + +class ChooseFastestDatasetSerializationTest( + dataset_serialization_test_base.DatasetSerializationTestBase): + + def testCore(self): + num_outputs = 10 + batch_size = 2 + + def build_ds(): + dataset = dataset_ops.Dataset.range(num_outputs) + map_fn = lambda x: x * 2 + return optimization._ChooseFastestDataset([ # pylint: disable=protected-access + dataset.map(map_fn).batch(batch_size), + dataset.batch(batch_size).map(map_fn) + ]) + + self.run_core_tests(build_ds, None, num_outputs // 2) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/python/data/experimental/ops/optimization.py b/tensorflow/python/data/experimental/ops/optimization.py index 22a36646ea..75769b8998 100644 --- a/tensorflow/python/data/experimental/ops/optimization.py +++ b/tensorflow/python/data/experimental/ops/optimization.py @@ -129,3 +129,48 @@ class _NonSerializableDataset(dataset_ops.UnaryUnchangedStructureDataset): self._input_dataset._variant_tensor, # pylint: disable=protected-access **dataset_ops.flat_structure(self))) super(_NonSerializableDataset, self).__init__(input_dataset, variant_tensor) + + +class _ChooseFastestDataset(dataset_ops.DatasetV2): + """A `Dataset` that merges two input datasets.""" + + def __init__(self, datasets, num_experiments=10): + """Chooses the fastest of some input datasets. + + Given input datasets, produces elements as quickly as the fastest of the + inputs. Note that this dataset assumes that input datasets have the same + elements in the same order, though this is not enforced besides checking + that the input datasets have compatible output types, output shapes, and + cardinality at runtime. The resulting dataset produces elements that are + identical to the input elements, and in the same order. + + Note that the time to first iteration is longer when this dataset is used + due to the overhead of dynamically picking the faster dataset. Namely, + for the first num_experiments iterations, this dataset will pull from all + of its inputs simultaneously in order to determine which input is the + fastest. For all subsequent iterations, that input will be used. + + Args: + datasets: A list of `Datasets` that all have the same elements in the same + order. + num_experiments: The number of experiments to run before deciding which + dataset is fastest. In each "experiment" iteration, the dataset will + call from all its inputs simultaneously, and update its knowledge of + which input is the fastest. + + Returns: + A `Dataset` that has the same elements the inputs. + """ + self._datasets = list(datasets) + variant_tensor = ( + gen_experimental_dataset_ops.experimental_choose_fastest_dataset( + [dataset._variant_tensor for dataset in self._datasets], # pylint: disable=protected-access + num_experiments=num_experiments)) + super(_ChooseFastestDataset, self).__init__(variant_tensor) + + def _inputs(self): + return self._datasets + + @property + def _element_structure(self): + return self._datasets[0]._element_structure # pylint: disable=protected-access -- GitLab From 191f1d43e4c064c6460a6d4329c8e030f6b94114 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 27 Oct 2018 00:09:20 +0000 Subject: [PATCH 0794/2345] Add test case for using dataset with class_weight Signed-off-by: Yong Tang Pylint fix Signed-off-by: Yong Tang --- .../python/keras/engine/training_utils_test.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tensorflow/python/keras/engine/training_utils_test.py b/tensorflow/python/keras/engine/training_utils_test.py index ec44252c9b..dfc27cbc3f 100644 --- a/tensorflow/python/keras/engine/training_utils_test.py +++ b/tensorflow/python/keras/engine/training_utils_test.py @@ -225,6 +225,24 @@ class StandardizeWeightsTest(keras_parameterized.TestCase): expected = sample_weights * np.array([0.5, 1., 0.5, 0.5, 1.5]) self.assertAllClose(weights, expected) + @tf_test_util.run_in_graph_and_eager_modes + def test_dataset_with_class_weight(self): + model = testing_utils.get_small_functional_mlp(1, 4, input_dim=3) + optimizer = RMSPropOptimizer(learning_rate=0.001) + loss = 'mse' + metrics = ['mae', metrics_module.CategoricalAccuracy()] + model.compile(optimizer, loss, metrics=metrics) + + inputs = np.zeros((10, 3), np.float32) + targets = np.zeros((10, 4), np.float32) + dataset = dataset_ops.Dataset.from_tensor_slices((inputs, targets)) + dataset = dataset.repeat(100) + dataset = dataset.batch(10) + class_weight_np = np.array([0.25, 0.25, 0.25, 0.25]) + class_weight = dict(enumerate(class_weight_np)) + + model.fit(dataset, epochs=1, steps_per_epoch=2, verbose=1, + class_weight=class_weight) if __name__ == '__main__': test.main() -- GitLab From aba19e4143bd12070dcec103c55173cc3c0368b1 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Wed, 16 Jan 2019 10:53:44 -0800 Subject: [PATCH 0795/2345] Add a (failing) randomized test for FusedBatchNorm training I had to add a --tf_xla_reference_device flag so that I could set the reference device to GPU:0. The CPU kernel for FusedBatchNorm does not support NCHW. PiperOrigin-RevId: 229586592 --- tensorflow/compiler/tests/randomized_tests.cc | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/tests/randomized_tests.cc b/tensorflow/compiler/tests/randomized_tests.cc index d23fd12516..735014fc89 100644 --- a/tensorflow/compiler/tests/randomized_tests.cc +++ b/tensorflow/compiler/tests/randomized_tests.cc @@ -80,6 +80,7 @@ int64 tf_xla_random_seed = 0; int32 tf_xla_test_repetitions = 20; int64 tf_xla_max_tensor_size = 10000LL; string* tf_xla_test_device_ptr; // initial value set in main() +string* tf_xla_reference_device_ptr; // initial value set in main() bool tf_xla_test_use_jit = true; string LocalDeviceToFullDeviceName(const string& device) { @@ -321,6 +322,9 @@ class OpTest : public ::testing::Test { // for use as reduction indices. Tensor RandomReductionIndices(int rank); + // Returns a random bit. + bool RandomBool(); + struct WindowedSpatialDims { Padding padding; std::vector kernel_dims; @@ -453,6 +457,11 @@ std::vector OpTest::RandomDims(int min_rank, int max_rank, return dims; } +bool OpTest::RandomBool() { + std::bernoulli_distribution d(0.5); + return d(generator()); +} + Tensor OpTest::RandomTensor(DataType dtype, bool needs_unique_values, absl::Span shape) { Tensor tensor(dtype, TensorShape(shape)); @@ -829,8 +838,8 @@ OpTest::TestResult OpTest::ExpectTfAndXlaOutputsAreClose( VLOG(1) << "Input: " << input_tensors.back().DebugString(); } - string cpu_device = - LocalDeviceToFullDeviceName(absl::StrCat(DEVICE_CPU, ":0")); + string reference_device = + LocalDeviceToFullDeviceName(*tf_xla_reference_device_ptr); string test_device = LocalDeviceToFullDeviceName(*tf_xla_test_device_ptr); DeviceNameUtils::ParsedName parsed_name; @@ -845,9 +854,9 @@ OpTest::TestResult OpTest::ExpectTfAndXlaOutputsAreClose( std::vector expected_inputs, test_inputs; std::vector expected_fetches, test_fetches; Status status = builder.BuildGraph( - absl::StrCat("test", num_tests_, "_expected"), cpu_device, - /* use_jit= */ false, &graph, /* test_node_def= */ nullptr, - &expected_inputs, &expected_fetches); + absl::StrCat("test", num_tests_, "_expected"), reference_device, + /*use_jit=*/false, &graph, /*test_node_def=*/nullptr, &expected_inputs, + &expected_fetches); if (!status.ok()) { LOG(ERROR) << "Expected graph construction failed: " << status; return kFatalError; @@ -3346,11 +3355,41 @@ TEST_F(OpTest, ZerosLike) { }); } +// Example failing run: +// --tf_xla_reference_device=GPU:0 +// --tf_xla_test_use_jit=true --tf_xla_test_device=GPU:0 +// --tf_xla_test_repetitions=2 +// --gunit_filter='OpTest.FusedBatchNormTraining' +// --tf_xla_random_seed=2838146746 +TEST_F(OpTest, FusedBatchNormTraining) { + bool is_nhwc = RandomBool(); + std::vector x_dims = RandomDims(/*min_rank=*/4, /*max_rank=*/4, + /*min_size=*/5, /*max_size=*/20); + std::vector scale_dims = {x_dims[is_nhwc ? 3 : 1]}; + std::vector offset_dims = {x_dims[is_nhwc ? 3 : 1]}; + std::vector mean_dims = {0}; + std::vector variance_dims = {0}; + DataType type = DT_FLOAT; + Repeatedly([&] { + return ExpectTfAndXlaOutputsAreClose( + OpTestBuilder("FusedBatchNorm") + .RandomInput(type, x_dims) + .RandomInput(type, scale_dims) + .RandomInput(type, offset_dims) + .RandomInput(type, mean_dims) + .RandomInput(type, variance_dims) + .Attr("T", type) + .Attr("data_format", is_nhwc ? "NHWC" : "NCHW") + .Attr("epsilon", static_cast(1.001e-05)) + .Attr("is_training", true)); + }); +} } // anonymous namespace } // namespace tensorflow int main(int argc, char** argv) { tensorflow::tf_xla_test_device_ptr = new tensorflow::string("GPU:0"); + tensorflow::tf_xla_reference_device_ptr = new tensorflow::string("CPU:0"); std::vector flag_list = { tensorflow::Flag( "tf_xla_random_seed", &tensorflow::tf_xla_random_seed, @@ -3366,6 +3405,9 @@ int main(int argc, char** argv) { "Maximum number of elements for random input tensors."), tensorflow::Flag("tf_xla_test_device", tensorflow::tf_xla_test_device_ptr, "Tensorflow device type to use for test"), + tensorflow::Flag("tf_xla_reference_device", + tensorflow::tf_xla_reference_device_ptr, + "Tensorflow device type to use for reference"), tensorflow::Flag("tf_xla_test_use_jit", &tensorflow::tf_xla_test_use_jit, "Use JIT compilation for the operator under test"), }; -- GitLab From 2eaf93a349fe2d4d4a2d89ea723302d2b34ec98e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 10:54:45 -0800 Subject: [PATCH 0796/2345] Replace calls to deprecated googletest macros *TEST_CASE() with *TEST_SUITE() PiperOrigin-RevId: 229586776 --- .../compiler/tf2xla/literal_util_test.cc | 2 +- .../partial_run_mgr_test.cc | 2 +- tensorflow/core/framework/bfloat16_test.cc | 2 +- tensorflow/core/framework/model_test.cc | 17 ++++---- tensorflow/core/kernels/conv_ops_test.cc | 42 +++++++++---------- tensorflow/core/lib/gtl/int_type_test.cc | 2 +- tensorflow/core/nccl/nccl_manager_test.cc | 2 +- .../bidirectional_sequence_lstm_test.cc | 2 +- tensorflow/lite/kernels/conv_test.cc | 2 +- .../lite/kernels/depthwise_conv_test.cc | 4 +- .../lite/kernels/fully_connected_test.cc | 4 +- tensorflow/lite/kernels/reshape_test.cc | 6 +-- .../lite/kernels/transpose_conv_test.cc | 2 +- tensorflow/lite/models/speech_test.cc | 8 ++-- .../lite/toco/import_tensorflow_test.cc | 8 ++-- tensorflow/lite/toco/tooling_util_test.cc | 4 +- 16 files changed, 55 insertions(+), 54 deletions(-) diff --git a/tensorflow/compiler/tf2xla/literal_util_test.cc b/tensorflow/compiler/tf2xla/literal_util_test.cc index 15f4c38da2..44bccfe647 100644 --- a/tensorflow/compiler/tf2xla/literal_util_test.cc +++ b/tensorflow/compiler/tf2xla/literal_util_test.cc @@ -49,7 +49,7 @@ using Types = std::pair, std::pair, std::pair>; -TYPED_TEST_CASE(LiteralUtilTest, Types); +TYPED_TEST_SUITE(LiteralUtilTest, Types); TYPED_TEST(LiteralUtilTest, LiteralToQuantizedHostTensor) { using int_type = typename TypeParam::first_type; diff --git a/tensorflow/core/distributed_runtime/partial_run_mgr_test.cc b/tensorflow/core/distributed_runtime/partial_run_mgr_test.cc index 5f7c0cb3ca..a2b799c3e4 100644 --- a/tensorflow/core/distributed_runtime/partial_run_mgr_test.cc +++ b/tensorflow/core/distributed_runtime/partial_run_mgr_test.cc @@ -139,7 +139,7 @@ TEST_P(StatusPropagationTest, PartialRunDoneFirst) { // ExecutorDone and PartialRunDone. Status ExecutorError() { return errors::Internal("executor error"); } Status PartialRunError() { return errors::Internal("partial run error"); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( PartialRunMgr, StatusPropagationTest, ::testing::Values( StatusTestParam{Status::OK(), Status::OK(), Status::OK()}, diff --git a/tensorflow/core/framework/bfloat16_test.cc b/tensorflow/core/framework/bfloat16_test.cc index ce97085494..7da1727e47 100644 --- a/tensorflow/core/framework/bfloat16_test.cc +++ b/tensorflow/core/framework/bfloat16_test.cc @@ -75,7 +75,7 @@ TEST_P(Bfloat16Test, TruncateTest) { EXPECT_EQ(GetParam().expected_rounding, float(rounded)); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( Bfloat16Test_Instantiation, Bfloat16Test, ::testing::Values( Bfloat16TestParam{ diff --git a/tensorflow/core/framework/model_test.cc b/tensorflow/core/framework/model_test.cc index 013f1e61c8..1d7f407e18 100644 --- a/tensorflow/core/framework/model_test.cc +++ b/tensorflow/core/framework/model_test.cc @@ -83,9 +83,10 @@ TEST_P(AsyncInterleaveManyTest, Model) { EXPECT_GE(async_interleave_many->OutputTime(&input_times), 0); } -INSTANTIATE_TEST_CASE_P(Test, AsyncInterleaveManyTest, - ::testing::Combine(::testing::Values(1, 2), - ::testing::Values(0, 50, 100, 200))); +INSTANTIATE_TEST_SUITE_P(Test, AsyncInterleaveManyTest, + ::testing::Combine(::testing::Values(1, 2), + ::testing::Values(0, 50, 100, + 200))); class AsyncKnownRatioTest : public ::testing::TestWithParam> {}; @@ -156,10 +157,10 @@ TEST_P(AsyncKnownRatioTest, Model) { EXPECT_GE(async_known_many->OutputTime(&input_times), 0); } -INSTANTIATE_TEST_CASE_P(Test, AsyncKnownRatioTest, - ::testing::Combine(::testing::Values(1, 2, 4, 8), - ::testing::Values(0, 50, 100, 200), - ::testing::Values(0, 1, 2, 4))); +INSTANTIATE_TEST_SUITE_P(Test, AsyncKnownRatioTest, + ::testing::Combine(::testing::Values(1, 2, 4, 8), + ::testing::Values(0, 50, 100, 200), + ::testing::Values(0, 1, 2, 4))); TEST(InterleaveManyTest, Model) { std::shared_ptr interleave_many = @@ -245,7 +246,7 @@ TEST_P(KnownRatioTest, Model) { num_inputs_per_output * (50 + 100) + 64); } -INSTANTIATE_TEST_CASE_P(Test, KnownRatioTest, ::testing::Values(0, 1, 2, 4)); +INSTANTIATE_TEST_SUITE_P(Test, KnownRatioTest, ::testing::Values(0, 1, 2, 4)); TEST(SourceTest, Model) { std::shared_ptr source = model::MakeSourceNode({0, "source", nullptr}); diff --git a/tensorflow/core/kernels/conv_ops_test.cc b/tensorflow/core/kernels/conv_ops_test.cc index fc93915e16..a4cd67804e 100644 --- a/tensorflow/core/kernels/conv_ops_test.cc +++ b/tensorflow/core/kernels/conv_ops_test.cc @@ -952,8 +952,8 @@ class FusedConv2DWithBiasOpTest : public FusedConv2DOpTest {}; template class FusedConv2DWithBatchNormOpTest : public FusedConv2DOpTest {}; -TYPED_TEST_CASE_P(FusedConv2DWithBiasOpTest); -TYPED_TEST_CASE_P(FusedConv2DWithBatchNormOpTest); +TYPED_TEST_SUITE_P(FusedConv2DWithBiasOpTest); +TYPED_TEST_SUITE_P(FusedConv2DWithBatchNormOpTest); // -------------------------------------------------------------------------- // // Conv2D + BiasAdd + {Relu} // @@ -1035,29 +1035,29 @@ TYPED_TEST_P(FusedConv2DWithBatchNormOpTest, SpatialConvolutionAndRelu) { this->VerifyConv2DWithBatchNormAndRelu(filter_size, filter_count); } -REGISTER_TYPED_TEST_CASE_P(FusedConv2DWithBiasOpTest, // - OneByOneConvolution, // - ImageSizeConvolution, // - SpatialConvolution, // - OneByOneConvolutionAndRelu, // - ImageSizeConvolutionAndRelu, // - SpatialConvolutionAndRelu); - -REGISTER_TYPED_TEST_CASE_P(FusedConv2DWithBatchNormOpTest, // - OneByOneConvolution, // - ImageSizeConvolution, // - SpatialConvolution, // - OneByOneConvolutionAndRelu, // - ImageSizeConvolutionAndRelu, // - SpatialConvolutionAndRelu); +REGISTER_TYPED_TEST_SUITE_P(FusedConv2DWithBiasOpTest, // + OneByOneConvolution, // + ImageSizeConvolution, // + SpatialConvolution, // + OneByOneConvolutionAndRelu, // + ImageSizeConvolutionAndRelu, // + SpatialConvolutionAndRelu); + +REGISTER_TYPED_TEST_SUITE_P(FusedConv2DWithBatchNormOpTest, // + OneByOneConvolution, // + ImageSizeConvolution, // + SpatialConvolution, // + OneByOneConvolutionAndRelu, // + ImageSizeConvolutionAndRelu, // + SpatialConvolutionAndRelu); using FusedBiasAddDataTypes = ::testing::Types; -INSTANTIATE_TYPED_TEST_CASE_P(Test, FusedConv2DWithBiasOpTest, - FusedBiasAddDataTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(Test, FusedConv2DWithBiasOpTest, + FusedBiasAddDataTypes); using FusedBatchNormDataTypes = ::testing::Types; -INSTANTIATE_TYPED_TEST_CASE_P(Test, FusedConv2DWithBatchNormOpTest, - FusedBatchNormDataTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(Test, FusedConv2DWithBatchNormOpTest, + FusedBatchNormDataTypes); //////////////////////////////////////////////////////////////////////////////// // Performance benchmarks for the FusedConv2DWithBiasOp. // diff --git a/tensorflow/core/lib/gtl/int_type_test.cc b/tensorflow/core/lib/gtl/int_type_test.cc index 61d364017c..89d2d0e8fe 100644 --- a/tensorflow/core/lib/gtl/int_type_test.cc +++ b/tensorflow/core/lib/gtl/int_type_test.cc @@ -45,7 +45,7 @@ typedef ::testing::Types SupportedIntTypes; -TYPED_TEST_CASE(IntTypeTest, SupportedIntTypes); +TYPED_TEST_SUITE(IntTypeTest, SupportedIntTypes); TYPED_TEST(IntTypeTest, TestInitialization) { constexpr typename TestFixture::T a; diff --git a/tensorflow/core/nccl/nccl_manager_test.cc b/tensorflow/core/nccl/nccl_manager_test.cc index 58bb84ac57..e65af13389 100644 --- a/tensorflow/core/nccl/nccl_manager_test.cc +++ b/tensorflow/core/nccl/nccl_manager_test.cc @@ -209,7 +209,7 @@ const Scalar NcclManagerTest::max_ = // Instantiate tests for float and double. using TypeList = ::testing::Types; -TYPED_TEST_CASE(NcclManagerTest, TypeList); +TYPED_TEST_SUITE(NcclManagerTest, TypeList); // Test basic sum reduction. TYPED_TEST(NcclManagerTest, BasicSumReduction) { diff --git a/tensorflow/lite/kernels/bidirectional_sequence_lstm_test.cc b/tensorflow/lite/kernels/bidirectional_sequence_lstm_test.cc index 59ea47a2a2..707f06af83 100644 --- a/tensorflow/lite/kernels/bidirectional_sequence_lstm_test.cc +++ b/tensorflow/lite/kernels/bidirectional_sequence_lstm_test.cc @@ -400,7 +400,7 @@ class BidirectionalLSTMOpModel : public SingleOpModel { // indicating whether to use quantization or not. class LSTMOpTest : public ::testing::TestWithParam {}; -INSTANTIATE_TEST_CASE_P(QuantizationOrNot, LSTMOpTest, ::testing::Bool()); +INSTANTIATE_TEST_SUITE_P(QuantizationOrNot, LSTMOpTest, ::testing::Bool()); TEST_P(LSTMOpTest, BlackBoxTestNoCifgNoPeepholeNoProjectionNoClipping) { const int n_batch = 1; diff --git a/tensorflow/lite/kernels/conv_test.cc b/tensorflow/lite/kernels/conv_test.cc index 478df3354f..d0350b2fa7 100644 --- a/tensorflow/lite/kernels/conv_test.cc +++ b/tensorflow/lite/kernels/conv_test.cc @@ -1069,7 +1069,7 @@ TEST_P(ConvolutionOpTest, DISABLED_PointwiseMultifilterHybrid) { 0.0474))); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( ConvolutionOpTest, ConvolutionOpTest, ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMap))); diff --git a/tensorflow/lite/kernels/depthwise_conv_test.cc b/tensorflow/lite/kernels/depthwise_conv_test.cc index d924e6f700..75aed4cc4a 100644 --- a/tensorflow/lite/kernels/depthwise_conv_test.cc +++ b/tensorflow/lite/kernels/depthwise_conv_test.cc @@ -437,11 +437,11 @@ TEST_P(QuantizedDepthwiseConvolutionOpTest, SimpleDilatedTestPaddingSame) { ElementsAreArray({4, 7, 3, 6, 10, 4, 2, 3, 1})); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( DepthwiseConvolutionOpTest, DepthwiseConvolutionOpTest, ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMap))); -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( QuantizedDepthwiseConvolutionOpTest, QuantizedDepthwiseConvolutionOpTest, ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMap))); diff --git a/tensorflow/lite/kernels/fully_connected_test.cc b/tensorflow/lite/kernels/fully_connected_test.cc index d1d29fc7e6..03f4ea7143 100644 --- a/tensorflow/lite/kernels/fully_connected_test.cc +++ b/tensorflow/lite/kernels/fully_connected_test.cc @@ -725,11 +725,11 @@ TEST_P(QuantizedFullyConnectedOpTest, ElementsAre(175, 177, 179, 243, 245, 247)); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( FloatFullyConnectedOpTest, FloatFullyConnectedOpTest, ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMap))); -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( QuantizedFullyConnectedOpTest, QuantizedFullyConnectedOpTest, ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMapNoPie))); diff --git a/tensorflow/lite/kernels/reshape_test.cc b/tensorflow/lite/kernels/reshape_test.cc index f98f3eb9ae..e2210aeaf0 100644 --- a/tensorflow/lite/kernels/reshape_test.cc +++ b/tensorflow/lite/kernels/reshape_test.cc @@ -236,9 +236,9 @@ TEST_P(ReshapeOpTest, Strings) { ElementsAreArray({"1", "2", "3", "4", "5", "6", "7", "8"})); } -INSTANTIATE_TEST_CASE_P(VariedShapeSpec, ReshapeOpTest, - ::testing::Values(kAsReshapeOption, kAsConstantTensor, - kAsTensor)); +INSTANTIATE_TEST_SUITE_P(VariedShapeSpec, ReshapeOpTest, + ::testing::Values(kAsReshapeOption, kAsConstantTensor, + kAsTensor)); } // namespace } // namespace tflite diff --git a/tensorflow/lite/kernels/transpose_conv_test.cc b/tensorflow/lite/kernels/transpose_conv_test.cc index 0520d84a30..44d1336b99 100644 --- a/tensorflow/lite/kernels/transpose_conv_test.cc +++ b/tensorflow/lite/kernels/transpose_conv_test.cc @@ -252,7 +252,7 @@ TEST_P(TransposeConvOpTest, AccuracyTest) { EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 3, 4, 1})); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( TransposeConvOpTest, TransposeConvOpTest, ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMap))); diff --git a/tensorflow/lite/models/speech_test.cc b/tensorflow/lite/models/speech_test.cc index 17b7e8f28e..f3509d1ece 100644 --- a/tensorflow/lite/models/speech_test.cc +++ b/tensorflow/lite/models/speech_test.cc @@ -196,10 +196,10 @@ TEST_P(SpeechTest, DISABLED_TtsTest) { // 200s just to bring up the Android emulator.) static const int kAllInvocations = -1; static const int kFirstFewInvocations = 10; -INSTANTIATE_TEST_CASE_P(LongTests, SpeechTest, - ::testing::Values(kAllInvocations)); -INSTANTIATE_TEST_CASE_P(ShortTests, SpeechTest, - ::testing::Values(kFirstFewInvocations)); +INSTANTIATE_TEST_SUITE_P(LongTests, SpeechTest, + ::testing::Values(kAllInvocations)); +INSTANTIATE_TEST_SUITE_P(ShortTests, SpeechTest, + ::testing::Values(kFirstFewInvocations)); } // namespace } // namespace tflite diff --git a/tensorflow/lite/toco/import_tensorflow_test.cc b/tensorflow/lite/toco/import_tensorflow_test.cc index de7f4cdb7e..8ff3f7733a 100644 --- a/tensorflow/lite/toco/import_tensorflow_test.cc +++ b/tensorflow/lite/toco/import_tensorflow_test.cc @@ -257,8 +257,8 @@ std::vector TestTypes() { return {DT_FLOAT, DT_INT32, DT_INT64, DT_BOOL, DT_QUINT8, DT_COMPLEX64}; } -INSTANTIATE_TEST_CASE_P(ShapeImportTest, ShapeImportTest, - ::testing::ValuesIn(TestTypes())); +INSTANTIATE_TEST_SUITE_P(ShapeImportTest, ShapeImportTest, + ::testing::ValuesIn(TestTypes())); class ContentImportTest : public ::testing::Test { public: @@ -418,8 +418,8 @@ TEST_P(TypeImportTest, BasicTypeInference) { model.operators[0].get()); ASSERT_THAT(op->output_data_types, ::testing::ElementsAre(GetParam().second)); } -INSTANTIATE_TEST_CASE_P(BasicTypeInference, TypeImportTest, - ::testing::ValuesIn(UnaryTestTypes())); +INSTANTIATE_TEST_SUITE_P(BasicTypeInference, TypeImportTest, + ::testing::ValuesIn(UnaryTestTypes())); TEST(ImportTest, TypeInferenceWithFixedOutputType) { // Create an op that has a fixed output type (bool). diff --git a/tensorflow/lite/toco/tooling_util_test.cc b/tensorflow/lite/toco/tooling_util_test.cc index faa6fe412e..f063ce71e9 100644 --- a/tensorflow/lite/toco/tooling_util_test.cc +++ b/tensorflow/lite/toco/tooling_util_test.cc @@ -96,8 +96,8 @@ TEST_P(ShapeTest, Agrees) { } } -INSTANTIATE_TEST_CASE_P(AgreeBroadcast, ShapeTest, - ::testing::ValuesIn(CreateShapePairs())); +INSTANTIATE_TEST_SUITE_P(AgreeBroadcast, ShapeTest, + ::testing::ValuesIn(CreateShapePairs())); static const char kNegativeValuesMessage[] = "Tensor shape should not include negative values"; -- GitLab From 5f9d03ee64231a5ed2b2cb1ae984a339300547c6 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 15 Jan 2019 22:00:31 +0000 Subject: [PATCH 0797/2345] Skip the metrics as suggested in review feedback Signed-off-by: Yong Tang --- tensorflow/python/keras/engine/training_utils_test.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/keras/engine/training_utils_test.py b/tensorflow/python/keras/engine/training_utils_test.py index dfc27cbc3f..64102d24ee 100644 --- a/tensorflow/python/keras/engine/training_utils_test.py +++ b/tensorflow/python/keras/engine/training_utils_test.py @@ -27,6 +27,7 @@ from tensorflow.python.data.ops import readers from tensorflow.python.eager import context from tensorflow.python.framework import tensor_util from tensorflow.python.keras import keras_parameterized +from tensorflow.python.keras import testing_utils from tensorflow.python.keras.engine import training_utils from tensorflow.python.keras.utils import tf_utils from tensorflow.python.platform import test @@ -225,13 +226,9 @@ class StandardizeWeightsTest(keras_parameterized.TestCase): expected = sample_weights * np.array([0.5, 1., 0.5, 0.5, 1.5]) self.assertAllClose(weights, expected) - @tf_test_util.run_in_graph_and_eager_modes def test_dataset_with_class_weight(self): model = testing_utils.get_small_functional_mlp(1, 4, input_dim=3) - optimizer = RMSPropOptimizer(learning_rate=0.001) - loss = 'mse' - metrics = ['mae', metrics_module.CategoricalAccuracy()] - model.compile(optimizer, loss, metrics=metrics) + model.compile('rmsprop', 'mse') inputs = np.zeros((10, 3), np.float32) targets = np.zeros((10, 4), np.float32) -- GitLab From 168eca540fc3b2090b15535d6728ed88520fae49 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Wed, 16 Jan 2019 11:14:30 -0800 Subject: [PATCH 0798/2345] Update tests to use the new distribute_py_test build rule. PiperOrigin-RevId: 229590924 --- tensorflow/contrib/distribute/python/BUILD | 50 +++++++++++-------- .../core/platform/default/distribute.bzl | 2 + 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 384fe53923..3a4f23e597 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -640,12 +640,11 @@ distribute_py_test( ) # TODO(b/121200287): Remove this in 2.0 -cuda_py_test( +distribute_py_test( name = "keras_backward_compat_test", srcs = ["keras_backward_compat_test.py"], - additional_deps = [ - ":keras_test_lib", - ], + full_precision = True, + main = "keras_backward_compat_test.py", shard_count = 16, tags = [ "multi_and_single_gpu", @@ -654,6 +653,9 @@ cuda_py_test( "no_windows_gpu", "notsan", ], + deps = [ + ":keras_test_lib", + ], ) py_library( @@ -680,13 +682,12 @@ py_library( ], ) -cuda_py_test( +distribute_py_test( name = "keras_dnn_correctness_test", size = "medium", srcs = ["keras_dnn_correctness_test.py"], - additional_deps = [ - ":keras_correctness_test_lib", - ], + full_precision = True, + main = "keras_dnn_correctness_test.py", # Shard count is set to an odd number to distribute tasks across # shards more evenly. shard_count = 19, @@ -697,15 +698,17 @@ cuda_py_test( "no_windows_gpu", "notsan", ], + deps = [ + ":keras_correctness_test_lib", + ], ) -cuda_py_test( +distribute_py_test( name = "keras_image_model_correctness_test", size = "medium", srcs = ["keras_image_model_correctness_test.py"], - additional_deps = [ - ":keras_correctness_test_lib", - ], + full_precision = True, + main = "keras_image_model_correctness_test.py", # Shard count is set to an odd number to distribute tasks across # shards more evenly. shard_count = 31, @@ -716,15 +719,17 @@ cuda_py_test( "no_windows_gpu", "notsan", ], + deps = [ + ":keras_correctness_test_lib", + ], ) -cuda_py_test( +distribute_py_test( name = "keras_embedding_model_correctness_test", size = "medium", srcs = ["keras_embedding_model_correctness_test.py"], - additional_deps = [ - ":keras_correctness_test_lib", - ], + full_precision = True, + main = "keras_embedding_model_correctness_test.py", # Shard count is set to an odd number to distribute tasks across # shards more evenly. shard_count = 31, @@ -735,15 +740,17 @@ cuda_py_test( "no_windows_gpu", "notsan", ], + deps = [ + ":keras_correctness_test_lib", + ], ) -cuda_py_test( +distribute_py_test( name = "keras_lstm_model_correctness_test", size = "medium", srcs = ["keras_lstm_model_correctness_test.py"], - additional_deps = [ - ":keras_correctness_test_lib", - ], + full_precision = True, + main = "keras_lstm_model_correctness_test.py", # Shard count is set to an odd number to distribute tasks across # shards more evenly. shard_count = 31, @@ -754,6 +761,9 @@ cuda_py_test( "no_windows_gpu", "notsan", ], + deps = [ + ":keras_correctness_test_lib", + ], ) py_library( diff --git a/tensorflow/core/platform/default/distribute.bzl b/tensorflow/core/platform/default/distribute.bzl index a3afc37f03..ea8fa8708e 100644 --- a/tensorflow/core/platform/default/distribute.bzl +++ b/tensorflow/core/platform/default/distribute.bzl @@ -11,6 +11,7 @@ def distribute_py_test( main = None, args = [], shard_count = 1, + full_precision = False, **kwargs): """Generates py_test targets for CPU and GPU. @@ -26,6 +27,7 @@ def distribute_py_test( **kwargs: extra keyword arguments to the test. """ + _ignore = (full_precision) cuda_py_test( name = name, srcs = srcs, -- GitLab From 42f6aa2d5aeda0a586fd64c82e7cc71c98e8e047 Mon Sep 17 00:00:00 2001 From: Tong Shen Date: Wed, 16 Jan 2019 11:15:04 -0800 Subject: [PATCH 0799/2345] Add a DFSFrom() algorithm. PiperOrigin-RevId: 229591031 --- tensorflow/core/graph/algorithm.cc | 39 +++++++++++++++++++++++------- tensorflow/core/graph/algorithm.h | 12 +++++++++ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/tensorflow/core/graph/algorithm.cc b/tensorflow/core/graph/algorithm.cc index 25bd3516a1..5ad1c19dc1 100644 --- a/tensorflow/core/graph/algorithm.cc +++ b/tensorflow/core/graph/algorithm.cc @@ -22,25 +22,29 @@ limitations under the License. #include "tensorflow/core/platform/logging.h" namespace tensorflow { - -void DFS(const Graph& g, const std::function& enter, - const std::function& leave, - const NodeComparator& stable_comparator, - const EdgeFilter& edge_filter) { +namespace { +template +void DFSFromHelper(const Graph& g, gtl::ArraySlice start, + const std::function& enter, + const std::function& leave, + const NodeComparator& stable_comparator, + const EdgeFilter& edge_filter) { // Stack of work to do. struct Work { - Node* node; + T node; bool leave; // Are we entering or leaving n? }; - std::vector stack; - stack.push_back(Work{g.source_node(), false}); + std::vector stack(start.size()); + for (int i = 0; i < start.size(); ++i) { + stack[i] = Work{start[i], false}; + } std::vector visited(g.num_node_ids(), false); while (!stack.empty()) { Work w = stack.back(); stack.pop_back(); - Node* n = w.node; + T n = w.node; if (w.leave) { leave(n); continue; @@ -80,6 +84,23 @@ void DFS(const Graph& g, const std::function& enter, } } } +} // namespace + +void DFS(const Graph& g, const std::function& enter, + const std::function& leave, + const NodeComparator& stable_comparator, + const EdgeFilter& edge_filter) { + DFSFromHelper(g, {g.source_node()}, enter, leave, stable_comparator, + edge_filter); +} + +void DFSFrom(const Graph& g, gtl::ArraySlice start, + const std::function& enter, + const std::function& leave, + const NodeComparator& stable_comparator, + const EdgeFilter& edge_filter) { + DFSFromHelper(g, start, enter, leave, stable_comparator, edge_filter); +} void ReverseDFS(const Graph& g, const std::function& enter, const std::function& leave, diff --git a/tensorflow/core/graph/algorithm.h b/tensorflow/core/graph/algorithm.h index 45f8a29a92..3479605df8 100644 --- a/tensorflow/core/graph/algorithm.h +++ b/tensorflow/core/graph/algorithm.h @@ -55,6 +55,18 @@ extern void DFS(const Graph& g, const std::function& enter, const NodeComparator& stable_comparator = {}, const EdgeFilter& edge_filter = {}); +// Perform a depth-first-search on g starting at the 'start' nodes. +// If enter is not empty, calls enter(n) before visiting any children of n. +// If leave is not empty, calls leave(n) after visiting all children of n. +// If stable_comparator is set, a stable ordering of visit is achieved by +// sorting a node's neighbors first before visiting them. +// If edge_filter is set then ignores edges for which edge_filter returns false. +extern void DFSFrom(const Graph& g, gtl::ArraySlice start, + const std::function& enter, + const std::function& leave, + const NodeComparator& stable_comparator = {}, + const EdgeFilter& edge_filter = {}); + // Perform a reverse depth-first-search on g starting at the sink node. // If enter is not empty, calls enter(n) before visiting any parents of n. // If leave is not empty, calls leave(n) after visiting all parents of n. -- GitLab From 3e41d77991f3807eb796a62573f2451cedee0c63 Mon Sep 17 00:00:00 2001 From: Katherine Wu Date: Wed, 16 Jan 2019 11:23:28 -0800 Subject: [PATCH 0800/2345] Add mean tensor to Keras metrics. (From tf.metrics.mean_tensor). PiperOrigin-RevId: 229592753 --- tensorflow/python/keras/metrics.py | 111 ++++++++++++++++++ tensorflow/python/keras/metrics_test.py | 150 ++++++++++++++++++++++++ 2 files changed, 261 insertions(+) diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index 245dffe99e..d6010e3cb0 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -29,6 +29,7 @@ from tensorflow.python.eager import context from tensorflow.python.eager import function from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops +from tensorflow.python.framework import tensor_shape from tensorflow.python.keras import backend as K from tensorflow.python.keras.engine.base_layer import Layer from tensorflow.python.keras.losses import binary_crossentropy @@ -2300,6 +2301,116 @@ class MeanIoU(Metric): return dict(list(base_config.items()) + list(config.items())) +class MeanTensor(Metric): + """Computes the element-wise (weighted) mean of the given tensors. + + `MeanTensor` returns a tensor with the same shape of the input tensors. The + mean value is updated by keeping local variables `total` and `count`. The + `total` tracks the sum of the weighted values, and `count` stores the sum of + the weighted counts. + + Usage: + + ```python + m = tf.metrics.MeanTensor() + m.update_state([0, 1, 2, 3]) + m.update_state([4, 5, 6, 7]) + print('Result: ', m.result().numpy()) # Result: [2, 3, 4, 5] + m.update_state([12, 10, 8, 6], sample_weights= [0, 0.2, 0.5, 1]) + print('Result: ', m.result().numpy()) # Result: [2, 3.636, 4.8, 5.333] + ``` + """ + + def __init__(self, name='mean_tensor', dtype=None): + """Creates a `MeanTensor` instance. + + Args: + name: (Optional) string name of the metric instance. + dtype: (Optional) data type of the metric result. + """ + super(MeanTensor, self).__init__(name=name, dtype=dtype) + self._shape = None + self._total = None + self._count = None + self._built = False + + def _build(self, shape): + self._shape = tensor_shape.TensorShape(shape) + # Create new state variables + self._total = self.add_weight( + 'total', shape=shape, initializer=init_ops.zeros_initializer) + self._count = self.add_weight( + 'count', shape=shape, initializer=init_ops.zeros_initializer) + with ops.init_scope(): + if not context.executing_eagerly(): + K._initialize_variables(K._get_session()) # pylint: disable=protected-access + self._built = True + + @property + def total(self): + return self._total if self._built else None + + @property + def count(self): + return self._count if self._built else None + + def update_state(self, values, sample_weight=None): + """Accumulates statistics for computing the element-wise mean. + + Args: + values: Per-example value. + sample_weight: Optional weighting of each example. Defaults to 1. + + Returns: + Update op. + """ + values = math_ops.cast(values, self._dtype) + if not self._built: + self._build(values.shape) + elif values.shape != self._shape: + raise ValueError('MeanTensor input values must always have the same ' + 'shape. Expected shape (set during the first call): {}. ' + 'Got: {}'.format(self._shape, values.get_shape())) + + num_values = array_ops.ones_like(values) + if sample_weight is not None: + sample_weight = math_ops.cast(sample_weight, self._dtype) + + # Update dimensions of weights to match with values if possible. + values, _, sample_weight = squeeze_or_expand_dimensions( + values, None, sample_weight) + try: + # Broadcast weights if possible. + sample_weight = weights_broadcast_ops.broadcast_weights( + sample_weight, values) + except ValueError: + # Reduce values to same ndim as weight array + ndim = K.ndim(values) + weight_ndim = K.ndim(sample_weight) + values = math_ops.reduce_mean( + values, axis=list(range(weight_ndim, ndim))) + + num_values = math_ops.multiply(num_values, sample_weight) + values = math_ops.multiply(values, sample_weight) + + update_total_op = state_ops.assign_add(self._total, values) + with ops.control_dependencies([update_total_op]): + return state_ops.assign_add(self._count, num_values) + + def result(self): + if not self._built: + raise ValueError( + 'MeanTensor does not have any result yet. Please call the MeanTensor ' + 'instance or use `.update_state(value)` before retrieving the result.' + ) + return math_ops.div_no_nan(self.total, self.count) + + def reset_states(self): + if self._built: + for v in self.variables: + K.set_value(v, np.zeros(self._shape.as_list())) + + def accuracy(y_true, y_pred): y_pred.get_shape().assert_is_compatible_with(y_true.get_shape()) if y_true.dtype != y_pred.dtype: diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index bf2f4d88fa..bd94132a9a 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -23,6 +23,7 @@ import os import numpy as np from tensorflow.python.eager import context +from tensorflow.python.eager import function as eager_function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops @@ -30,6 +31,7 @@ from tensorflow.python.framework import test_util from tensorflow.python.keras import keras_parameterized from tensorflow.python.keras import layers from tensorflow.python.keras import metrics +from tensorflow.python.keras import Model from tensorflow.python.keras import testing_utils from tensorflow.python.ops import array_ops from tensorflow.python.ops import variables @@ -1050,6 +1052,154 @@ class MeanIoUTest(test.TestCase): self.assertAllClose(self.evaluate(result), expected_result, atol=1e-3) +class MeanTensorTest(keras_parameterized.TestCase): + + @test_util.run_in_graph_and_eager_modes + def test_config(self): + m = metrics.MeanTensor(name='mean_by_element') + + # check config + self.assertEqual(m.name, 'mean_by_element') + self.assertTrue(m.stateful) + self.assertEqual(m.dtype, dtypes.float32) + self.assertEqual(len(m.variables), 0) + + with self.assertRaisesRegexp(ValueError, 'does not have any result yet'): + m.result() + + self.evaluate(m([[3], [5], [3]])) + self.assertAllEqual(m._shape, [3, 1]) + + m2 = metrics.MeanTensor.from_config(m.get_config()) + self.assertEqual(m2.name, 'mean_by_element') + self.assertTrue(m2.stateful) + self.assertEqual(m2.dtype, dtypes.float32) + self.assertEqual(len(m2.variables), 0) + + @test_util.run_in_graph_and_eager_modes + def test_unweighted(self): + m = metrics.MeanTensor(dtype=dtypes.float64) + + # check __call__() + self.assertAllClose(self.evaluate(m([100, 40])), [100, 40]) + self.assertAllClose(self.evaluate(m.total), [100, 40]) + self.assertAllClose(self.evaluate(m.count), [1, 1]) + + # check update_state() and result() + state accumulation + tensor input + update_op = m.update_state(ops.convert_n_to_tensor([1, 5])) + self.evaluate(update_op) + self.assertAllClose(self.evaluate(m.result()), [50.5, 22.5]) + self.assertAllClose(self.evaluate(m.total), [101, 45]) + self.assertAllClose(self.evaluate(m.count), [2, 2]) + + # check reset_states() + m.reset_states() + self.assertAllClose(self.evaluate(m.total), [0, 0]) + self.assertAllClose(self.evaluate(m.count), [0, 0]) + + @test_util.run_in_graph_and_eager_modes + def test_weighted(self): + m = metrics.MeanTensor(dtype=dtypes.float64) + self.assertEqual(m.dtype, dtypes.float64) + + # check scalar weight + result_t = m([100, 30], sample_weight=0.5) + self.assertAllClose(self.evaluate(result_t), [100, 30]) + self.assertAllClose(self.evaluate(m.total), [50, 15]) + self.assertAllClose(self.evaluate(m.count), [0.5, 0.5]) + + # check weights not scalar and weights rank matches values rank + result_t = m([1, 5], sample_weight=[1, 0.2]) + result = self.evaluate(result_t) + self.assertAllClose(result, [51 / 1.5, 16 / 0.7], 2) + self.assertAllClose(self.evaluate(m.total), [51, 16]) + self.assertAllClose(self.evaluate(m.count), [1.5, 0.7]) + + # check weights broadcast + result_t = m([1, 2], sample_weight=0.5) + self.assertAllClose(self.evaluate(result_t), [51.5 / 2, 17 / 1.2]) + self.assertAllClose(self.evaluate(m.total), [51.5, 17]) + self.assertAllClose(self.evaluate(m.count), [2, 1.2]) + + # check weights squeeze + result_t = m([1, 5], sample_weight=[[1], [0.2]]) + self.assertAllClose(self.evaluate(result_t), [52.5 / 3, 18 / 1.4]) + self.assertAllClose(self.evaluate(m.total), [52.5, 18]) + self.assertAllClose(self.evaluate(m.count), [3, 1.4]) + + # check weights expand + m = metrics.MeanTensor((2, 1), dtype=dtypes.float64) + self.evaluate(variables.variables_initializer(m.variables)) + result_t = m([[1], [5]], sample_weight=[1, 0.2]) + self.assertAllClose(self.evaluate(result_t), [[1], [5]]) + self.assertAllClose(self.evaluate(m.total), [[1], [1]]) + self.assertAllClose(self.evaluate(m.count), [[1], [0.2]]) + + @test_util.run_in_graph_and_eager_modes + def test_invalid_value_shape(self): + m = metrics.MeanTensor(dtype=dtypes.float64) + m([1]) + with self.assertRaisesRegexp( + ValueError, 'MeanTensor input values must always have the same shape'): + m([1, 5]) + + @test_util.run_in_graph_and_eager_modes + def test_build_in_tf_function(self): + """Ensure that variables are created correctly in a tf function.""" + m = metrics.MeanTensor(dtype=dtypes.float64) + + @eager_function.defun + def call_metric(x): + return m(x) + + self.assertAllClose(self.evaluate(call_metric([100, 40])), [100, 40]) + self.assertAllClose(self.evaluate(m.total), [100, 40]) + self.assertAllClose(self.evaluate(m.count), [1, 1]) + self.assertAllClose(self.evaluate(call_metric([20, 2])), [60, 21]) + + def test_in_keras_model(self): + with context.eager_mode(): + class ModelWithMetric(Model): + + def __init__(self): + super(ModelWithMetric, self).__init__() + self.dense1 = layers.Dense( + 3, activation='relu', kernel_initializer='ones') + self.dense2 = layers.Dense( + 1, activation='sigmoid', kernel_initializer='ones') + self.mean_tensor = metrics.MeanTensor() + + def call(self, x): + x = self.dense1(x) + x = self.dense2(x) + self.mean_tensor(self.dense1.kernel) + return x + + model = ModelWithMetric() + model.compile( + loss='mae', + optimizer='rmsprop', + run_eagerly=True) + + x = np.ones((100, 4)) + y = np.zeros((100, 1)) + model.evaluate(x, y, batch_size=50) + self.assertAllClose(self.evaluate(model.mean_tensor.result()), + np.ones((4, 3))) + self.assertAllClose(self.evaluate(model.mean_tensor.total), + np.full((4, 3), 2)) + self.assertAllClose(self.evaluate(model.mean_tensor.count), + np.full((4, 3), 2)) + + model.evaluate(x, y, batch_size=25) + self.assertAllClose(self.evaluate(model.mean_tensor.result()), + np.ones((4, 3))) + self.assertAllClose(self.evaluate(model.mean_tensor.total), + np.full((4, 3), 4)) + self.assertAllClose(self.evaluate(model.mean_tensor.count), + np.full((4, 3), 4)) + + def _get_model(compile_metrics): model_layers = [ layers.Dense(3, activation='relu', kernel_initializer='ones'), -- GitLab From 0f91a840d86210f298c8caf0e8640b4b9925a058 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 11:25:00 -0800 Subject: [PATCH 0801/2345] Support lists of floating points in custom op attributes. PiperOrigin-RevId: 229593054 --- tensorflow/lite/toco/tflite/operator.cc | 15 +++++++++++++++ tensorflow/lite/toco/tflite/operator_test.cc | 14 +++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/toco/tflite/operator.cc b/tensorflow/lite/toco/tflite/operator.cc index d9c051ebf7..088673fd95 100644 --- a/tensorflow/lite/toco/tflite/operator.cc +++ b/tensorflow/lite/toco/tflite/operator.cc @@ -1647,6 +1647,13 @@ class TensorFlowUnsupported : public BaseOperator { } fbb->EndVector(start, /*typed=*/true, /*fixed=*/false); has_valid_attr = true; + } else if (attr.list().f_size() > 0) { + auto start = fbb->StartVector(key); + for (const float v : attr.list().f()) { + fbb->Add(v); + } + fbb->EndVector(start, /*typed=*/true, /*fixed=*/false); + has_valid_attr = true; } else { LOG(WARNING) << "Ignoring unsupported type in list attribute with key '" @@ -1707,6 +1714,14 @@ class TensorFlowUnsupported : public BaseOperator { } break; } + case 13: { // flexbuffers::FBT_VECTOR_FLOAT: { + auto* list = (*attr)[key].mutable_list(); + const auto& vector = value.AsTypedVector(); + for (size_t i = 0; i < vector.size(); i++) { + list->add_f(vector[i].AsFloat()); + } + break; + } default: LOG(WARNING) << "Ignoring unsupported attribute type with key '" << key << "'"; diff --git a/tensorflow/lite/toco/tflite/operator_test.cc b/tensorflow/lite/toco/tflite/operator_test.cc index 215eda82f6..f77780488a 100644 --- a/tensorflow/lite/toco/tflite/operator_test.cc +++ b/tensorflow/lite/toco/tflite/operator_test.cc @@ -569,6 +569,12 @@ TEST_F(OperatorTest, TensorFlowUnsupported) { (*attr)["str_attr"].set_s("Hello World"); (*attr)["int_attr"].set_i(17); (*attr)["bool_attr"].set_b(true); + { + auto* list = (*attr)["list_float_attr"].mutable_list(); + list->add_f(std::numeric_limits::min()); + list->add_f(2.0); + list->add_f(-std::numeric_limits::max()); + } { auto* list = (*attr)["list_int_attr"].mutable_list(); list->add_i(1); @@ -588,7 +594,13 @@ TEST_F(OperatorTest, TensorFlowUnsupported) { EXPECT_EQ("Hello World", output_attr.at("str_attr").s()); EXPECT_EQ(17, output_attr.at("int_attr").i()); EXPECT_EQ(true, output_attr.at("bool_attr").b()); - + { + const auto& list = output_attr.at("list_float_attr").list(); + ASSERT_EQ(3, list.f_size()); + EXPECT_EQ(std::numeric_limits::min(), list.f(0)); + EXPECT_EQ(2.0, list.f(1)); + EXPECT_EQ(-std::numeric_limits::max(), list.f(2)); + } { const auto& list = output_attr.at("list_int_attr").list(); ASSERT_EQ(4, list.i_size()); -- GitLab From 6d5147395469d35d695dbd9d2498ad36bcfcca46 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 11:25:34 -0800 Subject: [PATCH 0802/2345] Support previously added 'truncate' attribute on floating datatype casts to apply in tf2xla as well. PiperOrigin-RevId: 229593179 --- tensorflow/compiler/tests/randomized_tests.cc | 34 +++++++++++++- tensorflow/compiler/tf2xla/kernels/cast_op.cc | 47 +++++++++++++++++++ tensorflow/compiler/xla/primitive_util.cc | 16 +++++++ tensorflow/compiler/xla/primitive_util.h | 4 ++ 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/tests/randomized_tests.cc b/tensorflow/compiler/tests/randomized_tests.cc index 735014fc89..1521cc760b 100644 --- a/tensorflow/compiler/tests/randomized_tests.cc +++ b/tensorflow/compiler/tests/randomized_tests.cc @@ -63,6 +63,7 @@ limitations under the License. #include "tensorflow/core/graph/graph.h" #include "tensorflow/core/graph/graph_constructor.h" #include "tensorflow/core/kernels/ops_util.h" +#include "tensorflow/core/lib/bfloat16/bfloat16.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/platform/test.h" @@ -769,8 +770,22 @@ Status TensorsAreEqualImpl(const Tensor& x, const Tensor& y) { for (int i = 0; i < Tx.size(); ++i) { if (Tx(i) != Ty(i)) { return errors::InvalidArgument(absl::StrCat( - i, "-th tensor element isn't equal: ", Tx(i), " vs. ", Ty(i), - ". x = ", x.DebugString(), "y = ", y.DebugString())); + i, "-th tensor element isn't equal: ", Str(Tx(i)), " vs. ", + Str(Ty(i)), ". x = ", x.DebugString(), "y = ", y.DebugString())); + } + } + return Status::OK(); +} + +Status TensorsAreEqualImplBfloat16(const Tensor& x, const Tensor& y) { + auto Tx = x.flat(); + auto Ty = y.flat(); + for (int i = 0; i < Tx.size(); ++i) { + if (Tx(i) != Ty(i)) { + return errors::InvalidArgument(absl::StrCat( + i, "-th tensor element isn't equal: ", static_cast(Tx(i)), + " vs. ", static_cast(Ty(i)), ". x = ", x.DebugString(), + "y = ", y.DebugString())); } } return Status::OK(); @@ -806,6 +821,8 @@ Status TensorsAreClose(const Tensor& a, const Tensor& b, double atol, return TensorsAreEqualImpl(a, b); case DT_BOOL: return TensorsAreEqualImpl(a, b); + case DT_BFLOAT16: + return TensorsAreEqualImplBfloat16(a, b); default: LOG(FATAL) << "Unexpected type : " << DataTypeString(a.dtype()); } @@ -1380,6 +1397,19 @@ TEST_F(OpTest, Cast) { }); } +TEST_F(OpTest, CastBF16) { + Repeatedly([this]() { + DataType src_type, dst_type; + src_type = Choose({DT_FLOAT}); + dst_type = Choose({DT_BFLOAT16}); + return ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Cast") + .RandomInput(src_type) + .Attr("SrcT", src_type) + .Attr("DstT", dst_type) + .Attr("Truncate", true)); + }); +} + TEST_F(OpTest, Ceil) { Repeatedly([this]() { return ExpectTfAndXlaOutputsAreClose( diff --git a/tensorflow/compiler/tf2xla/kernels/cast_op.cc b/tensorflow/compiler/tf2xla/kernels/cast_op.cc index ce8131eeb4..db58c2e651 100644 --- a/tensorflow/compiler/tf2xla/kernels/cast_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/cast_op.cc @@ -12,6 +12,7 @@ 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/tf2xla/lib/util.h" #include "tensorflow/compiler/tf2xla/type_util.h" #include "tensorflow/compiler/tf2xla/xla_helpers.h" @@ -19,6 +20,7 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/xla_op_registry.h" #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/compiler/xla/primitive_util.h" +#include "tensorflow/compiler/xla/xla_data.pb.h" #include "tensorflow/core/framework/kernel_def_builder.h" namespace tensorflow { @@ -31,6 +33,20 @@ class CastOp : public XlaOpKernel { OP_REQUIRES_OK(ctx, ctx->GetAttr("DstT", &dst_dtype_)); OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(src_dtype_, &src_type_)); OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(dst_dtype_, &dst_type_)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("Truncate", &use_truncation_)); + } + + xla::PrimitiveType GetUnsignedIntTypeOfSameWidth(int64 src_bitwidth) { + switch (src_bitwidth) { + case 16: + return xla::U16; + case 32: + return xla::U32; + case 64: + return xla::U64; + default: + return xla::PRIMITIVE_TYPE_INVALID; + } } void Compile(XlaOpKernelContext* ctx) override { @@ -48,6 +64,36 @@ class CastOp : public XlaOpKernel { // imaginary part. output = xla::ConvertElementType(xla::Real(input), dst_type_); } else { + if (use_truncation_) { + OP_REQUIRES( + ctx, + xla::primitive_util::IsFloatingPointType(src_type_) && + xla::primitive_util::IsFloatingPointType(dst_type_), + errors::Unimplemented("Truncate attribute is only " + "implemented for floating point datatypes.")); + int mantissa_difference = + xla::primitive_util::SignificandWidth(src_type_) - + xla::primitive_util::SignificandWidth(dst_type_); + OP_REQUIRES(ctx, mantissa_difference > 0, + errors::Unimplemented( + "Truncate attribute is only implemented in cases where " + "dst datatype " + "has fewer mantissa bits than the src datatype")); + int src_bitwidth = xla::primitive_util::BitWidth(src_type_); + + // Bitcast to same-width integer, mask off the LSBs, bitcast back to the + // source datatype. + int64 mask = ~((1L << mantissa_difference) - 1); + xla::PrimitiveType same_width_int = + GetUnsignedIntTypeOfSameWidth(src_bitwidth); + OP_REQUIRES(ctx, same_width_int != xla::PRIMITIVE_TYPE_INVALID, + errors::Unimplemented("Unexpected type bitwidth")); + input = xla::BitcastConvertType( + xla::And( + xla::BitcastConvertType(input, same_width_int), + ::tensorflow::IntegerLiteral(builder, same_width_int, mask)), + src_type_); + } output = xla::ConvertElementType(input, dst_type_); } @@ -57,6 +103,7 @@ class CastOp : public XlaOpKernel { protected: DataType src_dtype_, dst_dtype_; xla::PrimitiveType src_type_, dst_type_; + bool use_truncation_; TF_DISALLOW_COPY_AND_ASSIGN(CastOp); }; diff --git a/tensorflow/compiler/xla/primitive_util.cc b/tensorflow/compiler/xla/primitive_util.cc index 04b40efbed..3386d2e097 100644 --- a/tensorflow/compiler/xla/primitive_util.cc +++ b/tensorflow/compiler/xla/primitive_util.cc @@ -18,11 +18,27 @@ limitations under the License. #include "absl/strings/ascii.h" #include "absl/strings/numbers.h" #include "tensorflow/compiler/xla/util.h" +#include "tensorflow/compiler/xla/xla_data.pb.h" #include "tensorflow/core/platform/logging.h" namespace xla { namespace primitive_util { +int SignificandWidth(PrimitiveType type) { + switch (type) { + case F32: + return std::numeric_limits::digits; + case F64: + return std::numeric_limits::digits; + case BF16: + return kBFloat16MantissaBits + 1; + case F16: + return 11; + default: + LOG(FATAL) << "Not a floating data type " << type; + } +} + bool IsFloatingPointType(PrimitiveType type) { return type == F16 || type == F32 || type == F64 || type == BF16; } diff --git a/tensorflow/compiler/xla/primitive_util.h b/tensorflow/compiler/xla/primitive_util.h index 739a7c409c..d32505335d 100644 --- a/tensorflow/compiler/xla/primitive_util.h +++ b/tensorflow/compiler/xla/primitive_util.h @@ -29,6 +29,10 @@ limitations under the License. namespace xla { namespace primitive_util { +// Returns the count of significand (mantissa) bits for float datatypes. +// For non-float datatypes, results in a LOG(FATAL). +int SignificandWidth(PrimitiveType type); + // The number of exponent bits in a BF16 value. const int kBFloat16ExponentBits = 8; -- GitLab From d80c2aa144b092937ffd1f019cfe461ea668839f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 11:29:39 -0800 Subject: [PATCH 0803/2345] - Fixed multiple broken links in the best_practices.md guide as a result of the recent move out of contrib PiperOrigin-RevId: 229594101 --- .../lite/g3doc/performance/best_practices.md | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tensorflow/lite/g3doc/performance/best_practices.md b/tensorflow/lite/g3doc/performance/best_practices.md index 090f19e810..5f41a70275 100644 --- a/tensorflow/lite/g3doc/performance/best_practices.md +++ b/tensorflow/lite/g3doc/performance/best_practices.md @@ -1,6 +1,9 @@ # Performance best practices -Mobile and embedded devices have limited computational resources and it is important to keep your application resource efficient. We have compiled a list of best practices and strategies you can use to optimize your model and application when using Tensorflow Lite. +Mobile and embedded devices have limited computational resources and it is +important to keep your application resource efficient. We have compiled a list +of best practices and strategies that you can use to optimize your model and +application when using TensorFlow Lite. ## Choose the best model for the task Depending on the task you will need to make a tradeoff between model complexity and size. If your task requires high accuracy then you may need a large and complex model. Some tasks may work with a less precise model, for these tasks it is better to use a smaller but less precise model. Smaller models not only use less disk space and memory but are generally faster and more energy efficient. For example, graphs below show accuracy and latency tradeoff for some common image classification models. @@ -10,7 +13,7 @@ Depending on the task you will need to make a tradeoff between model complexity ![latency vs model size](../images/performance/model_size_vs_latency.png "Latency vs Model size") -One example of models optimized for mobile devices are [MobileNets](https://arxiv.org/abs/1704.04861), which are optimized for mobile vision applications. Tensorflow Lite [models page](../models.md) lists several other models that have been optimized specifically for mobile and embedded devices. +One example of models optimized for mobile devices are [MobileNets](https://arxiv.org/abs/1704.04861), which are optimized for mobile vision applications. TensorFlow Lite [models page](../models.md) lists several other models that have been optimized specifically for mobile and embedded devices. You can retrain the listed models on your own dataset by using transfer learning. Check out our transfer learning tutorial for [image classification](https://codelabs.developers.google.com/codelabs/tensorflow-for-poets/#0) and @@ -18,20 +21,37 @@ You can retrain the listed models on your own dataset by using transfer learning ## Profile your model -Once you have selected a candidate model that is right for your task, it is a good practice to profile and benchmark your model. Tensorflow Lite [benchmarking tool](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/benchmark) has a built-in profiler that shows per operator profiling statistics. This can help in understanding performance bottlenecks and which operators dominate the computation time. +Once you have selected a candidate model that is right for your task, it is a good practice to profile and benchmark your model. TensorFlow Lite [benchmarking tool](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/benchmark) has a built-in profiler that shows per operator profiling statistics. This can help in understanding performance bottlenecks and which operators dominate the computation time. ## Profile and optimize operators in the graph If a particular operator appears frequently in the model and based on profiling you find the operator consuming the most amount of time, you can look into optimizing the operator. - This scenario should be rare as Tensorflow Lite has optimized versions for most ops. However you may be able to write a faster version of a custom op, if you know the constraints in which the operator is executed. Check out our [custom operator documentation](../custom_operators.md). + This scenario should be rare as TensorFlow Lite has optimized versions for most ops. However you may be able to write a faster version of a custom op, if you know the constraints in which the operator is executed. Check out our [custom operator documentation](../custom_operators.md). ## Quantize your model If your model uses floating point weights or activations then it may be possible to reduce the size of model up to ~4x by using quantization and other model optimizations. Check out our [model optimization toolkit](model_optimization.md) for details about optimizing your model. ## Tweak the number of threads -Tensorflow Lite supports multi-threaded kernels for many operators. You can increase the number of threads and speed up execution of operators. Increasing the number of threads will however make your model use more resources and power. For some applications latency may be more important than energy efficiency. You can increase the number of threads by setting the number of [interpreter](https://github.com/tensorflow/tensorflow/blob/1084594657a5d139102ac794f84d1427a710e39a/tensorflow/lite/interpreter.h#L337) threads. Multi-threaded execution however comes at the cost of increased performance variability depending on what else is been executed concurrently. This is particularly the case for mobile apps. For example, isolated tests may show 2x speed up vs single-threaded but if another app is executing at the same time may result in worst performance than single-threaded. + +TensorFlow Lite supports multi-threaded kernels for many operators. You can +increase the number of threads and speed up execution of operators. Increasing +the number of threads will however make your model use more resources and power. +For some applications latency may be more important than energy efficiency. You +can increase the number of threads by setting the number of +[interpreter](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/interpreter.h#L333) +threads. Multi-threaded execution however comes at the cost of increased +performance variability depending on what else is been executed concurrently. +This is particularly the case for mobile apps. For example, isolated tests may +show 2x speed up vs single-threaded but if another app is executing at the same +time may result in worst performance than single-threaded. ## Eliminate redundant copies -If your application is not careful, there can be redundant copies when feeding the input to the model and reading output from the model. Make sure to eliminate redundant copies. If you are using higher level APIs like Java API, make sure to carefully check the documentation for performance caveats. For example, the Java API is a lot faster if ByteBuffers are used as [inputs](https://github.com/tensorflow/tensorflow/blob/6305a6d83552ba6a472cd72398b60d9241467f1f/tensorflow/lite/java/src/main/java/org/tensorflow/lite/Interpreter.java#L151). + +If your application is not careful, there can be redundant copies when feeding +the input to the model and reading output from the model. Make sure to eliminate +redundant copies. If you are using higher level APIs like Java API, make sure to +carefully check the documentation for performance caveats. For example, the Java +API is a lot faster if ByteBuffers are used as +[inputs](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/java/src/main/java/org/tensorflow/lite/Interpreter.java#L175). ## Profile your application with platform specific tools Platform specific tools like [Android profiler](https://developer.android.com/studio/profile/android-profiler) and [Instruments](https://help.apple.com/instruments/mac/current/) provide a wealth of profiling information that can be used to debug your app. Sometimes the performance bug may be not in the model but in parts of application code that interact with the model. Make sure to familiarize yourself with platform specific profiling tools and best practices for your platform. @@ -64,7 +84,7 @@ are a great choice for large models that have high arithmetic intensity. ## Need more help -The Tensorflow team is happy to help diagnose and address specific performance +The TensorFlow team is happy to help diagnose and address specific performance issues you may be facing. Please file an issue on [GitHub](https://github.com/tensorflow/tensorflow/issues) with details of the issue. -- GitLab From 43d9f971759be4a0a683bfdbaa646025552e8f1e Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Wed, 16 Jan 2019 11:38:46 -0800 Subject: [PATCH 0804/2345] Fix more TFLite tests and binaries Use the tf_cc_binary where appropriate. Also fix an issue with schema generation and blacklist lstm zip tests temporarily. PiperOrigin-RevId: 229596167 --- tensorflow/lite/build_def.bzl | 2 +- tensorflow/lite/schema/BUILD | 1 - tensorflow/lite/testing/BUILD | 5 +++-- tensorflow/lite/tools/benchmark/BUILD | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tensorflow/lite/build_def.bzl b/tensorflow/lite/build_def.bzl index 03b217c120..3c48b655f7 100644 --- a/tensorflow/lite/build_def.bzl +++ b/tensorflow/lite/build_def.bzl @@ -263,7 +263,7 @@ def generated_test_models(): "logical_and", "logical_or", "logical_xor", - "lstm", + #"lstm", TODO(b/122889684): Resolve toco structured line parsing in oss. "max_pool", "maximum", "mean", diff --git a/tensorflow/lite/schema/BUILD b/tensorflow/lite/schema/BUILD index 69d5458c6e..ea516764c9 100644 --- a/tensorflow/lite/schema/BUILD +++ b/tensorflow/lite/schema/BUILD @@ -70,7 +70,6 @@ flatbuffer_cc_library( "--no-union-value-namespacing", "--gen-object-api", ], - gen_reflections = True, out_prefix = "reflection/", ) diff --git a/tensorflow/lite/testing/BUILD b/tensorflow/lite/testing/BUILD index 19c950b4f8..ce85a39385 100644 --- a/tensorflow/lite/testing/BUILD +++ b/tensorflow/lite/testing/BUILD @@ -12,6 +12,7 @@ load( load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite") load( "//tensorflow:tensorflow.bzl", + "tf_cc_binary", "tf_cc_test", "py_test", ) @@ -230,7 +231,7 @@ cc_test( ], ) -cc_binary( +tf_cc_binary( name = "nnapi_example", srcs = ["nnapi_example.cc"], deps = [ @@ -380,7 +381,7 @@ tf_cc_test( ], ) -cc_binary( +tf_cc_binary( name = "tflite_diff", srcs = ["tflite_diff_example_test.cc"], deps = [ diff --git a/tensorflow/lite/tools/benchmark/BUILD b/tensorflow/lite/tools/benchmark/BUILD index bc47406cd9..e68997a00b 100644 --- a/tensorflow/lite/tools/benchmark/BUILD +++ b/tensorflow/lite/tools/benchmark/BUILD @@ -4,6 +4,7 @@ package(default_visibility = [ licenses(["notice"]) # Apache 2.0 +load("//tensorflow:tensorflow.bzl", "tf_cc_binary") load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite") load("//tensorflow/lite:build_def.bzl", "tflite_copts") load("//tensorflow/lite:build_def.bzl", "tflite_linkopts") @@ -35,7 +36,7 @@ cc_binary( ], ) -cc_binary( +tf_cc_binary( name = "benchmark_model_plus_flex", srcs = [ "benchmark_plus_flex_main.cc", -- GitLab From e388dfcde40d44d3c6bf5ee6835b823ede73bb7a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 11:56:47 -0800 Subject: [PATCH 0805/2345] Export BUILD file so it is visible at //third_party/tensorflow/core:BUILD PiperOrigin-RevId: 229599457 --- tensorflow/core/BUILD | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 7ba9afb5a8..6c9afc51e9 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -70,6 +70,9 @@ package(default_visibility = [ licenses(["notice"]) # Apache 2.0 +# Export the BUILD file so automated tooling can check licenses +exports_files(["BUILD"]) + load( "//tensorflow:tensorflow.bzl", "cc_header_only_library", -- GitLab From 45a95587b973cd0f82ab240b038b94157d015fc3 Mon Sep 17 00:00:00 2001 From: Yunxing Dai Date: Wed, 16 Jan 2019 11:57:21 -0800 Subject: [PATCH 0806/2345] Don't rematerialize copy nodes. Copy insertion adds copies to prevent buffer clobbering. Rematerialize those copies could reintroduce the clobbering. This CL adds copies into the blacklist of rematerialization pass. PiperOrigin-RevId: 229599559 --- .../xla/service/hlo_rematerialization.cc | 9 ++++ .../xla/service/hlo_rematerialization_test.cc | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/tensorflow/compiler/xla/service/hlo_rematerialization.cc b/tensorflow/compiler/xla/service/hlo_rematerialization.cc index 9ca14ca18a..5c1793933c 100644 --- a/tensorflow/compiler/xla/service/hlo_rematerialization.cc +++ b/tensorflow/compiler/xla/service/hlo_rematerialization.cc @@ -57,6 +57,15 @@ using ::tensorflow::strings::HumanReadableNumBytes; // Returns true if the given instruction is rematerializable. bool IsRematerializable(const HloInstruction* instruction) { + if (instruction->opcode() == HloOpcode::kCopy) { + if (LayoutUtil::Equal(instruction->shape().layout(), + instruction->operand(0)->shape().layout())) { + // Don't rematerialize copies added by copy insertion (layout doesn't + // change). + return false; + } + } + // Don't rematerialize instructions with side effects or instructions which // cannot be cloned safely. switch (instruction->opcode()) { diff --git a/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc b/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc index c4bb475619..102a360ad8 100644 --- a/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc +++ b/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc @@ -499,6 +499,52 @@ TEST_F(HloRematerializationTest, InstructionRematerializedMultipleTimes) { EXPECT_THAT(add_4->operand(0), op::Broadcast(param)); } +TEST_F(HloRematerializationTest, CopyNotRematerialized) { + // Test that copies are not rematerialized. + auto module = CreateNewVerifiedModule(); + + auto builder = HloComputation::Builder(TestName()); + auto param = builder.AddInstruction( + HloInstruction::CreateParameter(0, vec1024_shape_, "param")); + + auto copy = builder.AddInstruction( + HloInstruction::CreateUnary(vec1024_shape_, HloOpcode::kCopy, param)); + + auto negate_a_1 = builder.AddInstruction( + HloInstruction::CreateUnary(vec1024_shape_, HloOpcode::kNegate, copy)); + + auto negate_a_2 = builder.AddInstruction(HloInstruction::CreateUnary( + vec1024_shape_, HloOpcode::kNegate, negate_a_1)); + + auto negate_b_1 = builder.AddInstruction( + HloInstruction::CreateUnary(vec1024_shape_, HloOpcode::kNegate, copy)); + + auto negate_b_2 = builder.AddInstruction(HloInstruction::CreateUnary( + vec1024_shape_, HloOpcode::kNegate, negate_b_1)); + + builder.AddInstruction(HloInstruction::CreateTuple({negate_a_2, negate_b_2})); + + HloComputation* entry_computation = + module->AddEntryComputation(builder.Build()); + + TF_ASSERT_OK_AND_ASSIGN(bool changed, + RunHloRematerialization( + /*memory_limit_bytes=*/1 * 1024, module.get())); + + auto count_copies = [](const HloComputation* computation) { + int64 copy_count = 0; + for (auto* instruction : computation->instructions()) { + if (instruction->opcode() == HloOpcode::kCopy) { + copy_count++; + } + } + return copy_count; + }; + EXPECT_TRUE(changed); + + EXPECT_EQ(count_copies(entry_computation), 1); +} + class IndirectUseTest : public HloRematerializationTest, public ::testing::WithParamInterface {}; -- GitLab From ee5d6e6deaac2b129bd9ac0c5ac3a82bb4bde8b8 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Wed, 16 Jan 2019 11:58:08 -0800 Subject: [PATCH 0807/2345] Tag a bunch of tests oss_serial to address spurious nightly GPU failures Still not entirely sure what changed to make these not work when run together, but the CUDA upgrade seems likely. They pass individually with test_output=streamed. PiperOrigin-RevId: 229599721 --- tensorflow/contrib/eager/python/examples/densenet/BUILD | 2 ++ tensorflow/contrib/eager/python/examples/l2hmc/BUILD | 3 +++ .../contrib/eager/python/examples/linear_regression/BUILD | 5 ++++- tensorflow/contrib/eager/python/examples/resnet50/BUILD | 2 ++ tensorflow/contrib/eager/python/examples/revnet/BUILD | 4 ++++ tensorflow/contrib/eager/python/examples/rnn_colorbot/BUILD | 3 +++ tensorflow/contrib/eager/python/examples/rnn_ptb/BUILD | 3 +++ tensorflow/contrib/eager/python/examples/spinn/BUILD | 1 + tensorflow/contrib/sparsemax/BUILD | 6 ++++++ 9 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/eager/python/examples/densenet/BUILD b/tensorflow/contrib/eager/python/examples/densenet/BUILD index e2154fcc5f..56a682ec55 100644 --- a/tensorflow/contrib/eager/python/examples/densenet/BUILD +++ b/tensorflow/contrib/eager/python/examples/densenet/BUILD @@ -27,6 +27,7 @@ cuda_py_test( tags = [ "no_pip", "optonly", + "oss_serial", ], ) @@ -45,5 +46,6 @@ cuda_py_test( "nomsan", "notsan", "optonly", + "oss_serial", ], ) diff --git a/tensorflow/contrib/eager/python/examples/l2hmc/BUILD b/tensorflow/contrib/eager/python/examples/l2hmc/BUILD index 7bdf9053de..78548c51c9 100644 --- a/tensorflow/contrib/eager/python/examples/l2hmc/BUILD +++ b/tensorflow/contrib/eager/python/examples/l2hmc/BUILD @@ -36,4 +36,7 @@ cuda_py_test( "//tensorflow/contrib/eager/python:tfe", "//third_party/py/numpy", ], + tags = [ + "oss_serial", + ], ) diff --git a/tensorflow/contrib/eager/python/examples/linear_regression/BUILD b/tensorflow/contrib/eager/python/examples/linear_regression/BUILD index 74ce9e84f0..6a178ddcec 100644 --- a/tensorflow/contrib/eager/python/examples/linear_regression/BUILD +++ b/tensorflow/contrib/eager/python/examples/linear_regression/BUILD @@ -23,7 +23,10 @@ cuda_py_test( ":linear_regression", "//tensorflow:tensorflow_py", ], - tags = ["no_windows"], # TODO: needs investigation on Windows + tags = [ + "no_windows", # TODO: needs investigation on Windows + "oss_serial", + ], ) cuda_py_test( diff --git a/tensorflow/contrib/eager/python/examples/resnet50/BUILD b/tensorflow/contrib/eager/python/examples/resnet50/BUILD index f3135a9668..04ac78a2d3 100644 --- a/tensorflow/contrib/eager/python/examples/resnet50/BUILD +++ b/tensorflow/contrib/eager/python/examples/resnet50/BUILD @@ -40,6 +40,7 @@ cuda_py_test( "nomsan", # Fix b/118130911 "notsan", # Fix b/118130911 "optonly", + "oss_serial", ], ) @@ -58,5 +59,6 @@ cuda_py_test( "nomsan", "notsan", "optonly", + "oss_serial", ], ) diff --git a/tensorflow/contrib/eager/python/examples/revnet/BUILD b/tensorflow/contrib/eager/python/examples/revnet/BUILD index 4f0d46b1ba..0d85bf63ad 100644 --- a/tensorflow/contrib/eager/python/examples/revnet/BUILD +++ b/tensorflow/contrib/eager/python/examples/revnet/BUILD @@ -73,6 +73,9 @@ cuda_py_test( ":ops", "//tensorflow:tensorflow_py", ], + tags = [ + "oss_serial", + ], ) cuda_py_test( @@ -101,6 +104,7 @@ cuda_py_test( tags = [ "no_pip", # depends on blocks_test, which is not available in pip package "optonly", + "oss_serial", ], ) diff --git a/tensorflow/contrib/eager/python/examples/rnn_colorbot/BUILD b/tensorflow/contrib/eager/python/examples/rnn_colorbot/BUILD index d500b632eb..576f60396e 100644 --- a/tensorflow/contrib/eager/python/examples/rnn_colorbot/BUILD +++ b/tensorflow/contrib/eager/python/examples/rnn_colorbot/BUILD @@ -25,4 +25,7 @@ cuda_py_test( "//tensorflow/contrib/eager/python:tfe", "//tensorflow:tensorflow_py", ], + tags = [ + "oss_serial", + ], ) diff --git a/tensorflow/contrib/eager/python/examples/rnn_ptb/BUILD b/tensorflow/contrib/eager/python/examples/rnn_ptb/BUILD index 2cc2fcbfeb..f9bf82a7d8 100644 --- a/tensorflow/contrib/eager/python/examples/rnn_ptb/BUILD +++ b/tensorflow/contrib/eager/python/examples/rnn_ptb/BUILD @@ -35,4 +35,7 @@ cuda_py_test( "//third_party/py/numpy", "//tensorflow:tensorflow_py", ], + tags = [ + "oss_serial", + ], ) diff --git a/tensorflow/contrib/eager/python/examples/spinn/BUILD b/tensorflow/contrib/eager/python/examples/spinn/BUILD index 5966f1d487..9b0fbaa679 100644 --- a/tensorflow/contrib/eager/python/examples/spinn/BUILD +++ b/tensorflow/contrib/eager/python/examples/spinn/BUILD @@ -42,5 +42,6 @@ cuda_py_test( "no-internal-py3", # flaky "no_cuda_on_cpu_tap", "no_pip", # because spinn.py is under third_party/. + "oss_serial", ], ) diff --git a/tensorflow/contrib/sparsemax/BUILD b/tensorflow/contrib/sparsemax/BUILD index d7ba754f70..ed4eca1a60 100644 --- a/tensorflow/contrib/sparsemax/BUILD +++ b/tensorflow/contrib/sparsemax/BUILD @@ -49,6 +49,9 @@ cuda_py_tests( "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", ], + tags = [ + "oss_serial", + ], ) cuda_py_tests( @@ -64,4 +67,7 @@ cuda_py_tests( "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", ], + tags = [ + "oss_serial", + ], ) -- GitLab From 8a5ddef315a5cd96e4e129bbd2b8ff1fa9f23ef6 Mon Sep 17 00:00:00 2001 From: Reed Wanderman-Milne Date: Wed, 16 Jan 2019 12:00:48 -0800 Subject: [PATCH 0808/2345] Fix nn_fused_batchnorm_test. Also set test size to medium, becuase it takes significantly less than five minutes to run. PiperOrigin-RevId: 229600169 --- tensorflow/python/BUILD | 2 +- tensorflow/python/ops/nn_fused_batchnorm_test.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index adba0b49fa..e32be0039f 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -3516,7 +3516,7 @@ cuda_py_test( cuda_py_test( name = "nn_fused_batchnorm_test", - size = "large", + size = "medium", srcs = ["ops/nn_fused_batchnorm_test.py"], additional_deps = [ ":array_ops", diff --git a/tensorflow/python/ops/nn_fused_batchnorm_test.py b/tensorflow/python/ops/nn_fused_batchnorm_test.py index 4e5cff5c16..69e753aa95 100644 --- a/tensorflow/python/ops/nn_fused_batchnorm_test.py +++ b/tensorflow/python/ops/nn_fused_batchnorm_test.py @@ -22,6 +22,7 @@ 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.ops import array_ops from tensorflow.python.ops import gradient_checker from tensorflow.python.ops import gradients_impl @@ -436,6 +437,7 @@ class BatchNormalizationTest(test.TestCase): self._test_training( x_shape, dtype, [6], np.float32, use_gpu=False, data_format='NHWC') + @test_util.run_deprecated_v1 def testBatchNormGradShape1(self): for is_training in [True, False]: x_shape = [1, 1, 6, 1] @@ -463,6 +465,7 @@ class BatchNormalizationTest(test.TestCase): data_format='NHWC', is_training=is_training) + @test_util.run_deprecated_v1 def testBatchNormGradShape2(self): for is_training in [True, False]: x_shape = [1, 1, 6, 2] @@ -483,6 +486,7 @@ class BatchNormalizationTest(test.TestCase): data_format='NHWC', is_training=is_training) + @test_util.run_deprecated_v1 def testBatchNormGradShape3(self): for is_training in [True, False]: x_shape = [1, 2, 1, 6] @@ -496,6 +500,7 @@ class BatchNormalizationTest(test.TestCase): data_format='NCHW', is_training=is_training) + @test_util.run_deprecated_v1 def testBatchNormGradShape4(self): for is_training in [True, False]: x_shape = [5, 7, 11, 4] @@ -523,6 +528,7 @@ class BatchNormalizationTest(test.TestCase): data_format='NHWC', is_training=is_training) + @test_util.run_deprecated_v1 @test_util.disable_xla('This test never passed for XLA') def testBatchNormGradShape5(self): for is_training in [True, False]: @@ -582,6 +588,7 @@ class BatchNormalizationTest(test.TestCase): is_training=is_training, err_tolerance=err_tolerance) + @test_util.run_deprecated_v1 def testBatchNormGradGradConfig1(self): config = { 'shape': [2, 3, 4, 5], @@ -590,6 +597,7 @@ class BatchNormalizationTest(test.TestCase): } self._testBatchNormGradGrad(config) + @test_util.run_deprecated_v1 def testBatchNormGradGradConfig2(self): config = { 'shape': [2, 3, 2, 2], @@ -598,6 +606,7 @@ class BatchNormalizationTest(test.TestCase): } self._testBatchNormGradGrad(config) + @test_util.run_deprecated_v1 def testBatchNormGradGradConfig3(self): config = { 'shape': [2, 3, 4, 5], @@ -606,6 +615,7 @@ class BatchNormalizationTest(test.TestCase): } self._testBatchNormGradGrad(config) + @test_util.run_deprecated_v1 def testBatchNormGradGradConfig4(self): config = { 'shape': [2, 3, 2, 2], -- GitLab From d6f6485eaee272fa4bd72c9d4625c2520f699e7f Mon Sep 17 00:00:00 2001 From: Andy Ly Date: Wed, 16 Jan 2019 12:08:53 -0800 Subject: [PATCH 0809/2345] [Grappler] Improve MutableGraphView tests by checking graph and node connectivity with GraphView, and checking node fanins and fanouts explicitly. PiperOrigin-RevId: 229601739 --- tensorflow/core/grappler/BUILD | 1 + .../core/grappler/mutable_graph_view_test.cc | 1215 ++++++++--------- 2 files changed, 569 insertions(+), 647 deletions(-) diff --git a/tensorflow/core/grappler/BUILD b/tensorflow/core/grappler/BUILD index 475fa64201..bd95e89878 100644 --- a/tensorflow/core/grappler/BUILD +++ b/tensorflow/core/grappler/BUILD @@ -233,5 +233,6 @@ tf_cc_test( "//tensorflow/core:testlib", "//tensorflow/core/grappler/inputs:trivial_test_graph_input_yielder", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) diff --git a/tensorflow/core/grappler/mutable_graph_view_test.cc b/tensorflow/core/grappler/mutable_graph_view_test.cc index 597bf8a7ee..b5bf33033c 100644 --- a/tensorflow/core/grappler/mutable_graph_view_test.cc +++ b/tensorflow/core/grappler/mutable_graph_view_test.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/mutable_graph_view.h" #include "absl/strings/substitute.h" +#include "absl/types/span.h" #include "tensorflow/cc/ops/standard_ops.h" #include "tensorflow/core/framework/function_testlib.h" #include "tensorflow/core/framework/types.pb.h" @@ -29,6 +30,115 @@ namespace grappler { namespace { using ::tensorflow::test::function::NDef; +using FDH = FunctionDefHelper; + +void CompareNodeFanins(const MutableGraphView& graph, NodeDef* node, + absl::Span fanins) { + ASSERT_EQ(node->input_size(), fanins.size()); + for (int i = 0; i < node->input_size(); ++i) { + TensorId tensor_id = ParseTensorName(fanins[i]); + EXPECT_EQ(ParseTensorName(node->input(i)), tensor_id); + int port; + if (tensor_id.index() == Graph::kControlSlot) { + port = Graph::kControlSlot; + } else { + port = i; + } + MutableGraphView::InputPort input_port(node, port); + MutableGraphView::OutputPort output_port = + graph.GetOutputPort(tensor_id.node(), tensor_id.index()); + EXPECT_TRUE(graph.GetFanin(input_port).contains(output_port)); + EXPECT_TRUE(graph.GetFanout(output_port).contains(input_port)); + } +} + +void CompareNodeFanouts(const MutableGraphView& graph, NodeDef* node, + absl::Span fanouts) { + auto node_fanouts = + graph.GetFanouts(*node, /*include_controlled_nodes=*/true); + EXPECT_EQ(node_fanouts.size(), fanouts.size()); + for (const string& fanout : fanouts) { + TensorId tensor_id = ParseTensorName(fanout); + MutableGraphView::InputPort input_port(graph.GetNode(tensor_id.node()), + tensor_id.index()); + EXPECT_TRUE(node_fanouts.contains(input_port)); + } +} + +void CheckNode(const MutableGraphView& graph, absl::string_view node_name, + absl::string_view op, absl::string_view device, + absl::Span> attrs, + absl::Span fanins, + absl::Span fanouts) { + NodeDef* node = graph.GetNode(node_name); + ASSERT_NE(node, nullptr); + EXPECT_EQ(node->op(), op); + EXPECT_EQ(node->device(), device); + EXPECT_EQ(node->attr_size(), attrs.size()); + for (const auto& attr : attrs) { + auto it = node->attr().find(attr.first); + ASSERT_NE(it, node->attr().end()); + EXPECT_TRUE(AreAttrValuesEqual(it->second, attr.second.proto)); + } + CompareNodeFanins(graph, node, fanins); + CompareNodeFanouts(graph, node, fanouts); +} + +void CheckGraph(const MutableGraphView& mutable_graph) { + GraphView immutable_graph(mutable_graph.graph()); + EXPECT_EQ(mutable_graph.graph()->node_size(), + immutable_graph.graph()->node_size()); + EXPECT_EQ(mutable_graph.graph(), immutable_graph.graph()); + + auto check_edges = + [](const absl::flat_hash_set& mutable_edges, + const absl::flat_hash_set& immutable_edges) { + EXPECT_EQ(mutable_edges.size(), immutable_edges.size()); + for (const auto& fanin_edge : mutable_edges) { + GraphView::Edge immutable_edge( + {fanin_edge.src.node, fanin_edge.src.port_id}, + {fanin_edge.dst.node, fanin_edge.dst.port_id}); + EXPECT_TRUE(immutable_edges.contains(immutable_edge)); + } + }; + + // Check graph connectivity. + for (auto& node : *mutable_graph.graph()->mutable_node()) { + EXPECT_EQ(&node, immutable_graph.GetNode(node.name())); + + auto mutable_fanins = + mutable_graph.GetFanins(node, /*include_controlling_nodes=*/true); + auto immutable_fanins = + immutable_graph.GetFanins(node, /*include_controlling_nodes=*/true); + EXPECT_EQ(mutable_fanins.size(), immutable_fanins.size()); + for (const auto& fanin : mutable_fanins) { + GraphView::OutputPort immutable_fanin(fanin.node, fanin.port_id); + EXPECT_TRUE(immutable_fanins.contains(immutable_fanin)); + } + + auto mutable_fanouts = + mutable_graph.GetFanouts(node, /*include_controlled_nodes=*/true); + auto immutable_fanouts = + immutable_graph.GetFanouts(node, /*include_controlled_nodes=*/true); + EXPECT_EQ(mutable_fanouts.size(), immutable_fanouts.size()); + for (const auto& fanout : mutable_fanouts) { + GraphView::InputPort immutable_fanout(fanout.node, fanout.port_id); + EXPECT_TRUE(immutable_fanouts.contains(immutable_fanout)); + } + + auto mutable_fanin_edges = + mutable_graph.GetFaninEdges(node, /*include_controlling_edges=*/true); + auto immutable_fanin_edges = + immutable_graph.GetFaninEdges(node, /*include_controlling_edges=*/true); + check_edges(mutable_fanin_edges, immutable_fanin_edges); + + auto mutable_fanout_edges = + mutable_graph.GetFanoutEdges(node, /*include_controlled_edges=*/true); + auto immutable_fanout_edges = + immutable_graph.GetFanoutEdges(node, /*include_controlled_edges=*/true); + check_edges(mutable_fanout_edges, immutable_fanout_edges); + } +} TEST(MutableGraphViewTest, AddAndUpdateFanouts) { // Actual node.op() is not important in this test. @@ -43,42 +153,23 @@ TEST(MutableGraphViewTest, AddAndUpdateFanouts) { MutableGraphView graph(&graph_def); NodeDef* new_bar = graph.AddNode(NDef("new_bar", "NotImportant", {}, {})); - NodeDef* bar = graph.GetNode("bar"); - - EXPECT_TRUE(graph.UpdateFanouts(bar->name(), new_bar->name()).ok()); - - // Fanout nodes must have their inputs updated. - NodeDef* foo_1 = graph.GetNode("foo_1"); - ASSERT_NE(foo_1, nullptr); - ASSERT_EQ(foo_1->input_size(), 3); - EXPECT_EQ(foo_1->input(0), "new_bar"); - EXPECT_EQ(foo_1->input(1), "other"); - EXPECT_EQ(foo_1->input(2), "new_bar:1"); - - NodeDef* foo_2 = graph.GetNode("foo_2"); - ASSERT_NE(foo_2, nullptr); - ASSERT_EQ(foo_2->input_size(), 2); - EXPECT_EQ(foo_2->input(0), "other:1"); - EXPECT_EQ(foo_2->input(1), "new_bar:2"); - - NodeDef* foo_3 = graph.GetNode("foo_3"); - ASSERT_NE(foo_3, nullptr); - ASSERT_EQ(foo_3->input_size(), 2); - EXPECT_EQ(foo_3->input(0), "other:2"); - EXPECT_EQ(foo_3->input(1), "^new_bar"); - - // And fanouts mapping must be also updated for both nodes. - bool include_control_fanouts = true; - auto old_node_fanouts = graph.GetFanouts(*bar, include_control_fanouts); - auto new_node_fanouts = graph.GetFanouts(*new_bar, include_control_fanouts); - - EXPECT_TRUE(old_node_fanouts.empty()); - - EXPECT_EQ(new_node_fanouts.size(), 4); - EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_1, 0)), 1); - EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_1, 2)), 1); - EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_2, 1)), 1); - EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_3, -1)), 1); + + EXPECT_TRUE(graph.UpdateFanouts("bar", new_bar->name()).ok()); + + // Fanins and fanouts must be updated. + CheckNode(graph, "bar", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "other", "NotImportant", "", {}, {}, + {"foo_1:1", "foo_2", "foo_3"}); + CheckNode(graph, "foo_1", "NotImportant", "", {}, + {"new_bar", "other", "new_bar:1"}, {}); + CheckNode(graph, "foo_2", "NotImportant", "", {}, {"other:1", "new_bar:2"}, + {}); + CheckNode(graph, "foo_3", "NotImportant", "", {}, {"other:2", "^new_bar"}, + {}); + CheckNode(graph, "new_bar", "NotImportant", "", {}, {}, + {"foo_1:0", "foo_1:2", "foo_2:1", "^foo_3"}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddAndUpdateFanoutsKeepControls) { @@ -92,37 +183,21 @@ TEST(MutableGraphViewTest, AddAndUpdateFanoutsKeepControls) { MutableGraphView graph(&graph_def); NodeDef* new_bar = graph.AddNode(NDef("new_bar", "Identity", {"bar_1:2"})); - NodeDef* bar_2 = graph.GetNode("bar_2"); - - EXPECT_TRUE(graph.UpdateFanouts(bar_2->name(), new_bar->name()).ok()); - - // Fanout nodes must have their inputs updated. - NodeDef* foo_1 = graph.GetNode("foo_1"); - ASSERT_NE(foo_1, nullptr); - ASSERT_EQ(foo_1->input_size(), 4); - EXPECT_EQ(foo_1->input(0), "new_bar"); - EXPECT_EQ(foo_1->input(1), "other"); - EXPECT_EQ(foo_1->input(2), "new_bar:1"); - EXPECT_EQ(foo_1->input(3), "^new_bar"); - - NodeDef* foo_2 = graph.GetNode("foo_2"); - ASSERT_NE(foo_2, nullptr); - ASSERT_EQ(foo_2->input_size(), 3); - EXPECT_EQ(foo_2->input(0), "other:1"); - EXPECT_EQ(foo_2->input(1), "new_bar:2"); - EXPECT_EQ(foo_2->input(2), "^new_bar"); - - // And fanouts mapping must be also updated for both nodes. - bool include_control_fanouts = true; - auto old_node_fanouts = graph.GetFanouts(*bar_2, include_control_fanouts); - auto new_node_fanouts = graph.GetFanouts(*new_bar, include_control_fanouts); - - EXPECT_TRUE(old_node_fanouts.empty()); - EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_1, 0)), 1); - EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_1, 2)), 1); - EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_1, -1)), 1); - EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_2, 1)), 1); - EXPECT_EQ(new_node_fanouts.count(MutableGraphView::InputPort(foo_2, -1)), 1); + + EXPECT_TRUE(graph.UpdateFanouts("bar_2", new_bar->name()).ok()); + + // Fanins and fanouts must be updated. + CheckNode(graph, "bar_1", "Switch", "", {}, {}, {"bar_2", "new_bar"}); + CheckNode(graph, "bar_2", "Identity", "", {}, {"bar_1:1"}, {}); + CheckNode(graph, "other", "NotImportant", "", {}, {}, {"foo_1:1", "foo_2"}); + CheckNode(graph, "foo_1", "NotImportant", "", {}, + {"new_bar", "other", "new_bar:1", "^new_bar"}, {}); + CheckNode(graph, "foo_2", "NotImportant", "", {}, + {"other:1", "new_bar:2", "^new_bar"}, {}); + CheckNode(graph, "new_bar", "Identity", "", {}, {"bar_1:2"}, + {"foo_1", "foo_1:2", "^foo_1", "foo_2:1", "^foo_2"}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddAndUpdateFanoutsWithoutSelfLoops) { @@ -137,56 +212,16 @@ TEST(MutableGraphViewTest, AddAndUpdateFanoutsWithoutSelfLoops) { // `new_bar` reads the output of an original `bar` node. NodeDef* new_bar = graph.AddNode(NDef("new_bar", "NewBar", {"bar"}, {})); - NodeDef* bar = graph.GetNode("bar"); EXPECT_TRUE(graph.UpdateFanouts("bar", new_bar->name()).ok()); - // Foo node must read from `new_bar`. - NodeDef* foo_1 = graph.GetNode("foo_1"); - ASSERT_NE(foo_1, nullptr); - ASSERT_EQ(foo_1->input_size(), 1); - EXPECT_EQ(foo_1->input(0), "new_bar"); - - NodeDef* foo_2 = graph.GetNode("foo_2"); - ASSERT_NE(foo_2, nullptr); - ASSERT_EQ(foo_2->input_size(), 1); - EXPECT_EQ(foo_2->input(0), "^new_bar"); - - // And the `new_bar` should read from the original `bar`. - ASSERT_EQ(new_bar->input_size(), 1); - ASSERT_EQ(new_bar->input(0), "bar"); - - // And fanouts mapping must be also updated for both nodes. - bool include_control_fanouts = true; - auto bar_fanouts = graph.GetFanouts(*bar, include_control_fanouts); - auto new_bar_fanouts = graph.GetFanouts(*new_bar, include_control_fanouts); + // Fanins and fanouts must be updated. + CheckNode(graph, "bar", "NotImportant", "", {}, {}, {"new_bar"}); + CheckNode(graph, "foo_1", "NotImportant", "", {}, {"new_bar"}, {}); + CheckNode(graph, "foo_2", "NotImportant", "", {}, {"^new_bar"}, {}); + CheckNode(graph, "new_bar", "NewBar", "", {}, {"bar"}, {"foo_1", "^foo_2"}); - EXPECT_EQ(bar_fanouts.size(), 1); - EXPECT_EQ(bar_fanouts.count(MutableGraphView::InputPort(new_bar, 0)), 1); - - EXPECT_EQ(new_bar_fanouts.size(), 2); - EXPECT_EQ(new_bar_fanouts.count(MutableGraphView::InputPort(foo_1, 0)), 1); - EXPECT_EQ(new_bar_fanouts.count(MutableGraphView::InputPort(foo_2, -1)), 1); -} - -void CompareNodeInputs(const MutableGraphView& graph, const NodeDef* expected, - NodeDef* actual) { - ASSERT_EQ(actual->input_size(), expected->input_size()); - for (int i = 0; i < actual->input_size(); ++i) { - EXPECT_EQ(actual->input(i), expected->input(i)); - TensorId tensor_id = ParseTensorName(expected->input(i)); - int port; - if (tensor_id.index() == Graph::kControlSlot) { - port = Graph::kControlSlot; - } else { - port = i; - } - MutableGraphView::InputPort input_port(actual, port); - MutableGraphView::OutputPort output_port = - graph.GetOutputPort(tensor_id.node(), tensor_id.index()); - EXPECT_EQ(graph.GetFanin(input_port).contains(output_port), true); - EXPECT_EQ(graph.GetFanout(output_port).contains(input_port), true); - } + CheckGraph(graph); } TEST(MutableGraphViewTest, UpdateFanoutsToSwitchWithControlFromSwitch) { @@ -213,27 +248,13 @@ TEST(MutableGraphViewTest, UpdateFanoutsToSwitchWithControlFromSwitch) { EXPECT_EQ(graph.graph()->node_size(), 5); - NodeDef expected = NDef("", "", {}, {}); - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - CompareNodeInputs(graph, &expected, a); + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"^e"}); + CheckNode(graph, "b", "Switch", "", {}, {}, {"e:1"}); + CheckNode(graph, "c", "NotImportant", "", {}, {}, {"e:0"}); + CheckNode(graph, "d", "NotImportant", "", {}, {}, {"^e"}); + CheckNode(graph, "e", "NotImportant", "", {}, {"c", "b", "^a", "^d"}, {}); - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - CompareNodeInputs(graph, &expected, b); - - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - CompareNodeInputs(graph, &expected, c); - - NodeDef* d = graph.GetNode("d"); - ASSERT_NE(d, nullptr); - CompareNodeInputs(graph, &expected, d); - - NodeDef* e = graph.GetNode("e"); - ASSERT_NE(e, nullptr); - expected = NDef("", "", {"c", "b", "^a", "^d"}); - CompareNodeInputs(graph, &expected, e); + CheckGraph(graph); } TEST(MutableGraphViewTest, UpdateFanoutsToSwitchWithNoControlFromSwitch) { @@ -249,27 +270,13 @@ TEST(MutableGraphViewTest, UpdateFanoutsToSwitchWithNoControlFromSwitch) { EXPECT_EQ(graph.graph()->node_size(), 5); - NodeDef expected = NDef("", "", {}, {}); - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - CompareNodeInputs(graph, &expected, a); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - CompareNodeInputs(graph, &expected, b); + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"^e"}); + CheckNode(graph, "b", "Switch", "", {}, {}, {"e:0", "e:1"}); + CheckNode(graph, "c", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "d", "NotImportant", "", {}, {}, {"^e"}); + CheckNode(graph, "e", "NotImportant", "", {}, {"b", "b", "^a", "^d"}, {}); - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - CompareNodeInputs(graph, &expected, c); - - NodeDef* d = graph.GetNode("d"); - ASSERT_NE(d, nullptr); - CompareNodeInputs(graph, &expected, d); - - NodeDef* e = graph.GetNode("e"); - ASSERT_NE(e, nullptr); - expected = NDef("", "", {"b", "b", "^a", "^d"}); - CompareNodeInputs(graph, &expected, e); + CheckGraph(graph); } GraphDef SimpleMutateFaninGraph() { @@ -287,129 +294,152 @@ GraphDef SimpleMutateFaninGraph() { return graph_def; } -void TestAddRegularFanin(absl::string_view node_name, +absl::flat_hash_map> GetNodeInputsFromGraph( + const GraphDef& graph, absl::string_view node_to_exclude) { + absl::flat_hash_map> node_inputs; + for (const auto& node : graph.node()) { + if (node.name() == node_to_exclude) { + continue; + } + node_inputs[node.name()] = + std::vector(node.input().begin(), node.input().end()); + } + return node_inputs; +} + +void CheckUnmodifiedNodeFanins( + const GraphDef& graph, absl::string_view node_to_exclude, + const absl::flat_hash_map>& + unmodified_node_inputs) { + for (const auto& node : graph.node()) { + if (node.name() == node_to_exclude) { + continue; + } + auto it = unmodified_node_inputs.find(node.name()); + ASSERT_NE(it, unmodified_node_inputs.end()); + ASSERT_EQ(it->second.size(), node.input_size()); + for (int i = 0; i < node.input_size(); ++i) { + EXPECT_EQ(node.input(i), it->second[i]); + } + } +} + +void TestAddRegularFanin(absl::string_view node_name, bool node_exists, const TensorId& fanin_to_add, bool success, const string& error_msg, - const NodeDef* expected_node) { + absl::Span expected_fanins) { GraphDef graph_def = SimpleMutateFaninGraph(); MutableGraphView graph(&graph_def); - auto node = graph.GetNode(node_name); - if (expected_node == nullptr) { - EXPECT_EQ(node, nullptr); - } else { + NodeDef* node = graph.GetNode(node_name); + if (node_exists) { EXPECT_NE(node, nullptr); + } else { + EXPECT_EQ(node, nullptr); } + absl::flat_hash_map> unmodified_node_inputs = + GetNodeInputsFromGraph(graph_def, node_name); + Status s = graph.AddRegularFanin(node_name, fanin_to_add); EXPECT_EQ(s.ok(), success); if (!success) { EXPECT_EQ(s.error_message(), error_msg); } - if (expected_node != nullptr) { - CompareNodeInputs(graph, expected_node, node); + if (node_exists) { + CompareNodeFanins(graph, node, expected_fanins); } + + CheckUnmodifiedNodeFanins(graph_def, node_name, unmodified_node_inputs); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddRegularFanin) { - NodeDef expected_node; string error_msg; // Add input to node with 1 input 0 controls. - expected_node = NDef("", "", {"a", "b:1"}); - TestAddRegularFanin("foo_1", {"b", 1}, /*success=*/true, error_msg, - &expected_node); + TestAddRegularFanin("foo_1", /*node_exists=*/true, {"b", 1}, /*success=*/true, + error_msg, {"a", "b:1"}); // Add input to node with multiple inputs and 0 controls. - expected_node = NDef("", "", {"b", "a:1", "a:1", "b:2"}); - TestAddRegularFanin("foo_3", {"b", 2}, /*success=*/true, error_msg, - &expected_node); + TestAddRegularFanin("foo_3", /*node_exists=*/true, {"b", 2}, /*success=*/true, + error_msg, {"b", "a:1", "a:1", "b:2"}); // Add input to node with 1 input multiple controls. - expected_node = NDef("", "", {"b", "a", "^c"}); - TestAddRegularFanin("foo_2", {"a", 0}, /*success=*/true, error_msg, - &expected_node); + TestAddRegularFanin("foo_2", /*node_exists=*/true, {"a", 0}, /*success=*/true, + error_msg, {"b", "a", "^c"}); // Add input to node with multiple inputs and controls. - expected_node = NDef("", "", {"a", "b:2", "b:2", "a:1", "^d", "^c"}); - TestAddRegularFanin("foo_4", {"a", 1}, /*success=*/true, error_msg, - &expected_node); + TestAddRegularFanin("foo_4", /*node_exists=*/true, {"a", 1}, /*success=*/true, + error_msg, {"a", "b:2", "b:2", "a:1", "^d", "^c"}); // Add input to node with 0 inputs 0 controls. - expected_node = NDef("", "", {"a:1"}); - TestAddRegularFanin("foo_5", {"a", 1}, /*success=*/true, error_msg, - &expected_node); + TestAddRegularFanin("foo_5", /*node_exists=*/true, {"a", 1}, /*success=*/true, + error_msg, {"a:1"}); // Add input to node with 0 inputs multiple controls. - expected_node = NDef("", "", {"c:1", "^b", "^a"}); - TestAddRegularFanin("foo_6", {"c", 1}, /*success=*/true, error_msg, - &expected_node); + TestAddRegularFanin("foo_6", /*node_exists=*/true, {"c", 1}, /*success=*/true, + error_msg, {"c:1", "^b", "^a"}); // Add control to node with 1 input 0 controls. - expected_node = NDef("", "", {"a"}); error_msg = "Can't add invalid fanin '^b' as regular fanin to node 'foo_1'."; - TestAddRegularFanin("foo_1", {"b", Graph::kControlSlot}, /*success=*/false, - error_msg, &expected_node); + TestAddRegularFanin("foo_1", /*node_exists=*/true, {"b", Graph::kControlSlot}, + /*success=*/false, error_msg, {"a"}); // Add control to node with multiple inputs and 0 controls. - expected_node = NDef("", "", {"b", "a:1", "a:1"}); error_msg = "Can't add invalid fanin '^c' as regular fanin to node 'foo_3'."; - TestAddRegularFanin("foo_3", {"c", Graph::kControlSlot}, /*success=*/false, - error_msg, &expected_node); + TestAddRegularFanin("foo_3", /*node_exists=*/true, {"c", Graph::kControlSlot}, + /*success=*/false, error_msg, {"b", "a:1", "a:1"}); // Add control to node with 1 input multiple controls. - expected_node = NDef("", "", {"b", "^a", "^c"}); error_msg = "Can't add invalid fanin '^d' as regular fanin to node 'foo_2'."; - TestAddRegularFanin("foo_2", {"d", Graph::kControlSlot}, /*success=*/false, - error_msg, &expected_node); + TestAddRegularFanin("foo_2", /*node_exists=*/true, {"d", Graph::kControlSlot}, + /*success=*/false, error_msg, {"b", "^a", "^c"}); // Add control to node with multiple input multiple controls. - expected_node = NDef("", "", {"a", "b:2", "b:2", "^c", "^d"}); error_msg = "Can't add invalid fanin '^a' as regular fanin to node 'foo_4'."; - TestAddRegularFanin("foo_4", {"a", Graph::kControlSlot}, - /*success=*/false, error_msg, &expected_node); + TestAddRegularFanin("foo_4", /*node_exists=*/true, {"a", Graph::kControlSlot}, + /*success=*/false, error_msg, + {"a", "b:2", "b:2", "^c", "^d"}); // Add control to node with 0 inputs 0 controls. - expected_node = NDef("", "", {}); error_msg = "Can't add invalid fanin '^a' as regular fanin to node 'foo_5'."; - TestAddRegularFanin("foo_5", {"a", Graph::kControlSlot}, /*success=*/false, - error_msg, &expected_node); + TestAddRegularFanin("foo_5", /*node_exists=*/true, {"a", Graph::kControlSlot}, + /*success=*/false, error_msg, {}); // Add control to node with 0 inputs multiple controls. - expected_node = NDef("", "", {"^a", "^b"}); error_msg = "Can't add invalid fanin '^c' as regular fanin to node 'foo_6'."; - TestAddRegularFanin("foo_6", {"c", Graph::kControlSlot}, /*success=*/false, - error_msg, &expected_node); + TestAddRegularFanin("foo_6", /*node_exists=*/true, {"c", Graph::kControlSlot}, + /*success=*/false, error_msg, {"^a", "^b"}); // Add control to node with control that already exists. - expected_node = NDef("", "", {"b", "^a", "^c"}); error_msg = "Can't add invalid fanin '^a' as regular fanin to node 'foo_2'."; - TestAddRegularFanin("foo_2", {"a", Graph::kControlSlot}, - /*success=*/false, error_msg, &expected_node); + TestAddRegularFanin("foo_2", /*node_exists=*/true, {"a", Graph::kControlSlot}, + /*success=*/false, error_msg, {"b", "^a", "^c"}); // Add fanin to node where node is missing. error_msg = "Can't add fanin 'a:0' as regular fanin to missing node 'foo_missing'."; - TestAddRegularFanin("foo_missing", {"a", 0}, /*success=*/false, error_msg, - nullptr); + TestAddRegularFanin("foo_missing", /*node_exists=*/false, {"a", 0}, + /*success=*/false, error_msg, {}); // Add fanin to node where fanin is missing. - expected_node = NDef("", "", {"a"}); error_msg = "Can't add missing fanin 'bar_missing:0' as regular fanin to node " "'foo_1'."; - TestAddRegularFanin("foo_1", {"bar_missing", 0}, /*success=*/false, error_msg, - &expected_node); + TestAddRegularFanin("foo_1", /*node_exists=*/true, {"bar_missing", 0}, + /*success=*/false, error_msg, {"a"}); // Add fanin to node where node and fanin are missing. error_msg = "Can't add missing fanin 'bar_missing:0' as regular fanin to missing " "node 'foo_missing'."; - TestAddRegularFanin("foo_missing", {"bar_missing", 0}, /*success=*/false, - error_msg, /*expected_node=*/nullptr); + TestAddRegularFanin("foo_missing", /*node_exists=*/false, {"bar_missing", 0}, + /*success=*/false, error_msg, {}); // Add control fanin to node where node and fanin are missing. error_msg = "Can't add invalid/missing fanin '^bar_missing' as regular fanin to " "missing node 'foo_missing'."; - TestAddRegularFanin("foo_missing", {"bar_missing", Graph::kControlSlot}, - /*success=*/false, error_msg, /*expected_node=*/nullptr); + TestAddRegularFanin("foo_missing", /*node_exists=*/false, + {"bar_missing", Graph::kControlSlot}, + /*success=*/false, error_msg, {}); // Add self to create cycle. error_msg = "Can't add fanin 'foo_6:2' as regular fanin to self."; - expected_node = NDef("", "", {"^a", "^b"}); - TestAddRegularFanin("foo_6", {"foo_6", 2}, /*success=*/false, error_msg, - &expected_node); + TestAddRegularFanin("foo_6", /*node_exists=*/true, {"foo_6", 2}, + /*success=*/false, error_msg, {"^a", "^b"}); } -void CheckFanout(const MutableGraphView& graph, const TensorId& fanin, - absl::string_view node_name) { +void CheckFanoutRemoved(const MutableGraphView& graph, const TensorId& fanin, + absl::string_view node_name) { MutableGraphView::OutputPort output_port = graph.GetOutputPort(fanin.node(), fanin.index()); auto fanouts = graph.GetFanout(output_port); @@ -418,166 +448,166 @@ void CheckFanout(const MutableGraphView& graph, const TensorId& fanin, } } -void TestRemoveRegularFanin(absl::string_view node_name, +void TestRemoveRegularFanin(absl::string_view node_name, bool node_exists, const TensorId& fanin_to_remove, bool success, const string& error_msg, - const NodeDef* expected_node) { + absl::Span expected_fanins) { GraphDef graph_def = SimpleMutateFaninGraph(); MutableGraphView graph(&graph_def); - auto node = graph.GetNode(node_name); - if (expected_node == nullptr) { - EXPECT_EQ(nullptr, node); - } else { + NodeDef* node = graph.GetNode(node_name); + if (node_exists) { EXPECT_NE(nullptr, node); + } else { + EXPECT_EQ(nullptr, node); } + absl::flat_hash_map> unmodified_node_inputs = + GetNodeInputsFromGraph(graph_def, node_name); + Status s = graph.RemoveRegularFanin(node_name, fanin_to_remove); EXPECT_EQ(s.ok(), success); if (!success) { EXPECT_EQ(s.error_message(), error_msg); } - if (expected_node != nullptr) { - CompareNodeInputs(graph, expected_node, node); + if (node_exists) { + CompareNodeFanins(graph, node, expected_fanins); if (success) { - CheckFanout(graph, fanin_to_remove, node_name); + CheckFanoutRemoved(graph, fanin_to_remove, node_name); } } + + CheckUnmodifiedNodeFanins(graph_def, node_name, unmodified_node_inputs); + + CheckGraph(graph); } TEST(MutableGraphViewTest, RemoveRegularFanin) { - NodeDef expected_node; string error_msg; // Remove input from node with 1 input 0 controls. - expected_node = NDef("", "", {}); - TestRemoveRegularFanin("foo_1", {"a", 0}, /*success=*/true, error_msg, - &expected_node); + TestRemoveRegularFanin("foo_1", /*node_exists=*/true, {"a", 0}, + /*success=*/true, error_msg, {}); // Remove input from node with multiple inputs and 0 controls. - expected_node = NDef("", "", {"b"}); - TestRemoveRegularFanin("foo_3", {"a", 1}, /*success=*/true, error_msg, - &expected_node); + TestRemoveRegularFanin("foo_3", /*node_exists=*/true, {"a", 1}, + /*success=*/true, error_msg, {"b"}); // Remove input from node with 1 input multiple controls. - expected_node = NDef("", "", {"^a", "^c"}); - TestRemoveRegularFanin("foo_2", {"b", 0}, /*success=*/true, error_msg, - &expected_node); + TestRemoveRegularFanin("foo_2", /*node_exists=*/true, {"b", 0}, + /*success=*/true, error_msg, {"^a", "^c"}); // Remove input from node with multiple inputs and controls. - expected_node = NDef("", "", {"a", "^c", "^d"}); - TestRemoveRegularFanin("foo_4", {"b", 2}, /*success=*/true, error_msg, - &expected_node); + TestRemoveRegularFanin("foo_4", /*node_exists=*/true, {"b", 2}, + /*success=*/true, error_msg, {"a", "^c", "^d"}); // Remove input from node with multiple inputs and controls, and results in // shifting of ports. - expected_node = NDef("", "", {"b:2", "b:2", "^c", "^d"}); - TestRemoveRegularFanin("foo_4", {"a", 0}, /*success=*/true, error_msg, - &expected_node); + TestRemoveRegularFanin("foo_4", /*node_exists=*/true, {"a", 0}, + /*success=*/true, error_msg, + {"b:2", "b:2", "^c", "^d"}); // Remove control from node with 1 input multiple controls. - expected_node = NDef("", "", {"b", "^a", "^c"}); error_msg = "Can't remove invalid fanin '^a' as regular fanin from node 'foo_2'."; - TestRemoveRegularFanin("foo_2", {"a", Graph::kControlSlot}, - /*success=*/false, error_msg, &expected_node); + TestRemoveRegularFanin("foo_2", /*node_exists=*/true, + {"a", Graph::kControlSlot}, + /*success=*/false, error_msg, {"b", "^a", "^c"}); // Remove control from node with multiple input multiple controls. - expected_node = NDef("", "", {"a", "b:2", "b:2", "^c", "^d"}); error_msg = "Can't remove invalid fanin '^d' as regular fanin from node 'foo_4'."; - TestRemoveRegularFanin("foo_4", {"d", Graph::kControlSlot}, - /*success=*/false, error_msg, &expected_node); + TestRemoveRegularFanin( + "foo_4", /*node_exists=*/true, {"d", Graph::kControlSlot}, + /*success=*/false, error_msg, {"a", "b:2", "b:2", "^c", "^d"}); // Remove control from node with 0 inputs multiple controls. - expected_node = NDef("", "", {"^a", "^b"}); error_msg = "Can't remove invalid fanin '^a' as regular fanin from node 'foo_6'."; - TestRemoveRegularFanin("foo_6", {"a", Graph::kControlSlot}, - /*success=*/false, error_msg, &expected_node); + TestRemoveRegularFanin("foo_6", /*node_exists=*/true, + {"a", Graph::kControlSlot}, + /*success=*/false, error_msg, {"^a", "^b"}); // Remove input from node with 0 inputs 0 controls. - expected_node = NDef("", "", {}); error_msg = ""; - TestRemoveRegularFanin("foo_5", {"a", 1}, /*success=*/true, error_msg, - &expected_node); + TestRemoveRegularFanin("foo_5", /*node_exists=*/true, {"a", 1}, + /*success=*/true, error_msg, {}); // Remove input from node with 0 inputs multiple controls. - expected_node = NDef("", "", {"^a", "^b"}); - TestRemoveRegularFanin("foo_6", {"a", 1}, /*success=*/true, error_msg, - &expected_node); + TestRemoveRegularFanin("foo_6", /*node_exists=*/true, {"a", 1}, + /*success=*/true, error_msg, {"^a", "^b"}); // Remove control from node with 1 input 0 controls. - expected_node = NDef("", "", {"a"}); error_msg = "Can't remove invalid fanin '^b' as regular fanin from node 'foo_1'."; - TestRemoveRegularFanin("foo_1", {"b", Graph::kControlSlot}, - /*success=*/false, error_msg, &expected_node); + TestRemoveRegularFanin("foo_1", /*node_exists=*/true, + {"b", Graph::kControlSlot}, + /*success=*/false, error_msg, {"a"}); // Remove control from node with multiple inputs and 0 controls. - expected_node = NDef("", "", {"b", "a:1", "a:1"}); error_msg = "Can't remove invalid fanin '^c' as regular fanin from node 'foo_3'."; - TestRemoveRegularFanin("foo_3", {"c", Graph::kControlSlot}, - /*success=*/false, error_msg, &expected_node); + TestRemoveRegularFanin("foo_3", /*node_exists=*/true, + {"c", Graph::kControlSlot}, + /*success=*/false, error_msg, {"b", "a:1", "a:1"}); // Remove control from node with 0 inputs 0 controls. - expected_node = NDef("", "", {}); error_msg = "Can't remove invalid fanin '^a' as regular fanin from node 'foo_5'."; - TestRemoveRegularFanin("foo_5", {"a", Graph::kControlSlot}, - /*success=*/false, error_msg, &expected_node); + TestRemoveRegularFanin("foo_5", /*node_exists=*/true, + {"a", Graph::kControlSlot}, + /*success=*/false, error_msg, {}); // Remove fanin from node where node is missing. error_msg = "Can't remove fanin 'a:0' as regular fanin from missing node " "'foo_missing'."; - TestRemoveRegularFanin("foo_missing", {"a", 0}, /*success=*/false, error_msg, - /*expected_node=*/nullptr); + TestRemoveRegularFanin("foo_missing", /*node_exists=*/false, {"a", 0}, + /*success=*/false, error_msg, {}); // Remove fanin from node where fanin is missing. - expected_node = NDef("", "", {"a"}); error_msg = "Can't remove missing fanin 'bar_missing:0' as regular fanin from node " "'foo_1'."; - TestRemoveRegularFanin("foo_1", {"bar_missing", 0}, /*success=*/false, - error_msg, &expected_node); + TestRemoveRegularFanin("foo_1", /*node_exists=*/true, {"bar_missing", 0}, + /*success=*/false, error_msg, {"a"}); // Remove fanin from node where node and fanin are missing. error_msg = "Can't remove missing fanin 'bar_missing:0' as regular fanin from " "missing node 'foo_missing'."; - TestRemoveRegularFanin("foo_missing", {"bar_missing", 0}, /*success=*/false, - error_msg, - /*expected_node=*/nullptr); + TestRemoveRegularFanin("foo_missing", /*node_exists=*/false, + {"bar_missing", 0}, /*success=*/false, error_msg, {}); // Remove control from node where node and fanin are missing. error_msg = "Can't remove invalid/missing fanin '^bar_missing' as regular fanin from " "missing node 'foo_missing'."; - TestRemoveRegularFanin("foo_missing", {"bar_missing", Graph::kControlSlot}, - /*success=*/false, error_msg, - /*expected_node=*/nullptr); + TestRemoveRegularFanin("foo_missing", /*node_exists=*/false, + {"bar_missing", Graph::kControlSlot}, + /*success=*/false, error_msg, {}); // Remove self. error_msg = "Can't remove fanin 'foo_6:2' as regular fanin from self."; - expected_node = NDef("", "", {"^a", "^b"}); - TestRemoveRegularFanin("foo_6", {"foo_6", 2}, /*success=*/false, error_msg, - &expected_node); + TestRemoveRegularFanin("foo_6", /*node_exists=*/true, {"foo_6", 2}, + /*success=*/false, error_msg, {"^a", "^b"}); } -void TestRemoveAllFanins(absl::string_view node_name, +void TestRemoveAllFanins(absl::string_view node_name, bool node_exists, bool keep_controlling_nodes, bool success, const string& error_msg, - const NodeDef* expected_node) { + absl::Span expected_fanins) { GraphDef graph_def = SimpleMutateFaninGraph(); MutableGraphView graph(&graph_def); - auto node = graph.GetNode(node_name); + NodeDef* node = graph.GetNode(node_name); absl::flat_hash_set fanin_strings; - if (expected_node == nullptr) { - EXPECT_EQ(node, nullptr); - } else { + if (node_exists) { EXPECT_NE(node, nullptr); fanin_strings.insert(node->input().begin(), node->input().end()); + } else { + EXPECT_EQ(node, nullptr); } + absl::flat_hash_map> unmodified_node_inputs = + GetNodeInputsFromGraph(graph_def, node_name); + Status s = graph.RemoveAllFanins(node_name, keep_controlling_nodes); EXPECT_EQ(s.ok(), success); if (!success) { EXPECT_EQ(s.error_message(), error_msg); } - if (expected_node != nullptr) { - CompareNodeInputs(graph, expected_node, node); + if (node_exists) { + CompareNodeFanins(graph, node, expected_fanins); if (success) { TensorId tensor_id; auto retained_inputs = absl::flat_hash_set(node->input().begin(), @@ -585,177 +615,192 @@ void TestRemoveAllFanins(absl::string_view node_name, for (const string& fanin : fanin_strings) { if (!retained_inputs.contains(fanin)) { tensor_id = ParseTensorName(fanin); - CheckFanout(graph, tensor_id, node_name); + CheckFanoutRemoved(graph, tensor_id, node_name); } } } } + + CheckUnmodifiedNodeFanins(graph_def, node_name, unmodified_node_inputs); + + CheckGraph(graph); } TEST(MutableGraphViewTest, RemoveAllFanins) { - NodeDef expected_node; string error_msg; // Remove all fanins from node with no control dependencies. - expected_node = NDef("", "", {}); - TestRemoveAllFanins("foo_3", /*keep_controlling_nodes=*/false, - /*success=*/true, error_msg, &expected_node); + TestRemoveAllFanins("foo_3", /*node_exists=*/true, + /*keep_controlling_nodes=*/false, + /*success=*/true, error_msg, {}); // Remove all fanins from node with control dependencies. - TestRemoveAllFanins("foo_4", /*keep_controlling_nodes=*/false, - /*success=*/true, error_msg, &expected_node); + TestRemoveAllFanins("foo_4", /*node_exists=*/true, + /*keep_controlling_nodes=*/false, + /*success=*/true, error_msg, {}); // Remove all fanins from node with no control dependencies and preserve // control dependencies. - TestRemoveAllFanins("foo_3", /*keep_controlling_nodes=*/true, - /*success=*/true, error_msg, &expected_node); + TestRemoveAllFanins("foo_3", /*node_exists=*/true, + /*keep_controlling_nodes=*/true, + /*success=*/true, error_msg, {}); // Remove all fanins from node with control dependencies and preserve control // dependencies. - expected_node = NDef("", "", {"^c", "^d"}); - TestRemoveAllFanins("foo_4", /*keep_controlling_nodes=*/true, - /*success=*/true, error_msg, &expected_node); + TestRemoveAllFanins("foo_4", /*node_exists=*/true, + /*keep_controlling_nodes=*/true, + /*success=*/true, error_msg, {"^c", "^d"}); // Remove all fanins from node with no fanins. - expected_node = NDef("", "", {}); - TestRemoveAllFanins("foo_5", /*keep_controlling_nodes=*/false, - /*success=*/true, error_msg, &expected_node); - TestRemoveAllFanins("foo_5", /*keep_controlling_nodes=*/true, - /*success=*/true, error_msg, &expected_node); + TestRemoveAllFanins("foo_5", /*node_exists=*/true, + /*keep_controlling_nodes=*/false, + /*success=*/true, error_msg, {}); + TestRemoveAllFanins("foo_5", /*node_exists=*/true, + /*keep_controlling_nodes=*/true, + /*success=*/true, error_msg, {}); // Remove all fanins from node with only control dependencies. - TestRemoveAllFanins("foo_6", /*keep_controlling_nodes=*/false, - /*success=*/true, error_msg, &expected_node); - expected_node = NDef("", "", {"^a", "^b"}); - TestRemoveAllFanins("foo_6", /*keep_controlling_nodes=*/true, - /*success=*/true, error_msg, &expected_node); + TestRemoveAllFanins("foo_6", /*node_exists=*/true, + /*keep_controlling_nodes=*/false, + /*success=*/true, error_msg, {}); + TestRemoveAllFanins("foo_6", /*node_exists=*/true, + /*keep_controlling_nodes=*/true, + /*success=*/true, error_msg, {"^a", "^b"}); // Remove all fanins from node where node is missing. error_msg = "Can't remove all fanins from missing node 'foo_missing'."; - TestRemoveAllFanins("foo_missing", /*keep_controlling_nodes=*/false, - /*success=*/false, error_msg, /*expected_node=*/nullptr); - TestRemoveAllFanins("foo_missing", /*keep_controlling_nodes=*/true, - /*success=*/false, error_msg, /*expected_node=*/nullptr); + TestRemoveAllFanins("foo_missing", /*node_exists=*/false, + /*keep_controlling_nodes=*/false, + /*success=*/false, error_msg, {}); + TestRemoveAllFanins("foo_missing", /*node_exists=*/false, + /*keep_controlling_nodes=*/true, + /*success=*/false, error_msg, {}); } -void TestUpdateFanin(absl::string_view node_name, const TensorId& from_fanin, - const TensorId& to_fanin, bool success, - const string& error_msg, const NodeDef* expected_node) { +void TestUpdateFanin(absl::string_view node_name, bool node_exists, + const TensorId& from_fanin, const TensorId& to_fanin, + bool success, const string& error_msg, + absl::Span expected_fanins) { GraphDef graph_def = SimpleMutateFaninGraph(); MutableGraphView graph(&graph_def); - auto node = graph.GetNode(node_name); - if (expected_node == nullptr) { - EXPECT_EQ(node, nullptr); - } else { + NodeDef* node = graph.GetNode(node_name); + if (node_exists) { EXPECT_NE(node, nullptr); + } else { + EXPECT_EQ(node, nullptr); } + absl::flat_hash_map> unmodified_node_inputs = + GetNodeInputsFromGraph(graph_def, node_name); + Status s = graph.UpdateFanin(node_name, from_fanin, to_fanin); EXPECT_EQ(s.ok(), success); if (!success) { EXPECT_EQ(s.error_message(), error_msg); } - if (expected_node != nullptr) { - CompareNodeInputs(graph, expected_node, node); + if (node_exists) { + CompareNodeFanins(graph, node, expected_fanins); if (success) { - CheckFanout(graph, from_fanin, node_name); + CheckFanoutRemoved(graph, from_fanin, node_name); } } + + CheckUnmodifiedNodeFanins(graph_def, node_name, unmodified_node_inputs); + + CheckGraph(graph); } TEST(MutableGraphViewTest, UpdateFanin) { - NodeDef expected_node; string error_msg; // Update fanin from non control to non control. - expected_node = NDef("", "", {"a", "b:3", "b:3", "^c", "^d"}); - TestUpdateFanin("foo_4", {"b", 2}, {"b", 3}, /*success=*/true, error_msg, - &expected_node); + TestUpdateFanin("foo_4", /*node_exists=*/true, {"b", 2}, {"b", 3}, + /*success=*/true, error_msg, {"a", "b:3", "b:3", "^c", "^d"}); // Update fanin from non control to control. - expected_node = NDef("", "", {"a", "^c", "^d", "^b"}); - TestUpdateFanin("foo_4", {"b", 2}, {"b", Graph::kControlSlot}, - /*success=*/true, error_msg, &expected_node); + TestUpdateFanin("foo_4", /*node_exists=*/true, {"b", 2}, + {"b", Graph::kControlSlot}, + /*success=*/true, error_msg, {"a", "^c", "^d", "^b"}); // Update fanin from control to non control. - expected_node = NDef("", "", {"a", "b:2", "b:2", "d:1", "^c"}); - TestUpdateFanin("foo_4", {"d", Graph::kControlSlot}, {"d", 1}, - /*success=*/true, error_msg, &expected_node); + TestUpdateFanin( + "foo_4", /*node_exists=*/true, {"d", Graph::kControlSlot}, {"d", 1}, + /*success=*/true, error_msg, {"a", "b:2", "b:2", "d:1", "^c"}); // Update fanin from control to control. - expected_node = NDef("", "", {"a", "b:2", "b:2", "^d"}); - TestUpdateFanin("foo_4", {"c", Graph::kControlSlot}, + TestUpdateFanin("foo_4", /*node_exists=*/true, {"c", Graph::kControlSlot}, {"b", Graph::kControlSlot}, /*success=*/true, error_msg, - &expected_node); + {"a", "b:2", "b:2", "^d"}); // Update fanin from control to existing control. - expected_node = NDef("", "", {"a", "b:2", "b:2", "^d"}); - TestUpdateFanin("foo_4", {"c", Graph::kControlSlot}, + TestUpdateFanin("foo_4", /*node_exists=*/true, {"c", Graph::kControlSlot}, {"d", Graph::kControlSlot}, /*success=*/true, error_msg, - &expected_node); + {"a", "b:2", "b:2", "^d"}); // Update fanin of node where from and to fanins are the same. - expected_node = NDef("", "", {"a"}); - TestUpdateFanin("foo_1", {"a", -1}, {"a", -1}, /*success=*/true, error_msg, - &expected_node); - TestUpdateFanin("foo_1", {"a", 0}, {"a", 0}, /*success=*/true, error_msg, - &expected_node); - TestUpdateFanin("foo_1", {"a", 1}, {"a", 1}, /*success=*/true, error_msg, - &expected_node); + TestUpdateFanin("foo_1", /*node_exists=*/true, {"a", -1}, {"a", -1}, + /*success=*/true, error_msg, {"a"}); + TestUpdateFanin("foo_1", /*node_exists=*/true, {"a", 0}, {"a", 0}, + /*success=*/true, error_msg, {"a"}); + TestUpdateFanin("foo_1", /*node_exists=*/true, {"a", 1}, {"a", 1}, + /*success=*/true, error_msg, {"a"}); // Update fanin of node where node is missing. error_msg = "Can't update fanin 'a:0' to fanin 'a:1' in missing node 'foo_missing'."; - TestUpdateFanin("foo_missing", {"a", 0}, {"a", 1}, /*success=*/false, - error_msg, - /*expected_node=*/nullptr); + TestUpdateFanin("foo_missing", /*node_exists=*/false, {"a", 0}, {"a", 1}, + /*success=*/false, error_msg, {}); // Update fanin of node where from fanin is missing. error_msg = "Can't update missing fanin 'from_bar_missing:0' to fanin 'a:1' in node " "'foo_1'."; - TestUpdateFanin("foo_1", {"from_bar_missing", 0}, {"a", 1}, - /*success=*/false, error_msg, &expected_node); + TestUpdateFanin("foo_1", /*node_exists=*/true, {"from_bar_missing", 0}, + {"a", 1}, + /*success=*/false, error_msg, {"a"}); // Update fanin of node where to fanin is missing. error_msg = "Can't update fanin 'a:0' to missing fanin 'to_bar_missing:1' in node " "'foo_1'."; - TestUpdateFanin("foo_1", {"a", 0}, {"to_bar_missing", 1}, /*success=*/false, - error_msg, &expected_node); + TestUpdateFanin("foo_1", /*node_exists=*/true, {"a", 0}, + {"to_bar_missing", 1}, /*success=*/false, error_msg, {"a"}); // Update fanin of node where from/to fanins and node are missing. error_msg = "Can't update missing fanin 'from_bar_missing:0' to missing fanin " "'to_bar_missing:1' in missing node 'foo_missing'."; - TestUpdateFanin("foo_missing", {"from_bar_missing", 0}, {"to_bar_missing", 1}, - /*success=*/false, error_msg, /*expected_node=*/nullptr); + TestUpdateFanin("foo_missing", /*node_exists=*/false, {"from_bar_missing", 0}, + {"to_bar_missing", 1}, + /*success=*/false, error_msg, {}); // Update fanin of node where from fanin is invalid. error_msg = "Can't update invalid fanin 'a:-2' to fanin 'a:0' in node 'foo_1'."; - TestUpdateFanin("foo_1", {"a", -2}, {"a", 0}, - /*success=*/false, error_msg, &expected_node); + TestUpdateFanin("foo_1", /*node_exists=*/true, {"a", -2}, {"a", 0}, + /*success=*/false, error_msg, {"a"}); // Update fanin of node where to fanin is invalid. error_msg = "Can't update fanin 'a:0' to invalid fanin 'a:-2' in node 'foo_1'."; - TestUpdateFanin("foo_1", {"a", 0}, {"a", -2}, - /*success=*/false, error_msg, &expected_node); + TestUpdateFanin("foo_1", /*node_exists=*/true, {"a", 0}, {"a", -2}, + /*success=*/false, error_msg, {"a"}); // Update fanin of node where from/to fanins are invalid and missing and node // is missing. error_msg = "Can't update invalid/missing fanin 'from_bar_missing:-2' to " "invalid/missing fanin 'to_bar_missing:-3' in missing node " "'foo_missing'."; - TestUpdateFanin("foo_missing", {"from_bar_missing", -2}, - {"to_bar_missing", -3}, - /*success=*/false, error_msg, /*expected_node=*/nullptr); + TestUpdateFanin("foo_missing", /*node_exists=*/false, + {"from_bar_missing", -2}, {"to_bar_missing", -3}, + /*success=*/false, error_msg, {}); // Update to self to create cycle. - expected_node = NDef("", "", {"a", "b:2", "b:2", "^c", "^d"}); error_msg = "Can't update fanin 'b:2' to fanin 'foo_4:3' in self 'foo_4'."; - TestUpdateFanin("foo_4", {"b", 2}, {"foo_4", 3}, /*success=*/false, error_msg, - &expected_node); + TestUpdateFanin("foo_4", /*node_exists=*/true, {"b", 2}, {"foo_4", 3}, + /*success=*/false, error_msg, + {"a", "b:2", "b:2", "^c", "^d"}); error_msg = "Can't update fanin 'b:2' to fanin '^foo_4' in self 'foo_4'."; - TestUpdateFanin("foo_4", {"b", 2}, {"foo_4", Graph::kControlSlot}, - /*success=*/false, error_msg, &expected_node); + TestUpdateFanin( + "foo_4", /*node_exists=*/true, {"b", 2}, {"foo_4", Graph::kControlSlot}, + /*success=*/false, error_msg, {"a", "b:2", "b:2", "^c", "^d"}); error_msg = "Can't update fanin '^c' to fanin 'foo_4:4' in self 'foo_4'."; - TestUpdateFanin("foo_4", {"c", Graph::kControlSlot}, {"foo_4", 4}, - /*success=*/false, error_msg, &expected_node); + TestUpdateFanin( + "foo_4", /*node_exists=*/true, {"c", Graph::kControlSlot}, {"foo_4", 4}, + /*success=*/false, error_msg, {"a", "b:2", "b:2", "^c", "^d"}); error_msg = "Can't update fanin '^c' to fanin '^foo_4' in self 'foo_4'."; - TestUpdateFanin("foo_4", {"c", Graph::kControlSlot}, + TestUpdateFanin("foo_4", /*node_exists=*/true, {"c", Graph::kControlSlot}, {"foo_4", Graph::kControlSlot}, /*success=*/false, error_msg, - &expected_node); + {"a", "b:2", "b:2", "^c", "^d"}); } void TestUpdateFaninFromFaninToNodeAsSwitchControl(const TensorId& fanin) { @@ -777,20 +822,12 @@ void TestUpdateFaninFromFaninToNodeAsSwitchControl(const TensorId& fanin) { EXPECT_EQ(graph.graph()->node_size(), 3); - NodeDef expected; - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - expected = NDef("", "", {}); - CompareNodeInputs(graph, &expected, a); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - CompareNodeInputs(graph, &expected, b); + string fanout = IsControlInput(fanin) ? AsControlDependency("c") : "c"; + CheckNode(graph, "a", "NotImportant", "", {}, {}, {fanout}); + CheckNode(graph, "b", "Switch", "", {}, {}, {}); + CheckNode(graph, "c", "NotImportant", "", {}, {tensor_id_str}, {}); - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - expected = NDef("", "", {tensor_id_str}); - CompareNodeInputs(graph, &expected, c); + CheckGraph(graph); } TEST(MutableGraphViewTest, UpdateFaninToNodeAsSwitchControl) { @@ -816,59 +853,24 @@ TEST(MutableGraphViewTest, DedupControllingFaninsOnGraphInit) { MutableGraphView graph(&graph_def); EXPECT_EQ(graph.graph()->node_size(), 11); - NodeDef expected; - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - expected = NDef("", "", {}); - CompareNodeInputs(graph, &expected, a); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - CompareNodeInputs(graph, &expected, b); - - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - CompareNodeInputs(graph, &expected, c); - - NodeDef* d = graph.GetNode("d"); - ASSERT_NE(d, nullptr); - expected = NDef("", "", {"c:1"}); - CompareNodeInputs(graph, &expected, d); - - NodeDef* foo_1 = graph.GetNode("foo_1"); - ASSERT_NE(foo_1, nullptr); - expected = NDef("", "", {"a", "b:1"}); - CompareNodeInputs(graph, &expected, foo_1); - - NodeDef* foo_2 = graph.GetNode("foo_2"); - ASSERT_NE(foo_2, nullptr); - expected = NDef("", "", {"a", "^b"}); - CompareNodeInputs(graph, &expected, foo_2); - - NodeDef* foo_3 = graph.GetNode("foo_3"); - ASSERT_NE(foo_3, nullptr); - expected = NDef("", "", {"a", "b:1"}); - CompareNodeInputs(graph, &expected, foo_3); - - NodeDef* foo_4 = graph.GetNode("foo_4"); - ASSERT_NE(foo_4, nullptr); - expected = NDef("", "", {"a:2", "b:1"}); - CompareNodeInputs(graph, &expected, foo_4); - - NodeDef* foo_5 = graph.GetNode("foo_5"); - ASSERT_NE(foo_5, nullptr); - expected = NDef("", "", {"a:2", "b:1"}); - CompareNodeInputs(graph, &expected, foo_5); - - NodeDef* foo_6 = graph.GetNode("foo_6"); - ASSERT_NE(foo_6, nullptr); - expected = NDef("", "", {"d", "^d"}); - CompareNodeInputs(graph, &expected, foo_6); - - NodeDef* foo_7 = graph.GetNode("foo_7"); - ASSERT_NE(foo_7, nullptr); - expected = NDef("", "", {"a:3", "b:2", "d", "^d"}); - CompareNodeInputs(graph, &expected, foo_7); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, + {"foo_1", "foo_2", "foo_3", "foo_4", "foo_5", "foo_7"}); + CheckNode(graph, "b", "NotImportant", "", {}, {}, + {"foo_1:1", "^foo_2", "foo_3:1", "foo_4:1", "foo_5:1", "foo_7:1"}); + CheckNode(graph, "c", "Switch", "", {}, {}, {"d"}); + CheckNode(graph, "d", "Identity", "", {}, {"c:1"}, + {"foo_6", "^foo_6", "foo_7:2", "^foo_7"}); + CheckNode(graph, "foo_1", "IdentityN", "", {}, {"a", "b:1"}, {}); + CheckNode(graph, "foo_2", "IdentityN", "", {}, {"a", "^b"}, {}); + CheckNode(graph, "foo_3", "IdentityN", "", {}, {"a", "b:1"}, {}); + CheckNode(graph, "foo_4", "IdentityN", "", {}, {"a:2", "b:1"}, {}); + CheckNode(graph, "foo_5", "NotImportant", "", {}, {"a:2", "b:1"}, {}); + CheckNode(graph, "foo_6", "Identity", "", {}, {"d", "^d"}, {}); + CheckNode(graph, "foo_7", "NotImportant", "", {}, {"a:3", "b:2", "d", "^d"}, + {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, DedupControllingFaninsOnAddFanin) { @@ -881,17 +883,14 @@ TEST(MutableGraphViewTest, DedupControllingFaninsOnAddFanin) { MutableGraphView graph(&graph_def); EXPECT_TRUE(graph.AddRegularFanin("b", {"a", 2}).ok()); - NodeDef expected; - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - expected = NDef("", "", {"a:2"}); - CompareNodeInputs(graph, &expected, b); + CheckNode(graph, "b", "NotImportant", "", {}, {"a:2"}, {}); EXPECT_TRUE(graph.AddControllingFanin("c", {"a", Graph::kControlSlot}).ok()); - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - expected = NDef("", "", {"a:1"}); - CompareNodeInputs(graph, &expected, c); + CheckNode(graph, "c", "NotImportant", "", {}, {"a:1"}, {}); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b:0", "c:0"}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnAddFanin) { @@ -903,29 +902,18 @@ TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnAddFanin) { MutableGraphView graph(&graph_def); EXPECT_TRUE(graph.AddRegularFanin("c", {"b", 2}).ok()); - NodeDef expected; - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - expected = NDef("", "", {"b:2"}); - CompareNodeInputs(graph, &expected, c); + CheckNode(graph, "c", "", "", {}, {"b:2"}, {}); EXPECT_TRUE(graph.AddControllingFanin("c", {"b", Graph::kControlSlot}).ok()); - expected = NDef("", "", {"b:2", "^b"}); - CompareNodeInputs(graph, &expected, c); + CheckNode(graph, "c", "", "", {}, {"b:2", "^b"}, {}); EXPECT_TRUE(graph.AddControllingFanin("c", {"b", Graph::kControlSlot}).ok()); - expected = NDef("", "", {"b:2", "^b"}); - CompareNodeInputs(graph, &expected, c); + CheckNode(graph, "c", "", "", {}, {"b:2", "^b"}, {}); EXPECT_TRUE(graph.AddControllingFanin("d", {"b", Graph::kControlSlot}).ok()); - NodeDef* d = graph.GetNode("d"); - ASSERT_NE(d, nullptr); - expected = NDef("", "", {"^b"}); - CompareNodeInputs(graph, &expected, d); + CheckNode(graph, "d", "", "", {}, {"^b"}, {}); EXPECT_TRUE(graph.AddControllingFanin("d", {"b", Graph::kControlSlot}).ok()); - expected = NDef("", "", {"^b"}); - CompareNodeInputs(graph, &expected, d); - EXPECT_TRUE(graph.AddRegularFanin("d", {"b", 3}).ok()); - expected = NDef("", "", {"b:3", "^b"}); - CompareNodeInputs(graph, &expected, d); + CheckNode(graph, "d", "", "", {}, {"^b"}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, DedupControllingFaninsOnUpdateFanin) { @@ -938,10 +926,12 @@ TEST(MutableGraphViewTest, DedupControllingFaninsOnUpdateFanin) { MutableGraphView graph(&graph_def); EXPECT_TRUE(graph.UpdateFanin("c", {"a", 1}, {"b", 2}).ok()); - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - NodeDef expected = NDef("", "", {"b:2"}); - CompareNodeInputs(graph, &expected, c); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "b", "NotImportant", "", {}, {}, {"c"}); + CheckNode(graph, "c", "NotImportant", "", {}, {"b:2"}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnUpdateFanin) { @@ -957,23 +947,16 @@ TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnUpdateFanin) { .UpdateFanin("d", {"b", Graph::kControlSlot}, {"c", Graph::kControlSlot}) .ok()); - NodeDef expected; - NodeDef* d = graph.GetNode("d"); - ASSERT_NE(d, nullptr); - expected = NDef("", "", {"c", "^c"}); - CompareNodeInputs(graph, &expected, d); + CheckNode(graph, "d", "NotImportant", "", {}, {"c", "^c"}, {}); EXPECT_TRUE(graph.UpdateFanin("e", {"b", 0}, {"c", 3}).ok()); - NodeDef* e = graph.GetNode("e"); - ASSERT_NE(e, nullptr); - expected = NDef("", "", {"c:3", "^c"}); - CompareNodeInputs(graph, &expected, e); + CheckNode(graph, "e", "NotImportant", "", {}, {"c:3", "^c"}, {}); EXPECT_TRUE( graph.UpdateFanin("e", {"c", 3}, {"c", Graph::kControlSlot}).ok()); - ASSERT_NE(e, nullptr); - expected = NDef("", "", {"^c"}); - CompareNodeInputs(graph, &expected, e); + CheckNode(graph, "e", "NotImportant", "", {}, {"^c"}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnAddFanin) { @@ -986,19 +969,12 @@ TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnAddFanin) { MutableGraphView graph(&graph_def); EXPECT_TRUE(graph.AddRegularFanin("c", {"a", 3}).ok()); - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - - auto fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true); - EXPECT_EQ(fanouts.size(), 2); - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(b, 0)), 1); + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b", "c"}); + CheckNode(graph, "b", "NotImportant", "", {}, {"a:1"}, {"^c"}); + CheckNode(graph, "c", "NotImportant", "", {}, {"a:3", "^b"}, {}); - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(c, 0)), 1); + CheckGraph(graph); } TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnRemoveFanin) { @@ -1011,15 +987,11 @@ TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnRemoveFanin) { MutableGraphView graph(&graph_def); EXPECT_TRUE(graph.RemoveRegularFanin("c", {"a", 2}).ok()); - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b"}); + CheckNode(graph, "b", "NotImportant", "", {}, {"a:1"}, {}); + CheckNode(graph, "c", "NotImportant", "", {}, {}, {}); - auto fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true); - EXPECT_EQ(fanouts.size(), 1); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(b, 0)), 1); + CheckGraph(graph); } TEST(MutableGraphViewTest, KeepMaxRegularOutputPortOnRemoveFanin) { @@ -1032,15 +1004,12 @@ TEST(MutableGraphViewTest, KeepMaxRegularOutputPortOnRemoveFanin) { MutableGraphView graph(&graph_def); EXPECT_TRUE(graph.RemoveRegularFanin("b", {"a", 1}).ok()); - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - auto fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true); - EXPECT_EQ(fanouts.size(), 1); + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"c"}); + CheckNode(graph, "b", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "c", "NotImportant", "", {}, {"a:2"}, {}); - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - EXPECT_EQ(fanouts.count(MutableGraphView::InputPort(c, 0)), 1); + CheckGraph(graph); } TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnUpdateFanin) { @@ -1053,22 +1022,12 @@ TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnUpdateFanin) { MutableGraphView graph(&graph_def); EXPECT_TRUE(graph.UpdateFanin("c", {"a", 2}, {"b", 3}).ok()); - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - - auto a_fanouts = graph.GetFanouts(*a, /*include_controlled_nodes=*/true); - EXPECT_EQ(a_fanouts.size(), 1); - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - EXPECT_EQ(a_fanouts.count(MutableGraphView::InputPort(b, 0)), 1); + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b"}); + CheckNode(graph, "b", "NotImportant", "", {}, {"a:1"}, {"c"}); + CheckNode(graph, "c", "NotImportant", "", {}, {"b:3"}, {}); - auto b_fanouts = graph.GetFanouts(*b, /*include_controlled_nodes=*/true); - EXPECT_EQ(b_fanouts.size(), 1); - - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - EXPECT_EQ(b_fanouts.count(MutableGraphView::InputPort(c, 0)), 1); + CheckGraph(graph); } TEST(MutableGraphViewTest, AddControllingFaninMissing) { @@ -1096,15 +1055,11 @@ TEST(MutableGraphViewTest, AddControllingFaninMissing) { EXPECT_EQ(s.error_message(), expected_msg); ASSERT_EQ(graph.graph()->node_size(), 2); - NodeDef expected; - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - expected = NDef("", "", {}); - CompareNodeInputs(graph, &expected, a); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - CompareNodeInputs(graph, &expected, b); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "b", "NotImportant", "", {}, {}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddControllingFaninExistingControl) { @@ -1118,16 +1073,11 @@ TEST(MutableGraphViewTest, AddControllingFaninExistingControl) { EXPECT_TRUE(graph.AddControllingFanin("a", {"b", Graph::kControlSlot}).ok()); ASSERT_EQ(graph.graph()->node_size(), 2); - NodeDef expected; - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - expected = NDef("", "", {"^b"}); - CompareNodeInputs(graph, &expected, a); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - expected = NDef("", "", {}); - CompareNodeInputs(graph, &expected, b); + + CheckNode(graph, "a", "NotImportant", "", {}, {"^b"}, {}); + CheckNode(graph, "b", "NotImportant", "", {}, {}, {"^a"}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddControllingFaninNotSwitch) { @@ -1141,16 +1091,11 @@ TEST(MutableGraphViewTest, AddControllingFaninNotSwitch) { EXPECT_TRUE(graph.AddControllingFanin("a", {"b", 2}).ok()); ASSERT_EQ(graph.graph()->node_size(), 2); - NodeDef expected; - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - expected = NDef("", "", {"^b"}); - CompareNodeInputs(graph, &expected, a); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - expected = NDef("", "", {}); - CompareNodeInputs(graph, &expected, b); + + CheckNode(graph, "a", "NotImportant", "", {}, {"^b"}, {}); + CheckNode(graph, "b", "NotImportant", "", {}, {}, {"^a"}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddControllingFaninSwitch) { @@ -1167,14 +1112,11 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitch) { EXPECT_EQ(s.error_message(), expected_msg); ASSERT_EQ(graph.graph()->node_size(), 2); - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - NodeDef expected = NDef("", "", {}); - CompareNodeInputs(graph, &expected, a); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - CompareNodeInputs(graph, &expected, b); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "b", "Switch", "", {}, {}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddControllingFaninSwitchWithIdentity) { @@ -1189,10 +1131,12 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithIdentity) { EXPECT_TRUE(graph.AddControllingFanin("a", {"switch", 0}).ok()); ASSERT_EQ(graph.graph()->node_size(), 3); - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - NodeDef expected = NDef("", "", {"^identity"}); - CompareNodeInputs(graph, &expected, a); + + CheckNode(graph, "a", "NotImportant", "", {}, {"^identity"}, {}); + CheckNode(graph, "switch", "Switch", "", {}, {}, {"identity"}); + CheckNode(graph, "identity", "Identity", "", {}, {"switch"}, {"^a"}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddControllingFaninSwitchWithNoExistingIdentity) { @@ -1208,20 +1152,15 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithNoExistingIdentity) { EXPECT_TRUE(graph.AddControllingFanin("a", {"switch", 0}).ok()); ASSERT_EQ(graph.graph()->node_size(), 3); - NodeDef expected; - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - expected = NDef("", "", {"^ConstantFoldingCtrl/switch_0"}); - CompareNodeInputs(graph, &expected, a); - - NodeDef* identity = graph.GetNode("ConstantFoldingCtrl/switch_0"); - ASSERT_NE(identity, nullptr); - expected = NDef("", "", {"switch"}); - CompareNodeInputs(graph, &expected, identity); - EXPECT_EQ(identity->op(), "Identity"); - EXPECT_EQ(identity->device(), kDevice); - ASSERT_TRUE(identity->attr().count("T")); - EXPECT_EQ(identity->attr().at("T").type(), DT_FLOAT); + + CheckNode(graph, "a", "NotImportant", "", {}, + {"^ConstantFoldingCtrl/switch_0"}, {}); + CheckNode(graph, "switch", "Switch", kDevice, {{"T", DT_FLOAT}}, {}, + {"ConstantFoldingCtrl/switch_0"}); + CheckNode(graph, "ConstantFoldingCtrl/switch_0", "Identity", kDevice, + {{"T", DT_FLOAT}}, {"switch"}, {"^a"}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddControllingFaninSwitchWithExistingAddedIdentity) { @@ -1236,10 +1175,15 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithExistingAddedIdentity) { EXPECT_TRUE(graph.AddControllingFanin("a", {"switch", 0}).ok()); ASSERT_EQ(graph.graph()->node_size(), 3); - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - NodeDef expected = NDef("", "", {"^ConstantFoldingCtrl/switch_0"}); - CompareNodeInputs(graph, &expected, a); + + CheckNode(graph, "a", "NotImportant", "", {}, + {"^ConstantFoldingCtrl/switch_0"}, {}); + CheckNode(graph, "switch", "Switch", "", {}, {}, + {"ConstantFoldingCtrl/switch_0"}); + CheckNode(graph, "ConstantFoldingCtrl/switch_0", "Identity", "", {}, + {"switch"}, {"^a"}); + + CheckGraph(graph); } void TestAddControllingFaninSelfLoops(absl::string_view node_name, @@ -1259,30 +1203,14 @@ void TestAddControllingFaninSelfLoops(absl::string_view node_name, EXPECT_EQ(s.error_message(), error_msg); EXPECT_EQ(graph.graph()->node_size(), 5); - NodeDef expected; - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - expected = NDef("", "", {}, {}); - CompareNodeInputs(graph, &expected, a); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - CompareNodeInputs(graph, &expected, b); - - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - expected = NDef("", "", {"b:0"}); - CompareNodeInputs(graph, &expected, c); - - NodeDef* d = graph.GetNode("d"); - ASSERT_NE(d, nullptr); - expected = NDef("", "", {"b:1"}); - CompareNodeInputs(graph, &expected, d); - - NodeDef* e = graph.GetNode("e"); - ASSERT_NE(e, nullptr); - expected = NDef("", "", {"^a"}); - CompareNodeInputs(graph, &expected, e); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"^e"}); + CheckNode(graph, "b", "Switch", "", {{"T", DT_FLOAT}}, {}, {"c", "d"}); + CheckNode(graph, "c", "Identity", "", {}, {"b"}, {}); + CheckNode(graph, "d", "Identity", "", {}, {"b:1"}, {}); + CheckNode(graph, "e", "NotImportant", "", {}, {"^a"}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, AddControllingFaninSelfLoops) { @@ -1332,23 +1260,13 @@ TEST(MutableGraphViewTest, AddControllingFaninSelfLoopsGeneratedIdentity) { EXPECT_EQ(s.error_message(), expected_msg); EXPECT_EQ(graph.graph()->node_size(), 4); - NodeDef expected; - NodeDef* a = graph.GetNode("a"); - ASSERT_NE(a, nullptr); - expected = NDef("", "", {}, {}); - CompareNodeInputs(graph, &expected, a); - - NodeDef* b = graph.GetNode("b"); - ASSERT_NE(b, nullptr); - CompareNodeInputs(graph, &expected, b); - - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - CompareNodeInputs(graph, &expected, c); - - NodeDef* d = graph.GetNode("ConstantFoldingCtrl/b_1"); - ASSERT_NE(d, nullptr); - CompareNodeInputs(graph, &expected, d); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "b", "Switch", "", {{"T", DT_FLOAT}}, {}, {}); + CheckNode(graph, "c", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "ConstantFoldingCtrl/b_1", "Identity", "", {}, {}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, RemoveControllingFaninMissing) { @@ -1364,10 +1282,13 @@ TEST(MutableGraphViewTest, RemoveControllingFaninMissing) { EXPECT_TRUE(graph.RemoveControllingFanin("d", "c").ok()); ASSERT_EQ(graph.graph()->node_size(), 4); - NodeDef* d = graph.GetNode("d"); - ASSERT_NE(d, nullptr); - NodeDef expected = NDef("", "", {"^a", "^b"}); - CompareNodeInputs(graph, &expected, d); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"^d"}); + CheckNode(graph, "b", "NotImportant", "", {}, {}, {"^d"}); + CheckNode(graph, "c", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "d", "NotImportant", "", {}, {"^a", "^b"}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, RemoveControllingFaninExisting) { @@ -1384,10 +1305,13 @@ TEST(MutableGraphViewTest, RemoveControllingFaninExisting) { EXPECT_TRUE(graph.RemoveControllingFanin("d", "a").ok()); ASSERT_EQ(graph.graph()->node_size(), 4); - NodeDef* d = graph.GetNode("d"); - ASSERT_NE(d, nullptr); - NodeDef expected = NDef("", "", {"^c", "^b"}); - CompareNodeInputs(graph, &expected, d); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {}); + CheckNode(graph, "b", "NotImportant", "", {}, {}, {"^d"}); + CheckNode(graph, "c", "NotImportant", "", {}, {}, {"^d"}); + CheckNode(graph, "d", "NotImportant", "", {}, {"^c", "^b"}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, RemoveControllingFaninOnRegularFanin) { @@ -1403,10 +1327,12 @@ TEST(MutableGraphViewTest, RemoveControllingFaninOnRegularFanin) { EXPECT_TRUE(graph.RemoveControllingFanin("c", "b").ok()); ASSERT_EQ(graph.graph()->node_size(), 3); - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - NodeDef expected = NDef("", "", {"a", "b"}); - CompareNodeInputs(graph, &expected, c); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b", "c"}); + CheckNode(graph, "b", "NotImportant", "", {}, {"a"}, {"c:1"}); + CheckNode(graph, "c", "NotImportant", "", {}, {"a", "b"}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, RemoveControllingFaninSelfLoop) { @@ -1424,10 +1350,12 @@ TEST(MutableGraphViewTest, RemoveControllingFaninSelfLoop) { EXPECT_EQ(s.error_message(), expected_msg); ASSERT_EQ(graph.graph()->node_size(), 3); - NodeDef* c = graph.GetNode("c"); - ASSERT_NE(c, nullptr); - NodeDef expected = NDef("", "", {"a", "b"}); - CompareNodeInputs(graph, &expected, c); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b", "c"}); + CheckNode(graph, "b", "NotImportant", "", {}, {"a"}, {"c:1"}); + CheckNode(graph, "c", "NotImportant", "", {}, {"a", "b"}, {}); + + CheckGraph(graph); } TEST(MutableGraphViewTest, DeleteNodes) { @@ -1444,21 +1372,14 @@ TEST(MutableGraphViewTest, DeleteNodes) { EXPECT_NE(graph.GetNode("foo_1"), nullptr); graph.DeleteNodes({"foo_1"}); + EXPECT_EQ(graph.graph()->node_size(), 3); EXPECT_EQ(graph.GetNode("foo_1"), nullptr); - NodeDef* bar = graph.GetNode("bar"); - NodeDef* other = graph.GetNode("other"); - NodeDef* foo_2 = graph.GetNode("foo_2"); - - bool include_control_fanouts = true; - auto bar_fanouts = graph.GetFanouts(*bar, include_control_fanouts); - auto other_fanouts = graph.GetFanouts(*other, include_control_fanouts); - - EXPECT_EQ(bar_fanouts.size(), 1); - EXPECT_EQ(bar_fanouts.count(MutableGraphView::InputPort(foo_2, 1)), 1); + CheckNode(graph, "bar", "NotImportant", "", {}, {}, {"foo_2:1"}); + CheckNode(graph, "other", "NotImportant", "", {}, {}, {"foo_2"}); + CheckNode(graph, "foo_2", "NotImportant", "", {}, {"other:1", "bar:2"}, {}); - EXPECT_EQ(other_fanouts.size(), 1); - EXPECT_EQ(other_fanouts.count(MutableGraphView::InputPort(foo_2, 0)), 1); + CheckGraph(graph); } } // namespace -- GitLab From b2723af89145e5238df417b9bfd47fef29d00354 Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Wed, 16 Jan 2019 13:05:06 -0800 Subject: [PATCH 0810/2345] Don't run ops.device(None) in eager. PiperOrigin-RevId: 229610975 --- tensorflow/python/framework/ops.py | 3 +++ tensorflow/python/ops/resource_variable_ops.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index 079fad4210..6da6637b0b 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -102,6 +102,9 @@ class _UserDeviceSpec(object): class NullContextmanager(object): + def __init__(self, *args, **kwargs): + pass + def __enter__(self): pass diff --git a/tensorflow/python/ops/resource_variable_ops.py b/tensorflow/python/ops/resource_variable_ops.py index 1a895fee87..c8f20029ac 100644 --- a/tensorflow/python/ops/resource_variable_ops.py +++ b/tensorflow/python/ops/resource_variable_ops.py @@ -409,11 +409,13 @@ class ResourceVariable(variables.VariableV1): # Use attr_scope and device(None) to simulate the behavior of # colocate_with when the variable we want to colocate with doesn't # yet exist. + device_context_manager = ( + ops.device if self._in_graph_mode else ops.NullContextmanager) attr = attr_value_pb2.AttrValue( list=attr_value_pb2.AttrValue.ListValue( s=[compat.as_bytes("loc:@%s" % handle_name)])) with ops.get_default_graph()._attr_scope({"_class": attr}): - with ops.name_scope("Initializer"), ops.device(None): + with ops.name_scope("Initializer"), device_context_manager(None): initial_value = ops.convert_to_tensor( initial_value() if init_from_fn else initial_value, name="initial_value", dtype=dtype) -- GitLab From 71472ddbe17a51ba10003d271700bb0123ceb991 Mon Sep 17 00:00:00 2001 From: Yunxing Dai Date: Wed, 16 Jan 2019 13:29:51 -0800 Subject: [PATCH 0811/2345] Remove dynamic dynamic dimensions in XLABuilder::Build XLA backend cannot handle dynamic dimension yet, remove all dynamic dimensions before building xla program until we have support in the backend. PiperOrigin-RevId: 229615685 --- tensorflow/compiler/xla/client/xla_builder.cc | 19 +++++++++++++++++++ .../compiler/xla/client/xla_builder_test.cc | 4 +++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_builder.cc index 4b360c6563..20298d175d 100644 --- a/tensorflow/compiler/xla/client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_builder.cc @@ -322,6 +322,25 @@ StatusOr XlaBuilder::Build(int64 root_id) { return AppendStatus(first_error_, backtrace); } + // TODO(b/121223198): XLA backend cannot handle dynamic dimensions yet, remove + // all dynamic dimensions before building xla program until we have support in + // the backend. + std::function remove_dynamic_dimension = + [&](ShapeProto* shape) { + if (shape->tuple_shapes_size() != 0) { + for (int64 i = 0; i < shape->tuple_shapes_size(); ++i) { + remove_dynamic_dimension(shape->mutable_tuple_shapes(i)); + } + } + for (int64 i = 0; i < shape->dimensions_size(); ++i) { + shape->set_is_dynamic_dimension(i, false); + } + }; + + for (auto& instruction : instructions_) { + remove_dynamic_dimension(instruction.mutable_shape()); + } + HloComputationProto entry; SetProtoIdAndName(&entry, name_, kNameSeparator, GetNextId()); TF_ASSIGN_OR_RETURN(ProgramShape program_shape, GetProgramShape(root_id)); diff --git a/tensorflow/compiler/xla/client/xla_builder_test.cc b/tensorflow/compiler/xla/client/xla_builder_test.cc index abc11b4732..feee8187c7 100644 --- a/tensorflow/compiler/xla/client/xla_builder_test.cc +++ b/tensorflow/compiler/xla/client/xla_builder_test.cc @@ -463,7 +463,9 @@ TEST_F(XlaBuilderTest, DynamicParameter) { ->parameter_instruction(0) ->shape() .tuple_shapes(1); - EXPECT_TRUE(param_shape.is_dynamic_dimension(0)); + // TODO(b/121223198): The dynamic dimension should be set once we enable + // dynamic dimensions in xla builder. + EXPECT_FALSE(param_shape.is_dynamic_dimension(0)); } TEST_F(XlaBuilderTest, AfterAllWithNonTokenOperands) { -- GitLab From c432f294845786ca4d26e4d8091cab4dfa1e796f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 13:40:43 -0800 Subject: [PATCH 0812/2345] Restore 0-arg version of Tensor::DebugString. PiperOrigin-RevId: 229617823 --- tensorflow/core/framework/tensor.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/framework/tensor.h b/tensorflow/core/framework/tensor.h index 44293c5c19..4cfad893d5 100644 --- a/tensorflow/core/framework/tensor.h +++ b/tensorflow/core/framework/tensor.h @@ -529,7 +529,8 @@ class Tensor { // `num_values` is the number of actual data values in the tensor // included in the message. If the tensor might be resident in // GPU/TPU memory use DeviceSafeDebugString instead. - string DebugString(int num_values = 3) const; + string DebugString(int num_values) const; + string DebugString() const { return DebugString(3); } // Variant of DebugString() that should be used for possibly non-CPU tensors. // If the tensor is not resident on CPU, we can't read its values as -- GitLab From 440ee60cda0912d2275a61b714a33564716bfd5a Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Wed, 16 Jan 2019 13:54:48 -0800 Subject: [PATCH 0813/2345] [TF:XLA] Bump open source llvm revision to r351319 PiperOrigin-RevId: 229620326 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 354148f034..b8ef43d27e 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -502,11 +502,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 = "c6e6de0b5cd524acf276188cb6aae427568070cd1b9cb9c0e3aab8044f3ac278", - strip_prefix = "llvm-a42cde684126a3797f3eda9c894c379e7a8c66c1", + sha256 = "2027c5043ea3458376fbf4263aaab3b257736b9e4a1e9e9984f15e245177979d", + strip_prefix = "llvm-82a2c96ee81a617918d9bcc0fbe9af860e437f5f", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/a42cde684126a3797f3eda9c894c379e7a8c66c1.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/a42cde684126a3797f3eda9c894c379e7a8c66c1.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/82a2c96ee81a617918d9bcc0fbe9af860e437f5f.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/82a2c96ee81a617918d9bcc0fbe9af860e437f5f.tar.gz", ], ) -- GitLab From 955d8f1377c3f8e7a2ec4b548126d20d70e063f0 Mon Sep 17 00:00:00 2001 From: Alan Chiao Date: Wed, 16 Jan 2019 14:13:41 -0800 Subject: [PATCH 0814/2345] Clarify tensor_utils neon memory alignment assumptions. PiperOrigin-RevId: 229624071 --- .../internal/optimized/neon_tensor_utils.cc | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/tensorflow/lite/kernels/internal/optimized/neon_tensor_utils.cc b/tensorflow/lite/kernels/internal/optimized/neon_tensor_utils.cc index cf1bda2661..3e7b6c22be 100644 --- a/tensorflow/lite/kernels/internal/optimized/neon_tensor_utils.cc +++ b/tensorflow/lite/kernels/internal/optimized/neon_tensor_utils.cc @@ -90,20 +90,28 @@ void NeonMatrixBatchVectorMultiplyAccumulate( int n_batch, float* __restrict__ result, int result_stride) { const int kWeightsPerUint32 = 4; const int kWeightsPerNeonLane = 16; - // If the number of rows is not divisible by kWeightsPerUint32, we set a - // flag and allocate an aligned memory block. The flag is used to use the - // aligned memory block later in the kernel loop. + // Assuming *matrix is kWeightsPerUint32-byte aligned, + // every row of the matrix is also + // kWeightsPerUint32-byte aligned as long as cols is + // a multiple of kWeightsPerUint32. The assumption + // is currently satisfied by TFLite's 16-byte memory + // alignment scheme. + // + // Otherwise, we allocate an aligned memory block and set + // a flag to later copy rows from matrix to the block + // for aligned multiplication. bool unaligned = false; - int8* aligned_row = nullptr; + int8_t* aligned_row = nullptr; void* aligned_row_free = nullptr; if ((m_cols & (kWeightsPerUint32 - 1)) != 0) { unaligned = true; - aligned_row = (int8*)aligned_alloc(kWeightsPerUint32, m_cols, // NOLINT - &aligned_row_free); + aligned_row = (int8_t*)aligned_alloc(kWeightsPerUint32, m_cols, // NOLINT + &aligned_row_free); } void* aligned_vec_free = nullptr; - int8* aligned_vec = (int8*)aligned_alloc(kWeightsPerUint32, m_cols, // NOLINT - &aligned_vec_free); + int8_t* aligned_vec = + (int8_t*)aligned_alloc(kWeightsPerUint32, m_cols, // NOLINT + &aligned_vec_free); // If m_cols is not at least kWeightsPerNeonLane, we cannot use the main // vectorized loop, and we need to process sequentially. postamble_start shows @@ -114,13 +122,13 @@ void NeonMatrixBatchVectorMultiplyAccumulate( for (batch = 0; batch < n_batch; ++batch) { const float batch_scaling_factor = scaling_factors[batch]; // Copy the vector data to an aligned vector. - memcpy(aligned_vec, vectors + batch * m_cols, sizeof(int8) * m_cols); + memcpy(aligned_vec, vectors + batch * m_cols, sizeof(int8_t) * m_cols); // Compute dot-product for every column. for (row = 0; row < m_rows; ++row, result += result_stride) { // Get the address of the first element of the row. - int8* row_ptr = (int8*)matrix + row * m_cols; // NOLINT + int8_t* row_ptr = (int8_t*)matrix + row * m_cols; // NOLINT if (unaligned) { - memcpy(aligned_row, row_ptr, sizeof(int8) * m_cols); + memcpy(aligned_row, row_ptr, sizeof(int8_t) * m_cols); row_ptr = aligned_row; } @@ -135,7 +143,8 @@ void NeonMatrixBatchVectorMultiplyAccumulate( col = 0; for (; col < postamble_start; col += kWeightsPerNeonLane) { // Load 16 8-bit values from the row and vector, each, to operate on. - // Here the assumption is that each buffer is 4-byte aligned. + // Here the assumption is that each buffer is 4-byte aligned. Otherwise, + // performance may suffer significantly. TFLITE_CHECK_EQ((uintptr_t)(&row_ptr[col]) & (kWeightsPerUint32 - 1), 0); const int8x16_t s1_8x16 = vld1q_s8((const int8_t*)(aligned_vec + col)); @@ -164,6 +173,7 @@ void NeonMatrixBatchVectorMultiplyAccumulate( if ((m_cols - postamble_start) >= (kWeightsPerNeonLane >> 1)) { // Load 8 8-bit values from the row and column each to operate on. // Here the assumption is that each buffer is 4-bytes aligned. + // Otherwise, performance may suffer significantly. TFLITE_CHECK_EQ((uintptr_t)(&row_ptr[col]) & (kWeightsPerUint32 - 1), 0); const int8x8_t s1_8x8 = vld1_s8((const int8_t*)(aligned_vec + col)); -- GitLab From 8ae3ff414ca860f6d4fdc92b255b483c1d9c302f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 14:20:57 -0800 Subject: [PATCH 0815/2345] Update tf.distribute.Strategy.broadcast() API to match accepted RFC: https://github.com/tensorflow/community/blob/master/rfcs/20181016-replicator.md PiperOrigin-RevId: 229625402 --- tensorflow/python/distribute/distribute_lib.py | 15 +++++++++------ tensorflow/python/distribute/values.py | 4 ++-- ...tensorflow.distribute.-mirrored-strategy.pbtxt | 2 +- .../v1/tensorflow.distribute.-strategy.pbtxt | 2 +- ...tensorflow.distribute.-mirrored-strategy.pbtxt | 2 +- .../v2/tensorflow.distribute.-strategy.pbtxt | 2 +- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/tensorflow/python/distribute/distribute_lib.py b/tensorflow/python/distribute/distribute_lib.py index 32789c821e..e7d52c6c98 100644 --- a/tensorflow/python/distribute/distribute_lib.py +++ b/tensorflow/python/distribute/distribute_lib.py @@ -500,10 +500,11 @@ class DistributionStrategy(object): inputs = input_iterator.get_next() return self._extended.call_for_each_replica(fn, args=(inputs,)) - @doc_controls.do_not_generate_docs # DEPRECATED, moving to `extended` - def broadcast(self, tensor, destinations=None): - """DEPRECATED: use extended.broadcast_to() instead.""" - return self._extended.broadcast_to(tensor, destinations) + # TODO(b/121296772,b/121300973): Add logical_device argument (default of 0). + def broadcast(self, tensor): + """Broadcasts `tensor` to all replicas, returning a per-replica value.""" + _require_cross_replica_context_extended(self._extended) + return self._extended._broadcast(tensor) # pylint: disable=protected-access def reduce(self, reduce_op, value): """Reduce `value` across replicas. @@ -758,8 +759,7 @@ class DistributionStrategyExtended(object): * `d.make_dataset_iterator(dataset)` (or the deprecated `d.distribute_dataset(dataset).make_one_shot_iterator()`): in cross-replica context, produces an iterator with locality T - * `d.extended.broadcast_to(t)`: in cross-replica context, produces a value - with locality M + * `d.broadcast(t)`: in cross-replica context, produces a value with locality M * `d.extended.broadcast_to(t, v)`: in cross-replica context, produces a value with locality V(`v`) * `d.extended.call_for_each_replica(fn, ...)`: in cross-replica context, runs @@ -1025,6 +1025,9 @@ class DistributionStrategyExtended(object): assert not isinstance(destinations, (list, tuple)) return self._broadcast_to(tensor, destinations) + def _broadcast(self, tensor): + return self._broadcast_to(tensor, None) # Default implementation + def _broadcast_to(self, tensor, destinations): raise NotImplementedError("must be implemented in descendants") diff --git a/tensorflow/python/distribute/values.py b/tensorflow/python/distribute/values.py index 19967f2c5e..feaeea80cb 100644 --- a/tensorflow/python/distribute/values.py +++ b/tensorflow/python/distribute/values.py @@ -605,8 +605,8 @@ def validate_colocate(v, extended): def _apply_aggregation(strategy, value, aggregation, destinations): if aggregation == vs.VariableAggregation.ONLY_FIRST_REPLICA: - return strategy.broadcast(strategy.unwrap(value)[0], - destinations=destinations) + return strategy.extended.broadcast_to(strategy.unwrap(value)[0], + destinations=destinations) reduce_op = reduce_util.ReduceOp.from_variable_aggregation(aggregation) return strategy.extended.reduce_to(reduce_op, value, destinations) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt index c554df489a..4cb78b08f8 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-mirrored-strategy.pbtxt @@ -17,7 +17,7 @@ tf_class { } member_method { name: "broadcast" - argspec: "args=[\'self\', \'tensor\', \'destinations\'], varargs=None, keywords=None, defaults=[\'None\'], " + argspec: "args=[\'self\', \'tensor\'], varargs=None, keywords=None, defaults=None" } member_method { name: "colocate_vars_with" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt index cce83be3d0..11c1479b5b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.-strategy.pbtxt @@ -16,7 +16,7 @@ tf_class { } member_method { name: "broadcast" - argspec: "args=[\'self\', \'tensor\', \'destinations\'], varargs=None, keywords=None, defaults=[\'None\'], " + argspec: "args=[\'self\', \'tensor\'], varargs=None, keywords=None, defaults=None" } member_method { name: "colocate_vars_with" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt index c554df489a..4cb78b08f8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-mirrored-strategy.pbtxt @@ -17,7 +17,7 @@ tf_class { } member_method { name: "broadcast" - argspec: "args=[\'self\', \'tensor\', \'destinations\'], varargs=None, keywords=None, defaults=[\'None\'], " + argspec: "args=[\'self\', \'tensor\'], varargs=None, keywords=None, defaults=None" } member_method { name: "colocate_vars_with" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt index cce83be3d0..11c1479b5b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.-strategy.pbtxt @@ -16,7 +16,7 @@ tf_class { } member_method { name: "broadcast" - argspec: "args=[\'self\', \'tensor\', \'destinations\'], varargs=None, keywords=None, defaults=[\'None\'], " + argspec: "args=[\'self\', \'tensor\'], varargs=None, keywords=None, defaults=None" } member_method { name: "colocate_vars_with" -- GitLab From b0b56368f0c9269e4b003e2bc07020043eef53c8 Mon Sep 17 00:00:00 2001 From: Thomas O'Malley Date: Wed, 16 Jan 2019 14:33:55 -0800 Subject: [PATCH 0816/2345] Add optional `validation_freq` argument to `fit`. This allows user to configure how many training epochs occur between each validation run, or by providing a list users can explicitly specify which epochs should be followed by a validation run. PiperOrigin-RevId: 229627830 --- tensorflow/python/keras/engine/training.py | 21 +++++++++++++ .../python/keras/engine/training_arrays.py | 14 +++++++-- .../keras/engine/training_distributed.py | 23 +++++++++++--- .../python/keras/engine/training_generator.py | 12 ++++++- .../python/keras/engine/training_test.py | 29 +++++++++++++++++ .../python/keras/engine/training_utils.py | 31 +++++++++++++++++++ .../golden/v1/tensorflow.keras.-model.pbtxt | 4 +-- .../v1/tensorflow.keras.-sequential.pbtxt | 4 +-- .../v1/tensorflow.keras.models.-model.pbtxt | 4 +-- .../tensorflow.keras.models.-sequential.pbtxt | 4 +-- .../golden/v2/tensorflow.keras.-model.pbtxt | 4 +-- .../v2/tensorflow.keras.-sequential.pbtxt | 4 +-- ...ensorflow.keras.layers.-linear-model.pbtxt | 4 +-- .../v2/tensorflow.keras.models.-model.pbtxt | 4 +-- .../tensorflow.keras.models.-sequential.pbtxt | 4 +-- 15 files changed, 140 insertions(+), 26 deletions(-) diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index 6b0422e71c..e6ede64573 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -636,6 +636,7 @@ class Model(Network): initial_epoch=0, steps_per_epoch=None, validation_steps=None, + validation_freq=1, max_queue_size=10, workers=1, use_multiprocessing=False, @@ -740,6 +741,13 @@ class Model(Network): 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. + 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 + validation run is performed, e.g. `validation_freq=2` runs + validation every 2 epochs. If a Container, specifies the epochs on + which to run validation, e.g. `validation_freq=[1, 2, 10]` runs + validation at the end of the 1st, 2nd, and 10th epochs. max_queue_size: Integer. Used for generator or `keras.utils.Sequence` input only. Maximum size for the generator queue. If unspecified, `max_queue_size` will default to 10. @@ -846,6 +854,7 @@ class Model(Network): callbacks=callbacks, validation_data=validation_data, validation_steps=validation_steps, + validation_freq=validation_freq, class_weight=class_weight, max_queue_size=max_queue_size, workers=workers, @@ -868,6 +877,7 @@ class Model(Network): callbacks=callbacks, validation_data=validation_data, validation_steps=validation_steps, + validation_freq=validation_freq, class_weight=class_weight, workers=0, shuffle=shuffle, @@ -930,6 +940,7 @@ class Model(Network): callbacks=callbacks, validation_data=validation_data, validation_steps=validation_steps, + validation_freq=validation_freq, workers=0, shuffle=shuffle, initial_epoch=initial_epoch, @@ -951,6 +962,7 @@ class Model(Network): initial_epoch=initial_epoch, steps_per_epoch=steps_per_epoch, validation_steps=validation_steps, + validation_freq=validation_freq, steps_name='steps_per_epoch') def evaluate(self, @@ -1426,6 +1438,7 @@ class Model(Network): callbacks=None, validation_data=None, validation_steps=None, + validation_freq=1, class_weight=None, max_queue_size=10, workers=1, @@ -1480,6 +1493,13 @@ class Model(Network): to yield from `generator` before stopping. Optional for `Sequence`: if unspecified, will use the `len(validation_data)` as a number of steps. + 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 + validation run is performed, e.g. `validation_freq=2` runs + validation every 2 epochs. If a Container, specifies the epochs on + which to run validation, e.g. `validation_freq=[1, 2, 10]` runs + validation at the end of the 1st, 2nd, and 10th epochs. class_weight: Dictionary mapping class indices to a weight for the class. max_queue_size: Integer. Maximum size for the generator queue. @@ -1535,6 +1555,7 @@ class Model(Network): callbacks=callbacks, validation_data=validation_data, validation_steps=validation_steps, + validation_freq=validation_freq, class_weight=class_weight, max_queue_size=max_queue_size, workers=workers, diff --git a/tensorflow/python/keras/engine/training_arrays.py b/tensorflow/python/keras/engine/training_arrays.py index a356f8b328..3b6a13839a 100644 --- a/tensorflow/python/keras/engine/training_arrays.py +++ b/tensorflow/python/keras/engine/training_arrays.py @@ -56,6 +56,7 @@ def model_iteration(model, initial_epoch=0, steps_per_epoch=None, validation_steps=None, + validation_freq=1, mode=ModeKeys.TRAIN, validation_in_fit=False, steps_name='steps', @@ -84,6 +85,13 @@ def model_iteration(model, the default value of `None`. validation_steps: Number of steps to run validation for (only if doing validation from data tensors). Ignored with the default value of `None`. + 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 + validation run is performed, e.g. `validation_freq=2` runs + validation every 2 epochs. If a Container, specifies the epochs on + which to run validation, e.g. `validation_freq=[1, 2, 10]` runs + validation at the end of the 1st, 2nd, and 10th epochs. mode: One of ModeKeys.TRAIN/ModeKeys.TEST/ModeKeys.PREDICT. validation_in_fit: DEPRECATED: if true, then this method is invoked from within training iteration (for validation). In this case, do not copy @@ -320,8 +328,10 @@ def model_iteration(model, if len(results) == 1: results = results[0] - # Run the test loop every epoch during training. - if do_validation and not callbacks.model.stop_training: + # Run the test loop every `validation_freq` epochs during training. + if (do_validation and + training_utils.should_run_validation(validation_freq, epoch) and + not callbacks.model.stop_training): val_results = model_iteration( model, val_inputs, diff --git a/tensorflow/python/keras/engine/training_distributed.py b/tensorflow/python/keras/engine/training_distributed.py index 58a8637a84..8b7e5bb5cc 100644 --- a/tensorflow/python/keras/engine/training_distributed.py +++ b/tensorflow/python/keras/engine/training_distributed.py @@ -30,6 +30,7 @@ from tensorflow.python.keras import backend as K from tensorflow.python.keras import callbacks as cbks from tensorflow.python.keras.engine import distributed_training_utils from tensorflow.python.keras.engine import training_arrays +from tensorflow.python.keras.engine import training_utils from tensorflow.python.keras.utils.generic_utils import Progbar from tensorflow.python.ops import array_ops from tensorflow.python.platform import tf_logging as logging @@ -51,7 +52,8 @@ def fit_distributed(model, sample_weight=None, initial_epoch=0, steps_per_epoch=None, - validation_steps=None): + validation_steps=None, + validation_freq=1): """Fit loop for Distribution Strategies.""" distributed_training_utils.validate_callbacks(callbacks, model.optimizer) distributed_training_utils.validate_inputs( @@ -111,7 +113,8 @@ def fit_distributed(model, val_dataset=val_dataset, initial_epoch=initial_epoch, steps_per_epoch=steps_per_epoch, - validation_steps=validation_steps) + validation_steps=validation_steps, + validation_freq=1) else: return training_arrays.fit_loop( model, @@ -124,7 +127,8 @@ def fit_distributed(model, shuffle=shuffle, initial_epoch=initial_epoch, steps_per_epoch=steps_per_epoch, - validation_steps=validation_steps) + validation_steps=validation_steps, + validation_freq=validation_freq) def evaluate_distributed(model, @@ -206,7 +210,8 @@ def experimental_tpu_fit_loop(model, initial_epoch=0, steps_per_epoch=None, val_dataset=None, - validation_steps=None): + validation_steps=None, + validation_freq=1): """Fit loop for training with TPU DistributionStrategy. Arguments: @@ -224,6 +229,13 @@ def experimental_tpu_fit_loop(model, validation_steps: Number of steps to run validation for (only if doing validation from data tensors). Ignored with the default value of `None`. + 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 + validation run is performed, e.g. `validation_freq=2` runs + validation every 2 epochs. If a Container, specifies the epochs on + which to run validation, e.g. `validation_freq=[1, 2, 10]` runs + validation at the end of the 1st, 2nd, and 10th epochs. Returns: Returns `None`. @@ -363,7 +375,8 @@ def experimental_tpu_fit_loop(model, if callbacks.model.stop_training: break - if do_validation: + if (do_validation and + training_utils.should_run_validation(validation_freq, epoch)): logging.info('Running validation at fit epoch: %s', epoch) if model._compile_distribution: diff --git a/tensorflow/python/keras/engine/training_generator.py b/tensorflow/python/keras/engine/training_generator.py index 8a5e24856c..23b5d7e8c5 100644 --- a/tensorflow/python/keras/engine/training_generator.py +++ b/tensorflow/python/keras/engine/training_generator.py @@ -46,6 +46,7 @@ def model_iteration(model, callbacks=None, validation_data=None, validation_steps=None, + validation_freq=1, class_weight=None, max_queue_size=10, workers=1, @@ -74,6 +75,13 @@ def model_iteration(model, `keras.utils.data_utils.Sequence` object or Eager Iterator or Dataset. validation_steps: Total number of steps (batches of samples) before declaring validation finished. + 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 + validation run is performed, e.g. `validation_freq=2` runs + validation every 2 epochs. If a Container, specifies the epochs on + which to run validation, e.g. `validation_freq=[1, 2, 10]` runs + validation at the end of the 1st, 2nd, and 10th epochs. class_weight: Dictionary mapping class indices to a weight for the class. max_queue_size: Integer. Maximum size for the generator queue. If unspecified, `max_queue_size` will default to 10. @@ -257,7 +265,9 @@ def model_iteration(model, results = results[0] # Run the test loop every epoch during training. - if do_validation and not callbacks.model.stop_training: + if (do_validation and + training_utils.should_run_validation(validation_freq, epoch) and + not callbacks.model.stop_training): val_results = model_iteration( model, validation_data, diff --git a/tensorflow/python/keras/engine/training_test.py b/tensorflow/python/keras/engine/training_test.py index fa26385cc4..6be4da70f6 100644 --- a/tensorflow/python/keras/engine/training_test.py +++ b/tensorflow/python/keras/engine/training_test.py @@ -22,6 +22,7 @@ import io import logging import sys +from absl.testing import parameterized import numpy as np import six @@ -803,6 +804,34 @@ class TrainingTest(keras_parameterized.TestCase): test_model_loss = test_model.train_on_batch(train_x, train_y) self.assertAlmostEqual(test_model_loss, reference_model_loss, places=4) + @keras_parameterized.run_with_all_model_types + @keras_parameterized.run_all_keras_modes + @parameterized.named_parameters( + ('default', 1, 4), ('integer_two', 2, 2), ('integer_four', 4, 1), + ('simple_list', [1, 3, 4], 3), ('duplicated_list', [4, 2, 2], 2)) + def test_validation_freq(self, validation_freq, expected_runs): + x, y = np.ones((10, 10)), np.ones((10, 1)) + model = testing_utils.get_small_mlp(2, 1, 10) + model.compile('sgd', 'mse') + + class ValCounter(keras.callbacks.Callback): + + def __init__(self): + self.val_runs = 0 + + def on_test_begin(self, logs=None): + self.val_runs += 1 + + val_counter = ValCounter() + model.fit( + x, + y, + epochs=4, + validation_data=(x, y), + validation_freq=validation_freq, + callbacks=[val_counter]) + self.assertEqual(val_counter.val_runs, expected_runs) + class TestExceptionsAndWarnings(keras_parameterized.TestCase): diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py index c36b637829..3affb84147 100644 --- a/tensorflow/python/keras/engine/training_utils.py +++ b/tensorflow/python/keras/engine/training_utils.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function import abc +import collections from collections import OrderedDict import copy import json @@ -1502,3 +1503,33 @@ def should_run_multi_worker(): tf_config = json.loads(os.environ.get('TF_CONFIG', '{}')) cluster_spec = server_lib.ClusterSpec(tf_config.get('cluster', {})) return tf_config and 'master' not in cluster_spec.jobs + + +def should_run_validation(validation_freq, epoch): + """Checks if validation should be run this epoch. + + Arguments: + validation_freq: Integer or list. If an integer, specifies how many training + epochs to run before a new validation run is performed. If a list, + specifies the epochs on which to run validation. + epoch: Integer, the number of the training epoch just completed. + + Returns: + Bool, True if validation should be run. + + Raises: + ValueError: if `validation_freq` is an Integer and less than 1, or if + it is neither an Integer nor a Sequence. + """ + # `epoch` is 0-indexed internally but 1-indexed in the public API. + one_indexed_epoch = epoch + 1 + + if isinstance(validation_freq, int): + if validation_freq < 1: + raise ValueError('`validation_freq` can not be less than 1.') + return one_indexed_epoch % validation_freq == 0 + + if not isinstance(validation_freq, collections.Container): + raise ValueError('`validation_freq` must be an Integer or ' + '`collections.Container` (e.g. list, tuple, etc.)') + return one_indexed_epoch in validation_freq diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.-model.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.-model.pbtxt index 0a81c3a663..283cc6a735 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.-model.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.-model.pbtxt @@ -175,11 +175,11 @@ tf_class { } member_method { name: "fit" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'10\', \'1\', \'False\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'validation_freq\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'1\', \'10\', \'1\', \'False\'], " } member_method { name: "fit_generator" - argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " + argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'validation_freq\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'1\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " } member_method { name: "from_config" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.-sequential.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.-sequential.pbtxt index 2a9ceec30e..95e405aeba 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.-sequential.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.-sequential.pbtxt @@ -180,11 +180,11 @@ tf_class { } member_method { name: "fit" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'10\', \'1\', \'False\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'validation_freq\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'1\', \'10\', \'1\', \'False\'], " } member_method { name: "fit_generator" - argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " + argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'validation_freq\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'1\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " } member_method { name: "from_config" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-model.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-model.pbtxt index 55bd285612..eb1ab1d9dd 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-model.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-model.pbtxt @@ -175,11 +175,11 @@ tf_class { } member_method { name: "fit" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'10\', \'1\', \'False\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'validation_freq\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'1\', \'10\', \'1\', \'False\'], " } member_method { name: "fit_generator" - argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " + argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'validation_freq\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'1\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " } member_method { name: "from_config" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-sequential.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-sequential.pbtxt index a82424167e..c69cf28174 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-sequential.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.models.-sequential.pbtxt @@ -180,11 +180,11 @@ tf_class { } member_method { name: "fit" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'10\', \'1\', \'False\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'validation_freq\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'1\', \'10\', \'1\', \'False\'], " } member_method { name: "fit_generator" - argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " + argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'validation_freq\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'1\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " } member_method { name: "from_config" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.-model.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.-model.pbtxt index 0a81c3a663..283cc6a735 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.-model.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.-model.pbtxt @@ -175,11 +175,11 @@ tf_class { } member_method { name: "fit" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'10\', \'1\', \'False\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'validation_freq\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'1\', \'10\', \'1\', \'False\'], " } member_method { name: "fit_generator" - argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " + argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'validation_freq\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'1\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " } member_method { name: "from_config" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.-sequential.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.-sequential.pbtxt index 2a9ceec30e..95e405aeba 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.-sequential.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.-sequential.pbtxt @@ -180,11 +180,11 @@ tf_class { } member_method { name: "fit" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'10\', \'1\', \'False\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'validation_freq\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'1\', \'10\', \'1\', \'False\'], " } member_method { name: "fit_generator" - argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " + argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'validation_freq\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'1\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " } member_method { name: "from_config" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-linear-model.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-linear-model.pbtxt index d40a999bd8..c4726cf824 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-linear-model.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-linear-model.pbtxt @@ -180,11 +180,11 @@ tf_class { } member_method { name: "fit" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'10\', \'1\', \'False\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'validation_freq\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'1\', \'10\', \'1\', \'False\'], " } member_method { name: "fit_generator" - argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " + argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'validation_freq\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'1\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " } member_method { name: "from_config" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-model.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-model.pbtxt index 55bd285612..eb1ab1d9dd 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-model.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-model.pbtxt @@ -175,11 +175,11 @@ tf_class { } member_method { name: "fit" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'10\', \'1\', \'False\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'validation_freq\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'1\', \'10\', \'1\', \'False\'], " } member_method { name: "fit_generator" - argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " + argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'validation_freq\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'1\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " } member_method { name: "from_config" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-sequential.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-sequential.pbtxt index a82424167e..c69cf28174 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-sequential.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.models.-sequential.pbtxt @@ -180,11 +180,11 @@ tf_class { } member_method { name: "fit" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'10\', \'1\', \'False\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'epochs\', \'verbose\', \'callbacks\', \'validation_split\', \'validation_data\', \'shuffle\', \'class_weight\', \'sample_weight\', \'initial_epoch\', \'steps_per_epoch\', \'validation_steps\', \'validation_freq\', \'max_queue_size\', \'workers\', \'use_multiprocessing\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'1\', \'1\', \'None\', \'0.0\', \'None\', \'True\', \'None\', \'None\', \'0\', \'None\', \'None\', \'1\', \'10\', \'1\', \'False\'], " } member_method { name: "fit_generator" - argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " + argspec: "args=[\'self\', \'generator\', \'steps_per_epoch\', \'epochs\', \'verbose\', \'callbacks\', \'validation_data\', \'validation_steps\', \'validation_freq\', \'class_weight\', \'max_queue_size\', \'workers\', \'use_multiprocessing\', \'shuffle\', \'initial_epoch\'], varargs=None, keywords=None, defaults=[\'None\', \'1\', \'1\', \'None\', \'None\', \'None\', \'1\', \'None\', \'10\', \'1\', \'False\', \'True\', \'0\'], " } member_method { name: "from_config" -- GitLab From 661103ed87cdf0f5a792100b543006b71d62697a Mon Sep 17 00:00:00 2001 From: Revan Sopher Date: Wed, 16 Jan 2019 14:52:57 -0800 Subject: [PATCH 0817/2345] Bump absl_py from 0.2.2 to 0.7.0, add absl_py's new dep enum34. PiperOrigin-RevId: 229631203 --- tensorflow/opensource_only.files | 1 + tensorflow/workspace.bzl | 18 ++++++++++++++---- .../systemlibs/absl_py.absl.testing.BUILD | 6 +++++- third_party/systemlibs/enum34.BUILD | 13 +++++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 third_party/systemlibs/enum34.BUILD diff --git a/tensorflow/opensource_only.files b/tensorflow/opensource_only.files index 03b5e5a73c..30dfce3c83 100644 --- a/tensorflow/opensource_only.files +++ b/tensorflow/opensource_only.files @@ -150,6 +150,7 @@ tensorflow/third_party/systemlibs/cython.BUILD tensorflow/third_party/systemlibs/double_conversion.BUILD tensorflow/third_party/systemlibs/zlib.BUILD tensorflow/third_party/systemlibs/jsoncpp.BUILD +tensorflow/third_party/systemlibs/enum34.BUILD tensorflow/third_party/systemlibs/re2.BUILD tensorflow/third_party/systemlibs/lmdb.BUILD tensorflow/third_party/systemlibs/googleapis.BUILD diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index b8ef43d27e..6fcccd4147 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -317,19 +317,29 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "absl_py", - sha256 = "95160f778a62c7a60ddeadc7bf2d83f85a23a27359814aca12cf949e896fa82c", - strip_prefix = "abseil-py-pypi-v0.2.2", + sha256 = "595726be4bf3f7e6d64a1a255fa03717b693c01b913768abd52649cbb7ddf2bd", + strip_prefix = "abseil-py-pypi-v0.7.0", system_build_file = clean_dep("//third_party/systemlibs:absl_py.BUILD"), system_link_files = { "//third_party/systemlibs:absl_py.absl.flags.BUILD": "absl/flags/BUILD", "//third_party/systemlibs:absl_py.absl.testing.BUILD": "absl/testing/BUILD", }, urls = [ - "https://mirror.bazel.build/github.com/abseil/abseil-py/archive/pypi-v0.2.2.tar.gz", - "https://github.com/abseil/abseil-py/archive/pypi-v0.2.2.tar.gz", + "https://mirror.bazel.build/github.com/abseil/abseil-py/archive/pypi-v0.7.0.tar.gz", + "https://github.com/abseil/abseil-py/archive/pypi-v0.7.0.tar.gz", ], ) + tf_http_archive( + name = "enum34_archive", + urls = [ + "https://mirror.bazel.build/pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz", + "https://pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz", + ], + sha256 = "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1", + build_file = clean_dep("//third_party/systemlibs:enum34.BUILD"), + ) + tf_http_archive( name = "org_python_pypi_backports_weakref", build_file = clean_dep("//third_party:backports_weakref.BUILD"), diff --git a/third_party/systemlibs/absl_py.absl.testing.BUILD b/third_party/systemlibs/absl_py.absl.testing.BUILD index c1b794c1e9..7629509ebb 100644 --- a/third_party/systemlibs/absl_py.absl.testing.BUILD +++ b/third_party/systemlibs/absl_py.absl.testing.BUILD @@ -2,6 +2,10 @@ licenses(["notice"]) # Apache 2.0 py_library( name = "parameterized", - testonly = 1, visibility = ["//visibility:public"], ) + +py_library( + name = "absltest", + visibility = ["//visiblity:public"], +) diff --git a/third_party/systemlibs/enum34.BUILD b/third_party/systemlibs/enum34.BUILD new file mode 100644 index 0000000000..a5ac25592b --- /dev/null +++ b/third_party/systemlibs/enum34.BUILD @@ -0,0 +1,13 @@ +# Description: +# enum34 provides a backport of the enum module for Python 2. + +licenses(["notice"]) # MIT + +exports_files(["LICENSE"]) + +py_library( + name = "enum", + srcs = ["enum34-1.1.6/enum/__init__.py"], + srcs_version = "PY2AND3", + visibility = ["//visibility:public"], +) -- GitLab From 715f5c73cfaef2991d393024f019d7906ae27e8d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 14:53:05 -0800 Subject: [PATCH 0818/2345] Update NASCell to LayerRNNCell. PiperOrigin-RevId: 229631237 --- .../rnn/python/kernel_tests/rnn_cell_test.py | 13 +++ tensorflow/contrib/rnn/python/ops/rnn_cell.py | 80 +++++++++++-------- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/tensorflow/contrib/rnn/python/kernel_tests/rnn_cell_test.py b/tensorflow/contrib/rnn/python/kernel_tests/rnn_cell_test.py index aa1d7d2b01..d7ee7fb8fa 100644 --- a/tensorflow/contrib/rnn/python/kernel_tests/rnn_cell_test.py +++ b/tensorflow/contrib/rnn/python/kernel_tests/rnn_cell_test.py @@ -29,7 +29,9 @@ 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 +from tensorflow.python.framework import test_util from tensorflow.python.keras import initializers +from tensorflow.python.keras import layers as keras_layers from tensorflow.python.keras import testing_utils from tensorflow.python.keras import utils from tensorflow.python.ops import array_ops @@ -763,6 +765,17 @@ class RNNCellTest(test.TestCase): self.assertEqual(new_h.shape[1], num_proj) self.assertAllClose(np.concatenate(res[1], axis=1), expected_state) + @test_util.run_in_graph_and_eager_modes + def testNASCellKerasRNN(self): + """Tests that NASCell works with keras RNN layer.""" + cell = contrib_rnn_cell.NASCell(10) + seq_input = ops.convert_to_tensor( + np.random.rand(2, 3, 5), name="seq_input", dtype=dtypes.float32) + rnn_layer = keras_layers.RNN(cell=cell) + rnn_outputs = rnn_layer(seq_input) + self.evaluate([variables.global_variables_initializer()]) + self.assertEqual(self.evaluate(rnn_outputs).shape, (2, 10)) + def testUGRNNCell(self): num_units = 2 batch_size = 3 diff --git a/tensorflow/contrib/rnn/python/ops/rnn_cell.py b/tensorflow/contrib/rnn/python/ops/rnn_cell.py index a2179cae0c..482e547a16 100644 --- a/tensorflow/contrib/rnn/python/ops/rnn_cell.py +++ b/tensorflow/contrib/rnn/python/ops/rnn_cell.py @@ -1462,7 +1462,7 @@ class LayerNormBasicLSTMCell(rnn_cell_impl.RNNCell): return new_h, new_state -class NASCell(rnn_cell_impl.RNNCell): +class NASCell(rnn_cell_impl.LayerRNNCell): """Neural Architecture Search (NAS) recurrent network cell. This implements the recurrent cell from the paper: @@ -1475,23 +1475,28 @@ class NASCell(rnn_cell_impl.RNNCell): The class uses an optional projection layer. """ - def __init__(self, num_units, num_proj=None, use_biases=False, reuse=None): + # NAS cell's architecture base. + _NAS_BASE = 8 + + def __init__(self, num_units, num_proj=None, use_bias=False, reuse=None, + **kwargs): """Initialize the parameters for a NAS cell. Args: - num_units: int, The number of units in the NAS cell + num_units: int, The number of units in the NAS cell. num_proj: (optional) int, The output dimensionality for the projection matrices. If None, no projection is performed. - use_biases: (optional) bool, If True then use biases within the cell. This + use_bias: (optional) bool, If True then use biases within the cell. This is False by default. reuse: (optional) Python boolean describing whether to reuse variables in an existing scope. If not `True`, and the existing scope already has the given variables, an error is raised. + **kwargs: Additional keyword arguments. """ - super(NASCell, self).__init__(_reuse=reuse) + super(NASCell, self).__init__(_reuse=reuse, **kwargs) self._num_units = num_units self._num_proj = num_proj - self._use_biases = use_biases + self._use_bias = use_bias self._reuse = reuse if num_proj is not None: @@ -1509,6 +1514,33 @@ class NASCell(rnn_cell_impl.RNNCell): def output_size(self): return self._output_size + def build(self, inputs_shape): + input_size = tensor_shape.dimension_value( + tensor_shape.TensorShape(inputs_shape).with_rank(2)[1]) + if input_size is None: + raise ValueError("Could not infer input size from inputs.get_shape()[-1]") + + num_proj = self._num_units if self._num_proj is None else self._num_proj + + # Variables for the NAS cell. `recurrent_kernel` is all matrices multiplying + # the hiddenstate and `kernel` is all matrices multiplying the inputs. + self.recurrent_kernel = self.add_variable( + "recurrent_kernel", [num_proj, self._NAS_BASE * self._num_units]) + self.kernel = self.add_variable( + "kernel", [input_size, self._NAS_BASE * self._num_units]) + + if self._use_bias: + self.bias = self.add_variable("bias", + shape=[self._NAS_BASE * self._num_units], + initializer=init_ops.zeros_initializer) + + # Projection layer if specified + if self._num_proj is not None: + self.projection_weights = self.add_variable( + "projection_weights", [self._num_units, self._num_proj]) + + self.built = True + def call(self, inputs, state): """Run one step of NAS Cell. @@ -1535,38 +1567,20 @@ class NASCell(rnn_cell_impl.RNNCell): tanh = math_ops.tanh relu = nn_ops.relu - num_proj = self._num_units if self._num_proj is None else self._num_proj - (c_prev, m_prev) = state - dtype = inputs.dtype - input_size = inputs.get_shape().with_rank(2).dims[1] - if input_size.value is None: - raise ValueError("Could not infer input size from inputs.get_shape()[-1]") - # Variables for the NAS cell. W_m is all matrices multiplying the - # hiddenstate and W_inputs is all matrices multiplying the inputs. - concat_w_m = vs.get_variable("recurrent_kernel", - [num_proj, 8 * self._num_units], dtype) - concat_w_inputs = vs.get_variable( - "kernel", [input_size.value, 8 * self._num_units], dtype) - - m_matrix = math_ops.matmul(m_prev, concat_w_m) - inputs_matrix = math_ops.matmul(inputs, concat_w_inputs) - - if self._use_biases: - b = vs.get_variable( - "bias", - shape=[8 * self._num_units], - initializer=init_ops.zeros_initializer(), - dtype=dtype) - m_matrix = nn_ops.bias_add(m_matrix, b) + m_matrix = math_ops.matmul(m_prev, self.recurrent_kernel) + inputs_matrix = math_ops.matmul(inputs, self.kernel) + + if self._use_bias: + m_matrix = nn_ops.bias_add(m_matrix, self.bias) # The NAS cell branches into 8 different splits for both the hiddenstate # and the input m_matrix_splits = array_ops.split( - axis=1, num_or_size_splits=8, value=m_matrix) + axis=1, num_or_size_splits=self._NAS_BASE, value=m_matrix) inputs_matrix_splits = array_ops.split( - axis=1, num_or_size_splits=8, value=inputs_matrix) + axis=1, num_or_size_splits=self._NAS_BASE, value=inputs_matrix) # First layer layer1_0 = sigmoid(inputs_matrix_splits[0] + m_matrix_splits[0]) @@ -1598,9 +1612,7 @@ class NASCell(rnn_cell_impl.RNNCell): # Projection layer if specified if self._num_proj is not None: - concat_w_proj = vs.get_variable("projection_weights", - [self._num_units, self._num_proj], dtype) - new_m = math_ops.matmul(new_m, concat_w_proj) + new_m = math_ops.matmul(new_m, self.projection_weights) new_state = rnn_cell_impl.LSTMStateTuple(new_c, new_m) return new_m, new_state -- GitLab From f98110b559362b086409438b00f5b73c9d870b60 Mon Sep 17 00:00:00 2001 From: Blake Hechtman Date: Wed, 16 Jan 2019 14:54:24 -0800 Subject: [PATCH 0819/2345] [TF] Use compare and cast instead of floor for dropout. PiperOrigin-RevId: 229631446 --- .../layers/python/layers/layers_test.py | 2 +- .../python/util/parse_layer_parameters.py | 23 ++++++++++--------- .../python/keras/engine/topology_test.py | 6 ++--- .../python/keras/layers/wrappers_test.py | 2 +- tensorflow/python/ops/nn_ops.py | 14 +++++------ 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/tensorflow/contrib/layers/python/layers/layers_test.py b/tensorflow/contrib/layers/python/layers/layers_test.py index d791418c9d..1c0088186c 100644 --- a/tensorflow/contrib/layers/python/layers/layers_test.py +++ b/tensorflow/contrib/layers/python/layers/layers_test.py @@ -1356,7 +1356,7 @@ class DropoutTest(test.TestCase): with self.cached_session(): images = np.random.uniform(size=(5, height, width, 3)) output = _layers.dropout(images) - self.assertEqual(output.op.name, 'Dropout/dropout_1/mul') + self.assertEqual(output.op.name, 'Dropout/dropout_1/mul_1') output.get_shape().assert_is_compatible_with( ops.convert_to_tensor(images).get_shape()) diff --git a/tensorflow/contrib/receptive_field/python/util/parse_layer_parameters.py b/tensorflow/contrib/receptive_field/python/util/parse_layer_parameters.py index 0e3c46f17d..92ae1021bc 100644 --- a/tensorflow/contrib/receptive_field/python/util/parse_layer_parameters.py +++ b/tensorflow/contrib/receptive_field/python/util/parse_layer_parameters.py @@ -27,7 +27,8 @@ from tensorflow.python.platform import tf_logging as logging _UNCHANGED_RF_LAYER_OPS = [ "Add", "BiasAdd", "Cast", "Ceil", "ConcatV2", "Const", "Floor", "FusedBatchNorm", "Identity", "Log", "Mul", "Pow", "RealDiv", "Relu", - "Relu6", "Round", "Rsqrt", "Softplus", "Sub", "VariableV2", "LRN" + "Relu6", "Round", "Rsqrt", "Softplus", "Sub", "VariableV2", "LRN", + "GreaterEqual" ] # Different ways in which padding modes may be spelled. @@ -276,11 +277,11 @@ def get_layer_params(node, name_to_node, input_resolution=None, force=False): kernel_size_x, kernel_size_y = _conv_kernel_size(node, name_to_node) # Compute the padding for this node separately for each direction. total_padding_x, padding_x = _padding_size_conv_pool( - node, kernel_size_x, stride_x, input_resolution[1] - if input_resolution is not None else None) + node, kernel_size_x, stride_x, + input_resolution[1] if input_resolution is not None else None) total_padding_y, padding_y = _padding_size_conv_pool( - node, kernel_size_y, stride_y, input_resolution[0] - if input_resolution is not None else None) + node, kernel_size_y, stride_y, + input_resolution[0] if input_resolution is not None else None) elif node.op == "Pad": # Kernel and stride are simply 1 in this case. kernel_size_x = 1 @@ -294,11 +295,11 @@ def get_layer_params(node, name_to_node, input_resolution=None, force=False): kernel_size_x, kernel_size_y = _pool_kernel_size(node, name_to_node) # Compute the padding for this node separately for each direction. total_padding_x, padding_x = _padding_size_conv_pool( - node, kernel_size_x, stride_x, input_resolution[1] - if input_resolution is not None else None) + node, kernel_size_x, stride_x, + input_resolution[1] if input_resolution is not None else None) total_padding_y, padding_y = _padding_size_conv_pool( - node, kernel_size_y, stride_y, input_resolution[0] - if input_resolution is not None else None) + node, kernel_size_y, stride_y, + input_resolution[0] if input_resolution is not None else None) elif node.op in _UNCHANGED_RF_LAYER_OPS: # These nodes do not modify the RF parameters. kernel_size_x = 1 @@ -320,7 +321,7 @@ def get_layer_params(node, name_to_node, input_resolution=None, force=False): total_padding_y = None padding_y = None else: - raise ValueError("Unknown layer for operation '%s': %s" % (node.name, - node.op)) + raise ValueError( + "Unknown layer for operation '%s': %s" % (node.name, node.op)) return (kernel_size_x, kernel_size_y, stride_x, stride_y, padding_x, padding_y, total_padding_x, total_padding_y) diff --git a/tensorflow/python/keras/engine/topology_test.py b/tensorflow/python/keras/engine/topology_test.py index 9022dc9481..951988d852 100644 --- a/tensorflow/python/keras/engine/topology_test.py +++ b/tensorflow/python/keras/engine/topology_test.py @@ -358,17 +358,17 @@ class TopologyConstructionTest(keras_parameterized.TestCase): x = keras.layers.Dropout(0.5)(x, training=True) model = keras.models.Model(inp, x) # Would be `dropout/cond/Merge` by default - self.assertTrue(model.output.op.name.endswith('dropout/mul')) + self.assertTrue(model.output.op.name.endswith('dropout/mul_1')) # Test that argument is kept when applying the model inp2 = keras.layers.Input(shape=(2,)) out2 = model(inp2) - self.assertTrue(out2.op.name.endswith('dropout/mul')) + self.assertTrue(out2.op.name.endswith('dropout/mul_1')) # Test that argument is kept after loading a model config = model.get_config() model = keras.models.Model.from_config(config) - self.assertTrue(model.output.op.name.endswith('dropout/mul')) + self.assertTrue(model.output.op.name.endswith('dropout/mul_1')) def test_node_construction(self): # test basics diff --git a/tensorflow/python/keras/layers/wrappers_test.py b/tensorflow/python/keras/layers/wrappers_test.py index 00e5276d6a..313a2193eb 100644 --- a/tensorflow/python/keras/layers/wrappers_test.py +++ b/tensorflow/python/keras/layers/wrappers_test.py @@ -159,7 +159,7 @@ class TimeDistributedTest(test.TestCase): np.random.seed(1234) x = keras.layers.Input(shape=(3, 2)) y = keras.layers.TimeDistributed( - keras.layers.Dropout(.999))(x, training=True) + keras.layers.Dropout(.9999))(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) diff --git a/tensorflow/python/ops/nn_ops.py b/tensorflow/python/ops/nn_ops.py index f71fcef130..25609f9efa 100644 --- a/tensorflow/python/ops/nn_ops.py +++ b/tensorflow/python/ops/nn_ops.py @@ -3292,15 +3292,13 @@ def dropout_v2(x, rate, noise_shape=None, seed=None, name=None): # pylint: disa return x noise_shape = _get_noise_shape(x, noise_shape) - - keep_prob = 1 - rate - # uniform [keep_prob, 1.0 + keep_prob) - random_tensor = keep_prob - random_tensor += random_ops.random_uniform( + # Sample a uniform distribution on [0.0, 1.0) and select values larger than + # rate. + random_tensor = random_ops.random_uniform( noise_shape, seed=seed, dtype=x.dtype) - # 0. if [keep_prob, 1.0) and 1. if [1.0, 1.0 + keep_prob) - binary_tensor = math_ops.floor(random_tensor) - ret = math_ops.divide(x, keep_prob) * binary_tensor + keep_prob = 1 - rate + ret = (1 / keep_prob) * math_ops.cast(keep_prob >= random_tensor, + x.dtype) * x if not context.executing_eagerly(): ret.set_shape(x.get_shape()) return ret -- GitLab From 9850eea1c83422c90a84c5c520070577becec0a5 Mon Sep 17 00:00:00 2001 From: Mingming Liu Date: Wed, 16 Jan 2019 14:58:58 -0800 Subject: [PATCH 0820/2345] Add monitoring for file IO time and TF graph init time. PiperOrigin-RevId: 229632265 --- tensorflow/cc/saved_model/loader.cc | 43 +++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/tensorflow/cc/saved_model/loader.cc b/tensorflow/cc/saved_model/loader.cc index 85d3dd01fa..aaf06a74c6 100644 --- a/tensorflow/cc/saved_model/loader.cc +++ b/tensorflow/cc/saved_model/loader.cc @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/cc/saved_model/reader.h" #include "tensorflow/core/lib/io/path.h" #include "tensorflow/core/lib/monitoring/counter.h" +#include "tensorflow/core/lib/monitoring/sampler.h" #include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/platform/env.h" @@ -42,9 +43,28 @@ auto* load_latency = monitoring::Counter<1>::New( "/tensorflow/cc/saved_model/load_latency", "Latency in microseconds for SavedModels that were successfully loaded.", "model_path"); +auto* load_latency_by_stage = monitoring::Sampler<2>::New( + { + "/tensorflow/cc/saved_model/load_latency_by_stage", // metric name + "Distribution of wall time spent (in microseconds) in each stage " + "(restore graph from disk, run init graph op, etc) when loading the " + "model", + "model_path", + "stage", + }, + // Scale of 10, power of 1.5 with bucket count 36 (~20 minutes). + monitoring::Buckets::Exponential(10, 1.5, 36)); + constexpr char kLoadAttemptFail[] = "fail"; constexpr char kLoadAttemptSuccess[] = "success"; +uint64 GetLatencyMicroseconds(const uint64 start_microseconds) { + const uint64 end_microseconds = Env::Default()->NowMicros(); + // Avoid clock skew. + if (end_microseconds < start_microseconds) return 0; + return end_microseconds - start_microseconds; +} + Status LoadMetaGraphIntoSession(const MetaGraphDef& meta_graph_def, const SessionOptions& session_options, std::unique_ptr* session) { @@ -242,6 +262,7 @@ Status LoadSavedModelInternal(const SessionOptions& session_options, const string& export_dir, const std::unordered_set& tags, SavedModelBundle* const bundle) { + const uint64 read_start_microseconds = Env::Default()->NowMicros(); TF_RETURN_IF_ERROR(ReadMetaGraphDefFromSavedModel(export_dir, tags, &bundle->meta_graph_def)); @@ -256,12 +277,23 @@ Status LoadSavedModelInternal(const SessionOptions& session_options, bundle->meta_graph_def.saver_def().restore_op_name(), bundle->meta_graph_def.saver_def().filename_tensor_name(), asset_file_defs, bundle->session.get())); + // Record walltime spent in restoring graph from disk, but postpone metric + // increments until graph init finishes. + const uint64 restore_graph_walltime = + GetLatencyMicroseconds(read_start_microseconds); + + const uint64 graph_init_start_microseconds = Env::Default()->NowMicros(); string init_op_name; TF_RETURN_IF_ERROR( GetInitOp(export_dir, bundle->meta_graph_def, &init_op_name)); TF_RETURN_IF_ERROR(RunInitOp(run_options, export_dir, bundle->meta_graph_def, asset_file_defs, bundle->session.get(), init_op_name)); + load_latency_by_stage->GetCell(export_dir, "restore_graph") + ->Add(restore_graph_walltime); + // Record wall time spent in init op. + load_latency_by_stage->GetCell(export_dir, "init_graph") + ->Add(GetLatencyMicroseconds(graph_init_start_microseconds)); return Status::OK(); } @@ -275,16 +307,10 @@ Status LoadSavedModel(const SessionOptions& session_options, const uint64 start_microseconds = Env::Default()->NowMicros(); const Status status = LoadSavedModelInternal(session_options, run_options, export_dir, tags, bundle); - const uint64 load_latency_microsecs = [&]() -> uint64 { - const uint64 end_microseconds = Env::Default()->NowMicros(); - // Avoid clock skew. - if (end_microseconds < start_microseconds) return 0; - return end_microseconds - start_microseconds; - }(); auto log_and_count = [&](const string& status_str) { LOG(INFO) << "SavedModel load for tags { " << str_util::Join(tags, " ") << " }; Status: " << status_str << ". Took " - << load_latency_microsecs << " microseconds."; + << GetLatencyMicroseconds(start_microseconds) << " microseconds."; load_attempt_count->GetCell(export_dir, status_str)->IncrementBy(1); }; if (status.ok()) { @@ -292,7 +318,8 @@ Status LoadSavedModel(const SessionOptions& session_options, } else { log_and_count(kLoadAttemptFail); } - load_latency->GetCell(export_dir)->IncrementBy(load_latency_microsecs); + load_latency->GetCell(export_dir) + ->IncrementBy(GetLatencyMicroseconds(start_microseconds)); return status; } -- GitLab From 29b51d42ba4950658c566a0ca531b799e019ef7f Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Wed, 16 Jan 2019 15:10:08 -0800 Subject: [PATCH 0821/2345] Update tests to use the new distribute_py_test build rule. PiperOrigin-RevId: 229634446 --- tensorflow/contrib/distribute/python/BUILD | 66 +++++++--------------- 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 3a4f23e597..6ae3ec7fb0 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -344,10 +344,14 @@ cuda_py_test( ], ) -py_library( - name = "minimize_loss_test_lib", - testonly = 1, +distribute_py_test( + name = "minimize_loss_test", srcs = ["minimize_loss_test.py"], + main = "minimize_loss_test.py", + tags = [ + "multi_and_single_gpu", + "no_pip", + ], deps = [ ":combinations", ":mirrored_strategy", @@ -367,18 +371,6 @@ py_library( ], ) -cuda_py_test( - name = "minimize_loss_test", - srcs = ["minimize_loss_test.py"], - additional_deps = [ - ":minimize_loss_test_lib", - ], - tags = [ - "multi_and_single_gpu", - "no_pip", - ], -) - cuda_py_test( name = "moving_averages_test", srcs = ["moving_averages_test.py"], @@ -501,10 +493,14 @@ py_library( ], ) -py_library( - name = "step_fn_test_lib", - testonly = 1, +distribute_py_test( + name = "step_fn_test", srcs = ["step_fn_test.py"], + main = "step_fn_test.py", + tags = [ + "multi_and_single_gpu", + "no_pip", + ], deps = [ ":combinations", ":single_loss_example", @@ -517,18 +513,6 @@ py_library( ], ) -cuda_py_test( - name = "step_fn_test", - srcs = ["step_fn_test.py"], - additional_deps = [ - ":step_fn_test_lib", - ], - tags = [ - "multi_and_single_gpu", - "no_pip", - ], -) - py_library( name = "monitor", srcs = ["monitor.py"], @@ -766,10 +750,14 @@ distribute_py_test( ], ) -py_library( - name = "metrics_v1_test_lib", - testonly = 1, +distribute_py_test( + name = "metrics_v1_test", srcs = ["metrics_v1_test.py"], + main = "metrics_v1_test.py", + tags = [ + "multi_and_single_gpu", + "no_pip", + ], deps = [ ":combinations", "//tensorflow/python:math_ops", @@ -781,18 +769,6 @@ py_library( ], ) -cuda_py_test( - name = "metrics_v1_test", - srcs = ["metrics_v1_test.py"], - additional_deps = [ - ":metrics_v1_test_lib", - ], - tags = [ - "multi_and_single_gpu", - "no_pip", - ], -) - cuda_py_test( name = "warm_starting_util_test", size = "medium", -- GitLab From e0d156b3bfef7388b31b35793a2add7b0a5fab6d Mon Sep 17 00:00:00 2001 From: Yifei Feng Date: Wed, 16 Jan 2019 15:10:22 -0800 Subject: [PATCH 0822/2345] [WIP] Part II of #22562: Make TensorFlow core dynamically load cuda driver instead of static linking. PiperOrigin-RevId: 229634483 --- .../gpu/gpu_cudamalloc_allocator.cc | 6 +- tensorflow/stream_executor/build_defs.bzl | 3 +- tensorflow/stream_executor/cuda/BUILD | 7 +- .../stream_executor/cuda/cuda_driver.cc | 213 ++++++++++-------- .../cuda/cuda_driver_wrapper.h | 143 ++++++++++++ .../stream_executor/cuda/cuda_gpu_executor.cc | 5 +- 6 files changed, 277 insertions(+), 100 deletions(-) create mode 100644 tensorflow/stream_executor/cuda/cuda_driver_wrapper.h diff --git a/tensorflow/core/common_runtime/gpu/gpu_cudamalloc_allocator.cc b/tensorflow/core/common_runtime/gpu/gpu_cudamalloc_allocator.cc index d85ca8892f..4be1bbb7df 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_cudamalloc_allocator.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_cudamalloc_allocator.cc @@ -16,6 +16,7 @@ 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" @@ -41,7 +42,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 = cuMemAlloc(&rv, num_bytes); + CUresult res = tensorflow::wrap::cuMemAlloc(&rv, num_bytes); if (res != CUDA_SUCCESS) { LOG(ERROR) << "cuMemAlloc failed to allocate " << num_bytes; return nullptr; @@ -54,7 +55,8 @@ void* GPUcudaMallocAllocator::AllocateRaw(size_t alignment, size_t num_bytes) { void GPUcudaMallocAllocator::DeallocateRaw(void* ptr) { #ifdef GOOGLE_CUDA // free with cudaFree - CUresult res = cuMemFree(reinterpret_cast(ptr)); + CUresult res = + tensorflow::wrap::cuMemFree(reinterpret_cast(ptr)); if (res != CUDA_SUCCESS) { LOG(ERROR) << "cuMemFree failed to free " << ptr; } diff --git a/tensorflow/stream_executor/build_defs.bzl b/tensorflow/stream_executor/build_defs.bzl index a7ddf5a0d7..717c13d113 100644 --- a/tensorflow/stream_executor/build_defs.bzl +++ b/tensorflow/stream_executor/build_defs.bzl @@ -4,8 +4,9 @@ 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 ["@local_config_cuda//cuda:cuda_driver"] + return [] def tf_additional_cudnn_plugin_deps(): return [] diff --git a/tensorflow/stream_executor/cuda/BUILD b/tensorflow/stream_executor/cuda/BUILD index fcfda1337e..77a1851047 100644 --- a/tensorflow/stream_executor/cuda/BUILD +++ b/tensorflow/stream_executor/cuda/BUILD @@ -74,7 +74,10 @@ cc_library( cc_library( name = "cuda_driver", srcs = if_cuda_is_configured(["cuda_driver.cc"]), - hdrs = if_cuda_is_configured(["cuda_driver.h"]), + hdrs = if_cuda_is_configured([ + "cuda_driver.h", + "cuda_driver_wrapper.h", + ]), deps = if_cuda_is_configured([ ":cuda_diagnostics", "@com_google_absl//absl/base", @@ -84,6 +87,7 @@ cc_library( "//tensorflow/stream_executor:device_options", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", ] + tf_additional_cuda_driver_deps()), ) @@ -314,6 +318,7 @@ cc_library( "//tensorflow/stream_executor:timer", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", ]), alwayslink = True, ) diff --git a/tensorflow/stream_executor/cuda/cuda_driver.cc b/tensorflow/stream_executor/cuda/cuda_driver.cc index 7aea66e3af..c39e4f59eb 100644 --- a/tensorflow/stream_executor/cuda/cuda_driver.cc +++ b/tensorflow/stream_executor/cuda/cuda_driver.cc @@ -25,6 +25,7 @@ limitations under the License. #include "absl/container/inlined_vector.h" #include "absl/strings/str_cat.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" @@ -108,11 +109,11 @@ class CreatedContexts { // Formats CUresult to output prettified values into a log stream. string ToString(CUresult result) { const char *error_name; - if (cuGetErrorName(result, &error_name)) { + if (tensorflow::wrap::cuGetErrorName(result, &error_name)) { return absl::StrCat("UNKNOWN ERROR (", static_cast(result), ")"); } const char *error_string; - if (cuGetErrorString(result, &error_string)) { + if (tensorflow::wrap::cuGetErrorString(result, &error_string)) { return error_name; } return absl::StrCat(error_name, ": ", error_string); @@ -167,7 +168,7 @@ namespace { // Call cuCtxtSynchronize and crash if it doesn't succeed. void SynchronizeOrDie() { - auto res = cuCtxSynchronize(); + auto res = tensorflow::wrap::cuCtxSynchronize(); if (res != CUDA_SUCCESS) { LOG(FATAL) << "Synchronize found " << ToString(res) << " :: " << port::CurrentStackTrace(); @@ -203,7 +204,8 @@ ScopedActivateContext::ScopedActivateContext(CudaContext* cuda_context) { to_restore_ = (tls->depth == 1 ? nullptr : tls->context); // Set the context and update thread local. - CHECK_EQ(CUDA_SUCCESS, cuCtxSetCurrent(cuda_context->context())); + CHECK_EQ(CUDA_SUCCESS, + tensorflow::wrap::cuCtxSetCurrent(cuda_context->context())); tls->id = cuda_context->id(); tls->context = cuda_context; } @@ -228,7 +230,8 @@ ScopedActivateContext::~ScopedActivateContext() { } // Set context and update thread local. - CHECK_EQ(CUDA_SUCCESS, cuCtxSetCurrent(to_restore_->context())); + CHECK_EQ(CUDA_SUCCESS, + tensorflow::wrap::cuCtxSetCurrent(to_restore_->context())); tls->id = to_restore_->id(); tls->context = to_restore_; } @@ -290,7 +293,7 @@ static port::Status InternalInit() { if (FLAGS_gpuexec_cuda_driver_inject_init_error) { LOG(ERROR) << "injecting CUDA init error; initialization will fail"; } else { - res = cuInit(0 /* = flags */); + res = tensorflow::wrap::cuInit(0 /* = flags */); } if (res == CUDA_SUCCESS) { @@ -323,7 +326,7 @@ static port::Status InternalInit() { /* static */ port::Status CUDADriver::GetDevice(int device_ordinal, CUdevice *device) { - CUresult res = cuDeviceGet(device, device_ordinal); + CUresult res = tensorflow::wrap::cuDeviceGet(device, device_ordinal); if (res == CUDA_SUCCESS) { return port::Status::OK(); } @@ -337,7 +340,8 @@ static port::Status InternalInit() { string *device_name) { static const size_t kCharLimit = 64; absl::InlinedVector chars(kCharLimit); - CUresult res = cuDeviceGetName(chars.begin(), kCharLimit - 1, device); + CUresult res = + tensorflow::wrap::cuDeviceGetName(chars.begin(), kCharLimit - 1, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to get device name for " << device << ": " << ToString(res); @@ -388,9 +392,9 @@ bool DeviceOptionsToContextFlags(const DeviceOptions &device_options, unsigned int former_primary_context_flags; int former_primary_context_is_active; - CHECK_EQ(CUDA_SUCCESS, - cuDevicePrimaryCtxGetState(device, &former_primary_context_flags, - &former_primary_context_is_active)); + CHECK_EQ(CUDA_SUCCESS, tensorflow::wrap::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) @@ -398,15 +402,16 @@ bool DeviceOptionsToContextFlags(const DeviceOptions &device_options, << former_primary_context_flags << ") than the desired flag set (" << flags << ")."; } else { - CHECK_EQ(CUDA_SUCCESS, cuDevicePrimaryCtxSetFlags(device, flags)); + CHECK_EQ(CUDA_SUCCESS, + tensorflow::wrap::cuDevicePrimaryCtxSetFlags(device, flags)); } } former_context = CUDADriver::CurrentContextOrDie(); - res = cuDevicePrimaryCtxRetain(&new_context, device); + res = tensorflow::wrap::cuDevicePrimaryCtxRetain(&new_context, device); if (former_context != nullptr) { CUdevice former_device; - if (cuCtxGetDevice(&former_device) == CUDA_SUCCESS) { + if (tensorflow::wrap::cuCtxGetDevice(&former_device) == CUDA_SUCCESS) { if (former_device == device) { if (former_context == new_context) { VLOG(2) << "The primary context " << former_context << " for device " @@ -425,7 +430,7 @@ bool DeviceOptionsToContextFlags(const DeviceOptions &device_options, << former_context; } } - CHECK_EQ(CUDA_SUCCESS, cuCtxSetCurrent(former_context)); + CHECK_EQ(CUDA_SUCCESS, tensorflow::wrap::cuCtxSetCurrent(former_context)); if (res == CUDA_SUCCESS) { *context = CreatedContexts::Add(new_context); @@ -454,12 +459,12 @@ bool DeviceOptionsToContextFlags(const DeviceOptions &device_options, return; } CUcontext former_context = CurrentContext(); - CUresult res = cuCtxSetCurrent(context->context()); + CUresult res = tensorflow::wrap::cuCtxSetCurrent(context->context()); CUdevice device; - cuCtxGetDevice(&device); - cuCtxSetCurrent(former_context); + tensorflow::wrap::cuCtxGetDevice(&device); + tensorflow::wrap::cuCtxSetCurrent(former_context); - res = cuDevicePrimaryCtxRelease(device); + res = tensorflow::wrap::cuDevicePrimaryCtxRelease(device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to release CUDA context; leaking: " << ToString(res); @@ -471,7 +476,8 @@ bool DeviceOptionsToContextFlags(const DeviceOptions &device_options, /* static */ bool CUDADriver::FuncGetAttribute(CUfunction_attribute attribute, CUfunction func, int *attribute_value) { - CUresult res = cuFuncGetAttribute(attribute_value, attribute, func); + CUresult res = + tensorflow::wrap::cuFuncGetAttribute(attribute_value, attribute, func); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query kernel attribute. kernel: " << func << ", attribute: " << attribute; @@ -482,7 +488,7 @@ bool DeviceOptionsToContextFlags(const DeviceOptions &device_options, /* static */ bool CUDADriver::FuncSetCacheConfig(CUfunction function, CUfunc_cache cache_config) { - CUresult res = cuFuncSetCacheConfig(function, cache_config); + CUresult res = tensorflow::wrap::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); @@ -496,10 +502,11 @@ bool DeviceOptionsToContextFlags(const DeviceOptions &device_options, CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CUsharedconfig shared_mem_config; ScopedActivateContext activation(context); - CUresult result = cuCtxGetSharedMemConfig(&shared_mem_config); + CUresult result = + tensorflow::wrap::cuCtxGetSharedMemConfig(&shared_mem_config); if (result != CUDA_SUCCESS) { CUdevice device; - cuCtxGetDevice(&device); + tensorflow::wrap::cuCtxGetDevice(&device); LOG(ERROR) << "failed to get CUDA device shared memory config. " << "Context device ID: " << device << ", result: " << ToString(result); @@ -513,10 +520,11 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ port::Status CUDADriver::ContextSetSharedMemConfig( CudaContext* context, CUsharedconfig shared_mem_config) { ScopedActivateContext activation(context); - CUresult result = cuCtxSetSharedMemConfig(shared_mem_config); + CUresult result = + tensorflow::wrap::cuCtxSetSharedMemConfig(shared_mem_config); if (result != CUDA_SUCCESS) { CUdevice device; - cuCtxGetDevice(&device); + tensorflow::wrap::cuCtxGetDevice(&device); LOG(ERROR) << "failed to set CUDA device shared memory config. " << "Context device ID: " << device << ", config: " << shared_mem_config @@ -539,9 +547,9 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { << " gdy: " << grid_dim_y << " gdz: " << grid_dim_z << " bdx: " << block_dim_x << " bdy: " << block_dim_y << " bdz: " << block_dim_z; - 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); + 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); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to launch CUDA kernel: " << function << "; result: " << ToString(res); @@ -555,7 +563,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { const char *cubin_bytes, CUmodule *module) { ScopedActivateContext activation(context); - CUresult result = cuModuleLoadFatBinary(module, cubin_bytes); + CUresult result = + tensorflow::wrap::cuModuleLoadFatBinary(module, cubin_bytes); if (result != CUDA_SUCCESS) { return port::Status(port::error::INTERNAL, "failed to load in-memory CUBIN: " + ToString(result)); @@ -598,8 +607,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { // TODO(leary) Need to see if NVIDIA can expunge the leakiness in their // module loading: see http://b/13248943 - res = cuModuleLoadDataEx(module, ptx_data, TF_ARRAYSIZE(options), - options, option_values); + res = tensorflow::wrap::cuModuleLoadDataEx( + module, ptx_data, TF_ARRAYSIZE(options), options, option_values); } // The PTX JIT mutates the values in the option values array to reflect the @@ -638,7 +647,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CUdeviceptr location, uint8 value, size_t size) { ScopedActivateContext activation(context); - CUresult res = cuMemsetD8(location, value, size); + CUresult res = tensorflow::wrap::cuMemsetD8(location, value, size); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to memset memory: " << ToString(res); return false; @@ -651,7 +660,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { uint32 value, size_t uint32_count) { ScopedActivateContext activation(context); - CUresult res = cuMemsetD32(location, value, uint32_count); + CUresult res = tensorflow::wrap::cuMemsetD32(location, value, uint32_count); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to memset memory: " << ToString(res); return false; @@ -665,7 +674,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { size_t uint32_count, CUstream stream) { ScopedActivateContext activation(context); - CUresult res = cuMemsetD8Async(location, value, uint32_count, stream); + CUresult res = + tensorflow::wrap::cuMemsetD8Async(location, value, uint32_count, stream); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to enqueue async memset operation: " << ToString(res); return false; @@ -680,7 +690,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { size_t uint32_count, CUstream stream) { ScopedActivateContext activation(context); - CUresult res = cuMemsetD32Async(location, value, uint32_count, stream); + CUresult res = + tensorflow::wrap::cuMemsetD32Async(location, value, uint32_count, stream); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to enqueue async memset operation: " << ToString(res); return false; @@ -694,7 +705,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { StreamCallback callback, void *data) { // Note: flags param is required to be zero according to CUDA 6.0. - CUresult res = cuStreamAddCallback(stream, callback, data, 0 /* = flags */); + CUresult res = tensorflow::wrap::cuStreamAddCallback(stream, callback, data, + 0 /* = flags */); if (res != CUDA_SUCCESS) { LOG(ERROR) << "unable to add host callback: " << ToString(res); return false; @@ -708,7 +720,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CUfunction *function) { ScopedActivateContext activated{context}; CHECK(module != nullptr && kernel_name != nullptr); - CUresult res = cuModuleGetFunction(function, module, kernel_name); + CUresult res = + tensorflow::wrap::cuModuleGetFunction(function, module, kernel_name); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to get PTX kernel \"" << kernel_name << "\" from module: " << ToString(res); @@ -726,7 +739,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { ScopedActivateContext activated{context}; CHECK(module != nullptr && symbol_name != nullptr && (dptr != nullptr || bytes != nullptr)); - CUresult res = cuModuleGetGlobal(dptr, bytes, module, symbol_name); + CUresult res = + tensorflow::wrap::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. @@ -741,7 +755,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ void CUDADriver::UnloadModule(CudaContext *context, CUmodule module) { ScopedActivateContext activated{context}; - CUresult res = cuModuleUnload(module); + CUresult res = tensorflow::wrap::cuModuleUnload(module); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to unload module " << module << "; leaking: " << ToString(res); @@ -752,7 +766,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CudaContext* context) { ScopedActivateContext activated{context}; CUdevice device = -1; - CUresult result = cuCtxGetDevice(&device); + CUresult result = tensorflow::wrap::cuCtxGetDevice(&device); if (result == CUDA_SUCCESS) { return device; } @@ -768,7 +782,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { // up synchronization with respect to memsets and any other things that have // to occur on the default stream? ScopedActivateContext activated{context}; - CUresult res = cuStreamCreate(out, 0); + CUresult res = tensorflow::wrap::cuStreamCreate(out, 0); if (res != CUDA_SUCCESS) { LOG(ERROR) << "could not allocate CUDA stream for context " << context->context() << ": " << ToString(res); @@ -787,7 +801,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { } ScopedActivateContext activated{context}; - CUresult res = cuStreamDestroy(*stream); + CUresult res = tensorflow::wrap::cuStreamDestroy(*stream); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to destroy CUDA stream for context " << context->context() << ": " << ToString(res); @@ -802,7 +816,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { uint64 bytes) { ScopedActivateContext activated{context}; CUdeviceptr result = 0; - CUresult res = cuMemAlloc(&result, bytes); + CUresult res = tensorflow::wrap::cuMemAlloc(&result, bytes); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to allocate " << port::HumanReadableNumBytes::ToString(bytes) << " (" << bytes @@ -819,7 +833,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { void *location) { ScopedActivateContext activation(context); CUdeviceptr pointer = absl::bit_cast(location); - CUresult res = cuMemFree(pointer); + CUresult res = tensorflow::wrap::cuMemFree(pointer); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to free device memory at " << location << "; result: " << ToString(res); @@ -834,7 +848,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { ScopedActivateContext activation(context); CUdeviceptr result = 0; // "Portable" memory is visible to all CUDA contexts. Safe for our use model. - CUresult res = cuMemAllocManaged(&result, bytes, CU_MEM_ATTACH_GLOBAL); + CUresult res = + tensorflow::wrap::cuMemAllocManaged(&result, bytes, CU_MEM_ATTACH_GLOBAL); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to alloc " << bytes << " bytes unified memory; result: " << ToString(res); @@ -850,7 +865,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { void *location) { ScopedActivateContext activation(context); CUdeviceptr pointer = absl::bit_cast(location); - CUresult res = cuMemFree(pointer); + CUresult res = tensorflow::wrap::cuMemFree(pointer); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to free unified memory at " << location << "; result: " << ToString(res); @@ -865,7 +880,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { ScopedActivateContext activation(context); void *host_mem = nullptr; // "Portable" memory is visible to all CUDA contexts. Safe for our use model. - CUresult res = cuMemHostAlloc(&host_mem, bytes, CU_MEMHOSTALLOC_PORTABLE); + CUresult res = tensorflow::wrap::cuMemHostAlloc(&host_mem, bytes, + CU_MEMHOSTALLOC_PORTABLE); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to alloc " << bytes << " bytes on host: " << ToString(res); @@ -876,7 +892,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ void CUDADriver::HostDeallocate(CudaContext* context, void *location) { ScopedActivateContext activation(context); - CUresult res = cuMemFreeHost(location); + CUresult res = tensorflow::wrap::cuMemFreeHost(location); if (res != CUDA_SUCCESS) { LOG(ERROR) << "error deallocating host memory at " << location << ": " << ToString(res); @@ -887,8 +903,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { uint64 bytes) { ScopedActivateContext activation(context); // "Portable" memory is visible to all CUDA contexts. Safe for our use model. - CUresult res = - cuMemHostRegister(location, bytes, CU_MEMHOSTREGISTER_PORTABLE); + CUresult res = tensorflow::wrap::cuMemHostRegister( + location, bytes, CU_MEMHOSTREGISTER_PORTABLE); if (res != CUDA_SUCCESS) { LOG(ERROR) << "error registering host memory at " << location << ": " << ToString(res); @@ -900,7 +916,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ bool CUDADriver::HostUnregister(CudaContext* context, void *location) { ScopedActivateContext activation(context); - CUresult res = cuMemHostUnregister(location); + CUresult res = tensorflow::wrap::cuMemHostUnregister(location); if (res != CUDA_SUCCESS) { LOG(ERROR) << "error unregistering host memory at " << location << ": " << ToString(res); @@ -917,7 +933,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { } ScopedActivateContext activated{context}; - CUresult res = cuEventDestroy(*event); + CUresult res = tensorflow::wrap::cuEventDestroy(*event); *event = nullptr; switch (res) { @@ -941,7 +957,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CUevent event, CUstream stream) { ScopedActivateContext activated{context}; - CUresult res = cuEventRecord(event, stream); + CUresult res = tensorflow::wrap::cuEventRecord(event, stream); switch (res) { case CUDA_SUCCESS: return port::Status::OK(); @@ -962,7 +978,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ port::StatusOr CUDADriver::QueryEvent( CudaContext *context, CUevent event) { ScopedActivateContext activated{context}; - CUresult res = cuEventQuery(event); + CUresult res = tensorflow::wrap::cuEventQuery(event); if (res != CUDA_SUCCESS && res != CUDA_ERROR_NOT_READY) { return port::Status( port::error::INTERNAL, @@ -978,12 +994,12 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { ScopedActivateContext activated{context}; // The stop event must have completed in order for cuEventElapsedTime to // work. - CUresult res = cuEventSynchronize(stop); + CUresult res = tensorflow::wrap::cuEventSynchronize(stop); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to synchronize the stop event: " << ToString(res); return false; } - res = cuEventElapsedTime(elapsed_milliseconds, start, stop); + res = tensorflow::wrap::cuEventElapsedTime(elapsed_milliseconds, start, stop); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to get elapsed time between events: " << ToString(res); @@ -997,7 +1013,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CUstream stream, CUevent event) { ScopedActivateContext activation(context); - CUresult res = cuStreamWaitEvent(stream, event, 0 /* = flags */); + CUresult res = + tensorflow::wrap::cuStreamWaitEvent(stream, event, 0 /* = flags */); if (res != CUDA_SUCCESS) { LOG(ERROR) << "could not wait stream on event: " << ToString(res); return false; @@ -1008,7 +1025,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ bool CUDADriver::SynchronizeContext(CudaContext* context) { ScopedActivateContext activation(context); - CUresult res = cuCtxSynchronize(); + CUresult res = tensorflow::wrap::cuCtxSynchronize(); if (res != CUDA_SUCCESS) { LOG(ERROR) << "could not synchronize on CUDA context: " << ToString(res) << " :: " << port::CurrentStackTrace(); @@ -1022,7 +1039,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CUstream stream) { ScopedActivateContext activated{context}; CHECK(stream != nullptr); - CUresult res = cuStreamSynchronize(stream); + CUresult res = tensorflow::wrap::cuStreamSynchronize(stream); if (res != CUDA_SUCCESS) { port::Status status = port::InternalError( absl::StrCat("could not synchronize on CUDA stream: ", ToString(res))); @@ -1038,7 +1055,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CUstream stream) { ScopedActivateContext activated{context}; CHECK(stream != nullptr); - CUresult res = cuStreamQuery(stream); + CUresult res = tensorflow::wrap::cuStreamQuery(stream); if (res == CUDA_SUCCESS) { return true; } @@ -1054,7 +1071,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CUdeviceptr gpu_src, uint64 size) { ScopedActivateContext activation(context); - CUresult res = cuMemcpyDtoH(host_dst, gpu_src, size); + CUresult res = tensorflow::wrap::cuMemcpyDtoH(host_dst, gpu_src, size); if (res != CUDA_SUCCESS) { return port::InternalError( port::Printf("failed to synchronous memcpy from device to host: %s; " @@ -1072,7 +1089,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { const void *host_src, uint64 size) { ScopedActivateContext activation(context); - CUresult res = cuMemcpyHtoD(gpu_dst, host_src, size); + CUresult res = tensorflow::wrap::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;" @@ -1089,7 +1106,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { CUdeviceptr gpu_src, uint64 size) { ScopedActivateContext activation(context); - CUresult res = cuMemcpyDtoD(gpu_dst, gpu_src, size); + CUresult res = tensorflow::wrap::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; " @@ -1107,7 +1124,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { uint64 size, CUstream stream) { ScopedActivateContext activation(context); - CUresult res = cuMemcpyDtoHAsync(host_dst, gpu_src, size, stream); + CUresult res = + tensorflow::wrap::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; " @@ -1128,7 +1146,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { uint64 size, CUstream stream) { ScopedActivateContext activation(context); - CUresult res = cuMemcpyHtoDAsync(gpu_dst, host_src, size, stream); + CUresult res = + tensorflow::wrap::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; " @@ -1148,7 +1167,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { uint64 size, CUstream stream) { ScopedActivateContext activation(context); - CUresult result = cuMemcpyDtoDAsync(gpu_dst, gpu_src, size, stream); + CUresult result = + tensorflow::wrap::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" @@ -1185,7 +1205,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { } ScopedActivateContext activated{context}; - CUresult res = cuEventCreate(result, cuflags); + CUresult res = tensorflow::wrap::cuEventCreate(result, cuflags); if (res == CUDA_SUCCESS) { return port::Status::OK(); @@ -1201,7 +1221,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ int CUDADriver::GetDeviceCount() { int device_count = 0; - CUresult res = cuDeviceGetCount(&device_count); + CUresult res = tensorflow::wrap::cuDeviceGetCount(&device_count); if (res != CUDA_SUCCESS) { LOG(ERROR) << "could not retrieve CUDA device count: " << ToString(res); return 0; @@ -1216,8 +1236,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ port::StatusOr CUDADriver::GetPointerContext( CUdeviceptr pointer) { CudaContext* context = nullptr; - CUresult result = - cuPointerGetAttribute(&context, CU_POINTER_ATTRIBUTE_CONTEXT, pointer); + CUresult result = tensorflow::wrap::cuPointerGetAttribute( + &context, CU_POINTER_ATTRIBUTE_CONTEXT, pointer); if (result == CUDA_SUCCESS) { CHECK(context != nullptr) << "success should entail non-null context"; return context; @@ -1232,8 +1252,8 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ port::StatusOr CUDADriver::GetPointerMemorySpace( CUdeviceptr pointer) { unsigned int value; - CUresult result = - cuPointerGetAttribute(&value, CU_POINTER_ATTRIBUTE_MEMORY_TYPE, pointer); + CUresult result = tensorflow::wrap::cuPointerGetAttribute( + &value, CU_POINTER_ATTRIBUTE_MEMORY_TYPE, pointer); if (result == CUDA_SUCCESS) { switch (value) { case CU_MEMORYTYPE_DEVICE: @@ -1256,7 +1276,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { /* static */ port::Status CUDADriver::GetPointerAddressRange(CUdeviceptr dptr, CUdeviceptr *base, size_t *size) { - CUresult result = cuMemGetAddressRange(base, size, dptr); + CUresult result = tensorflow::wrap::cuMemGetAddressRange(base, size, dptr); if (result == CUDA_SUCCESS) { return port::Status::OK(); } else if (result == CUDA_ERROR_NOT_FOUND) { @@ -1291,7 +1311,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { *cc_major = 0; *cc_minor = 0; - CUresult res = cuDeviceGetAttribute( + CUresult res = tensorflow::wrap::cuDeviceGetAttribute( cc_major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, device); if (res != CUDA_SUCCESS) { return port::Status( @@ -1301,7 +1321,7 @@ CUDADriver::ContextGetSharedMemConfig(CudaContext* context) { ToString(res).c_str(), device)); } - res = cuDeviceGetAttribute( + res = tensorflow::wrap::cuDeviceGetAttribute( cc_minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, device); if (res != CUDA_SUCCESS) { return port::Status( @@ -1320,7 +1340,8 @@ template static port::StatusOr GetSimpleAttribute(CUdevice device, CUdevice_attribute attribute) { int value = -1; - CUresult result = cuDeviceGetAttribute(&value, attribute, device); + CUresult result = + tensorflow::wrap::cuDeviceGetAttribute(&value, attribute, device); if (result != CUDA_SUCCESS) { return port::Status( port::error::NOT_FOUND, @@ -1375,24 +1396,24 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ bool CUDADriver::GetGridLimits(int *x, int *y, int *z, CUdevice device) { int value; - CUresult res = - cuDeviceGetAttribute(&value, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X, device); + CUresult res = tensorflow::wrap::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 = - cuDeviceGetAttribute(&value, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Y, device); + res = tensorflow::wrap::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 = - cuDeviceGetAttribute(&value, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Z, device); + res = tensorflow::wrap::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; @@ -1402,7 +1423,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, } /* static */ bool CUDADriver::GetDriverVersion(int *driver_version) { - CUresult res = cuDriverGetVersion(driver_version); + CUresult res = tensorflow::wrap::cuDriverGetVersion(driver_version); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query driver version: " << ToString(res); return false; @@ -1414,7 +1435,8 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ port::StatusOr CUDADriver::GetDeviceAttribute( CUdevice_attribute attribute, CUdevice device) { int val; - CUresult res = cuDeviceGetAttribute(&val, attribute, device); + CUresult res = + tensorflow::wrap::cuDeviceGetAttribute(&val, attribute, device); if (res != CUDA_SUCCESS) { return port::Status( port::error::INTERNAL, @@ -1426,8 +1448,8 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ bool CUDADriver::IsEccEnabled(CUdevice device, bool *result) { int value = -1; - CUresult res = - cuDeviceGetAttribute(&value, CU_DEVICE_ATTRIBUTE_ECC_ENABLED, device); + CUresult res = tensorflow::wrap::cuDeviceGetAttribute( + &value, CU_DEVICE_ATTRIBUTE_ECC_ENABLED, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query ECC status: " << ToString(res); return false; @@ -1443,7 +1465,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, ScopedActivateContext activation(context); size_t free = 0; size_t total = 0; - CUresult res = cuMemGetInfo(&free, &total); + CUresult res = tensorflow::wrap::cuMemGetInfo(&free, &total); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query device memory info: " << ToString(res); return false; @@ -1457,7 +1479,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ bool CUDADriver::GetDeviceTotalMemory(CUdevice device, uint64 *result) { size_t value = -1; - CUresult res = cuDeviceTotalMem(&value, device); + CUresult res = tensorflow::wrap::cuDeviceTotalMem(&value, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query total available memory: " << ToString(res); return false; @@ -1472,7 +1494,8 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, static const int kBufferSize = 64; absl::InlinedVector chars(kBufferSize); chars[kBufferSize - 1] = '\0'; - CUresult res = cuDeviceGetPCIBusId(chars.begin(), kBufferSize - 1, device); + CUresult res = tensorflow::wrap::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; @@ -1500,7 +1523,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, << to_device.status(); return false; } - CUresult res = cuDeviceCanAccessPeer( + CUresult res = tensorflow::wrap::cuDeviceCanAccessPeer( &can_access_peer, from_device.ValueOrDie(), to_device.ValueOrDie()); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to detect peer access capability: " << ToString(res); @@ -1517,7 +1540,8 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, } ScopedActivateContext activated{from}; - CUresult result = cuCtxEnablePeerAccess(to->context(), 0 /* = flags */); + CUresult result = + tensorflow::wrap::cuCtxEnablePeerAccess(to->context(), 0 /* = flags */); if (result != CUDA_SUCCESS && result != CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED) { return port::Status( @@ -1535,8 +1559,9 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, ScopedActivateContext activation(context); int max_blocks; - CUresult result = cuOccupancyMaxActiveBlocksPerMultiprocessor( - &max_blocks, kernel, threads_per_block, dynamic_shared_memory_bytes); + CUresult result = + tensorflow::wrap::cuOccupancyMaxActiveBlocksPerMultiprocessor( + &max_blocks, kernel, threads_per_block, dynamic_shared_memory_bytes); if (result != CUDA_SUCCESS) { return port::Status( port::error::INTERNAL, @@ -1549,7 +1574,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ CUcontext CUDADriver::CurrentContextOrDie() { CUcontext current = nullptr; - CUresult result = cuCtxGetCurrent(¤t); + CUresult result = tensorflow::wrap::cuCtxGetCurrent(¤t); if (result != CUDA_SUCCESS) { LOG(FATAL) << "failed to query current context: " << ToString(result); } diff --git a/tensorflow/stream_executor/cuda/cuda_driver_wrapper.h b/tensorflow/stream_executor/cuda/cuda_driver_wrapper.h new file mode 100644 index 0000000000..ee99908bd5 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cuda_driver_wrapper.h @@ -0,0 +1,143 @@ +/* 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::GetLibcudaDsoHandle() \ + .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(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_gpu_executor.cc b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc index 70004495cd..51dec6b246 100644 --- a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc +++ b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc @@ -28,6 +28,7 @@ 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" @@ -501,7 +502,7 @@ int CUDAExecutor::CalculateOccupancy( CUfunction func) { int suggested_blocks = 0; int suggested_threads = 0; - CUresult err = cuOccupancyMaxPotentialBlockSize( + CUresult err = tensorflow::wrap::cuOccupancyMaxPotentialBlockSize( &suggested_blocks, &suggested_threads, func, nullptr, shared_memory_per_block, 0); CHECK_EQ(err, CUDA_SUCCESS); @@ -518,7 +519,7 @@ int CUDAExecutor::CompareOccupancy(int *initial_blocks, CUfunction func) { int suggested_blocks = 0; int suggested_threads = 0; - CUresult err = cuOccupancyMaxPotentialBlockSize( + CUresult err = tensorflow::wrap::cuOccupancyMaxPotentialBlockSize( &suggested_blocks, &suggested_threads, func, nullptr, shared_memory_per_block, 0); CHECK_EQ(err, CUDA_SUCCESS); -- GitLab From be1e0458309715cb8d0eea6baf1863a90a4f9ab5 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 15:10:59 -0800 Subject: [PATCH 0823/2345] Commented out a description of the delayed-build pattern. :) PiperOrigin-RevId: 229634588 --- tensorflow/python/keras/engine/sequential.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/keras/engine/sequential.py b/tensorflow/python/keras/engine/sequential.py index 425c8c3ef6..37eb2840b3 100644 --- a/tensorflow/python/keras/engine/sequential.py +++ b/tensorflow/python/keras/engine/sequential.py @@ -87,8 +87,8 @@ class Sequential(Model): model.add(Dense(32)) model.weights # returns list of length 4 - When using the delayed-build pattern (no input shape specified), you can - choose to manually build your model by calling `build(batch_input_shape)`: + # When using the delayed-build pattern (no input shape specified), you can + # choose to manually build your model by calling `build(batch_input_shape)`: model = Sequential() model.add(Dense(32)) model.add(Dense(32)) -- GitLab From a799b815e5b497fd85e36ec36e44df0f3cebf802 Mon Sep 17 00:00:00 2001 From: James Qin Date: Wed, 16 Jan 2019 15:23:15 -0800 Subject: [PATCH 0824/2345] Define .value() on DistributedVariable PiperOrigin-RevId: 229636699 --- tensorflow/python/distribute/values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/python/distribute/values.py b/tensorflow/python/distribute/values.py index feaeea80cb..585ae1bd6c 100644 --- a/tensorflow/python/distribute/values.py +++ b/tensorflow/python/distribute/values.py @@ -562,6 +562,9 @@ class DistributedVariable(DistributedDelegate): def read_value(self): return self._distribute_strategy.extended.read_var(self) + def value(self): + return self._get_closest().value() + def _should_act_as_resource_variable(self): """Pass resource_variable_ops.is_resource_variable check.""" pass -- GitLab From 7fa13a9954ddd0c0479f226a8cc71215b7dc260f Mon Sep 17 00:00:00 2001 From: Shivani Agrawal Date: Wed, 16 Jan 2019 15:31:20 -0800 Subject: [PATCH 0825/2345] Adds either a Note or TODO for tests without Eager coverage. Adds coverage for some more SparseTensors related tests and optimization test. PiperOrigin-RevId: 229638239 --- .../optimization/map_vectorization_test.py | 12 ++-- .../optimization/model_dataset_test.py | 14 ++-- .../optimization/optimize_dataset_test.py | 71 +++++++++++-------- .../parse_example_dataset_test.py | 45 ++++++++---- .../kernel_tests/restructured_dataset_test.py | 2 +- .../python/data/kernel_tests/dataset_test.py | 3 + .../from_sparse_tensor_slices_test.py | 4 +- .../kernel_tests/from_tensor_slices_test.py | 2 +- .../data/kernel_tests/from_tensors_test.py | 3 +- .../python/data/kernel_tests/optional_test.py | 6 +- .../data/kernel_tests/padded_batch_test.py | 2 + .../python/data/kernel_tests/reduce_test.py | 1 + 12 files changed, 97 insertions(+), 68 deletions(-) diff --git a/tensorflow/python/data/experimental/kernel_tests/optimization/map_vectorization_test.py b/tensorflow/python/data/experimental/kernel_tests/optimization/map_vectorization_test.py index 405b038eb2..ec543aeb76 100644 --- a/tensorflow/python/data/experimental/kernel_tests/optimization/map_vectorization_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/optimization/map_vectorization_test.py @@ -370,19 +370,17 @@ class MapVectorizationTest(test_base.DatasetTestBase, parameterized.TestCase): num_parallel_calls) self.assertDatasetsEqual(unoptimized, optimized) - # TODO(b/117581999): Add eager coverage for the following tests. - def testSkipEagerOptimizationBadMapFn(self): + def testOptimizationBadMapFn(self): # Test map functions that give an error def map_fn(x): # x has leading dimension 5, this will raise an error return array_ops.gather(x, 10) - - base_dataset = dataset_ops.Dataset.range(5).repeat(5).batch( - 5, drop_remainder=True) - _, optimized = self._get_test_datasets(base_dataset, map_fn) - nxt = dataset_ops.make_one_shot_iterator(optimized).get_next() with self.assertRaisesRegexp(errors.InvalidArgumentError, r"indices = 10 is not in \[0, 5\)"): + base_dataset = dataset_ops.Dataset.range(5).repeat(5).batch( + 5, drop_remainder=True) + _, optimized = self._get_test_datasets(base_dataset, map_fn) + nxt = dataset_ops.make_one_shot_iterator(optimized).get_next() self.evaluate(nxt) def testOptimizationWithCapturedInputs(self): diff --git a/tensorflow/python/data/experimental/kernel_tests/optimization/model_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/optimization/model_dataset_test.py index c6b9db0d52..5c1ae7a98a 100644 --- a/tensorflow/python/data/experimental/kernel_tests/optimization/model_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/optimization/model_dataset_test.py @@ -23,10 +23,11 @@ from tensorflow.python.data.experimental.ops import optimization from tensorflow.python.data.kernel_tests import test_base from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import errors +from tensorflow.python.framework import test_util from tensorflow.python.platform import test -# TODO(b/117581999): Add eager coverage for the following tests. +@test_util.run_all_in_graph_and_eager_modes class ModelDatasetTest(test_base.DatasetTestBase, parameterized.TestCase): def testAutotuneOption(self): @@ -37,14 +38,11 @@ class ModelDatasetTest(test_base.DatasetTestBase, parameterized.TestCase): options.experimental_autotune = True options.experimental_optimization.apply_default_optimizations = False dataset = dataset.with_options(options) + get_next = self.getNext(dataset) - iterator = dataset_ops.make_one_shot_iterator(dataset) - get_next = iterator.get_next() - - with self.cached_session() as sess: - self.assertEqual(0, self.evaluate(get_next)) - with self.assertRaises(errors.OutOfRangeError): - self.evaluate(get_next) + self.assertEqual(0, self.evaluate(get_next())) + with self.assertRaises(errors.OutOfRangeError): + self.evaluate(get_next()) if __name__ == "__main__": diff --git a/tensorflow/python/data/experimental/kernel_tests/optimization/optimize_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/optimization/optimize_dataset_test.py index e02f3b1572..f44ade049b 100644 --- a/tensorflow/python/data/experimental/kernel_tests/optimization/optimize_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/optimization/optimize_dataset_test.py @@ -29,6 +29,7 @@ from tensorflow.python.data.experimental.ops import scan_ops from tensorflow.python.data.experimental.ops import threadpool from tensorflow.python.data.kernel_tests import test_base from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.eager import context from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors from tensorflow.python.framework import ops @@ -112,35 +113,47 @@ class OptimizeDatasetTest(test_base.DatasetTestBase, parameterized.TestCase): get_next = self.getNext(dataset) self.evaluate(get_next()) - # TODO(b/117581999): Add eager coverage for the following tests. - def testSkipEagerOptimizationLargeInputFromTensor(self): - input_t = array_ops.placeholder(dtypes.int32, (None, None, None)) - dataset = dataset_ops.Dataset.from_tensors(input_t) - options = dataset_ops.Options() - options.experimental_optimization.apply_default_optimizations = False - dataset = dataset.with_options(options) - iterator = dataset_ops.make_initializable_iterator(dataset) - init_op = iterator.initializer - get_next = iterator.get_next() - - with self.cached_session() as sess: - sess.run(init_op, {input_t: np.ones([512, 1024, 1025], np.int32)}) - self.evaluate(get_next) - - # TODO(b/117581999): Add eager coverage for the following tests. - def testSkipEagerOptimizationLargeInputFromTensorSlices(self): - input_t = array_ops.placeholder(dtypes.int32, (None, None, None, None)) - dataset = dataset_ops.Dataset.from_tensor_slices(input_t) - options = dataset_ops.Options() - options.experimental_optimization.apply_default_optimizations = False - dataset = dataset.with_options(options) - iterator = dataset_ops.make_initializable_iterator(dataset) - init_op = iterator.initializer - get_next = iterator.get_next() - - with self.cached_session() as sess: - sess.run(init_op, {input_t: np.ones([1, 512, 1024, 1025], np.int32)}) - self.evaluate(get_next) + def testOptimizationLargeInputFromTensor(self): + def dataset_fn(input_t): + dataset = dataset_ops.Dataset.from_tensors(input_t) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + return dataset.with_options(options) + + if context.executing_eagerly(): + input_t = np.ones([512, 1024, 1025], np.int32) + get_next = self.getNext(dataset_fn(input_t)) + self.evaluate(get_next()) + else: + input_t = array_ops.placeholder(dtypes.int32, (None, None, None)) + iterator = dataset_ops.make_initializable_iterator(dataset_fn(input_t)) + init_op = iterator.initializer + get_next = iterator.get_next() + + with self.cached_session() as sess: + sess.run(init_op, {input_t: np.ones([512, 1024, 1025], np.int32)}) + self.evaluate(get_next) + + def testOptimizationLargeInputFromTensorSlices(self): + def dataset_fn(input_t): + dataset = dataset_ops.Dataset.from_tensor_slices(input_t) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + return dataset.with_options(options) + + if context.executing_eagerly(): + input_t = np.ones([1, 512, 1024, 1025], np.int32) + get_next = self.getNext(dataset_fn(input_t)) + self.evaluate(get_next()) + else: + input_t = array_ops.placeholder(dtypes.int32, (None, None, None, None)) + iterator = dataset_ops.make_initializable_iterator(dataset_fn(input_t)) + init_op = iterator.initializer + get_next = iterator.get_next() + + with self.cached_session() as sess: + sess.run(init_op, {input_t: np.ones([1, 512, 1024, 1025], np.int32)}) + self.evaluate(get_next) def testOptimizationNestedDataset(self): 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 76e0d4d72a..4dbb188f2c 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 @@ -27,6 +27,7 @@ from tensorflow.core.example import feature_pb2 from tensorflow.python.data.experimental.ops import parsing_ops as contrib_parsing_ops from tensorflow.python.data.kernel_tests import test_base from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.eager import context from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors_impl from tensorflow.python.framework import ops @@ -671,8 +672,7 @@ class ParseExampleDatasetTest(test_base.DatasetTestBase): for batch_size in (1, 10, 20, 100, 256): self._testSerializedContainingVarLenDenseLargerBatch(batch_size) - @test_util.run_deprecated_v1 - def testSkipEagerSerializedShapeMismatch(self): + def testSerializedShapeMismatch(self): aname = "a" bname = "b" cname = "c" @@ -695,19 +695,34 @@ class ParseExampleDatasetTest(test_base.DatasetTestBase): ] serialized = [m.SerializeToString() for m in original] - self._test( - ops.convert_to_tensor(serialized), { - aname: - parsing_ops.FixedLenSequenceFeature((2, 1), - dtype=dtypes.float32, - allow_missing=True, - default_value=[]), - bname: - parsing_ops.FixedLenSequenceFeature( - (2, 1, 1), dtype=dtypes.string, allow_missing=True), - }, - expected_err=(ValueError, - "Cannot reshape a tensor with 0 elements to shape")) + if context.executing_eagerly(): + self._test( + ops.convert_to_tensor(serialized), { + aname: + parsing_ops.FixedLenSequenceFeature((2, 1), + dtype=dtypes.float32, + allow_missing=True, + default_value=[]), + bname: + parsing_ops.FixedLenSequenceFeature( + (2, 1, 1), dtype=dtypes.string, allow_missing=True), + }, + expected_err=(errors_impl.InvalidArgumentError, + "Input to reshape is a tensor with 0 values")) + else: + self._test( + ops.convert_to_tensor(serialized), { + aname: + parsing_ops.FixedLenSequenceFeature((2, 1), + dtype=dtypes.float32, + allow_missing=True, + default_value=[]), + bname: + parsing_ops.FixedLenSequenceFeature( + (2, 1, 1), dtype=dtypes.string, allow_missing=True), + }, + expected_err=(ValueError, + "Cannot reshape a tensor with 0 elements to shape")) @test_util.run_deprecated_v1 def testSerializedContainingVarLenDense(self): 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 87a91415b0..ddac02b9e2 100644 --- a/tensorflow/python/data/experimental/kernel_tests/restructured_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/restructured_dataset_test.py @@ -27,7 +27,7 @@ from tensorflow.python.ops import array_ops from tensorflow.python.platform import test -# TODO(b/117581999): Add eager coverage +# TODO(b/117581999): Add eager specific test. class RestructuredDatasetTest(test_base.DatasetTestBase): @test_util.run_deprecated_v1 diff --git a/tensorflow/python/data/kernel_tests/dataset_test.py b/tensorflow/python/data/kernel_tests/dataset_test.py index db8a999491..f319b24bee 100644 --- a/tensorflow/python/data/kernel_tests/dataset_test.py +++ b/tensorflow/python/data/kernel_tests/dataset_test.py @@ -266,6 +266,7 @@ class DatasetTest(test_base.DatasetTestBase, parameterized.TestCase): round_trip_dataset, [self.evaluate(tf_value_fn())], requires_initialization=True) + # NOTE: This test is specific to graph mode and is skipped in eager mode. @test_util.run_deprecated_v1 def testSkipEagerSameGraphErrorOneShot(self): dataset = dataset_ops.Dataset.range(10) @@ -273,6 +274,7 @@ class DatasetTest(test_base.DatasetTestBase, parameterized.TestCase): with self.assertRaisesRegexp(ValueError, "must be from the same graph"): dataset = dataset.batch(2) + # NOTE: This test is specific to graph mode and is skipped in eager mode. @test_util.run_deprecated_v1 def testSkipEagerSameGraphErrorOneShotSimple(self): dataset = dataset_ops.Dataset.range(10) @@ -283,6 +285,7 @@ class DatasetTest(test_base.DatasetTestBase, parameterized.TestCase): str(mock_log.call_args), "Please ensure that all datasets in the " "pipeline are created in the same graph as the iterator.") + # NOTE: This test is specific to graph mode and is skipped in eager mode. @test_util.run_deprecated_v1 def testSkipEagerSameGraphErrorInitializable(self): dataset = dataset_ops.Dataset.range(10) diff --git a/tensorflow/python/data/kernel_tests/from_sparse_tensor_slices_test.py b/tensorflow/python/data/kernel_tests/from_sparse_tensor_slices_test.py index ef608ebb67..546c2fb2ed 100644 --- a/tensorflow/python/data/kernel_tests/from_sparse_tensor_slices_test.py +++ b/tensorflow/python/data/kernel_tests/from_sparse_tensor_slices_test.py @@ -29,11 +29,11 @@ from tensorflow.python.ops import array_ops from tensorflow.python.platform import test -@test_util.run_all_in_graph_and_eager_modes +# NOTE: deprecated method in V2, no eager coverage added. class FromSparseTensorSlicesTest(test_base.DatasetTestBase): @test_util.run_deprecated_v1 - def testSkipEagerFromSparseTensorSlices(self): + def testFromSparseTensorSlices(self): """Test a dataset based on slices of a `tf.SparseTensor`.""" st = array_ops.sparse_placeholder(dtypes.float64) iterator = dataset_ops.make_initializable_iterator( 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 9a480e5678..72db638771 100644 --- a/tensorflow/python/data/kernel_tests/from_tensor_slices_test.py +++ b/tensorflow/python/data/kernel_tests/from_tensor_slices_test.py @@ -53,7 +53,7 @@ class FromTensorSlicesTest(test_base.DatasetTestBase): with self.assertRaises(errors.OutOfRangeError): results = self.evaluate(get_next()) - def testSkipEagerFromTensorSlicesSparse(self): + def testFromTensorSlicesSparse(self): """Test a dataset that represents the slices from a tuple of tensors.""" components = (sparse_tensor.SparseTensorValue( indices=np.array([[0, 0], [1, 0], [2, 0]]), diff --git a/tensorflow/python/data/kernel_tests/from_tensors_test.py b/tensorflow/python/data/kernel_tests/from_tensors_test.py index ab3c15263f..82ccdebc7f 100644 --- a/tensorflow/python/data/kernel_tests/from_tensors_test.py +++ b/tensorflow/python/data/kernel_tests/from_tensors_test.py @@ -50,7 +50,7 @@ class FromTensorsTest(test_base.DatasetTestBase): self.assertDatasetProduces(dataset, expected_output=[components]) - def testSkipEagerFromTensorsSparse(self): + def testFromTensorsSparse(self): """Test a dataset that represents a single tuple of tensors.""" components = (sparse_tensor.SparseTensorValue( indices=np.array([[0]]), @@ -224,6 +224,7 @@ class FromTensorsTest(test_base.DatasetTestBase): self.assertEquals(dtypes.int64, get_next().dtype) self.assertEquals([3], get_next().shape) + # TODO(b/121264236): needs mechanism for multiple device in eager mode. def testSkipEagerSplitPipelineFailsWithPlacementError(self): with session.Session( target="", diff --git a/tensorflow/python/data/kernel_tests/optional_test.py b/tensorflow/python/data/kernel_tests/optional_test.py index ba5ee9b661..2269bb8724 100644 --- a/tensorflow/python/data/kernel_tests/optional_test.py +++ b/tensorflow/python/data/kernel_tests/optional_test.py @@ -75,7 +75,6 @@ class OptionalTest(test_base.DatasetTestBase, parameterized.TestCase): self.assertAllEqual(expected.dense_shape, self.evaluate(actual.dense_shape)) - @test_util.run_deprecated_v1 def testFromNone(self): value_structure = structure.TensorStructure(dtypes.float32, []) opt = optional_ops.Optional.none_from_structure(value_structure) @@ -269,9 +268,7 @@ class OptionalTest(test_base.DatasetTestBase, parameterized.TestCase): optional_ops.OptionalStructure( structure.TensorStructure(dtypes.float32, []))), ) - @test_util.run_deprecated_v1 - def testSkipEagerOptionalStructure(self, tf_value_fn, - expected_value_structure): + def testOptionalStructure(self, tf_value_fn, expected_value_structure): tf_value = tf_value_fn() opt = optional_ops.Optional.from_value(tf_value) @@ -306,6 +303,7 @@ class OptionalTest(test_base.DatasetTestBase, parameterized.TestCase): self.assertEqual( self.evaluate(tf_value), self.evaluate(round_trip_opt.get_value())) + # NOTE: This test is specific to graph mode and is skipped in eager mode. @parameterized.named_parameters( ("Tensor", np.array([1, 2, 3], dtype=np.int32), lambda: constant_op.constant([4, 5, 6], dtype=dtypes.int32), True), diff --git a/tensorflow/python/data/kernel_tests/padded_batch_test.py b/tensorflow/python/data/kernel_tests/padded_batch_test.py index dcfb2f507b..042af7a6f9 100644 --- a/tensorflow/python/data/kernel_tests/padded_batch_test.py +++ b/tensorflow/python/data/kernel_tests/padded_batch_test.py @@ -156,6 +156,7 @@ class PaddedBatchTest(test_base.DatasetTestBase, parameterized.TestCase): next_element = self.getNext(padded_dataset) self.evaluate(next_element()) + # NOTE: This test is specific to graph mode and is skipped in eager mode. @test_util.run_deprecated_v1 def testSkipEagerPaddedBatchDatasetShapeSpecifications(self): int_placeholder = array_ops.placeholder(dtypes.int32) @@ -228,6 +229,7 @@ class PaddedBatchTest(test_base.DatasetTestBase, parameterized.TestCase): _ = dataset_ops.Dataset.range(10).padded_batch( 5, padded_shapes=shape_as_tensor) + # NOTE: This test is specific to graph mode and is skipped in eager mode. @test_util.run_deprecated_v1 def testSkipEagerPaddedBatchShapeError(self): with self.assertRaisesRegexp( diff --git a/tensorflow/python/data/kernel_tests/reduce_test.py b/tensorflow/python/data/kernel_tests/reduce_test.py index 14bbc0bf72..93acc1565f 100644 --- a/tensorflow/python/data/kernel_tests/reduce_test.py +++ b/tensorflow/python/data/kernel_tests/reduce_test.py @@ -68,6 +68,7 @@ class ReduceTest(test_base.DatasetTestBase, parameterized.TestCase): self.assertEqual(((i + 1) * i) // 2, s) self.assertEqual(i, c) + # NOTE: This test is specific to graph mode and is skipped in eager mode. @test_util.run_deprecated_v1 def testSkipEagerSquareUsingPlaceholder(self): delta = array_ops.placeholder(dtype=dtypes.int64) -- GitLab From a88c90db14f92509e0da39072dc9bfd772a1c1aa Mon Sep 17 00:00:00 2001 From: Ben Barsdell Date: Fri, 14 Dec 2018 10:14:54 -0800 Subject: [PATCH 0826/2345] [XLA:GPU] Make sm_35 the min supported LLVM proc - XLA nvptx backend does not support sm_30, and TensorFlow also only supports sm_35 onward. --- .../xla/service/gpu/llvm_gpu_backend/nvptx_backend_lib.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/nvptx_backend_lib.cc b/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/nvptx_backend_lib.cc index eddaa877f2..153aab97d9 100644 --- a/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/nvptx_backend_lib.cc +++ b/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/nvptx_backend_lib.cc @@ -110,11 +110,9 @@ static string GetLibdeviceFilename(const string& libdevice_dir_path, } // Gets the GPU name as it's known to LLVM for a given compute capability. If -// we see an unrecognized compute capability, we return "sm_30". +// we see an unrecognized compute capability, we return "sm_35". static string GetSmName(std::pair compute_capability) { static auto* m = new std::map, int>({ - {{3, 0}, 30}, - {{3, 2}, 32}, {{3, 5}, 35}, {{3, 7}, 37}, {{5, 0}, 50}, @@ -127,7 +125,7 @@ static string GetSmName(std::pair compute_capability) { {{7, 2}, 72}, {{7, 5}, 75}, }); - int sm_version = 30; + int sm_version = 35; auto it = m->find(compute_capability); if (it != m->end()) { sm_version = it->second; -- GitLab From ea84f664dd7d4405240da8797bfd21b7bd860aa1 Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Wed, 16 Jan 2019 15:37:43 -0800 Subject: [PATCH 0827/2345] [tf.data] Fixing a bug in MultiDeviceIterator which would cause a deadlock if the iterator was re-initialized without trying to fetch elements from it. PiperOrigin-RevId: 229639479 --- .../core/kernels/data/multi_device_iterator_ops.cc | 2 +- tensorflow/python/data/kernel_tests/BUILD | 1 + .../data/kernel_tests/multi_device_iterator_test.py | 11 ++++++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/kernels/data/multi_device_iterator_ops.cc b/tensorflow/core/kernels/data/multi_device_iterator_ops.cc index eb08d2b28a..167276032b 100644 --- a/tensorflow/core/kernels/data/multi_device_iterator_ops.cc +++ b/tensorflow/core/kernels/data/multi_device_iterator_ops.cc @@ -151,7 +151,7 @@ class MultiDeviceIterator : public ResourceBase { void Reset() LOCKS_EXCLUDED(mu_) { { mutex_lock l(mu_); - if (!background_thread_finished_) { + if (background_thread_ && !background_thread_finished_) { cancelled_ = true; // Wake up the background thread. for (int i = 0; i < size_; ++i) { diff --git a/tensorflow/python/data/kernel_tests/BUILD b/tensorflow/python/data/kernel_tests/BUILD index 737ba28ceb..8206ce382c 100644 --- a/tensorflow/python/data/kernel_tests/BUILD +++ b/tensorflow/python/data/kernel_tests/BUILD @@ -405,6 +405,7 @@ cuda_py_test( srcs = ["multi_device_iterator_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", 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 2ae6a9893d..0c88d7533f 100644 --- a/tensorflow/python/data/kernel_tests/multi_device_iterator_test.py +++ b/tensorflow/python/data/kernel_tests/multi_device_iterator_test.py @@ -18,6 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from absl.testing import parameterized + from tensorflow.core.protobuf import config_pb2 from tensorflow.python.data.experimental.ops import optimization from tensorflow.python.data.kernel_tests import test_base @@ -36,17 +38,20 @@ from tensorflow.python.platform import test # / V2 mode, we should remove this annotation and the run_v1_only annotations # as well. @test_util.run_all_in_graph_and_eager_modes -class MultiDeviceIteratorTest(test_base.DatasetTestBase): +class MultiDeviceIteratorTest(test_base.DatasetTestBase, + parameterized.TestCase): @test_util.run_v1_only - def testNoGetNext(self): + @parameterized.parameters(0, 1, 42,) + def testInitOnly(self, num_inits): dataset = dataset_ops.Dataset.range(10) multi_device_iterator = multi_device_iterator_ops.MultiDeviceIterator( dataset, ["/cpu:1", "/cpu:2"]) config = config_pb2.ConfigProto(device_count={"CPU": 3}) with self.test_session(config=config): - self.evaluate(multi_device_iterator.initializer) + for _ in range(num_inits): + self.evaluate(multi_device_iterator.initializer) @test_util.run_v1_only def testBasic(self): -- GitLab From b1075f10d9facfdca80d28b881a434419e34ae2d Mon Sep 17 00:00:00 2001 From: Shashi Shekhar Date: Wed, 16 Jan 2019 15:59:42 -0800 Subject: [PATCH 0828/2345] Populate zero point for symmetric quantization as well. PiperOrigin-RevId: 229643065 --- tensorflow/lite/tools/optimize/subgraph_quantizer.cc | 11 +++++++++-- .../lite/tools/optimize/subgraph_quantizer_test.cc | 10 ++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/tools/optimize/subgraph_quantizer.cc b/tensorflow/lite/tools/optimize/subgraph_quantizer.cc index 30320304ec..bd6e6e81b2 100644 --- a/tensorflow/lite/tools/optimize/subgraph_quantizer.cc +++ b/tensorflow/lite/tools/optimize/subgraph_quantizer.cc @@ -37,12 +37,17 @@ const int8_t kMinQuantizedValue = -127; const int8_t kMaxQuantizedValue = 127; TfLiteStatus AddQuantizationParams(const std::vector& scales, + const std::vector& zero_point, int quantized_dimension, const uint8_t* buffer_data, size_t buffer_size, TensorType output_type, ModelT* model, TensorT* tensor) { tensor->quantization = absl::make_unique(); tensor->quantization->scale.assign(scales.begin(), scales.end()); + if (zero_point.size() != scales.size()) { + return kTfLiteError; + } + tensor->quantization->zero_point.assign(zero_point.begin(), zero_point.end()); tensor->quantization->quantized_dimension = quantized_dimension; model->buffers[tensor->buffer]->data.assign(buffer_data, buffer_data + buffer_size); @@ -168,8 +173,10 @@ TfLiteStatus SymmetricPerChannelQuantizeTensor(ModelT* model, TensorT* tensor, // Set the buffers and output type. uint8_t* uint8_buffer = reinterpret_cast(final_buffer.data()); size_t buffer_size = num_elements * sizeof(int8_t); - return AddQuantizationParams(scales, channel_dim_index, uint8_buffer, - buffer_size, TensorType_INT8, model, tensor); + std::vector zero_point(scales.size(), 0); + return AddQuantizationParams(scales, zero_point, channel_dim_index, + uint8_buffer, buffer_size, TensorType_INT8, + model, tensor); } // Symmetrically quantizes the bias for ops like Conv and DepthwiseConv. diff --git a/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc b/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc index 085b66e90f..1ea49da899 100644 --- a/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc +++ b/tensorflow/lite/tools/optimize/subgraph_quantizer_test.cc @@ -97,13 +97,18 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantizationWithUnitScale) { const std::vector& weights_scales = weights_tensor->quantization->scale; + const std::vector& weights_zero_points = + weights_tensor->quantization->zero_point; + ASSERT_EQ(weights_scales.size(), out_channel_size); + ASSERT_EQ(weights_zero_points.size(), out_channel_size); ASSERT_EQ(input_tensor->quantization->scale.size(), 1); ASSERT_EQ(output_tensor->quantization->scale.size(), 1); for (size_t i = 0; i < out_channel_size; i++) { EXPECT_EQ(weights_scales[i], 1); + EXPECT_EQ(weights_zero_points[i], 0); } EXPECT_EQ(input_tensor->quantization->scale[0], 1); @@ -190,8 +195,11 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantization) { ASSERT_TRUE(weights_tensor->quantization); const std::vector& weights_scales = weights_tensor->quantization->scale; + const std::vector& weights_zero_points = + weights_tensor->quantization->zero_point; ASSERT_EQ(weights_scales.size(), out_channel_size); + ASSERT_EQ(weights_zero_points.size(), out_channel_size); ASSERT_EQ(input_tensor->quantization->scale.size(), 1); ASSERT_EQ(output_tensor->quantization->scale.size(), 1); @@ -225,9 +233,11 @@ TEST(SubgraphQuantizerTest, VerifyConvQuantization) { for (size_t j = 0; j < num_values_in_channel; j++) { size_t element_idx = channel_idx * out_channel_size + j; auto scale = weights_scales[channel_idx]; + auto zero_point = weights_zero_points[channel_idx]; auto dequantized_value = weight_values[element_idx] * scale; EXPECT_NEAR(dequantized_value, weights_float_buffer[element_idx], scale / 2); + EXPECT_EQ(zero_point, 0); } } } -- GitLab From 0e0ef6e2aab7bf04baad53608745044ecb839594 Mon Sep 17 00:00:00 2001 From: AG Ramesh Date: Wed, 16 Jan 2019 16:18:39 -0800 Subject: [PATCH 0829/2345] Moving definition of Traits inside ifdef --- tensorflow/core/kernels/eigen_spatial_convolutions_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc b/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc index 14c6903a43..920e648972 100644 --- a/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc +++ b/tensorflow/core/kernels/eigen_spatial_convolutions_test.cc @@ -1605,11 +1605,11 @@ static void PackLhsHelper(int iters, /*inner_dim_reordered*/ false, // /*Alignment*/ 0>; - using Traits = typename Eigen::internal::gebp_traits; #if defined(TENSORFLOW_USE_MKLDNN_CONTRACTION_KERNEL) using PackLhsImpl = Eigen::internal::mkldnn_gemm_pack; #else + using Traits = typename Eigen::internal::gebp_traits; using PackLhsImpl = Eigen::internal::gemm_pack_lhs Date: Wed, 16 Jan 2019 16:05:18 -0800 Subject: [PATCH 0830/2345] Disable testing of user-defined GPU kernel under eager mode PiperOrigin-RevId: 229644310 --- tensorflow/tools/ci_build/builds/test_user_ops.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/builds/test_user_ops.sh b/tensorflow/tools/ci_build/builds/test_user_ops.sh index 25ecee4725..9da9c3b881 100755 --- a/tensorflow/tools/ci_build/builds/test_user_ops.sh +++ b/tensorflow/tools/ci_build/builds/test_user_ops.sh @@ -239,8 +239,15 @@ function run_op() { fi } +printf "\nTesting execution of user-defined op under graph mode:\n\n" run_op "$("${PYTHON_BIN_PATH}" -c "import tensorflow as tf; print(tf.Session('').run(tf.load_op_library('./${USER_OP_SO}').${USER_OP}(${OP_INPUT})))")" -run_op "$("${PYTHON_BIN_PATH}" -c "import tensorflow as tf; tf.enable_eager_execution(); print(tf.load_op_library('./${USER_OP_SO}').${USER_OP}(${OP_INPUT}).numpy())")" " in eager mode" + +if [[ "${IS_GPU}" == "0" ]]; then + printf "\nTesting execution of user-defined op under eager mode:\n\n" + run_op "$("${PYTHON_BIN_PATH}" -c "import tensorflow as tf; tf.enable_eager_execution(); print(tf.load_op_library('./${USER_OP_SO}').${USER_OP}(${OP_INPUT}).numpy())")" " in eager mode" +else + printf "\nSKIPPING the testing of execution of user-defined GPU kernel under eager mode. See b/122972785.\n\n" +fi popd -- GitLab From 1d2e3ab3f291bd887a8dc292bb3fd31f9106204e Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Wed, 16 Jan 2019 16:12:51 -0800 Subject: [PATCH 0831/2345] Always call PyObject_ClearWeakRefs This seems to work in Python 2 and be necessary for correctness in Python 3. weakreflist is not set and yet there are weakrefs which need to be cleared. Oddly it's only the weak key dictionary that fails in Python 3 before removing the conditional; using weakref.ref on its own seems fine. PiperOrigin-RevId: 229645639 --- tensorflow/python/eager/ops_test.py | 28 ++++++++++++++++++++++++ tensorflow/python/eager/pywrap_tensor.cc | 4 +--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/eager/ops_test.py b/tensorflow/python/eager/ops_test.py index 17a090d526..ab4bdaa601 100644 --- a/tensorflow/python/eager/ops_test.py +++ b/tensorflow/python/eager/ops_test.py @@ -18,6 +18,8 @@ from __future__ import division from __future__ import print_function import threading +import weakref + import numpy as np from tensorflow.core.protobuf import config_pb2 @@ -397,6 +399,32 @@ class OpsTest(test_util.TensorFlowTestCase): t1.start() t1.join() + def testWeakrefEagerTensor(self): + x = constant_op.constant([[1.]]) + x.at1 = constant_op.constant([[2.]]) + x.at2 = 3. + weak_x = weakref.ref(x) + weak_xat1 = weakref.ref(x.at1) + del x + self.assertIs(weak_x(), None) + self.assertIs(weak_xat1(), None) + + def testWeakKeyDictionaryTensor(self): + weak_key_dict = weakref.WeakKeyDictionary() + strong_x = constant_op.constant([[1.]]) + strong_y = constant_op.constant([[2.]]) + weak_key_dict[strong_x] = constant_op.constant([[3.]]) + weak_key_dict[strong_y] = constant_op.constant([[4.]]) + strong_y.a = constant_op.constant([[5.]]) + weak_x = weakref.ref(strong_x) + del strong_x + self.assertIs(weak_x(), None) + self.assertEqual([strong_y], list(weak_key_dict)) + self.assertEqual(1, len(list(weak_key_dict))) + self.assertEqual(1, len(weak_key_dict)) + del strong_y + self.assertEqual([], list(weak_key_dict)) + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/eager/pywrap_tensor.cc b/tensorflow/python/eager/pywrap_tensor.cc index 30a93fb0e4..35040c5d56 100644 --- a/tensorflow/python/eager/pywrap_tensor.cc +++ b/tensorflow/python/eager/pywrap_tensor.cc @@ -501,9 +501,7 @@ int EagerTensor_init(EagerTensor* self, PyObject* args, PyObject* kwds) { void EagerTensor_dealloc(EagerTensor* self) { // Clear weak references to self. // Needs to happen before any actual destruction. - if (self->weakreflist != nullptr) { - PyObject_ClearWeakRefs((PyObject*)self); - } + PyObject_ClearWeakRefs((PyObject*)self); TF_DeleteStatus(self->status); Py_DECREF(self->handle_data); -- GitLab From 0eeb360d8f04fd93b799a07bedbea6e6e899f55e Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Wed, 16 Jan 2019 16:12:52 -0800 Subject: [PATCH 0832/2345] Remove dead code block from normalization layer PiperOrigin-RevId: 229645644 --- tensorflow/python/keras/layers/normalization.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tensorflow/python/keras/layers/normalization.py b/tensorflow/python/keras/layers/normalization.py index 9167a747cd..171e1e231e 100644 --- a/tensorflow/python/keras/layers/normalization.py +++ b/tensorflow/python/keras/layers/normalization.py @@ -413,15 +413,6 @@ class BatchNormalizationV2(Layer): def _assign_moving_average(self, variable, value, momentum): with ops.name_scope(None, 'AssignMovingAvg', [variable, value, momentum]) as scope: - # TODO(b/120571621): We want to avoid colocating the variables here - # since TPUStrategy does not implement replica local variables. - # Remove this hack once we support TPULocalVariables. - is_tpu_strategy = False - if distribution_strategy_context.has_strategy(): - distribute = distribution_strategy_context.get_strategy() - if distribute.__class__.__name__ == 'TPUStrategy': - is_tpu_strategy = True - with ops.colocate_with(variable): decay = ops.convert_to_tensor(1.0 - momentum, name='decay') if decay.dtype != variable.dtype.base_dtype: -- GitLab From 889615c72ef16e1d3151d7ae52939258fdda796c Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Wed, 16 Jan 2019 16:45:06 -0800 Subject: [PATCH 0833/2345] Updates binary cross entropy logic in Keras when input is probabilities. Moves label smoothing logic into the cross entropy loss functions. PiperOrigin-RevId: 229651194 --- tensorflow/python/keras/backend.py | 12 +- tensorflow/python/keras/losses.py | 67 ++++++--- tensorflow/python/keras/losses_test.py | 127 +++++++++++++----- .../golden/v1/tensorflow.keras.losses.pbtxt | 4 +- .../golden/v1/tensorflow.keras.metrics.pbtxt | 4 +- .../golden/v2/tensorflow.keras.losses.pbtxt | 4 +- .../golden/v2/tensorflow.keras.metrics.pbtxt | 4 +- 7 files changed, 156 insertions(+), 66 deletions(-) diff --git a/tensorflow/python/keras/backend.py b/tensorflow/python/keras/backend.py index 2e53ee4ec9..f357f0efc7 100644 --- a/tensorflow/python/keras/backend.py +++ b/tensorflow/python/keras/backend.py @@ -3866,7 +3866,8 @@ def categorical_crossentropy(target, output, from_logits=False, axis=-1): axis = axis % len(output.shape) # scale preds so that the class probas of each sample sum to 1 output = output / math_ops.reduce_sum(output, axis, True) - # manual computation of crossentropy + + # Compute cross entropy from probabilities. epsilon_ = _to_tensor(epsilon(), output.dtype.base_dtype) output = clip_ops.clip_by_value(output, epsilon_, 1. - epsilon_) return -math_ops.reduce_sum(target * math_ops.log(output), axis) @@ -3948,10 +3949,13 @@ def binary_crossentropy(target, output, from_logits=False): """ if not from_logits: if context.executing_eagerly() or output.op.type != 'Sigmoid': - # transform back to logits epsilon_ = _to_tensor(epsilon(), output.dtype.base_dtype) - output = clip_ops.clip_by_value(output, epsilon_, 1 - epsilon_) - output = math_ops.log(output / (1 - output)) + output = clip_ops.clip_by_value(output, epsilon_, 1. - epsilon_) + + # Compute cross entropy from probabilities. + bce = target * math_ops.log(output + epsilon()) + bce += (1 - target) * math_ops.log(1 - output + epsilon()) + return -bce else: # When sigmoid activation function is used for output operation, we # use logits from the sigmoid function directly to compute loss in order diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index 66780de0f0..9144eb0bf9 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -24,6 +24,7 @@ import abc import six from tensorflow.python.framework import ops +from tensorflow.python.framework import smart_cond from tensorflow.python.keras import backend as K from tensorflow.python.keras.utils.generic_utils import deserialize_keras_object from tensorflow.python.keras.utils.generic_utils import serialize_keras_object @@ -291,7 +292,7 @@ class BinaryCrossentropy(Loss): Args: from_logits: Whether `output` is expected to be a logits tensor. By default, we consider that `output` encodes a probability distribution. - label_smoothing: If greater than `0` then smooth the labels. + label_smoothing: Float in [0, 1]. If > `0` then smooth the labels. reduction: Type of `tf.losses.Reduction` to apply to loss. Default value is `SUM_OVER_BATCH_SIZE`. name: Optional name for the op. @@ -304,7 +305,8 @@ class BinaryCrossentropy(Loss): name=None): super(BinaryCrossentropy, self).__init__(reduction=reduction, name=name) self.from_logits = from_logits - self.label_smoothing = label_smoothing + self.label_smoothing = ops.convert_to_tensor( + label_smoothing, dtype=K.floatx()) def call(self, y_true, y_pred): """Invokes the `BinaryCrossentropy` instance. @@ -318,11 +320,11 @@ class BinaryCrossentropy(Loss): """ y_pred = ops.convert_to_tensor(y_pred) y_true = math_ops.cast(y_true, y_pred.dtype) - - if self.label_smoothing > 0: - y_true = y_true * (1 - self.label_smoothing) + 0.5 * self.label_smoothing - - return binary_crossentropy(y_true, y_pred, from_logits=self.from_logits) + return binary_crossentropy( + y_true, + y_pred, + from_logits=self.from_logits, + label_smoothing=self.label_smoothing) @keras_export('keras.losses.CategoricalCrossentropy') @@ -349,8 +351,9 @@ class CategoricalCrossentropy(Loss): Args: from_logits: Whether `output` is expected to be a logits tensor. By default, we consider that `output` encodes a probability distribution. - label_smoothing: If greater than `0` then smooth the labels. This option is - currently not supported when `y_pred` is a sparse input (not one-hot). + label_smoothing: Float in [0, 1]. If > `0` then smooth the labels. This + option is currently not supported when `y_pred` is a sparse input + (not one-hot). reduction: Type of `tf.losses.Reduction` to apply to loss. Default value is `SUM_OVER_BATCH_SIZE`. name: Optional name for the op. @@ -364,7 +367,8 @@ class CategoricalCrossentropy(Loss): super(CategoricalCrossentropy, self).__init__( reduction=reduction, name=name) self.from_logits = from_logits - self.label_smoothing = label_smoothing + self.label_smoothing = ops.convert_to_tensor( + label_smoothing, dtype=K.floatx()) def call(self, y_true, y_pred): """Invokes the `CategoricalCrossentropy` instance. @@ -385,14 +389,11 @@ class CategoricalCrossentropy(Loss): y_true, y_pred, from_logits=self.from_logits) else: y_true = math_ops.cast(y_true, y_pred.dtype) - if self.label_smoothing > 0: - num_classes = math_ops.cast(array_ops.shape(y_true)[1], y_pred.dtype) - smooth_positives = 1.0 - self.label_smoothing - smooth_negatives = self.label_smoothing / num_classes - y_true = y_true * smooth_positives + smooth_negatives - return categorical_crossentropy( - y_true, y_pred, from_logits=self.from_logits) + y_true, + y_pred, + from_logits=self.from_logits, + label_smoothing=self.label_smoothing) @keras_export('keras.losses.Hinge') @@ -782,7 +783,29 @@ def logcosh(y_true, y_pred): @keras_export('keras.metrics.categorical_crossentropy', 'keras.losses.categorical_crossentropy') -def categorical_crossentropy(y_true, y_pred, from_logits=False): +def categorical_crossentropy(y_true, + y_pred, + from_logits=False, + label_smoothing=0): + """Computes the categorical crossentropy loss. + + Args: + y_true: tensor of true targets. + y_pred: tensor of predicted targets. + from_logits: Whether `y_pred` is expected to be a logits tensor. By default, + we consider that `y_pred` encodes a probability distribution. + label_smoothing: Float in [0, 1]. If > `0` then smooth the labels. + + Returns: + Categorical crossentropy loss value. + """ + + def _smooth_labels(): + num_classes = math_ops.cast(array_ops.shape(y_true)[1], y_pred.dtype) + return y_true * (1.0 - label_smoothing) + (label_smoothing / num_classes) + + y_true = smart_cond.smart_cond(label_smoothing, + _smooth_labels, lambda: y_true) return K.categorical_crossentropy(y_true, y_pred, from_logits=from_logits) @@ -795,7 +818,13 @@ def sparse_categorical_crossentropy(y_true, y_pred, from_logits=False): @keras_export('keras.metrics.binary_crossentropy', 'keras.losses.binary_crossentropy') -def binary_crossentropy(y_true, y_pred, from_logits=False): +def binary_crossentropy(y_true, y_pred, from_logits=False, label_smoothing=0): + + def _smooth_labels(): + return y_true * (1.0 - label_smoothing) + 0.5 * label_smoothing + + y_true = smart_cond.smart_cond(label_smoothing, + _smooth_labels, lambda: y_true) return K.mean( K.binary_crossentropy(y_true, y_pred, from_logits=from_logits), axis=-1) diff --git a/tensorflow/python/keras/losses_test.py b/tensorflow/python/keras/losses_test.py index 004c30f84d..d276854e29 100644 --- a/tensorflow/python/keras/losses_test.py +++ b/tensorflow/python/keras/losses_test.py @@ -27,7 +27,6 @@ from tensorflow.python import keras from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import test_util -from tensorflow.python.ops import array_ops from tensorflow.python.ops.losses import losses_impl from tensorflow.python.platform import test @@ -593,74 +592,132 @@ class BinaryCrossentropyTest(test.TestCase): self.assertAlmostEqual(self.evaluate(loss), 0.0, 3) def test_unweighted(self): + y_true = np.asarray([1, 0, 1, 0]).reshape([2, 2]) + y_pred = np.asarray([1, 1, 1, 0], dtype=np.float32).reshape([2, 2]) bce_obj = keras.losses.BinaryCrossentropy() - y_true = constant_op.constant([1, 0, 1, 0, 0, 1], shape=(2, 3)) - y_pred = constant_op.constant([1, 1, 1, 0, 1, 0], - shape=(2, 3), - dtype=dtypes.float32) loss = bce_obj(y_true, y_pred) - self.assertAlmostEqual(self.evaluate(loss), 8.0004, 3) + + # EPSILON = 1e-7, y = y_true, y` = y_pred, Y_MAX = 0.9999999 + # y` = clip_ops.clip_by_value(output, EPSILON, 1. - EPSILON) + # y` = [Y_MAX, Y_MAX, Y_MAX, EPSILON] + + # Loss = -(y log(y` + EPSILON) + (1 - y) log(1 - y` + EPSILON)) + # = [-log(Y_MAX + EPSILON), -log(1 - Y_MAX + EPSILON), + # -log(Y_MAX + EPSILON), -log(1)] + # = [0, 15.33, 0, 0] + # Reduced loss = 15.33 / 4 + + self.assertAlmostEqual(self.evaluate(loss), 3.833, 3) # Test with logits. - logits = constant_op.constant([10., 10., 10., -10., 10, -10], - shape=(2, 3), - dtype=dtypes.float32) + y_true = constant_op.constant([[1, 0, 1], [0, 1, 1]]) + logits = constant_op.constant([[100.0, -100.0, 100.0], + [100.0, 100.0, -100.0]]) bce_obj = keras.losses.BinaryCrossentropy(from_logits=True) loss = bce_obj(y_true, logits) - self.assertAlmostEqual(self.evaluate(loss), 5., 3) + + # Loss = max(x, 0) - x * z + log(1 + exp(-abs(x))) + # (where x = logits and z = y_true) + # = [((100 - 100 * 1 + log(1 + exp(-100))) + + # (0 + 100 * 0 + log(1 + exp(-100))) + + # (100 - 100 * 1 + log(1 + exp(-100))), + # ((100 - 100 * 0 + log(1 + exp(-100))) + + # (100 - 100 * 1 + log(1 + exp(-100))) + + # (0 + 100 * 1 + log(1 + exp(-100))))] + # = [(0 + 0 + 0) / 3, 200 / 3] + # Reduced loss = (0 + 66.666) / 2 + + self.assertAlmostEqual(self.evaluate(loss), 33.333, 3) def test_scalar_weighted(self): bce_obj = keras.losses.BinaryCrossentropy() - y_true = constant_op.constant([1, 0, 1, 0, 0, 1], shape=(2, 3)) - y_pred = constant_op.constant([1, 1, 1, 0, 1, 0], - shape=(2, 3), - dtype=dtypes.float32) + y_true = np.asarray([1, 0, 1, 0]).reshape([2, 2]) + y_pred = np.asarray([1, 1, 1, 0], dtype=np.float32).reshape([2, 2]) loss = bce_obj(y_true, y_pred, sample_weight=2.3) - self.assertAlmostEqual(self.evaluate(loss), 18.4010, 3) + + # EPSILON = 1e-7, y = y_true, y` = y_pred, Y_MAX = 0.9999999 + # y` = clip_ops.clip_by_value(output, EPSILON, 1. - EPSILON) + # y` = [Y_MAX, Y_MAX, Y_MAX, EPSILON] + + # Loss = -(y log(y` + EPSILON) + (1 - y) log(1 - y` + EPSILON)) + # = [-log(Y_MAX + EPSILON), -log(1 - Y_MAX + EPSILON), + # -log(Y_MAX + EPSILON), -log(1)] + # = [0, 15.33, 0, 0] + # Weighted loss = [0, 15.33 * 2.3, 0, 0] + # Reduced loss = 15.33 * 2.3 / 4 + + self.assertAlmostEqual(self.evaluate(loss), 8.817, 3) # Test with logits. - y_true = array_ops.ones((32, 1)) - logits = array_ops.ones((32, 1), dtype=dtypes.float32) + y_true = constant_op.constant([[1, 0, 1], [0, 1, 1]]) + logits = constant_op.constant([[100.0, -100.0, 100.0], + [100.0, 100.0, -100.0]]) bce_obj = keras.losses.BinaryCrossentropy(from_logits=True) loss = bce_obj(y_true, logits, sample_weight=2.3) - self.assertAlmostEqual(self.evaluate(loss), 0.7205, 3) + + # Loss = max(x, 0) - x * z + log(1 + exp(-abs(x))) + # (where x = logits and z = y_true) + # Loss = [(0 + 0 + 0) / 3, 200 / 3] + # Weighted loss = [0 * 2.3, 66.666 * 2.3] + # Reduced loss = (0 + 66.666 * 2.3) / 2 + + self.assertAlmostEqual(self.evaluate(loss), 76.667, 3) def test_sample_weighted(self): bce_obj = keras.losses.BinaryCrossentropy() - y_true = constant_op.constant([1, 0, 1, 0, 0, 1], shape=(2, 3)) - y_pred = constant_op.constant([1, 1, 1, 0, 1, 0], - shape=(2, 3), - dtype=dtypes.float64) + y_true = np.asarray([1, 0, 1, 0]).reshape([2, 2]) + y_pred = np.asarray([1, 1, 1, 0], dtype=np.float32).reshape([2, 2]) sample_weight = constant_op.constant([1.2, 3.4], shape=(2, 1)) loss = bce_obj(y_true, y_pred, sample_weight=sample_weight) - self.assertAlmostEqual(self.evaluate(loss), 21.4907, 3) + + # EPSILON = 1e-7, y = y_true, y` = y_pred, Y_MAX = 0.9999999 + # y` = clip_ops.clip_by_value(output, EPSILON, 1. - EPSILON) + # y` = [Y_MAX, Y_MAX, Y_MAX, EPSILON] + + # Loss = -(y log(y` + EPSILON) + (1 - y) log(1 - y` + EPSILON)) + # = [-log(Y_MAX + EPSILON), -log(1 - Y_MAX + EPSILON), + # -log(Y_MAX + EPSILON), -log(1)] + # = [0, 15.33, 0, 0] + # Reduced loss = 15.33 * 1.2 / 4 + + self.assertAlmostEqual(self.evaluate(loss), 4.6, 3) # Test with logits. - y_true = constant_op.constant([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) - logits = constant_op.constant( - [[100.0, -100.0, -100.0], [-100.0, 100.0, -100.0], - [-100.0, -100.0, 100.0]], - dtype=dtypes.float64) - weights = constant_op.constant([3, 2, 8]) + y_true = constant_op.constant([[1, 0, 1], [0, 1, 1]]) + logits = constant_op.constant([[100.0, -100.0, 100.0], + [100.0, 100.0, -100.0]]) + weights = constant_op.constant([4, 3]) bce_obj = keras.losses.BinaryCrossentropy(from_logits=True) loss = bce_obj(y_true, logits, sample_weight=weights) - self.assertAlmostEqual(self.evaluate(loss), 288.8888, 3) + + # Loss = max(x, 0) - x * z + log(1 + exp(-abs(x))) + # (where x = logits and z = y_true) + # Loss = [(0 + 0 + 0)/3, 200 / 3] + # Weighted loss = [0 * 4, 66.666 * 3] + # Reduced loss = (0 + 66.666 * 3) / 2 + + self.assertAlmostEqual(self.evaluate(loss), 100, 3) def test_no_reduction(self): - y_true = constant_op.constant(((1, 0, 1), (1, 1, 0), (0, 1, 1))) - logits = constant_op.constant(((100.0, -100.0, 100.0), - (100.0, -100.0, 100.0), - (100.0, 100.0, -100.0))) + y_true = constant_op.constant([[1, 0, 1], [0, 1, 1]]) + logits = constant_op.constant([[100.0, -100.0, 100.0], + [100.0, 100.0, -100.0]]) bce_obj = keras.losses.BinaryCrossentropy( from_logits=True, reduction=losses_impl.ReductionV2.NONE) loss = bce_obj(y_true, logits) - self.assertAllClose((0., 66.6666, 66.6666), self.evaluate(loss), 3) + + # Loss = max(x, 0) - x * z + log(1 + exp(-abs(x))) + # (where x = logits and z = y_true) + # Loss = [(0 + 0 + 0)/3, (200)/3] + + self.assertAllClose((0., 66.6666), self.evaluate(loss), 3) def test_label_smoothing(self): logits = constant_op.constant([[100.0, -100.0, -100.0]]) y_true = constant_op.constant([[1, 0, 1]]) label_smoothing = 0.1 # Loss: max(x, 0) - x * z + log(1 + exp(-abs(x))) + # (where x = logits and z = y_true) # Label smoothing: z' = z * (1 - L) + 0.5L # 1 = 1 - 0.5L # 0 = 0.5L diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt index b71e89883e..ce942de6d2 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt @@ -62,11 +62,11 @@ tf_module { } member_method { name: "binary_crossentropy" - argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\'], varargs=None, keywords=None, defaults=[\'False\'], " + argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } member_method { name: "categorical_crossentropy" - argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\'], varargs=None, keywords=None, defaults=[\'False\'], " + argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } member_method { name: "categorical_hinge" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt index 54f5b21f55..cae251cdfe 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt @@ -110,7 +110,7 @@ tf_module { } member_method { name: "binary_crossentropy" - argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\'], varargs=None, keywords=None, defaults=[\'False\'], " + argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } member_method { name: "categorical_accuracy" @@ -118,7 +118,7 @@ tf_module { } member_method { name: "categorical_crossentropy" - argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\'], varargs=None, keywords=None, defaults=[\'False\'], " + argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } member_method { name: "cosine" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt index 673593ed6c..e8809450fa 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt @@ -66,11 +66,11 @@ tf_module { } member_method { name: "binary_crossentropy" - argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\'], varargs=None, keywords=None, defaults=[\'False\'], " + argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } member_method { name: "categorical_crossentropy" - argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\'], varargs=None, keywords=None, defaults=[\'False\'], " + argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } member_method { name: "categorical_hinge" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt index 54f5b21f55..cae251cdfe 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt @@ -110,7 +110,7 @@ tf_module { } member_method { name: "binary_crossentropy" - argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\'], varargs=None, keywords=None, defaults=[\'False\'], " + argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } member_method { name: "categorical_accuracy" @@ -118,7 +118,7 @@ tf_module { } member_method { name: "categorical_crossentropy" - argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\'], varargs=None, keywords=None, defaults=[\'False\'], " + argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } member_method { name: "cosine" -- GitLab From af9f12801a72ef9e9cbfa3e0dbca3510388c7104 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 17:03:57 -0800 Subject: [PATCH 0834/2345] Fork contrib's timeseries into the canned estimator folder and clean it up to only include LSTMAutoRegressor and its dependencies. After the fork, the following changes were made: 1. Imports are pointed to the new fork except for symbols that were removed during the next steps of the clean up (see below). tensorflow/.../estimator imports were changed to tensorflow_estimator/... 2. estimators.py had all its symbols removed except for LSTMAutoRegressor which is the one we want to keep. 3. ar_model.py had all its symbols removed except for LSTMPredictionModel and ARModel (which uses LSTMPredictionModel). 4. state_management.py had ChainingStateManager removed and as state_management_test.py only tests ChainingStateManager, that test file was removed. 5. math_utils.py had all its symbols removed except for InputStatisticsFromMiniBatch and replicate_state. 6. examples folder was removed. This will come back in some form (perhaps an updated docstring in estimators.py) before LSTMAutoRegressor is exported. 7. input_pipeline.py was removed. This is going to be replaced with a user supplied tf.data-backed input_fn. For now, tests point to contrib's input_pipeline. 8. Cleaned up the BUILD and __init__.py files. Fixed rules and removed stale tags. PiperOrigin-RevId: 229654011 --- tensorflow/contrib/timeseries/examples/BUILD | 2 ++ tensorflow/contrib/timeseries/python/timeseries/BUILD | 1 + 2 files changed, 3 insertions(+) diff --git a/tensorflow/contrib/timeseries/examples/BUILD b/tensorflow/contrib/timeseries/examples/BUILD index 57797214d1..e10be88ece 100644 --- a/tensorflow/contrib/timeseries/examples/BUILD +++ b/tensorflow/contrib/timeseries/examples/BUILD @@ -105,6 +105,7 @@ py_binary( data = ["data/multivariate_periods.csv"], srcs_version = "PY2AND3", tags = ["no_pip"], + visibility = ["//visibility:public"], deps = select({ ":empty_condition": [], "//conditions:default": [], @@ -113,6 +114,7 @@ py_binary( "//tensorflow:tensorflow_py", "//tensorflow/contrib/timeseries/python/timeseries:estimators", "//tensorflow/contrib/timeseries/python/timeseries:model", + "//tensorflow/contrib/timeseries/python/timeseries:state_management", ], ) diff --git a/tensorflow/contrib/timeseries/python/timeseries/BUILD b/tensorflow/contrib/timeseries/python/timeseries/BUILD index a0c3204d41..2a22295197 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/BUILD +++ b/tensorflow/contrib/timeseries/python/timeseries/BUILD @@ -281,6 +281,7 @@ py_library( "input_pipeline.py", ], srcs_version = "PY2AND3", + visibility = ["//visibility:public"], deps = [ ":feature_keys", ":model_utils", -- GitLab From 574db93f256879e4182760b2661f2c398f48372e Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Wed, 16 Jan 2019 17:08:25 -0800 Subject: [PATCH 0835/2345] Add SerializableResource and GetSerializedResouceOp. This is needed by: 1. the new calibration design. The current int8 calibration workflow depends on a global resouce manager singleton TRTResourceManager (in resources/trt_resource_manager.h). This has been: - violating the resource manager design: resource manager should be per-device - pollutes the BUILD dependencies, and makes the kernel implementation not able to be used in other language bindings (Issue #23853) 2. the custom backend offline mode, where we'll do the conversion during execution and provide an offline tool to get the serizlied engine PiperOrigin-RevId: 229654702 --- tensorflow/contrib/BUILD | 2 +- tensorflow/contrib/tensorrt/BUILD | 49 +++++++++--- .../kernels/get_serialized_resource_op.cc | 73 +++++++++++++++++ .../get_serialized_resource_op_test.cc | 80 +++++++++++++++++++ .../ops/get_serialized_resource_op.cc | 40 ++++++++++ .../contrib/tensorrt/python/__init__.py | 2 +- .../ops/{trt_engine_op.py => trt_ops.py} | 6 +- .../tensorrt/python/trt_convert_test.py | 2 +- .../tensorrt/resources/trt_resources.cc | 61 ++++++++++++++ .../tensorrt/resources/trt_resources.h | 40 +++------- .../test/dynamic_input_shapes_test.py | 3 - .../tensorrt/test/quantization_mnist_test.py | 2 +- .../test/tf_trt_integration_test_base.py | 2 +- 13 files changed, 314 insertions(+), 48 deletions(-) create mode 100644 tensorflow/contrib/tensorrt/kernels/get_serialized_resource_op.cc create mode 100644 tensorflow/contrib/tensorrt/kernels/get_serialized_resource_op_test.cc create mode 100644 tensorflow/contrib/tensorrt/ops/get_serialized_resource_op.cc rename tensorflow/contrib/tensorrt/python/ops/{trt_engine_op.py => trt_ops.py} (87%) create mode 100644 tensorflow/contrib/tensorrt/resources/trt_resources.cc diff --git a/tensorflow/contrib/BUILD b/tensorflow/contrib/BUILD index 307bb6eca3..a4c3d9623a 100644 --- a/tensorflow/contrib/BUILD +++ b/tensorflow/contrib/BUILD @@ -196,7 +196,7 @@ cc_library( "//tensorflow/contrib/kinesis:dataset_kernels", ], }) + if_not_windows([ - "//tensorflow/contrib/tensorrt:trt_engine_op_kernel", + "//tensorflow/contrib/tensorrt:trt_op_kernels", ]), ) diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 7ae3caaaa2..80d79164a2 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -54,8 +54,9 @@ tf_cuda_cc_test( ) tf_custom_op_library( - name = "python/ops/_trt_engine_op.so", + name = "python/ops/_trt_ops.so", srcs = [ + "ops/get_serialized_resource_op.cc", "ops/trt_engine_op.cc", ], deps = [ @@ -80,8 +81,9 @@ tf_cuda_library( ) cc_library( - name = "trt_engine_op_kernel", + name = "trt_op_kernels", srcs = [ + "kernels/get_serialized_resource_op.cc", "kernels/trt_engine_op.cc", ], hdrs = [ @@ -109,9 +111,33 @@ cc_library( alwayslink = 1, # buildozer: disable=alwayslink-with-hdrs ) +tf_cuda_cc_test( + name = "get_serialized_resource_op_test", + size = "small", + srcs = ["kernels/get_serialized_resource_op_test.cc"], + tags = [ + "no_cuda_on_cpu_tap", + "no_windows", + "nomac", + ], + deps = [ + ":get_serialized_resource_op_op_lib", + ":trt_op_kernels", + ":trt_resources", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + "//tensorflow/core/kernels:ops_testutil", + ], +) + tf_gen_op_libs( op_lib_names = [ "trt_engine_op", + "get_serialized_resource_op", ], ) @@ -128,8 +154,9 @@ tf_cuda_library( ) tf_gen_op_wrapper_py( - name = "trt_engine_op", + name = "trt_ops", deps = [ + ":get_serialized_resource_op_op_lib", ":trt_engine_op_op_lib", ":trt_logging", ":trt_shape_function", @@ -137,16 +164,17 @@ tf_gen_op_wrapper_py( ) tf_custom_op_py_library( - name = "trt_engine_op_loader", - srcs = ["python/ops/trt_engine_op.py"], + name = "trt_ops_loader", + srcs = ["python/ops/trt_ops.py"], dso = [ - ":python/ops/_trt_engine_op.so", + ":python/ops/_trt_ops.so", ] + if_tensorrt([ "@local_config_tensorrt//:nv_infer", ]), kernels = [ - ":trt_engine_op_kernel", + ":trt_op_kernels", ":trt_engine_op_op_lib", + ":get_serialized_resource_op_op_lib", ":trt_shape_function", ], srcs_version = "PY2AND3", @@ -176,8 +204,8 @@ py_library( name = "trt_ops_py", srcs_version = "PY2AND3", deps = [ - ":trt_engine_op", - ":trt_engine_op_loader", + ":trt_ops", + ":trt_ops_loader", ], ) @@ -208,7 +236,7 @@ tf_py_wrap_cc( deps = [ ":test_utils", ":trt_conversion", - ":trt_engine_op_kernel", + ":trt_op_kernels", "//third_party/python_runtime:headers", ], ) @@ -218,6 +246,7 @@ tf_cuda_library( srcs = [ "resources/trt_int8_calibrator.cc", "resources/trt_resource_manager.cc", + "resources/trt_resources.cc", ], hdrs = [ "resources/trt_int8_calibrator.h", diff --git a/tensorflow/contrib/tensorrt/kernels/get_serialized_resource_op.cc b/tensorflow/contrib/tensorrt/kernels/get_serialized_resource_op.cc new file mode 100644 index 0000000000..f68bc2b485 --- /dev/null +++ b/tensorflow/contrib/tensorrt/kernels/get_serialized_resource_op.cc @@ -0,0 +1,73 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_CONTRIB_TENSORRT_KERNELS_GET_SERIALIZED_RESOURCE_OP_H_ +#define TENSORFLOW_CONTRIB_TENSORRT_KERNELS_GET_SERIALIZED_RESOURCE_OP_H_ + +#include +#include + +#include "tensorflow/contrib/tensorrt/resources/trt_resources.h" +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/resource_mgr.h" +#include "tensorflow/core/lib/core/refcount.h" + +#if GOOGLE_CUDA +#if GOOGLE_TENSORRT + +namespace tensorflow { +namespace tensorrt { + +class GetSerializedResourceOp : public OpKernel { + public: + explicit GetSerializedResourceOp(OpKernelConstruction* context) + : OpKernel(context) {} + + ~GetSerializedResourceOp() override {} + + void Compute(OpKernelContext* context) override { + // TODO(laigd): it will allocate the tensor on the device and copy the + // serialized string to that tensor, and later sess.run() will copy it back + // to host. We need to optimize this. + const string& container = context->input(0).scalar()(); + const string& resource_name = context->input(1).scalar()(); + + // Get the resource. + SerializableResourceBase* resource = nullptr; + OP_REQUIRES_OK(context, context->resource_manager()->Lookup( + container, resource_name, &resource)); + ::tensorflow::core::ScopedUnref sc(resource); + + // Serialize the resource as output. + string serialized_resource; + OP_REQUIRES_OK(context, resource->SerializeToString(&serialized_resource)); + + Tensor* output = nullptr; + OP_REQUIRES_OK(context, + context->allocate_output(0, TensorShape({}), &output)); + output->scalar()() = serialized_resource; + } +}; + +REGISTER_KERNEL_BUILDER(Name("GetSerializedResourceOp").Device(DEVICE_GPU), + GetSerializedResourceOp); + +} // namespace tensorrt +} // namespace tensorflow + +#endif // GOOGLE_TENSORRT +#endif // GOOGLE_CUDA +#endif // TENSORFLOW_CONTRIB_TENSORRT_KERNELS_GET_SERIALIZED_RESOURCE_OP_H_ diff --git a/tensorflow/contrib/tensorrt/kernels/get_serialized_resource_op_test.cc b/tensorflow/contrib/tensorrt/kernels/get_serialized_resource_op_test.cc new file mode 100644 index 0000000000..a91228e4c4 --- /dev/null +++ b/tensorflow/contrib/tensorrt/kernels/get_serialized_resource_op_test.cc @@ -0,0 +1,80 @@ +/* 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 +#include +#include +#include + +#include "tensorflow/contrib/tensorrt/resources/trt_resources.h" +#include "tensorflow/core/framework/fake_input.h" +#include "tensorflow/core/framework/node_def_builder.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/framework/types.h" +#include "tensorflow/core/kernels/ops_testutil.h" +#include "tensorflow/core/platform/test.h" + +#if GOOGLE_CUDA +#if GOOGLE_TENSORRT + +namespace tensorflow { +namespace tensorrt { + +class GetSerializedResourceOpTest : public OpsTestBase {}; + +TEST_F(GetSerializedResourceOpTest, Basic) { + // Create the GPU device. + std::unique_ptr device( + DeviceFactory::NewDevice("GPU", {}, "/job:worker/replica:0/task:0")); + + // Create the resource. + class MySerializableResource : public SerializableResourceBase { + public: + string DebugString() const override { return ""; } + Status SerializeToString(string* serialized) override { + *serialized = "my_serialized_str"; + return Status::OK(); + } + }; + const string container = "mycontainer"; + const string resource_name = "myresource"; + SerializableResourceBase* resource = new MySerializableResource(); + ResourceMgr* rm = device->resource_manager(); + EXPECT_TRUE(rm->Create(container, resource_name, resource).ok()); + + // Create the op. + SetDevice(DEVICE_GPU, std::move(device)); + TF_ASSERT_OK(NodeDefBuilder("op", "GetSerializedResourceOp") + .Input(FakeInput(DT_STRING)) + .Input(FakeInput(DT_STRING)) + .Finalize(node_def())); + TF_ASSERT_OK(InitOp()); + + // Execute the op. + AddInputFromArray(TensorShape({}), {container}); + AddInputFromArray(TensorShape({}), {resource_name}); + TF_ASSERT_OK(RunOpKernel()); + + // Verify the result. + // TODO(laigd): OpsTestBase::GetOutput() doesn't work. + Tensor* output = context_->mutable_output(0); + EXPECT_EQ("my_serialized_str", output->scalar()()); +} + +} // namespace tensorrt +} // namespace tensorflow + +#endif // GOOGLE_TENSORRT +#endif // GOOGLE_CUDA diff --git a/tensorflow/contrib/tensorrt/ops/get_serialized_resource_op.cc b/tensorflow/contrib/tensorrt/ops/get_serialized_resource_op.cc new file mode 100644 index 0000000000..59da73f5ef --- /dev/null +++ b/tensorflow/contrib/tensorrt/ops/get_serialized_resource_op.cc @@ -0,0 +1,40 @@ +/* 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. +==============================================================================*/ + +#if GOOGLE_CUDA +#if GOOGLE_TENSORRT + +#include "tensorflow/core/framework/common_shape_fns.h" +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/shape_inference.h" +#include "tensorflow/core/framework/tensor_shape.h" + +namespace tensorflow { + +REGISTER_OP("GetSerializedResourceOp") + .Input("container: string") + .Input("resource_name: string") + .Output("serialized_resource: string") + .SetShapeFn(shape_inference::ScalarShape) + .SetIsStateful() + .Doc(R"doc( +Gets a resource from a container managed by the resource manager and returns +its serialized representation. +)doc"); + +} // namespace tensorflow + +#endif // GOOGLE_TENSORRT +#endif // GOOGLE_CUDA diff --git a/tensorflow/contrib/tensorrt/python/__init__.py b/tensorflow/contrib/tensorrt/python/__init__.py index 7cdfe2b1a6..e2cf253ca0 100644 --- a/tensorflow/contrib/tensorrt/python/__init__.py +++ b/tensorflow/contrib/tensorrt/python/__init__.py @@ -19,7 +19,7 @@ from __future__ import division from __future__ import print_function # pylint: disable=unused-import,line-too-long -from tensorflow.contrib.tensorrt.python.ops import trt_engine_op +from tensorflow.contrib.tensorrt.python.ops import trt_ops from tensorflow.contrib.tensorrt.python.trt_convert import add_test_value from tensorflow.contrib.tensorrt.python.trt_convert import calib_graph_to_infer_graph from tensorflow.contrib.tensorrt.python.trt_convert import clear_test_values diff --git a/tensorflow/contrib/tensorrt/python/ops/trt_engine_op.py b/tensorflow/contrib/tensorrt/python/ops/trt_ops.py similarity index 87% rename from tensorflow/contrib/tensorrt/python/ops/trt_engine_op.py rename to tensorflow/contrib/tensorrt/python/ops/trt_ops.py index 31a313182b..1fee06854f 100644 --- a/tensorflow/contrib/tensorrt/python/ops/trt_engine_op.py +++ b/tensorflow/contrib/tensorrt/python/ops/trt_ops.py @@ -22,13 +22,13 @@ import platform if platform.system() != "Windows": # pylint: disable=wildcard-import,unused-import,g-import-not-at-top - from tensorflow.contrib.tensorrt.ops.gen_trt_engine_op import * + from tensorflow.contrib.tensorrt.ops.gen_trt_ops import * from tensorflow.contrib.util import loader from tensorflow.python.platform import resource_loader # pylint: enable=wildcard-import,unused-import,g-import-not-at-top - _trt_engine_op = loader.load_op_library( - resource_loader.get_path_to_datafile("_trt_engine_op.so")) + _trt_ops = loader.load_op_library( + resource_loader.get_path_to_datafile("_trt_ops.so")) else: raise RuntimeError("Windows platforms are not supported") diff --git a/tensorflow/contrib/tensorrt/python/trt_convert_test.py b/tensorflow/contrib/tensorrt/python/trt_convert_test.py index 14ab4772c4..9562704aeb 100644 --- a/tensorflow/contrib/tensorrt/python/trt_convert_test.py +++ b/tensorflow/contrib/tensorrt/python/trt_convert_test.py @@ -22,7 +22,7 @@ import os from tensorflow.contrib.tensorrt.python import trt_convert # pylint: disable=unused-import -from tensorflow.contrib.tensorrt.python.ops import trt_engine_op +from tensorflow.contrib.tensorrt.python.ops import trt_ops # pylint: enable=unused-import from tensorflow.core.framework import graph_pb2 from tensorflow.core.protobuf import config_pb2 diff --git a/tensorflow/contrib/tensorrt/resources/trt_resources.cc b/tensorflow/contrib/tensorrt/resources/trt_resources.cc new file mode 100644 index 0000000000..c19eb34dab --- /dev/null +++ b/tensorflow/contrib/tensorrt/resources/trt_resources.cc @@ -0,0 +1,61 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/contrib/tensorrt/resources/trt_resources.h" + +#if GOOGLE_CUDA +#if GOOGLE_TENSORRT + +namespace tensorflow { +namespace tensorrt { + +TRTCalibrationResource::~TRTCalibrationResource() { + VLOG(0) << "Destroying Calibration Resource " << std::endl << DebugString(); + builder_.reset(); + engine_.reset(); + // We need to manually destroy the builder and engine before the allocator + // is destroyed. + allocator_.reset(); +} + +string TRTCalibrationResource::DebugString() const { + std::stringstream oss; + using std::dec; + using std::endl; + using std::hex; + oss << " Calibrator = " << hex << calibrator_.get() << dec << endl + << " Builder = " << hex << builder_.get() << dec << endl + << " Engine = " << hex << engine_.get() << dec << endl + << " Logger = " << hex << &logger_ << dec << endl + << " Allocator = " << hex << allocator_.get() << dec << endl + << " Thread = " << hex << thr_.get() << dec << endl; + return oss.str(); +} + +Status TRTCalibrationResource::SerializeToString(string* serialized) { + calibrator_->waitAndSetDone(); + thr_->join(); + *serialized = calibrator_->getCalibrationTableAsString(); + if (!serialized->size()) { + return tensorflow::errors::Unknown("Calibration table is empty."); + } + return Status::OK(); +} + +} // namespace tensorrt +} // namespace tensorflow + +#endif // GOOGLE_TENSORRT +#endif // GOOGLE_CUDA diff --git a/tensorflow/contrib/tensorrt/resources/trt_resources.h b/tensorflow/contrib/tensorrt/resources/trt_resources.h index 11930f3671..d0a87f2c31 100644 --- a/tensorflow/contrib/tensorrt/resources/trt_resources.h +++ b/tensorflow/contrib/tensorrt/resources/trt_resources.h @@ -32,37 +32,23 @@ limitations under the License. #if GOOGLE_CUDA #if GOOGLE_TENSORRT - #include "tensorrt/include/NvInfer.h" namespace tensorflow { namespace tensorrt { -class TRTCalibrationResource : public tensorflow::ResourceBase { +class SerializableResourceBase : public tensorflow::ResourceBase { + public: + virtual Status SerializeToString(string* serialized) = 0; +}; + +class TRTCalibrationResource : public SerializableResourceBase { public: - ~TRTCalibrationResource() { - LOG(INFO) << "Destroying Calibration Resource " << std::endl - << DebugString(); - builder_.reset(); - engine_.reset(); - // We need to manually destroy the builder and engine before the allocator - // is destroyed. - allocator_.reset(); - } - - string DebugString() const override { - std::stringstream oss; - using std::dec; - using std::endl; - using std::hex; - oss << " Calibrator = " << hex << calibrator_.get() << dec << endl - << " Builder = " << hex << builder_.get() << dec << endl - << " Engine = " << hex << engine_.get() << dec << endl - << " Logger = " << hex << &logger_ << dec << endl - << " Allocator = " << hex << allocator_.get() << dec << endl - << " Thread = " << hex << thr_.get() << dec << endl; - return oss.str(); - } + ~TRTCalibrationResource() override; + + string DebugString() const override; + + Status SerializeToString(string* serialized) override; // Lookup table for temporary staging areas of input tensors for calibration. std::unordered_map> device_buffers_; @@ -82,6 +68,6 @@ class TRTCalibrationResource : public tensorflow::ResourceBase { } // namespace tensorrt } // namespace tensorflow -#endif -#endif +#endif // GOOGLE_TENSORRT +#endif // GOOGLE_CUDA #endif // TENSORFLOW_CONTRIB_TENSORRT_RESOURCES_TRT_RESOURCES_H_ diff --git a/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py b/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py index 90addecb14..cc28cd6087 100644 --- a/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py +++ b/tensorflow/contrib/tensorrt/test/dynamic_input_shapes_test.py @@ -20,9 +20,6 @@ from __future__ import print_function import numpy as np -# pylint: disable=unused-import -from tensorflow.contrib.tensorrt.python.ops import trt_engine_op -# pylint: enable=unused-import from tensorflow.contrib.tensorrt.test import tf_trt_integration_test_base as trt_test from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes diff --git a/tensorflow/contrib/tensorrt/test/quantization_mnist_test.py b/tensorflow/contrib/tensorrt/test/quantization_mnist_test.py index 966476dc3e..753b47f4d9 100644 --- a/tensorflow/contrib/tensorrt/test/quantization_mnist_test.py +++ b/tensorflow/contrib/tensorrt/test/quantization_mnist_test.py @@ -20,7 +20,7 @@ from __future__ import print_function from tensorflow.contrib.tensorrt.python import trt_convert # pylint: disable=unused-import -from tensorflow.contrib.tensorrt.python.ops import trt_engine_op +from tensorflow.contrib.tensorrt.python.ops import trt_ops # pylint: enable=unused-import from tensorflow.core.protobuf import config_pb2 from tensorflow.python import data diff --git a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py index f13c615cb1..4f47f80e73 100644 --- a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py +++ b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py @@ -27,7 +27,7 @@ import six from tensorflow.contrib.tensorrt.python import trt_convert # pylint: disable=unused-import -from tensorflow.contrib.tensorrt.python.ops import trt_engine_op +from tensorflow.contrib.tensorrt.python.ops import trt_ops # pylint: enable=unused-import from tensorflow.core.protobuf import config_pb2 from tensorflow.core.protobuf import rewriter_config_pb2 -- GitLab From 8b0e7dfcf0e5f4d0a15a1c32dd379292f2b344fb Mon Sep 17 00:00:00 2001 From: Katherine Wu Date: Wed, 16 Jan 2019 17:16:40 -0800 Subject: [PATCH 0836/2345] Correct add_weight docstring by removing mention of outdated functionality. PiperOrigin-RevId: 229655774 --- tensorflow/python/keras/engine/base_layer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/keras/engine/base_layer.py b/tensorflow/python/keras/engine/base_layer.py index 5a3540d12d..7bfc301239 100644 --- a/tensorflow/python/keras/engine/base_layer.py +++ b/tensorflow/python/keras/engine/base_layer.py @@ -256,7 +256,7 @@ class Layer(checkpointable.Checkpointable): synchronization=tf_variables.VariableSynchronization.AUTO, aggregation=tf_variables.VariableAggregation.NONE, **kwargs): - """Adds a new variable to the layer, or gets an existing one; returns it. + """Adds a new variable to the layer. Arguments: name: variable name. -- GitLab From 1d669ac6ccaa343add66d652f48b2ae943932a43 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Wed, 16 Jan 2019 17:28:46 -0800 Subject: [PATCH 0837/2345] [XLA] Delete dumped_computation_to_graphviz tool. Superceded by the interactive_graphviz tool. PiperOrigin-RevId: 229657392 --- tensorflow/compiler/xla/tools/BUILD | 27 ------ .../tools/dumped_computation_to_graphviz.cc | 84 ------------------- 2 files changed, 111 deletions(-) delete mode 100644 tensorflow/compiler/xla/tools/dumped_computation_to_graphviz.cc diff --git a/tensorflow/compiler/xla/tools/BUILD b/tensorflow/compiler/xla/tools/BUILD index a7ef32fd35..4412a6ec69 100644 --- a/tensorflow/compiler/xla/tools/BUILD +++ b/tensorflow/compiler/xla/tools/BUILD @@ -29,33 +29,6 @@ tf_cc_binary( ], ) -cc_library( - name = "dumped_computation_to_graphviz_library", - srcs = ["dumped_computation_to_graphviz.cc"], - deps = [ - "//tensorflow/compiler/xla:debug_options_flags", - "//tensorflow/compiler/xla:statusor", - "//tensorflow/compiler/xla:types", - "//tensorflow/compiler/xla:xla_data_proto", - "//tensorflow/compiler/xla/client", - "//tensorflow/compiler/xla/client:client_library", - "//tensorflow/compiler/xla/client:local_client", - "//tensorflow/compiler/xla/client:xla_computation", - "//tensorflow/compiler/xla/service", - "//tensorflow/compiler/xla/service:hlo_proto", - "//tensorflow/core:lib", - "@com_google_absl//absl/types:span", - ], -) - -tf_cc_binary( - name = "dumped_computation_to_graphviz", - deps = [ - ":dumped_computation_to_graphviz_library", - "//tensorflow/compiler/xla/service:interpreter_plugin", - ], -) - tf_cc_binary( name = "show_signature", srcs = ["show_signature.cc"], diff --git a/tensorflow/compiler/xla/tools/dumped_computation_to_graphviz.cc b/tensorflow/compiler/xla/tools/dumped_computation_to_graphviz.cc deleted file mode 100644 index b623556468..0000000000 --- a/tensorflow/compiler/xla/tools/dumped_computation_to_graphviz.cc +++ /dev/null @@ -1,84 +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. -==============================================================================*/ - -// Usage: dumped_computation_to_graphviz some_binary_snapshot_proto* -// -// Dumps a graphviz URL for a snapshot computation to the command line. -// -// some_binary_snapshot_proto is obtained by serializing the HloSnapshot from -// ServiceInterface::SnapshotComputation to disk. -// -// The GraphViz URL is placed into the log stderr, whereas computation -// statistics are printed on stdout (implementation note: getting computation -// statistics is how we trigger compilation to split out a GraphViz URL). - -#include -#include -#include - -#include "absl/types/span.h" -#include "tensorflow/compiler/xla/client/client.h" -#include "tensorflow/compiler/xla/client/client_library.h" -#include "tensorflow/compiler/xla/client/local_client.h" -#include "tensorflow/compiler/xla/client/xla_computation.h" -#include "tensorflow/compiler/xla/debug_options_flags.h" -#include "tensorflow/compiler/xla/service/hlo.pb.h" -#include "tensorflow/compiler/xla/service/service.h" -#include "tensorflow/compiler/xla/statusor.h" -#include "tensorflow/compiler/xla/types.h" -#include "tensorflow/compiler/xla/xla_data.pb.h" -#include "tensorflow/core/platform/env.h" -#include "tensorflow/core/platform/init_main.h" -#include "tensorflow/core/platform/logging.h" - -namespace xla { -namespace tools { - -void RealMain(absl::Span args) { - Client* client = ClientLibrary::LocalClientOrDie(); - for (char* arg : args) { - HloSnapshot module; - TF_CHECK_OK( - tensorflow::ReadBinaryProto(tensorflow::Env::Default(), arg, &module)); - XlaComputation computation = - client->LoadSnapshot(module).ConsumeValueOrDie(); - DebugOptions debug_options = GetDebugOptionsFromFlags(); - debug_options.set_xla_generate_hlo_graph(".*"); - ComputationStats stats = - client->GetComputationStats(computation, debug_options) - .ConsumeValueOrDie(); - fprintf(stdout, ">>> %s :: %s\n", arg, stats.DebugString().c_str()); - } -} - -} // namespace tools -} // namespace xla - -int main(int argc, char** argv) { - std::vector flag_list; - xla::AppendDebugOptionsFlags(&flag_list); - xla::string usage = tensorflow::Flags::Usage(argv[0], flag_list); - const bool parse_result = tensorflow::Flags::Parse(&argc, argv, flag_list); - if (!parse_result) { - LOG(ERROR) << "\n" << usage; - return 2; - } - tensorflow::port::InitMain(argv[0], &argc, &argv); - - absl::Span args(argv, argc); - args.remove_prefix(1); // Pop off the binary name, argv[0] - xla::tools::RealMain(args); - return 0; -} -- GitLab From 0f6bde31cf9593395529dce78cb4ec95eecbba3c Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Wed, 16 Jan 2019 18:04:59 -0800 Subject: [PATCH 0838/2345] tf.saved_model: Allow saving/loading concrete functions directly We'll want this for representing signatures, and I don't see any reason to limit it to that. Some fiddling so that the function gets registered with the eager context. Apparently that was only working when graph building. PiperOrigin-RevId: 229661863 --- tensorflow/python/eager/function.py | 56 +++++------ .../saved_model/function_deserialization.py | 18 ++++ .../saved_model/function_serialization.py | 65 +++++++++---- tensorflow/python/saved_model/load.py | 96 +++++++++++-------- tensorflow/python/saved_model/load_test.py | 51 ++++++++++ tensorflow/python/saved_model/save.py | 35 ++++--- .../saved_model/saved_object_graph.proto | 1 + 7 files changed, 224 insertions(+), 98 deletions(-) diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 9b63b6f31c..82dea51c29 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -256,13 +256,16 @@ class _EagerDefinedFunction(object): self._graph = graph self._stateful_ops = tuple(op for op in operations if op.op_def.is_stateful) - def add_to_graph(self, g): + def add_to_graph(self, g=None): # pylint: disable=protected-access - if self.name not in g._functions: - g._add_function(self) - for f in self._graph._functions.values(): - if f.name not in g._functions: - g._add_function(f) + if not g and context.executing_eagerly(): + context.context().add_function_def(self.definition) + else: + if self.name not in g._functions: + g._add_function(self) + for f in self._graph._functions.values(): + if f.name not in g._functions: + g._add_function(f) # pylint: enable=protected-access @property @@ -635,27 +638,26 @@ class ConcreteFunction(object): # method's functionality better. Remove register_gradient_functions argument # and figure out if these needs to be registered. - if not context.executing_eagerly() or g: - if not g: - g = ops.get_default_graph() - self._inference_function.add_to_graph(g) # pylint: disable=protected-access - - # pylint: disable=protected-access - if register_gradient_functions: - # There are two situations for the actual call of a defun: - # 1. If none of the input args are resource variables or watch by any - # tape, and it will run the _inference_function of concrete_func for - # forward pass, the gradient will be generated by standard mechanism. - # 2. Otherwise, defun will create two functions, one for forward pass, - # and the backward pass will be created via tape. - # When registering the function, we register both cases. - if self._backward_graph_function is None: - self._construct_backprop_function() - forward_function = self._forward_function - backward_function = self._backward_graph_function._inference_function - # pylint: enable=protected-access - forward_function.add_to_graph(g) - backward_function.add_to_graph(g) + if not context.executing_eagerly() and not g: + g = ops.get_default_graph() + self._inference_function.add_to_graph(g) # pylint: disable=protected-access + + # pylint: disable=protected-access + if register_gradient_functions: + # There are two situations for the actual call of a defun: + # 1. If none of the input args are resource variables or watch by any + # tape, and it will run the _inference_function of concrete_func for + # forward pass, the gradient will be generated by standard mechanism. + # 2. Otherwise, defun will create two functions, one for forward pass, + # and the backward pass will be created via tape. + # When registering the function, we register both cases. + if self._backward_graph_function is None: + self._construct_backprop_function() + forward_function = self._forward_function + backward_function = self._backward_graph_function._inference_function + # pylint: enable=protected-access + forward_function.add_to_graph(g) + backward_function.add_to_graph(g) def _construct_backprop_function(self): """Constructs the backprop function object for this function.""" diff --git a/tensorflow/python/saved_model/function_deserialization.py b/tensorflow/python/saved_model/function_deserialization.py index 4ab85ec8a0..5479bbb355 100644 --- a/tensorflow/python/saved_model/function_deserialization.py +++ b/tensorflow/python/saved_model/function_deserialization.py @@ -71,6 +71,24 @@ def _deserialize_function_spec(function_spec_proto, coder): kwargs_to_include, input_signature) +def recreate_concrete_function(saved_concrete_function, concrete_functions): + """Recreates a user-facing concrete function.""" + concrete_function = concrete_functions[saved_concrete_function.name] + input_signature = (saved_concrete_function.canonicalized_input_signature + .list_value.values) + # pylint: disable=protected-access + # Set metadata required for the concrete function to accept keyword and + # positional arguments in __call__. Normally this is set in + # get_concrete_function. + concrete_function._arg_keywords = [ + spec.tensor_spec_value.name for spec in input_signature] + # TODO(allenl): Should we preserve the number of allowed positional arguments? + concrete_function._num_positional_args = len(input_signature) + # pylint: enable=protected-access + concrete_function.add_to_graph() + return concrete_function + + def recreate_function(saved_function, concrete_functions): """Creates a `Function` from a `SavedFunction`. diff --git a/tensorflow/python/saved_model/function_serialization.py b/tensorflow/python/saved_model/function_serialization.py index d973f97d42..c79c61b60c 100644 --- a/tensorflow/python/saved_model/function_serialization.py +++ b/tensorflow/python/saved_model/function_serialization.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function from tensorflow.python.framework import func_graph as func_graph_module +from tensorflow.python.framework import tensor_spec from tensorflow.python.platform import tf_logging as logging from tensorflow.python.saved_model import nested_structure_coder from tensorflow.python.saved_model import saved_object_graph_pb2 @@ -38,6 +39,46 @@ def _serialize_function_spec(function_spec, coder): return proto +def _serialize_concrete_function_with_signature( + concrete_function, node_ids, signature_args, coder): + """Build a SavedConcreteFunction.""" + bound_inputs = [] + try: + for capture in concrete_function.captured_inputs: + bound_inputs.append(node_ids[capture]) + except KeyError: + # TODO(andresp): Would it better to throw an exception? + logging.warning( + "Concrete function %s not added to object based saved model as it " + "captures tensor %s which is unsupported or not reachable from root.", + concrete_function.name, capture) + return None + concrete_function_proto = saved_object_graph_pb2.SavedConcreteFunction() + concrete_function_proto.name = concrete_function.name + concrete_function_proto.canonicalized_input_signature.CopyFrom( + coder.encode_structure(signature_args)) + structured_outputs = func_graph_module.convert_structure_to_signature( + concrete_function.structured_outputs) + concrete_function_proto.output_signature.CopyFrom( + coder.encode_structure(structured_outputs)) + concrete_function_proto.bound_inputs.extend(bound_inputs) + return concrete_function_proto + + +def serialize_concrete_function(concrete_function, node_ids): + """Build a standalone SavedConcreteFunction.""" + coder = nested_structure_coder.StructureCoder() + signature = [] + # Construct a flat list of TensorSpecs for this concrete function. On load + # we'll parse them and recreate the call metadata. + for input_tensor, input_keyword in zip( + concrete_function.inputs, concrete_function._arg_keywords): # pylint: disable=protected-access + signature.append(tensor_spec.TensorSpec( + input_tensor.shape, input_tensor.dtype, name=input_keyword)) + return _serialize_concrete_function_with_signature( + concrete_function, node_ids, signature, coder) + + def serialize_function(function, node_ids): """Build a SavedFunction proto.""" coder = nested_structure_coder.StructureCoder() @@ -48,27 +89,11 @@ def serialize_function(function, node_ids): all_concrete_functions = \ function._list_all_concrete_functions_for_serialization() # pylint: disable=protected-access for concrete_function in all_concrete_functions: - bound_inputs = [] - try: - for capture in concrete_function.captured_inputs: - bound_inputs.append(node_ids[capture]) - except KeyError: - # TODO(andresp): Would it better to throw an exception? - logging.warning( - "Concrete function %s not added to object based saved model as it " - "captures tensor %s which is unsupported or not reachable from root.", - concrete_function.name, capture) - continue signature_args, signature_kwargs = \ concrete_function.structured_input_signature del signature_kwargs - concrete_function_proto = proto.concrete_function.add() - concrete_function_proto.name = concrete_function.name - concrete_function_proto.canonicalized_input_signature.CopyFrom( - coder.encode_structure(signature_args)) - structured_outputs = func_graph_module.convert_structure_to_signature( - concrete_function.structured_outputs) - concrete_function_proto.output_signature.CopyFrom( - coder.encode_structure(structured_outputs)) - concrete_function_proto.bound_inputs.extend(bound_inputs) + concrete_function_proto = _serialize_concrete_function_with_signature( + concrete_function, node_ids, signature_args, coder) + if concrete_function_proto is not None: + proto.concrete_function.add().CopyFrom(concrete_function_proto) return proto diff --git a/tensorflow/python/saved_model/load.py b/tensorflow/python/saved_model/load.py index 98990905f0..2aa4924b29 100644 --- a/tensorflow/python/saved_model/load.py +++ b/tensorflow/python/saved_model/load.py @@ -18,6 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import functools import os from tensorflow.python.lib.io import file_io @@ -48,53 +49,62 @@ class _Loader(object): function_deserialization.load_function_def_library( meta_graph.graph_def.library)) self._load_all() - self._setup_concrete_functions() + self._setup_functions() self._restore_checkpoint() - def _setup_concrete_functions(self): + def _setup_concrete_function( + self, concrete_function, seen_functions, coder): + """Setup captured tensors and outputs for a single concrete function.""" + name = concrete_function.name + bound_inputs = [ + self._get_tensor_from_node(node_id) + for node_id in concrete_function.bound_inputs] + bound_variables = [ + self._nodes[node_id] + for node_id in concrete_function.bound_inputs + if self._proto.nodes[node_id].WhichOneof("kind") == "variable" + ] + if name in seen_functions: + raise RuntimeError( + "Concrete function with a duplicate name: %s." % name) + else: + seen_functions.add(name) + # TODO(andresp): This is only injecting the captured inputs into the + # concrete function, note that we did not modify the FuncGraph + # itself. + function = self._concrete_functions[name] + function._captured_inputs = bound_inputs # pylint: disable=protected-access + function._func_graph.variables = bound_variables # pylint: disable=protected-access + # By setting the structured_outputs directly, we can rely on this + # function_lib.ConcreteFunction object to perform the output repacking + # logic. The only limitation of that logic is that it only works + # with output that is convertible to Tensors and the conversion + # always happens. For example tf.TensorShape([2, 3]) will be + # converted to Tensor representing [2, 3]. + original_outputs = coder.decode_proto( + concrete_function.output_signature) + # The original_outputs here had Tensors converted to TensorSpecs, so + # the restored function's structured_outputs field will not be + # exactly the same. Fortunately the repacking logic cares only about + # the structure. + # TODO(vbardiovsky): Should we just replicate the structures, with + # Nones instead of real objects? Decide when we start solving + # idempotency. + function._func_graph.structured_outputs = original_outputs # pylint: disable=protected-access + + def _setup_functions(self): + """Setup captures and output structure in restored concrete functions.""" seen_concrete_functions = set() coder = nested_structure_coder.StructureCoder() for object_proto in self._proto.nodes: - if object_proto.WhichOneof("kind") == "function": + if object_proto.WhichOneof("kind") == "concrete_function": + self._setup_concrete_function( + object_proto.concrete_function, seen_concrete_functions, coder) + elif object_proto.WhichOneof("kind") == "function": for concrete_function in object_proto.function.concrete_function: - name = concrete_function.name - bound_inputs = [ - self._get_tensor_from_node(node_id) - for node_id in concrete_function.bound_inputs - ] - bound_variables = [ - self._nodes[node_id] - for node_id in concrete_function.bound_inputs - if self._proto.nodes[node_id].WhichOneof("kind") == "variable" - ] - if name in seen_concrete_functions: - raise RuntimeError( - "Concrete function with a duplicate name: %s." % name) - else: - seen_concrete_functions.add(name) - # TODO(andresp): This is only injecting the captured inputs into the - # concrete function, note that we did not modify the FuncGraph - # itself. - revived_function = self._concrete_functions[name] - revived_function._captured_inputs = bound_inputs # pylint: disable=protected-access - revived_function._func_graph.variables = bound_variables # pylint: disable=protected-access - # By setting the structured_outputs directly, we can rely on this - # function_lib.ConcreteFunction object to perform the output - # repacking logic. The only limitation of that logic is that it only - # works with output that is convertible to Tensors and the - # conversion always happens. For example tf.TensorShape([2, 3]) - # will be converted to Tensor representing [2, 3]. - original_outputs = coder.decode_proto( - concrete_function.output_signature) - # The original_outputs here had Tensors converted to TensorSpecs, so - # the restored concrete function's structured_outputs field will not - # be exactly the same. Fortunately the repacking logic cares only - # about the structure. - # TODO(vbardiovsky): Should we just replicate the structures, with - # Nones instead of real objects? Decide when we start solving - # idempotency. - revived_function._func_graph.structured_outputs = original_outputs # pylint: disable=protected-access + self._setup_concrete_function( + concrete_function, seen_concrete_functions, coder) def _get_tensor_from_node(self, node_id): obj = self._nodes[node_id] @@ -137,6 +147,8 @@ class _Loader(object): "user_object": lambda: self._recreate_user_object(proto.user_object), "asset": lambda: self._recreate_asset(proto.asset), "function": lambda: self._recreate_function(proto.function), + "concrete_function": functools.partial( + self._recreate_concrete_function, proto.concrete_function), "variable": lambda: self._recreate_variable(proto.variable), } kind = proto.WhichOneof("kind") @@ -168,6 +180,10 @@ class _Loader(object): return function_deserialization.recreate_function( proto, self._concrete_functions), setattr + def _recreate_concrete_function(self, proto): + return function_deserialization.recreate_concrete_function( + proto, self._concrete_functions), setattr + def _recreate_variable(self, proto): # TODO(andresp): Can we use the checkpointed value as initializer? dummy_value = init_ops.Zeros(dtype=proto.dtype)(shape=proto.shape) diff --git a/tensorflow/python/saved_model/load_test.py b/tensorflow/python/saved_model/load_test.py index c8c8f6e1b3..e3dcbda50a 100644 --- a/tensorflow/python/saved_model/load_test.py +++ b/tensorflow/python/saved_model/load_test.py @@ -418,6 +418,57 @@ class LoadTest(test.TestCase): self.assertAllEqual([2, 4, 6], imported.f(constant_op.constant([1, 2, 3])).numpy()) + def test_concrete_function(self): + + @def_function.function( + input_signature=[tensor_spec.TensorSpec([None], dtypes.int32)]) + def func(x): + return 2 * x + + root = tracking.AutoCheckpointable() + root.f = func.get_concrete_function() + + self.assertAllEqual([2], root.f(constant_op.constant([1])).numpy()) + self.assertAllEqual([2, 4], root.f(constant_op.constant([1, 2])).numpy()) + + imported = self.cycle(root) + + self.assertAllEqual([2, 4, 6, 8], + imported.f(constant_op.constant([1, 2, 3, 4])).numpy()) + self.assertAllEqual([2, 4, 6], + imported.f(constant_op.constant([1, 2, 3])).numpy()) + + def test_concrete_function_no_signature(self): + @def_function.function + def func(x): + return 2 * x + + root = tracking.AutoCheckpointable() + root.f = func.get_concrete_function(constant_op.constant([1])) + self.assertAllEqual([4], root.f(constant_op.constant([2])).numpy()) + imported = self.cycle(root) + self.assertAllEqual([6], + imported.f(constant_op.constant([3])).numpy()) + + def test_concrete_function_backprop(self): + @def_function.function( + input_signature=[tensor_spec.TensorSpec([None], dtypes.float32)]) + def func(x): + return x ** 2. + root = tracking.AutoCheckpointable() + root.f = func.get_concrete_function() + + def _compute_gradient(function): + with backprop.GradientTape() as tape: + inp = constant_op.constant(1.) + tape.watch(inp) + output = function(inp) + return tape.gradient(output, inp) + + self.assertEqual(2., _compute_gradient(root.f).numpy()) + imported = self.cycle(root) + self.assertEqual(2., _compute_gradient(imported.f).numpy()) + def test_dict(self): root = tracking.AutoCheckpointable() root.variables = dict(a=variables.Variable(1.)) diff --git a/tensorflow/python/saved_model/save.py b/tensorflow/python/saved_model/save.py index c35e09457f..9c3ace7325 100644 --- a/tensorflow/python/saved_model/save.py +++ b/tensorflow/python/saved_model/save.py @@ -73,18 +73,24 @@ class _SaveableView(object): self.functions = util.ObjectIdentityDictionary() # Also add `Function`s as nodes. - for obj in self.nodes: + nodes_without_functions = list(self.nodes) + for obj in nodes_without_functions: self.functions[obj] = self._list_functions(obj) for function in self.functions[obj].values(): if function not in self.node_ids: self.node_ids[function] = len(self.nodes) self.nodes.append(function) - # Force listing the concrete functions for the side effects: - # - populate the cache for `Function`s that have an - # input_signature and have not been called. - # - force side effects of creation of concrete functions, e.g. create - # variables on first run. - function._list_all_concrete_functions_for_serialization() # pylint: disable=protected-access + # Avoids recursing into functions to see if other functions are + # assigned to attributes. This is sometimes true for concrete + # functions but not helpful. + self.functions[function] = {} + if isinstance(function, def_function.Function): + # Force listing the concrete functions for the side effects: + # - populate the cache for functions that have an input_signature + # and have not been called. + # - force side effects of creation of concrete functions, e.g. create + # variables on first run. + function._list_all_concrete_functions_for_serialization() # pylint: disable=protected-access @property def root(self): @@ -96,7 +102,7 @@ class _SaveableView(object): assert self.node_ids[node] == node_id object_proto = proto.nodes.add() object_proto.slot_variables.extend(self.slot_variables.get(node, ())) - if isinstance(node, def_function.Function): + if isinstance(node, (def_function.Function, defun.ConcreteFunction)): continue for child in node._checkpoint_dependencies: # pylint: disable=protected-access child_proto = object_proto.children.add() @@ -117,7 +123,8 @@ class _SaveableView(object): # We really don't want to throw an exception just because some object's # attribute accessor is broken. attribute_value = None - if isinstance(attribute_value, def_function.Function): + if isinstance(attribute_value, (def_function.Function, + defun.ConcreteFunction)): functions[attribute_name] = attribute_value return functions @@ -554,8 +561,11 @@ def _fill_meta_graph_def(meta_graph_def, saveable_view, signature_functions, concrete_functions = [] for obj in accessible_objects: for function in saveable_view.functions[obj].values(): - concrete_functions.extend( - function._list_all_concrete_functions_for_serialization()) # pylint: disable=protected-access + if isinstance(function, defun.ConcreteFunction): + concrete_functions.append(function) + else: + concrete_functions.extend( + function._list_all_concrete_functions_for_serialization()) # pylint: disable=protected-access with exported_graph.as_default(): signatures = _generate_signatures(signature_functions, resource_map) @@ -617,6 +627,9 @@ def _write_object_proto(obj, proto, asset_file_def_index, node_ids): elif isinstance(obj, def_function.Function): proto.function.CopyFrom( function_serialization.serialize_function(obj, node_ids)) + elif isinstance(obj, defun.ConcreteFunction): + proto.concrete_function.CopyFrom( + function_serialization.serialize_concrete_function(obj, node_ids)) else: registered_type_proto = revived_types.serialize(obj) if registered_type_proto is None: diff --git a/tensorflow/python/saved_model/saved_object_graph.proto b/tensorflow/python/saved_model/saved_object_graph.proto index 38940f4c1c..6f5a952083 100644 --- a/tensorflow/python/saved_model/saved_object_graph.proto +++ b/tensorflow/python/saved_model/saved_object_graph.proto @@ -54,6 +54,7 @@ message SavedObject { SavedAsset asset = 5; SavedFunction function = 6; SavedVariable variable = 7; + SavedConcreteFunction concrete_function = 8; } } -- GitLab From 6dae9a68f0e6e902aaf8811165b8f70d2cc3624a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 18:23:04 -0800 Subject: [PATCH 0839/2345] Go: Update generated wrapper functions for TensorFlow ops. PiperOrigin-RevId: 229663723 --- tensorflow/go/op/wrappers.go | 126 +++++++++++++++++------------------ 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index fbabb37c0e..134185a03b 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -18447,69 +18447,6 @@ func Timestamp(scope *Scope) (ts tf.Output) { return op.Output(0) } -// ResourceSparseApplyKerasMomentumAttr is an optional argument to ResourceSparseApplyKerasMomentum. -type ResourceSparseApplyKerasMomentumAttr func(optionalAttr) - -// ResourceSparseApplyKerasMomentumUseLocking sets the optional use_locking attribute to value. -// -// value: If `True`, updating of the var and accum tensors will be protected -// by a lock; otherwise the behavior is undefined, but may exhibit less -// contention. -// If not specified, defaults to false -func ResourceSparseApplyKerasMomentumUseLocking(value bool) ResourceSparseApplyKerasMomentumAttr { - return func(m optionalAttr) { - m["use_locking"] = value - } -} - -// ResourceSparseApplyKerasMomentumUseNesterov sets the optional use_nesterov attribute to value. -// -// value: If `True`, the tensor passed to compute grad will be -// var + momentum * accum, so in the end, the var you get is actually -// var + momentum * accum. -// If not specified, defaults to false -func ResourceSparseApplyKerasMomentumUseNesterov(value bool) ResourceSparseApplyKerasMomentumAttr { - return func(m optionalAttr) { - m["use_nesterov"] = value - } -} - -// Update relevant entries in '*var' and '*accum' according to the momentum scheme. -// -// Set use_nesterov = True if you want to use Nesterov momentum. -// -// That is for rows we have grad for, we update var and accum as follows: -// -// accum = accum * momentum - lr * grad -// var += accum -// -// Arguments: -// var_: Should be from a Variable(). -// accum: Should be from a Variable(). -// lr: Learning rate. Must be a scalar. -// grad: The gradient. -// indices: A vector of indices into the first dimension of var and accum. -// momentum: Momentum. Must be a scalar. -// -// Returns the created operation. -func ResourceSparseApplyKerasMomentum(scope *Scope, var_ tf.Output, accum tf.Output, lr tf.Output, grad tf.Output, indices tf.Output, momentum tf.Output, optional ...ResourceSparseApplyKerasMomentumAttr) (o *tf.Operation) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "ResourceSparseApplyKerasMomentum", - Input: []tf.Input{ - var_, accum, lr, grad, indices, momentum, - }, - Attrs: attrs, - } - return scope.AddOperation(opspec) -} - // VariableShapeAttr is an optional argument to VariableShape. type VariableShapeAttr func(optionalAttr) @@ -23474,6 +23411,69 @@ func TakeManySparseFromTensorsMap(scope *Scope, sparse_handles tf.Output, dtype return op.Output(0), op.Output(1), op.Output(2) } +// ResourceSparseApplyKerasMomentumAttr is an optional argument to ResourceSparseApplyKerasMomentum. +type ResourceSparseApplyKerasMomentumAttr func(optionalAttr) + +// ResourceSparseApplyKerasMomentumUseLocking sets the optional use_locking attribute to value. +// +// value: If `True`, updating of the var and accum tensors will be protected +// by a lock; otherwise the behavior is undefined, but may exhibit less +// contention. +// If not specified, defaults to false +func ResourceSparseApplyKerasMomentumUseLocking(value bool) ResourceSparseApplyKerasMomentumAttr { + return func(m optionalAttr) { + m["use_locking"] = value + } +} + +// ResourceSparseApplyKerasMomentumUseNesterov sets the optional use_nesterov attribute to value. +// +// value: If `True`, the tensor passed to compute grad will be +// var + momentum * accum, so in the end, the var you get is actually +// var + momentum * accum. +// If not specified, defaults to false +func ResourceSparseApplyKerasMomentumUseNesterov(value bool) ResourceSparseApplyKerasMomentumAttr { + return func(m optionalAttr) { + m["use_nesterov"] = value + } +} + +// Update relevant entries in '*var' and '*accum' according to the momentum scheme. +// +// Set use_nesterov = True if you want to use Nesterov momentum. +// +// That is for rows we have grad for, we update var and accum as follows: +// +// accum = accum * momentum - lr * grad +// var += accum +// +// Arguments: +// var_: Should be from a Variable(). +// accum: Should be from a Variable(). +// lr: Learning rate. Must be a scalar. +// grad: The gradient. +// indices: A vector of indices into the first dimension of var and accum. +// momentum: Momentum. Must be a scalar. +// +// Returns the created operation. +func ResourceSparseApplyKerasMomentum(scope *Scope, var_ tf.Output, accum tf.Output, lr tf.Output, grad tf.Output, indices tf.Output, momentum tf.Output, optional ...ResourceSparseApplyKerasMomentumAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ResourceSparseApplyKerasMomentum", + Input: []tf.Input{ + var_, accum, lr, grad, indices, momentum, + }, + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + // Assigns a new value to a variable. // // Any ReadVariableOp with a control dependency on this op is guaranteed to return -- GitLab From f960af59b7d86ed5ece2c604f0400ef4f3afb4b2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 18:24:53 -0800 Subject: [PATCH 0840/2345] Update ops-related pbtxt files. PiperOrigin-RevId: 229663932 --- .../core/ops/compat/ops_history.v1.pbtxt | 58 +++++++++++++++++++ tensorflow/core/ops/ops.pbtxt | 58 +++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/tensorflow/core/ops/compat/ops_history.v1.pbtxt b/tensorflow/core/ops/compat/ops_history.v1.pbtxt index 9990b8629c..3b7a7b812a 100644 --- a/tensorflow/core/ops/compat/ops_history.v1.pbtxt +++ b/tensorflow/core/ops/compat/ops_history.v1.pbtxt @@ -22275,6 +22275,28 @@ op { } is_stateful: true } +op { + name: "ExperimentalChooseFastestDataset" + input_arg { + name: "input_datasets" + type: DT_VARIANT + number_attr: "N" + } + output_arg { + name: "handle" + type: DT_VARIANT + } + attr { + name: "N" + type: "int" + has_minimum: true + minimum: 2 + } + attr { + name: "num_experiments" + type: "int" + } +} op { name: "ExperimentalDatasetCardinality" input_arg { @@ -23647,6 +23669,42 @@ op { } is_stateful: true } +op { + name: "ExperimentalTakeWhileDataset" + input_arg { + name: "input_dataset" + type: DT_VARIANT + } + input_arg { + name: "other_arguments" + type_list_attr: "Targuments" + } + output_arg { + name: "handle" + type: DT_VARIANT + } + attr { + name: "predicate" + type: "func" + } + attr { + name: "Targuments" + type: "list(type)" + has_minimum: true + } + attr { + name: "output_types" + type: "list(type)" + has_minimum: true + minimum: 1 + } + attr { + name: "output_shapes" + type: "list(shape)" + has_minimum: true + minimum: 1 + } +} op { name: "ExperimentalThreadPoolDataset" input_arg { diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt index 8212282623..92a7bb9a34 100644 --- a/tensorflow/core/ops/ops.pbtxt +++ b/tensorflow/core/ops/ops.pbtxt @@ -10435,6 +10435,28 @@ op { } is_stateful: true } +op { + name: "ExperimentalChooseFastestDataset" + input_arg { + name: "input_datasets" + type: DT_VARIANT + number_attr: "N" + } + output_arg { + name: "handle" + type: DT_VARIANT + } + attr { + name: "N" + type: "int" + has_minimum: true + minimum: 2 + } + attr { + name: "num_experiments" + type: "int" + } +} op { name: "ExperimentalDatasetCardinality" input_arg { @@ -11457,6 +11479,42 @@ op { } is_stateful: true } +op { + name: "ExperimentalTakeWhileDataset" + input_arg { + name: "input_dataset" + type: DT_VARIANT + } + input_arg { + name: "other_arguments" + type_list_attr: "Targuments" + } + output_arg { + name: "handle" + type: DT_VARIANT + } + attr { + name: "predicate" + type: "func" + } + attr { + name: "Targuments" + type: "list(type)" + has_minimum: true + } + attr { + name: "output_types" + type: "list(type)" + has_minimum: true + minimum: 1 + } + attr { + name: "output_shapes" + type: "list(shape)" + has_minimum: true + minimum: 1 + } +} op { name: "ExperimentalThreadPoolDataset" input_arg { -- GitLab From 52af2770f94f0f660e4019d95821052c08b18a41 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 20:07:31 -0800 Subject: [PATCH 0841/2345] Removing unnecessary parameters. PiperOrigin-RevId: 229673560 --- third_party/gpus/cuda_configure.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/third_party/gpus/cuda_configure.bzl b/third_party/gpus/cuda_configure.bzl index f8f3e90708..1988c4ae96 100644 --- a/third_party/gpus/cuda_configure.bzl +++ b/third_party/gpus/cuda_configure.bzl @@ -1067,10 +1067,10 @@ def _create_dummy_repository(repository_ctx): # Create dummy files for the CUDA toolkit since they are still required by # tensorflow/core/platform/default/build_config:cuda. - repository_ctx.file("cuda/cuda/include/cuda.h", "") - repository_ctx.file("cuda/cuda/include/cublas.h", "") - repository_ctx.file("cuda/cuda/include/cudnn.h", "") - repository_ctx.file("cuda/cuda/extras/CUPTI/include/cupti.h", "") + repository_ctx.file("cuda/cuda/include/cuda.h") + repository_ctx.file("cuda/cuda/include/cublas.h") + repository_ctx.file("cuda/cuda/include/cudnn.h") + repository_ctx.file("cuda/cuda/extras/CUPTI/include/cupti.h") repository_ctx.file("cuda/cuda/lib/%s" % _lib_name("cuda", cpu_value)) repository_ctx.file("cuda/cuda/lib/%s" % _lib_name("cudart", cpu_value)) repository_ctx.file( -- GitLab From 77e2ba9188f4343214adc30de63a98d4999103f7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 20:45:42 -0800 Subject: [PATCH 0842/2345] Go: Update generated wrapper functions for TensorFlow ops. PiperOrigin-RevId: 229676560 --- tensorflow/go/op/wrappers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index 134185a03b..666ebe33df 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -13421,7 +13421,7 @@ func StaticRegexReplaceReplaceGlobal(value bool) StaticRegexReplaceAttr { // Arguments: // input: The text to be processed. // pattern: The regular expression to match the input. -// rewrite: The rewrite to be applied to the matched expresion. +// rewrite: The rewrite to be applied to the matched expression. // // Returns The text after applying pattern and rewrite. func StaticRegexReplace(scope *Scope, input tf.Output, pattern string, rewrite string, optional ...StaticRegexReplaceAttr) (output tf.Output) { -- GitLab From 69b9e5358b7e64efd52f742847eeaf5f893762ab Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 22:02:05 -0800 Subject: [PATCH 0843/2345] Remove HostBuffers HostBuffers add significant complexity and have no users. PiperOrigin-RevId: 229682740 --- tensorflow/stream_executor/BUILD | 7 ---- tensorflow/stream_executor/dnn.h | 16 --------- tensorflow/stream_executor/host_buffer.h | 46 ------------------------ tensorflow/stream_executor/stream.cc | 33 ----------------- 4 files changed, 102 deletions(-) delete mode 100644 tensorflow/stream_executor/host_buffer.h diff --git a/tensorflow/stream_executor/BUILD b/tensorflow/stream_executor/BUILD index 7cf7e75a55..fca28e016a 100644 --- a/tensorflow/stream_executor/BUILD +++ b/tensorflow/stream_executor/BUILD @@ -206,7 +206,6 @@ cc_library( cc_library( name = "stream", srcs = [ - "host_buffer.h", "stream.cc", ], hdrs = ["stream.h"], @@ -453,12 +452,6 @@ cc_library( ], ) -cc_library( - name = "host_buffer", - hdrs = ["host_buffer.h"], - deps = [":dnn"], -) - tf_proto_library( name = "dnn_proto", srcs = ["dnn.proto"], diff --git a/tensorflow/stream_executor/dnn.h b/tensorflow/stream_executor/dnn.h index f5eef29109..f60d1ada24 100644 --- a/tensorflow/stream_executor/dnn.h +++ b/tensorflow/stream_executor/dnn.h @@ -2069,22 +2069,6 @@ class DnnSupport { QuantizedActivationMode mode, DeviceMemory* gpu_unquantized_dst) = 0; - // Enqueues an asynchronous copy of the contents of buffer_src to - // gpu_unquantized_dst. - virtual bool DoCopyHostBuffer2Device( - Stream* stream, HostBuffer* buffer_src, - DeviceMemory* gpu_unquantized_dst) { - return false; - } - - // Enqueues an asynchronous copy of the contents of gpu_unquantized_src to - // buffer_dst. - virtual bool DoCopyDevice2HostBuffer( - Stream* stream, const DeviceMemory& gpu_unquantized_src, - HostBuffer* buffer_dst) { - return false; - } - // Create an RNN descriptor based on model shapes and configurations. // The caller retains the ownership of the descriptor. // diff --git a/tensorflow/stream_executor/host_buffer.h b/tensorflow/stream_executor/host_buffer.h deleted file mode 100644 index 20299da517..0000000000 --- a/tensorflow/stream_executor/host_buffer.h +++ /dev/null @@ -1,46 +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. -==============================================================================*/ - -#ifndef TENSORFLOW_STREAM_EXECUTOR_HOST_BUFFER_H_ -#define TENSORFLOW_STREAM_EXECUTOR_HOST_BUFFER_H_ - -#include "tensorflow/stream_executor/dnn.h" - -namespace stream_executor { - -// A HostBuffer is a block of memory in host memory containing the data for a -// dnn::BatchDescriptor using a device-dependent memory layout. -// Derived classes provide methods to construct a HostBuffer for a specific -// device, and to copy data in and out of the buffer. -class HostBuffer { - public: - const dnn::BatchDescriptor& descriptor() const { return descriptor_; } - - // Returns a string describing the HostBuffer. - virtual string AsString() const = 0; - - protected: - // Construct a HostBuffer from the supplied dnn::BatchDescriptor. - explicit HostBuffer(const dnn::BatchDescriptor& descriptor) - : descriptor_(descriptor) {} - virtual ~HostBuffer() {} - - private: - const dnn::BatchDescriptor descriptor_; -}; - -} // namespace stream_executor - -#endif // TENSORFLOW_STREAM_EXECUTOR_HOST_BUFFER_H_ diff --git a/tensorflow/stream_executor/stream.cc b/tensorflow/stream_executor/stream.cc index 0dceacb228..e7485ca426 100644 --- a/tensorflow/stream_executor/stream.cc +++ b/tensorflow/stream_executor/stream.cc @@ -20,7 +20,6 @@ limitations under the License. #include "absl/strings/str_cat.h" #include "third_party/eigen3/Eigen/Core" #include "tensorflow/stream_executor/blas.h" -#include "tensorflow/stream_executor/host_buffer.h" #include "tensorflow/stream_executor/host_or_device_scalar.h" #include "tensorflow/stream_executor/lib/stacktrace.h" #include "tensorflow/stream_executor/platform.h" @@ -95,8 +94,6 @@ string ToVlogString(const void *ptr) { return out.str(); } -string ToVlogString(const HostBuffer &buffer) { return buffer.AsString(); } - template string ToVlogString(const std::complex &c) { // StrCat does not convert std::complex to text. @@ -2103,36 +2100,6 @@ Stream &Stream::ThenMemcpyH2DQuantized( return *this; } -Stream &Stream::ThenCopyHostBuffer2Device( - HostBuffer *buffer_src, DeviceMemory *gpu_unquantized_dst) { - VLOG_CALL(PARAM(*buffer_src), PARAM(gpu_unquantized_dst)); - - if (ok()) { - if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError( - dnn->DoCopyHostBuffer2Device(this, buffer_src, gpu_unquantized_dst)); - } else { - SetErrorAndLogNoDnnSupport(); - } - } - return *this; -} - -Stream &Stream::ThenCopyDevice2HostBuffer( - const DeviceMemory &gpu_unquantized_src, HostBuffer *buffer_dst) { - VLOG_CALL(PARAM(gpu_unquantized_src), PARAM(*buffer_dst)); - - if (ok()) { - if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError( - dnn->DoCopyDevice2HostBuffer(this, gpu_unquantized_src, buffer_dst)); - } else { - SetErrorAndLogNoDnnSupport(); - } - } - return *this; -} - Stream *Stream::GetOrCreateSubStream() { mutex_lock lock(mu_); -- GitLab From 8eb3cbcb423c4d840e88a910c5db47404207b8a4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 16 Jan 2019 23:25:53 -0800 Subject: [PATCH 0844/2345] Fix typo. PiperOrigin-RevId: 229689427 --- tensorflow/compiler/xla/g3doc/broadcasting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/g3doc/broadcasting.md b/tensorflow/compiler/xla/g3doc/broadcasting.md index 2870869a2c..5c0525c1e9 100644 --- a/tensorflow/compiler/xla/g3doc/broadcasting.md +++ b/tensorflow/compiler/xla/g3doc/broadcasting.md @@ -168,7 +168,7 @@ consult the Broadcasting of a lower-rank array to a higher-rank array **and** broadcasting using degenerate dimensions can both be performed in the same binary operation. -For example, a vector of size 4 and an matrix of size 1x2 can be added together +For example, a vector of size 4 and a matrix of size 1x2 can be added together using broadcast dimensions value of (0): |1 2 3 4| + [5 6] // [5 6] is a 1x2 matrix, not a vector. @@ -176,7 +176,7 @@ using broadcast dimensions value of (0): First the vector is broadcast up to rank 2 (matrix) using the broadcast dimensions. The single value (0) in the broadcast dimensions indicates that dimension zero of the vector matches to dimension zero of the matrix. This -produces an matrix of size 4xM where the value M is chosen to match the +produces a matrix of size 4xM where the value M is chosen to match the corresponding dimension size in the 1x2 array. Therefore, a 4x2 matrix is produced: -- GitLab From ca2d0b652e1bc8e6d637318a1fbcb56bd4e6f0d0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 17 Jan 2019 01:03:09 -0800 Subject: [PATCH 0845/2345] compat: Update forward compatibility horizon to 2019-01-17 PiperOrigin-RevId: 229698933 --- 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 773b3a41f9..c558722061 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, 1, 16) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 1, 17) @tf_export("compat.forward_compatible") -- GitLab From a286bc2819f4b3f3f42dcfe3fb8969979d6eaea3 Mon Sep 17 00:00:00 2001 From: Miguel Morin <32396311+miguelmorin@users.noreply.github.com> Date: Thu, 17 Jan 2019 11:45:25 +0000 Subject: [PATCH 0846/2345] Add details for running tests from nightly build images. Closes #24993. The current documentation guidelines throw an error after running the commands in the tensorflow nightly build container. Following this Magenta issue: https://github.com/tensorflow/magenta/issues/1232 running the test requires changing directory. Rather than changing the WORKDIR in the image, which would probably affect many uses and users, this change adds the direction of changing directory after mentioning the nightly build image. --- CONTRIBUTING.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a296f265f..4bd9c77d29 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -158,7 +158,10 @@ There are two ways to run TensorFlow unit tests. for the required packages. Alternatively, use the said [Docker images](https://hub.docker.com/r/tensorflow/tensorflow/tags/), e.g., `tensorflow/tensorflow:nightly-devel` and `tensorflow/tensorflow:nightly-devel-gpu` - for development to avoid installing the packages directly on your system. + for development to avoid installing the packages directly on your system (in + which case remember to change directory from `/root` to `/tensorflow` once + you get into the running container so `bazel` can find the `tensorflow` + workspace). Once you have the packages installed, you can run a specific unit test in bazel by doing as follows: -- GitLab From 0b537e5b7d4eca61b058d2415f8f93b253506a1a Mon Sep 17 00:00:00 2001 From: Grzegorz Pawelczak Date: Thu, 17 Jan 2019 12:20:35 +0000 Subject: [PATCH 0847/2345] Don't dump the whole literal into VLOG(1) --- tensorflow/compiler/jit/xla_device_context.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/jit/xla_device_context.cc b/tensorflow/compiler/jit/xla_device_context.cc index 1f3afe8822..28681bb8b0 100644 --- a/tensorflow/compiler/jit/xla_device_context.cc +++ b/tensorflow/compiler/jit/xla_device_context.cc @@ -131,7 +131,7 @@ void XlaDeviceContext::CopyCPUTensorToDevice(const Tensor* cpu_tensor, xla::ShapeUtil::MakeShape(shape.element_type(), xla::AsInt64Slice(shape.dimensions()))); - VLOG(1) << "Transfer to device as literal: " << literal.ToString() << " " + VLOG(2) << "Transfer to device as literal: " << literal.ToString() << " " << xla_tensor->shaped_buffer().ToString(); if (UseMultipleStreams() && !transfer_manager_->CanShapedBufferBeAccessedNow( @@ -214,7 +214,7 @@ void XlaDeviceContext::CopyDeviceTensorToCPU(const Tensor* device_tensor, device_to_host_stream_.get(), xla_tensor->shaped_buffer(), literal, [ref, xla_tensor, done](xla::Status status) { done([&]() -> Status { - VLOG(1) << "Transfer from device as literal: " + VLOG(2) << "Transfer from device as literal: " << xla_tensor->shaped_buffer().ToString(); return status; }()); -- GitLab From 9331096d56002c7fcb8bae411684b8b78fc196c4 Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Thu, 17 Jan 2019 06:29:06 -0800 Subject: [PATCH 0848/2345] Experimental implementation of tf.Module. PiperOrigin-RevId: 229735738 --- tensorflow/python/BUILD | 1 + tensorflow/python/__init__.py | 1 + tensorflow/python/module/BUILD | 30 ++ tensorflow/python/module/module.py | 402 ++++++++++++++++++ tensorflow/python/module/module_test.py | 355 ++++++++++++++++ .../v1/tensorflow.experimental.-module.pbtxt | 47 ++ .../golden/v1/tensorflow.experimental.pbtxt | 4 + .../v2/tensorflow.experimental.-module.pbtxt | 47 ++ .../golden/v2/tensorflow.experimental.pbtxt | 4 + 9 files changed, 891 insertions(+) create mode 100644 tensorflow/python/module/BUILD create mode 100644 tensorflow/python/module/module.py create mode 100644 tensorflow/python/module/module_test.py create mode 100644 tensorflow/tools/api/golden/v1/tensorflow.experimental.-module.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.experimental.-module.pbtxt diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index e32be0039f..b61c33f319 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -178,6 +178,7 @@ py_library( "//tensorflow/python/eager:def_function", "//tensorflow/python/eager:profiler", "//tensorflow/python/eager:remote", + "//tensorflow/python/module", "//tensorflow/python/ops/distributions", "//tensorflow/python/ops/linalg", "//tensorflow/python/ops/losses", diff --git a/tensorflow/python/__init__.py b/tensorflow/python/__init__.py index 5172dbb836..398fb375e1 100644 --- a/tensorflow/python/__init__.py +++ b/tensorflow/python/__init__.py @@ -82,6 +82,7 @@ from tensorflow.python import distribute from tensorflow.python import keras from tensorflow.python.feature_column import feature_column_lib as feature_column from tensorflow.python.layers import layers +from tensorflow.python.module import module from tensorflow.python.ops import bitwise_ops as bitwise from tensorflow.python.ops import image_ops as image from tensorflow.python.ops import manip_ops as manip diff --git a/tensorflow/python/module/BUILD b/tensorflow/python/module/BUILD new file mode 100644 index 0000000000..64c226e4c9 --- /dev/null +++ b/tensorflow/python/module/BUILD @@ -0,0 +1,30 @@ +package(default_visibility = ["//tensorflow:internal"]) + +load("//tensorflow:tensorflow.bzl", "tf_py_test") + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +py_library( + name = "module", + srcs = ["module.py"], + deps = [ + "//tensorflow/python:framework_ops", + "//tensorflow/python:util", + "//tensorflow/python:variables", + "//tensorflow/python/training/checkpointable:tracking", + "@six_archive//:six", + ], +) + +tf_py_test( + name = "module_test", + srcs = ["module_test.py"], + additional_deps = [ + ":module", + "//tensorflow/python:client_testlib", + "//tensorflow/python/compat:v2_compat", + "//tensorflow/python:variables", + ], +) diff --git a/tensorflow/python/module/module.py b/tensorflow/python/module/module.py new file mode 100644 index 0000000000..16db0b0e01 --- /dev/null +++ b/tensorflow/python/module/module.py @@ -0,0 +1,402 @@ +# 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. +# ============================================================================== +"""Modules encapsulate building stateful components.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import re +import sys + +import six + +from tensorflow.python.framework import ops +from tensorflow.python.ops import variables +from tensorflow.python.training.checkpointable 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 + + +class ModuleMetaclass(type): + """Metaclass for `tf.Module`.""" + + def __new__(mcs, name, bases, clsdict): + for key, value in clsdict.items(): + if key in ("__init__", "name_scope"): + continue + + elif tf_inspect.isfunction(value): + if getattr(value, "_no_module_name_scope", False): + # The function has been annotated to say that no autoscoping should + # be applied, so do not patch it. + continue + clsdict[key] = with_name_scope(value) + + elif isinstance(value, property): + 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__) + + return type.__new__(mcs, name, bases, clsdict) + + 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 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 tf_decorator.make_decorator(unbound_method, enter_name_scope) + + +@tf_export("experimental.Module") +class Module(six.with_metaclass(ModuleMetaclass, tracking.AutoCheckpointable)): + """Base neural network module class. + + A module is a named container for `tf.Variable`s, other `tf.Module`s and + functions which apply to user input. For example a dense layer in a neural + network might be implemented as a `tf.Module`: + + >>> class Dense(tf.Module): + ... def __init__(self, in_features, output_features): + ... super(Linear, self).__init__() + ... 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: + + >>> 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: + + >>> d.variables + (, ) + """ + + def __init__(self, name=None): + if name is None: + name = camel_to_snake(type(self).__name__) + else: + if not valid_identifier(name): + raise ValueError( + "%r is not a valid module name. Module names must be valid Python " + "identifiers (e.g. a valid class name)." % name) + + self._name = name + 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. + + NOTE: This is not the same as the `self.name_scope.name` which includes + parent module names. + """ + return self._name + + @property + def name_scope(self): + """Returns a `tf.name_scope` instance for this class.""" + # TODO(tomhennigan) Memoize once name scopes are re-entrant. + return ops.name_scope(self._scope_name) + + @property + def variables(self): + """Returns a tuple of variables owned by this module and it's submodules. + + Returns: + A tuple of variables for the current module (sorted by attribute name) + followed by variables from all submodules recursively (depth first). + """ + return tuple(walk(self, recurse_if=_IS_MODULE, predicate=_IS_VARIABLE)) + + @property + def owned_variables(self): + """Returns a tuple of variables that are attributes of the current module. + + See `variables` for a property which returns all variables from the current + module and all it's submodules recursively. + + Returns: + A tuple of variables which are attributes of the current module. Will + yield variables inside nested structures (lists etc) but not in other + modules. + """ + return tuple(walk(self, predicate=_IS_VARIABLE)) + + @property + def trainable_variables(self): + """Returns a tuple of variables owned by this module and it's submodules. + + Returns: + A tuple of variables for the current module (sorted by attribute name) + followed by variables from all submodules recursively (depth first). + """ + return tuple( + walk(self, recurse_if=_IS_MODULE, predicate=_IS_TRAINABLE_VARIABLE)) + + @property + def owned_trainable_variables(self): + """Returns a tuple of variables that are attributes of the current module. + + See `variables` for a property which returns all variables from the current + module and all it's submodules recursively. + + Returns: + A tuple of variables which are attributes of the current module. Will + yield variables inside nested structures (lists etc) but not in other + modules. + """ + return tuple(walk(self, predicate=_IS_TRAINABLE_VARIABLE)) + + @property + def owned_submodules(self): + """Returns an iterator of immediate child modules. + + Child modules are modules which are found as properties of the current + module. + + >>> a = tf.experimental.Module() + >>> b = tf.experimental.Module() + >>> c = tf.experimental.Module() + >>> a.b = b + >>> b.c = c + >>> assert list(a.owned_submodules) == [b] + >>> assert list(b.owned_submodules) == [c] + >>> assert list(c.owned_submodules) == [] + + Returns: + A generator over all child modules. + """ + return walk(self, predicate=_IS_MODULE) + + @property + def submodules(self): + """Returns an iterator of all sub-modules recursively. + + Submodules are modules which are properties of this module, or found as + properties of modules which are properties of this module (and so on). + + >>> a = tf.experimental.Module() + >>> b = tf.experimental.Module() + >>> c = tf.experimental.Module() + >>> a.b = b + >>> b.c = c + >>> assert list(a.submodules) == [b, c] + >>> assert list(b.submodules) == [c] + >>> assert list(c.submodules) == [] + + Returns: + A generator over all submodules. + """ + return walk(self, recurse_if=_IS_MODULE, predicate=_IS_MODULE) + + @classmethod + def no_name_scope(cls, method): + """Decorator to wrap a method, preventing automatic name scope wrapping. + + 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`. + + This logic is tied to ModuleMetaclass.__new__, if anything is + changed here corresponding changes will be needed there. + + Args: + method: the method to wrap. + + Returns: + The method, with a flag indicating no name scope wrapping should occur. + """ + setattr(method, "_no_module_name_scope", True) + return method + +_IS_VARIABLE = lambda o: isinstance(o, variables.Variable) +_IS_TRAINABLE_VARIABLE = lambda o: (_IS_VARIABLE(o) and o.trainable) +_IS_MODULE = lambda o: isinstance(o, Module) +_CAMEL_TO_SNAKE_R = re.compile(r"((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))") +_VALID_IDENTIFIER = re.compile(r"^[a-zA-Z_]([a-zA-Z0-9_])*$") + + +def valid_identifier(name): + return bool(_VALID_IDENTIFIER.match(name)) + + +def camel_to_snake(value): + return _CAMEL_TO_SNAKE_R.sub(r"_\1", value).lower() + + +def walk(o, recurse_if=None, predicate=None): + """Flattened attributes of `o` in sorted order by attribute name. + + >>> class Foo(object): + ... def __init__(self, prefix=''): + ... self.z = prefix + 'c' + ... self.a = [prefix + 'a', prefix + 'b'] + + >>> tuple(walk(Foo())) + ('a', 'b', 'c') + + If `predicate` is not None, then only values matching predicate are returned: + + >>> tuple(walk(Foo(), predicate=lambda v: v != 'a')) + ('b', 'c') + + If `recurse_if` is not None then it should be a callable which tests if the + given leaf should be expanded: + + >>> is_string = lambda v: isinstance(v, str) + >>> is_foo = lambda l: isinstance(l, Foo) + >>> o = Foo(prefix='root_') + >>> o.b = Foo(prefix='child_') + >>> tuple(walk(o, predicate=is_string)) + ('root_a', 'root_b', 'root_c') + >>> tuple(walk(o, recurse_if=is_foo, predicate=is_string)) + ('root_a', 'root_b', 'root_c', 'child_a', 'child_b', 'child_c') + + Args: + o: An object who's attributes are walked. + recurse_if: (Optional) Visited items of this type will be walked to extract + more leaves. If `None`, it will not recurse into leaves. + predicate: (Optional) If set then only values matching predicate are + yielded. + + Returns: + Attributes of `o` in name order. If `recurse_if` is not `None` then + attributes for which `recurse_if(attribute) == True` will be walked + recursively. If `predicate` is not `None` then only attributes for which + `predicate(attribute) == True` will be yielded. + """ + if predicate is None: + predicate = lambda _: True + return _walk_internal( + o, recurse_if=recurse_if, predicate=predicate, seen=set()) + + +def _walk_internal(o, recurse_if, predicate, seen): + """Implementation of `walk`.""" + if seen is None: + seen = set([id(o)]) + + o_dict = vars(o) + to_walk = [] + + for key in sorted(o_dict): + values = nest.flatten(o_dict[key]) + for value in values: + value_id = id(value) + if value_id in seen: + continue + + seen.add(value_id) + if predicate(value): + yield value + + if recurse_if is not None and recurse_if(value): + # Walk direct properties first then recurse. + to_walk.append(value) + + for value in to_walk: + for subvalue in _walk_internal(value, recurse_if, predicate, seen): + # Predicate is already tested for these values. + yield subvalue diff --git a/tensorflow/python/module/module_test.py b/tensorflow/python/module/module_test.py new file mode 100644 index 0000000000..e7c736696e --- /dev/null +++ b/tensorflow/python/module/module_test.py @@ -0,0 +1,355 @@ +# 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.Module`.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.compat import v2_compat +from tensorflow.python.framework import ops +from tensorflow.python.module import module +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + + +class TestModuleNaming(test.TestCase): + + def test_single_name(self): + mod = module.Module(name="simple") + self.assertEqual(mod.name, "simple") + self.assertEqual(mod.name_scope.name, "simple/") + + def test_construct_in_scope(self): + with ops.name_scope("foo"): + mod = module.Module(name="bar") + self.assertEqual(mod.name, "bar") + self.assertEqual(mod.name_scope.name, "foo/bar/") + + def test_enters_name_scope_in_call(self): + mod = ReturnsNameScopeModule() + for _ in range(3): + self.assertEqual(mod(), mod.name_scope.name) + + def test_enters_name_scope_in_other_method(self): + mod = ReturnsNameScopeModule() + for _ in range(3): + self.assertEqual(mod.alternative_forward(), mod.name_scope.name) + + def test_subclassed_module(self): + mod = SubclassedReturnsNameScopeModule() + for _ in range(3): + self.assertEqual(mod.alternative_forward(), mod.name_scope.name) + self.assertEqual(mod.alternative_alternative_forward(), + mod.name_scope.name) + + def test_submodule_created_late(self): + m = TreeModule() + self.assertEqual(m.name, "tree_module") + self.assertEqual(m.name_scope.name, "tree_module/") + leaf1 = m.new_leaf() + self.assertEqual(leaf1.name, "tree_module") + self.assertEqual(leaf1.name_scope.name, "tree_module/tree_module/") + + def test_does_not_evaluate_property_methods(self): + mod = PropertyThrowsWhenCalledModule() + with self.assertRaises(AssertionError): + mod.raise_assertion_error # pylint: disable=pointless-statement + + def test_overridden_name_scope(self): + mod = ModuleOverridingNameScope() + self.assertEqual(mod(), mod.name_scope.name) + self.assertEqual(mod.alternative_forward(), mod.name_scope.name) + + def test_patched_callable(self): + with ops.name_scope("foo"): + mod = module.Module(name="bar") + mod.foo = get_name_scope + # `foo` is not a method so we do not re-enter the name scope. + self.assertEqual(mod.foo(), "") + + def test_invalid_name(self): + msg = ".* is not a valid module name" + with self.assertRaisesRegexp(ValueError, msg): + module.Module(name="$Foo") + + def test_modules_not_numbered_in_eager(self): + mod = RecursiveModule(2) + self.assertEqual(mod.name_scope.name, "badger/") + self.assertEqual(mod.child.name_scope.name, "badger/badger/") + + mod = RecursiveModule(2) + self.assertEqual(mod.name_scope.name, "badger/") + self.assertEqual(mod.child.name_scope.name, "badger/badger/") + + def test_module_numbering_in_graph(self): + with ops.Graph().as_default(): + mod = RecursiveModule(2) + self.assertEqual(mod.name_scope.name, "badger/") + self.assertEqual(mod.child.name_scope.name, "badger/badger/") + + mod = RecursiveModule(2) + self.assertEqual(mod.name_scope.name, "badger_1/") + self.assertEqual(mod.child.name_scope.name, "badger_1/badger/") + + def test_ctor_error_closes_name_scope(self): + with self.assertRaises(ErrorModuleError): + # If super constructor is called then a name scope is opened then an error + # is thrown. The metaclass should handle this and close the namescope + # before re-throwing the exception. + ErrorModule(call_super=True) + + self.assertEqual("", get_name_scope()) + + def test_ctor_error_handles_ctor_not_opening_name_scope(self): + with self.assertRaises(ErrorModuleError): + # If super ctor is not called then the name scope isn't opened. We need to + # ensure that this doesn't trigger an exception (e.g. the metaclass trying + # to __exit__ a non-existant name scope). + ErrorModule(call_super=False) + + self.assertEqual("", get_name_scope()) + + def test_forward_method_closes_name_scope(self): + mod = ErrorModule(call_super=True, raise_in_constructor=False) + with self.assertRaises(ErrorModuleError): + mod() + + self.assertEqual("", get_name_scope()) + + +class VariableNamingTest(test.TestCase): + + def test_variable_names(self): + mod = RecursiveModule(3) + self.assertEqual(mod.w.name, "badger/mushroom:0") + self.assertEqual(mod.child.w.name, "badger/badger/mushroom:0") + self.assertEqual(mod.child.child.w.name, "badger/badger/badger/mushroom:0") + + +class VariableTrackingTest(test.TestCase): + + def test_variables(self): + m = RecursiveModule(3) + self.assertEqual(m.variables, (m.w, m.child.w, m.child.child.w)) + self.assertEqual(m.child.variables, (m.child.w, m.child.child.w)) + self.assertEqual(m.child.child.variables, (m.child.child.w,)) + + def test_owned_variables(self): + m = RecursiveModule(3) + self.assertEqual(m.owned_variables, (m.w,)) + self.assertEqual(m.child.owned_variables, (m.child.w,)) + self.assertEqual(m.child.child.owned_variables, (m.child.child.w,)) + + def test_trainable_variables(self): + m = RecursiveModule(3) + self.assertEqual(m.trainable_variables, + (m.w, m.child.w, m.child.child.w)) + self.assertEqual(m.child.trainable_variables, + (m.child.w, m.child.child.w)) + self.assertEqual(m.child.child.trainable_variables, (m.child.child.w,)) + + def test_trainable_variables_ignores_non_trainable(self): + m = RecursiveModule(3, trainable=False) + self.assertEqual(len(m.trainable_variables), 0) + self.assertEqual(len(m.child.trainable_variables), 0) + self.assertEqual(len(m.child.child.trainable_variables), 0) + + def test_owned_trainable_variables(self): + m = RecursiveModule(3) + self.assertEqual(m.owned_trainable_variables, (m.w,)) + self.assertEqual(m.child.owned_trainable_variables, (m.child.w,)) + self.assertEqual(m.child.child.owned_trainable_variables, + (m.child.child.w,)) + + def test_owned_trainable_variables_ignores_non_trainable(self): + m = RecursiveModule(3, trainable=False) + self.assertEqual(len(m.owned_trainable_variables), 0) + self.assertEqual(len(m.child.owned_trainable_variables), 0) + self.assertEqual(len(m.child.child.owned_trainable_variables), 0) + + +class ModuleTrackingTest(test.TestCase): + + def test_owned_submodules(self): + m = RecursiveModule(3) + self.assertEqual(list(m.owned_submodules), [m.child]) + self.assertEqual(list(m.child.owned_submodules), [m.child.child]) + self.assertEqual(list(m.child.child.owned_submodules), []) + + def test_submodules(self): + m = RecursiveModule(3) + self.assertEqual(list(m.submodules), [m.child, m.child.child]) + self.assertEqual(list(m.child.submodules), [m.child.child]) + self.assertEqual(list(m.child.child.submodules), []) + + def test_non_ctor_submodule(self): + m = TreeModule() + leaf1 = m.new_leaf() + self.assertEqual(set(m.submodules), {leaf1}) + leaf2 = m.new_leaf() + 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)) + + +def get_name_scope(): + with ops.name_scope("x") as ns: + return ns[:-2] + + +class ErrorModuleError(Exception): + pass + + +class ErrorModule(module.Module): + + def __init__(self, call_super, raise_in_constructor=True): + if call_super: + super(ErrorModule, self).__init__() + if raise_in_constructor: + raise ErrorModuleError("Deliberate error!") + + def __call__(self): + raise ErrorModuleError("Deliberate error!") + + +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") + + +class TreeModule(module.Module): + + def __init__(self, name=None): + super(TreeModule, self).__init__(name=name) + self._leaves = [] + + def new_leaf(self, name=None): + leaf = TreeModule(name=name) + self._leaves.append(leaf) + return leaf + + +class ReturnsNameScopeModule(module.Module): + + def alternative_forward(self): + return get_name_scope() + + def __call__(self): + return get_name_scope() + + +class SubclassedReturnsNameScopeModule(ReturnsNameScopeModule): + + def alternative_alternative_forward(self): + return get_name_scope() + + +class PropertyThrowsWhenCalledModule(module.Module): + + @property + def raise_assertion_error(self): + raise AssertionError + + +class ModuleOverridingNameScope(ReturnsNameScopeModule): + + @property + def name_scope(self): + 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 WalkTest(test.TestCase): + + def test_walk(self): + parent = SimpleModule() + child = parent.c + + self.assertEqual( + list(module.walk(parent, predicate=IS_MEMBER)), + [parent.a[0], parent.a[1], parent.z]) + + self.assertEqual( + list(module.walk(parent, recurse_if=IS_MODULE, predicate=IS_MEMBER)), + [parent.a[0], parent.a[1], parent.z, child.a[0], child.a[1], child.z]) + + +class MemberType(object): + """A simple type to search for.""" + pass + + +class SimpleModule(module.Module): + + def __init__(self, create_child=True): + super(SimpleModule, self).__init__() + self.z = MemberType() + self.a = [MemberType(), MemberType()] + if create_child: + self.c = SimpleModule(create_child=False) + + +IS_MEMBER = lambda v: isinstance(v, MemberType) +IS_MODULE = lambda v: isinstance(v, module.Module) + +if __name__ == "__main__": + v2_compat.enable_v2_behavior() + test.main() diff --git a/tensorflow/tools/api/golden/v1/tensorflow.experimental.-module.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.experimental.-module.pbtxt new file mode 100644 index 0000000000..c364b0217a --- /dev/null +++ b/tensorflow/tools/api/golden/v1/tensorflow.experimental.-module.pbtxt @@ -0,0 +1,47 @@ +path: "tensorflow.experimental.Module" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "name" + mtype: "" + } + member { + name: "name_scope" + mtype: "" + } + member { + name: "owned_submodules" + mtype: "" + } + member { + name: "owned_trainable_variables" + mtype: "" + } + member { + name: "owned_variables" + mtype: "" + } + member { + name: "submodules" + mtype: "" + } + member { + name: "trainable_variables" + mtype: "" + } + member { + name: "variables" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "no_name_scope" + argspec: "args=[\'cls\', \'method\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v1/tensorflow.experimental.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.experimental.pbtxt index 0c3f04e468..a7ee6d3e07 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.experimental.pbtxt @@ -1,5 +1,9 @@ path: "tensorflow.experimental" tf_module { + member { + name: "Module" + mtype: "" + } member_method { name: "function_executor_type" argspec: "args=[\'executor_type\'], varargs=None, keywords=None, defaults=None" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.experimental.-module.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.experimental.-module.pbtxt new file mode 100644 index 0000000000..c364b0217a --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.experimental.-module.pbtxt @@ -0,0 +1,47 @@ +path: "tensorflow.experimental.Module" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "name" + mtype: "" + } + member { + name: "name_scope" + mtype: "" + } + member { + name: "owned_submodules" + mtype: "" + } + member { + name: "owned_trainable_variables" + mtype: "" + } + member { + name: "owned_variables" + mtype: "" + } + member { + name: "submodules" + mtype: "" + } + member { + name: "trainable_variables" + mtype: "" + } + member { + name: "variables" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "no_name_scope" + argspec: "args=[\'cls\', \'method\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.experimental.pbtxt index 0c3f04e468..a7ee6d3e07 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.experimental.pbtxt @@ -1,5 +1,9 @@ path: "tensorflow.experimental" tf_module { + member { + name: "Module" + mtype: "" + } member_method { name: "function_executor_type" argspec: "args=[\'executor_type\'], varargs=None, keywords=None, defaults=None" -- GitLab From 31c1c097d8ee260ab1411011f5276b326cfb2ace Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 17 Jan 2019 07:36:10 -0800 Subject: [PATCH 0849/2345] Remove 19 uneeded C++ BUILD Dependencies PiperOrigin-RevId: 229744406 --- tensorflow/compiler/xla/service/BUILD | 10 ---------- tensorflow/compiler/xla/service/cpu/BUILD | 2 -- tensorflow/compiler/xla/service/gpu/BUILD | 4 ---- tensorflow/compiler/xla/service/interpreter/BUILD | 2 -- tensorflow/compiler/xla/service/llvm_ir/BUILD | 1 - 5 files changed, 19 deletions(-) diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index aef3c37159..e39e17c110 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -1062,7 +1062,6 @@ cc_library( "//tensorflow/compiler/xla:types", "//tensorflow/compiler/xla:util", "//tensorflow/core:lib", - "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", @@ -1100,7 +1099,6 @@ cc_library( ":buffer_value_containers", ":heap_simulator", ":hlo", - ":hlo_memory_scheduler", ":hlo_proto", ":logical_buffer", ":tuple_points_to_analysis", @@ -1240,7 +1238,6 @@ cc_library( deps = [ ":hlo", ":hlo_proto", - "//tensorflow/compiler/xla:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], @@ -1508,7 +1505,6 @@ cc_library( "//tensorflow/compiler/xla:util", "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/core:lib", - "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/types:optional", "@com_google_absl//absl/types:span", ], @@ -2069,7 +2065,6 @@ cc_library( "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:status_macros", - "//tensorflow/compiler/xla:statusor", "//tensorflow/compiler/xla:types", "//tensorflow/compiler/xla:util", "//tensorflow/compiler/xla:xla_data_proto", @@ -3030,10 +3025,7 @@ cc_library( ":hlo", ":hlo_pass", ":shape_inference", - "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:literal_util", - "//tensorflow/compiler/xla:shape_util", - "//tensorflow/compiler/xla:types", "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/core:lib", "@com_google_absl//absl/algorithm:container", @@ -3405,7 +3397,6 @@ cc_library( ":hlo_pass_pipeline", "//tensorflow/compiler/xla:shape_util", "//tensorflow/core:lib", - "@com_google_absl//absl/container:flat_hash_map", ], ) @@ -3593,7 +3584,6 @@ cc_library( "//tensorflow/compiler/xla:util", "//tensorflow/core:lib", "@com_google_absl//absl/algorithm:container", - "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:inlined_vector", ], ) diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index a7feab5a0a..d4535b204d 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -790,8 +790,6 @@ cc_library( ":target_machine_features", "//tensorflow/compiler/xla:util", "//tensorflow/compiler/xla/service:computation_layout", - "//tensorflow/compiler/xla/service:hlo", - "//tensorflow/compiler/xla/service:hlo_casting_utils", "//tensorflow/compiler/xla/service:layout_assignment", "//tensorflow/core:lib", "@com_google_absl//absl/container:flat_hash_map", diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index 1caf753c45..dc17aa4426 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -1013,14 +1013,10 @@ cc_library( srcs = ["variadic_op_splitter.cc"], hdrs = ["variadic_op_splitter.h"], deps = [ - ":ir_emission_utils", - "//tensorflow/compiler/xla:literal_util", "//tensorflow/compiler/xla:statusor", "//tensorflow/compiler/xla:util", - "//tensorflow/compiler/xla:window_util", "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/compiler/xla/service:hlo", - "//tensorflow/compiler/xla/service:hlo_casting_utils", "//tensorflow/compiler/xla/service:hlo_pass", "//tensorflow/core:lib", "@com_google_absl//absl/strings", diff --git a/tensorflow/compiler/xla/service/interpreter/BUILD b/tensorflow/compiler/xla/service/interpreter/BUILD index 9b4cafa4b4..545662543c 100644 --- a/tensorflow/compiler/xla/service/interpreter/BUILD +++ b/tensorflow/compiler/xla/service/interpreter/BUILD @@ -32,7 +32,6 @@ cc_library( "//tensorflow/compiler/xla:status_macros", "//tensorflow/compiler/xla:statusor", "//tensorflow/compiler/xla/service:algebraic_simplifier", - "//tensorflow/compiler/xla/service:batchnorm_expander", "//tensorflow/compiler/xla/service:compiler", "//tensorflow/compiler/xla/service:computation_placer", "//tensorflow/compiler/xla/service:dynamic_index_splitter", @@ -43,7 +42,6 @@ cc_library( "//tensorflow/compiler/xla/service:hlo_cost_analysis", "//tensorflow/compiler/xla/service:hlo_cse", "//tensorflow/compiler/xla/service:hlo_dce", - "//tensorflow/compiler/xla/service:hlo_get_dimension_size_rewriter", "//tensorflow/compiler/xla/service:hlo_module_config", "//tensorflow/compiler/xla/service:hlo_pass", "//tensorflow/compiler/xla/service:hlo_pass_pipeline", diff --git a/tensorflow/compiler/xla/service/llvm_ir/BUILD b/tensorflow/compiler/xla/service/llvm_ir/BUILD index 54e8fe1947..c5d59fb28e 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/BUILD +++ b/tensorflow/compiler/xla/service/llvm_ir/BUILD @@ -39,7 +39,6 @@ cc_library( "//tensorflow/compiler/xla/service:logical_buffer", "//tensorflow/core:lib", "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", "@llvm//:core", ], -- GitLab From cc6f6fb453f1cb864643452ff937f9f4606bda32 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Thu, 17 Jan 2019 07:40:48 -0800 Subject: [PATCH 0850/2345] Dump out an annotated TF graph from mark_for_compilation_pass for better visualization PiperOrigin-RevId: 229745040 --- .../compiler/jit/mark_for_compilation_pass.cc | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tensorflow/compiler/jit/mark_for_compilation_pass.cc b/tensorflow/compiler/jit/mark_for_compilation_pass.cc index 50afec020d..cb01690845 100644 --- a/tensorflow/compiler/jit/mark_for_compilation_pass.cc +++ b/tensorflow/compiler/jit/mark_for_compilation_pass.cc @@ -41,6 +41,7 @@ limitations under the License. #include "tensorflow/core/framework/types.h" #include "tensorflow/core/graph/algorithm.h" #include "tensorflow/core/graph/control_flow.h" +#include "tensorflow/core/graph/graph_constructor.h" #include "tensorflow/core/kernels/bounds_check.h" #include "tensorflow/core/lib/gtl/cleanup.h" #include "tensorflow/core/lib/strings/stringprintf.h" @@ -1150,6 +1151,27 @@ Status MarkForCompilationPass::RunImpl( if (flags->tf_xla_clustering_debug) { dump_graph::DumpGraphToFile("mark_for_compilation", **options.graph, options.flib_def); + + // We also dump out an annoated version of the TF graph where the nodes + // names are prefixed with the cluster names. This can help visualizing the + // clustering decisions on TensorBoard. + Graph new_graph((*options.graph)->op_registry()); + CopyGraph(**options.graph, &new_graph); + + for (Node* n : new_graph.nodes()) { + if (absl::optional cluster_name = + GetXlaClusterForNode(*n)) { + n->set_name(absl::StrCat(*cluster_name, "/", n->name())); + } else { + // There is room for improvement here. In particular, it may help to + // split these unclustered nodes into classes where every node in a + // specific class has edges to and from the same set of clusters. + n->set_name(absl::StrCat("unclustered/", n->name())); + } + } + + dump_graph::DumpGraphToFile("mark_for_compilation_annotated", new_graph, + options.flib_def); } VLogClusteringSummary(*graph); -- GitLab From 202525cd4bbab377e9ee67f1e4098e40b0925d9c Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Thu, 17 Jan 2019 07:43:30 -0800 Subject: [PATCH 0851/2345] Blacklist certain failing NNAPI zip tests PiperOrigin-RevId: 229745363 --- .../testing/generated_examples_zip_test.cc | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/testing/generated_examples_zip_test.cc b/tensorflow/lite/testing/generated_examples_zip_test.cc index a9a31ad088..45bd59a67d 100644 --- a/tensorflow/lite/testing/generated_examples_zip_test.cc +++ b/tensorflow/lite/testing/generated_examples_zip_test.cc @@ -107,6 +107,28 @@ std::map kBrokenTests = { {R"(^\/strided_slice_buggy)", "119786029"}, }; +// Additional list of tests that are expected to fail when +// --test_arg=--ignore_known_bugs=false +// and +// --test_arg=--use_nnapi=true +// Note that issues related to lack of NNAPI support for a particular op are +// handled separately; this list is specifically for broken cases where +// execution produces broken output. +// Key is a substring of the test name and value is a bug number. +std::map kBrokenNnapiTests = { + // Certain NNAPI kernels silently fail with int32 types. + {R"(^\/add.*dtype=tf\.int32)", "122987564"}, + {R"(^\/concat.*dtype=tf\.int32)", "122987564"}, + {R"(^\/mul.*dtype=tf\.int32)", "122987564"}, + {R"(^\/space_to_depth.*dtype=tf\.int32)", "122987564"}, + + // Certain NNAPI fully_connected shape permutations fail. + {R"(^\/fully_connected_constant_filter=True.*shape1=\[3,3\])", "122987564"}, + {R"(^\/fully_connected_constant_filter=True.*shape1=\[4,4\])", "122987564"}, + {R"(^\/fully_connected.*shape1=\[3,3\].*transpose_b=True)", "122987564"}, + {R"(^\/fully_connected.*shape1=\[4,4\].*shape2=\[4,1\])", "122987564"}, +}; + // Allows test data to be unarchived into a temporary directory and makes // sure those temporary directories are removed later. class ArchiveEnvironment : public ::testing::Environment { @@ -242,8 +264,13 @@ TEST_P(OpsTest, RunZipTests) { tflite::testing::TfLiteDriver test_driver(FLAGS_use_nnapi); test_driver.SetModelBaseDir(tflite_dir); + auto broken_tests = kBrokenTests; + if (FLAGS_use_nnapi) { + broken_tests.insert(kBrokenNnapiTests.begin(), kBrokenNnapiTests.end()); + } + string bug_number; - for (const auto& p : kBrokenTests) { + for (const auto& p : broken_tests) { if (RE2::PartialMatch(test_name, p.first)) { bug_number = p.second; } -- GitLab From f01d357ca3f08d7872dc1059e1a13f7fb4515ef2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 17 Jan 2019 08:34:02 -0800 Subject: [PATCH 0852/2345] Switch from deprecated strategy.distribute_dataset() to strategy.make_input_fn_iterator() in contrib/distribute tests. PiperOrigin-RevId: 229752452 --- .../distribute/python/metrics_v1_test.py | 5 ++--- .../distribute/python/minimize_loss_test.py | 21 ++++++++----------- .../python/mirrored_strategy_multigpu_test.py | 11 +++------- .../contrib/distribute/python/monitor.py | 2 +- .../distribute/python/optimizer_v2_test.py | 9 ++------ .../contrib/distribute/python/step_fn.py | 14 ++++++------- .../contrib/distribute/python/step_fn_test.py | 3 ++- 7 files changed, 26 insertions(+), 39 deletions(-) diff --git a/tensorflow/contrib/distribute/python/metrics_v1_test.py b/tensorflow/contrib/distribute/python/metrics_v1_test.py index 96006276f6..a663e809dd 100644 --- a/tensorflow/contrib/distribute/python/metrics_v1_test.py +++ b/tensorflow/contrib/distribute/python/metrics_v1_test.py @@ -95,8 +95,7 @@ class MetricsV1Test(test.TestCase, parameterized.TestCase): def _test_metric(self, distribution, dataset_fn, metric_fn, expected_fn): with ops.Graph().as_default(), distribution.scope(): - iterator = distribution.distribute_dataset( - dataset_fn).make_initializable_iterator() + iterator = distribution.make_input_fn_iterator(lambda _: dataset_fn()) if isinstance(distribution, tpu_strategy.TPUStrategy): def step_fn(ctx, inputs): value, update = distribution.extended.call_for_each_replica( @@ -121,7 +120,7 @@ class MetricsV1Test(test.TestCase, parameterized.TestCase): # replace "distribution.num_replicas_in_sync" with "1". batches_per_update = distribution.num_replicas_in_sync - self.evaluate(iterator.initializer) + self.evaluate(iterator.initialize()) self.evaluate(variables.local_variables_initializer()) batches_consumed = 0 diff --git a/tensorflow/contrib/distribute/python/minimize_loss_test.py b/tensorflow/contrib/distribute/python/minimize_loss_test.py index 37e6b418a0..f06c9b7564 100644 --- a/tensorflow/contrib/distribute/python/minimize_loss_test.py +++ b/tensorflow/contrib/distribute/python/minimize_loss_test.py @@ -41,12 +41,9 @@ from tensorflow.python.ops.losses import losses_impl class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): - def _get_iterator(self, ds): - if context.executing_eagerly(): - iterator = ds.make_one_shot_iterator() - else: - iterator = ds.make_initializable_iterator() - self.evaluate(iterator.initializer) + def _get_iterator(self, strategy, input_fn): + iterator = strategy.make_input_fn_iterator(lambda _: input_fn()) + self.evaluate(iterator.initialize()) return iterator @combinations.generate( @@ -70,7 +67,7 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): distribution.extended.call_for_each_replica( model_fn, args=(inputs,))) - iterator = self._get_iterator(distribution.distribute_dataset(dataset_fn)) + iterator = self._get_iterator(distribution, dataset_fn) def run_step(): return distribution.extended.experimental_run_steps_on_iterator( @@ -102,7 +99,7 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): model_fn, dataset_fn, layer = minimize_loss_example( optimizer_fn, use_bias=True, use_callable_loss=use_callable_loss) - iterator = self._get_iterator(distribution.distribute_dataset(dataset_fn)) + iterator = self._get_iterator(distribution, dataset_fn) def run_step(): return distribution.group( @@ -161,7 +158,7 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): distribution.extended.call_for_each_replica( model_fn, args=(inputs,))) - iterator = self._get_iterator(distribution.distribute_dataset(dataset_fn)) + iterator = self._get_iterator(distribution, dataset_fn) def run_step(): return distribution.extended.experimental_run_steps_on_iterator( @@ -230,7 +227,7 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): fetches += tuple(ops.get_collection(ops.GraphKeys.UPDATE_OPS)) return control_flow_ops.group(fetches) - iterator = self._get_iterator(distribution.distribute_dataset(dataset_fn)) + iterator = self._get_iterator(distribution, dataset_fn) def run_step(): return distribution.extended.experimental_run_steps_on_iterator( @@ -322,7 +319,7 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): distribution.extended.call_for_each_replica( model_fn, args=(inputs,))) - iterator = self._get_iterator(distribution.distribute_dataset(dataset_fn)) + iterator = self._get_iterator(distribution, dataset_fn) def run_step(): return distribution.extended.experimental_run_steps_on_iterator( @@ -413,7 +410,7 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): output=loss) return distribution.group(train_op) - iterator = self._get_iterator(distribution.distribute_dataset(dataset_fn)) + iterator = self._get_iterator(distribution, dataset_fn) def run_step(): initial_loss = lambda: constant_op.constant(1e7) diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py index c22e4f8ee6..d6337d106f 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py @@ -366,14 +366,9 @@ class MirroredStrategyVariableCreationTest(test.TestCase): (layer2.kernel, layer2.bias), (layer3.kernel, layer3.bias)] - ds = distribution.distribute_dataset( - lambda: dataset_ops.Dataset.from_tensors([[1.]]).repeat(10)) - if context.executing_eagerly(): - iterator = ds.make_one_shot_iterator() - else: - iterator = ds.make_initializable_iterator() - self.evaluate([iterator.initializer]) - + iterator = distribution.make_input_fn_iterator( + lambda _: dataset_ops.Dataset.from_tensors([[1.]]).repeat(10)) + self.evaluate(iterator.initialize()) features = iterator.get_next() with distribution.scope(): diff --git a/tensorflow/contrib/distribute/python/monitor.py b/tensorflow/contrib/distribute/python/monitor.py index 17b7ab74f6..53e35ea6b7 100644 --- a/tensorflow/contrib/distribute/python/monitor.py +++ b/tensorflow/contrib/distribute/python/monitor.py @@ -51,7 +51,7 @@ class Monitor(object): else: if session is None: raise ValueError("Should provide a `session` in Graph mode.") - session.run(step_callable._iterator.initializer) # pylint: disable=protected-access + session.run(step_callable.initialize()) self._run_step = session.make_callable(step_callable()) session.run(variables.global_variables_initializer()) diff --git a/tensorflow/contrib/distribute/python/optimizer_v2_test.py b/tensorflow/contrib/distribute/python/optimizer_v2_test.py index a832ef61f9..e388061b17 100644 --- a/tensorflow/contrib/distribute/python/optimizer_v2_test.py +++ b/tensorflow/contrib/distribute/python/optimizer_v2_test.py @@ -41,12 +41,7 @@ class MinimizeLossOptimizerV2Test(test.TestCase, parameterized.TestCase): with distribution.scope(): model_fn, dataset_fn, layer = minimize_loss_example( optimizer_fn, use_bias=True, use_callable_loss=use_callable_loss) - - ds = distribution.distribute_dataset(dataset_fn) - if context.executing_eagerly(): - iterator = ds.make_one_shot_iterator() - else: - iterator = ds.make_initializable_iterator() + iterator = distribution.make_input_fn_iterator(lambda _: dataset_fn()) def run_step(): return control_flow_ops.group( @@ -56,7 +51,7 @@ class MinimizeLossOptimizerV2Test(test.TestCase, parameterized.TestCase): if not context.executing_eagerly(): with self.cached_session() as sess: - sess.run(iterator.initializer) + sess.run(iterator.initialize()) run_step = sess.make_callable(run_step()) self.evaluate(variables.global_variables_initializer()) diff --git a/tensorflow/contrib/distribute/python/step_fn.py b/tensorflow/contrib/distribute/python/step_fn.py index 24b95f748b..27aad46b97 100644 --- a/tensorflow/contrib/distribute/python/step_fn.py +++ b/tensorflow/contrib/distribute/python/step_fn.py @@ -19,7 +19,6 @@ from __future__ import division from __future__ import print_function from tensorflow.python.eager import backprop -from tensorflow.python.eager import context from tensorflow.python.training import optimizer as optimizer_lib @@ -33,6 +32,9 @@ class Step(object): def distribution(self): return self._distribution + def initialize(self): + return [] + def __call__(self): """Perform one step of this training algorithm.""" raise NotImplementedError("must be implemented in descendants") @@ -50,12 +52,10 @@ class StandardInputStep(Step): def __init__(self, dataset_fn, distribution): super(StandardInputStep, self).__init__(distribution) - self._distributed_input = distribution.distribute_dataset(dataset_fn) - if context.executing_eagerly(): - self._iterator = self._distributed_input.make_one_shot_iterator() - else: - # TODO(priyag): Expose initializer via some initializer property. - self._iterator = self._distributed_input.make_initializable_iterator() + self._iterator = distribution.make_input_fn_iterator(lambda _: dataset_fn()) + + def initialize(self): + return self._iterator.initialize() class StandardSingleLossStep(StandardInputStep): diff --git a/tensorflow/contrib/distribute/python/step_fn_test.py b/tensorflow/contrib/distribute/python/step_fn_test.py index a77d6d0bec..9f48560b26 100644 --- a/tensorflow/contrib/distribute/python/step_fn_test.py +++ b/tensorflow/contrib/distribute/python/step_fn_test.py @@ -46,10 +46,11 @@ class SingleLossStepTest(test.TestCase, parameterized.TestCase): optimizer_fn, distribution, use_bias=True, iterations_per_step=2) if context.executing_eagerly(): + single_loss_step.initialize() run_step = single_loss_step else: with self.cached_session() as sess: - sess.run(single_loss_step._iterator.initializer) + sess.run(single_loss_step.initialize()) run_step = sess.make_callable(single_loss_step()) self.evaluate(variables.global_variables_initializer()) -- GitLab From 890a58c43242becfc29f0bfe6c5100b5f7faea94 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 17 Jan 2019 08:46:54 -0800 Subject: [PATCH 0853/2345] s/AutographParseError/AutoGraphParseError/ (properly camel-casing AutoGraph.) PiperOrigin-RevId: 229754536 --- tensorflow/python/autograph/__init__.py | 4 ++-- tensorflow/python/autograph/converters/control_flow_test.py | 2 +- tensorflow/python/autograph/converters/decorators_test.py | 2 +- tensorflow/python/autograph/converters/slices_test.py | 2 +- tensorflow/python/autograph/pyct/transformer.py | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tensorflow/python/autograph/__init__.py b/tensorflow/python/autograph/__init__.py index 6faeb01607..8fd6e72da6 100644 --- a/tensorflow/python/autograph/__init__.py +++ b/tensorflow/python/autograph/__init__.py @@ -50,7 +50,7 @@ from tensorflow.python.autograph.lang.directives import set_element_type from tensorflow.python.autograph.lang.directives import set_loop_options from tensorflow.python.autograph.lang.special_functions import stack from tensorflow.python.autograph.lang.special_functions import tensor_list -from tensorflow.python.autograph.pyct.transformer import AutographParseError +from tensorflow.python.autograph.pyct.transformer import AutoGraphParseError from tensorflow.python.util.all_util import remove_undocumented # TODO(mdan): Revisit this list once we finalize the generated code mechanism. @@ -77,7 +77,7 @@ _allowed_symbols = [ 'stack', 'tensor_list', # Exceptions - 'AutographParseError', + 'AutoGraphParseError', # Utilities: to be removed 'utils', ] diff --git a/tensorflow/python/autograph/converters/control_flow_test.py b/tensorflow/python/autograph/converters/control_flow_test.py index 034fcbe386..1a38d0db4d 100644 --- a/tensorflow/python/autograph/converters/control_flow_test.py +++ b/tensorflow/python/autograph/converters/control_flow_test.py @@ -184,7 +184,7 @@ class ControlFlowTest(converter_testing.TestCase): return b node, ctx = self.prepare(test_fn, {}) - with self.assertRaises(transformer.AutographParseError): + with self.assertRaises(transformer.AutoGraphParseError): control_flow.transform(node, ctx) @test_util.run_deprecated_v1 diff --git a/tensorflow/python/autograph/converters/decorators_test.py b/tensorflow/python/autograph/converters/decorators_test.py index abd76849d6..bcf502c62b 100644 --- a/tensorflow/python/autograph/converters/decorators_test.py +++ b/tensorflow/python/autograph/converters/decorators_test.py @@ -121,7 +121,7 @@ class DecoratorsTest(converter_testing.TestCase): return inner_fn(a) # Expected to fail because simple_decorator could not be imported. - with self.assertRaises(transformer.AutographParseError): + with self.assertRaises(transformer.AutoGraphParseError): test_fn(1) def test_nested_decorators_imported(self): diff --git a/tensorflow/python/autograph/converters/slices_test.py b/tensorflow/python/autograph/converters/slices_test.py index bd049afdfc..d674f266c8 100644 --- a/tensorflow/python/autograph/converters/slices_test.py +++ b/tensorflow/python/autograph/converters/slices_test.py @@ -68,7 +68,7 @@ class SliceTest(converter_testing.TestCase): def_.directives[directives.set_element_type] = { 'dtype': parser.parse_expression('tf.float32') } - with self.assertRaises(transformer.AutographParseError): + with self.assertRaises(transformer.AutoGraphParseError): slices.transform(node, ctx) diff --git a/tensorflow/python/autograph/pyct/transformer.py b/tensorflow/python/autograph/pyct/transformer.py index b6830534b3..9c776a737a 100644 --- a/tensorflow/python/autograph/pyct/transformer.py +++ b/tensorflow/python/autograph/pyct/transformer.py @@ -29,7 +29,7 @@ from tensorflow.python.autograph.pyct import pretty_printer from tensorflow.python.autograph.pyct import templates -class AutographParseError(SyntaxError): +class AutoGraphParseError(SyntaxError): pass @@ -495,8 +495,8 @@ class Base(gast.NodeTransformer): # In other words, we need to find how to suppress the "During handling # of the above exception, another exception occurred" message. six.reraise( - AutographParseError, - AutographParseError(msg, (original_file_path, original_line_number, + AutoGraphParseError, + AutoGraphParseError(msg, (original_file_path, original_line_number, original_col_offset, original_source_line)), sys.exc_info()[2]) finally: -- GitLab From a782c9df3e9c2cae1b49326f9c9b72eac1dfd794 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 17 Jan 2019 08:53:44 -0800 Subject: [PATCH 0854/2345] Replace --genrule_strategy w/ --strategy=Genrule This is in preparation for --genrule_strategy becoming a no-op in a future Bazel release. PiperOrigin-RevId: 229755652 --- .bazelrc | 2 +- tensorflow/tools/ci_build/install/.bazelrc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bazelrc b/.bazelrc index d5432aeb17..c70c571361 100644 --- a/.bazelrc +++ b/.bazelrc @@ -80,7 +80,7 @@ build --define=use_fast_cpp_protos=true build --define=allow_oversize_protos=true build --spawn_strategy=standalone -build --genrule_strategy=standalone +build --strategy=Genrule=standalone build -c opt # Other build flags. diff --git a/tensorflow/tools/ci_build/install/.bazelrc b/tensorflow/tools/ci_build/install/.bazelrc index 2060babd4a..4662e2e60a 100644 --- a/tensorflow/tools/ci_build/install/.bazelrc +++ b/tensorflow/tools/ci_build/install/.bazelrc @@ -5,7 +5,7 @@ startup --batch # Similarly, we need to workaround sandboxing issues: # https://github.com/bazelbuild/bazel/issues/418 -build --verbose_failures --spawn_strategy=standalone --genrule_strategy=standalone +build --verbose_failures --spawn_strategy=standalone --strategy=Genrule=standalone test --spawn_strategy=standalone # Force bazel output to use colors (good for jenkins) and print useful errors. -- GitLab From c9bd0744ea3d527503e7cc2e44c7306f0d25e421 Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Thu, 17 Jan 2019 09:01:33 -0800 Subject: [PATCH 0855/2345] MOve backend config related functions to a dedicated file. Use backend epsilon if epsilon argument is None. PiperOrigin-RevId: 229756902 --- tensorflow/python/keras/BUILD | 19 +++ tensorflow/python/keras/backend.py | 143 ++---------------- tensorflow/python/keras/backend_config.py | 126 +++++++++++++++ .../python/keras/backend_config_test.py | 55 +++++++ tensorflow/python/keras/backend_test.py | 18 --- tensorflow/python/keras/optimizer_v2/BUILD | 1 + .../python/keras/optimizer_v2/adadelta.py | 3 + .../keras/optimizer_v2/adadelta_test.py | 9 ++ .../python/keras/optimizer_v2/adagrad.py | 3 + .../python/keras/optimizer_v2/adagrad_test.py | 13 ++ tensorflow/python/keras/optimizer_v2/adam.py | 3 + .../python/keras/optimizer_v2/adam_test.py | 9 ++ .../python/keras/optimizer_v2/adamax.py | 3 + .../python/keras/optimizer_v2/adamax_test.py | 9 ++ tensorflow/python/keras/optimizer_v2/nadam.py | 3 + .../python/keras/optimizer_v2/nadam_test.py | 9 ++ .../python/keras/optimizer_v2/rmsprop.py | 3 + .../python/keras/optimizer_v2/rmsprop_test.py | 9 ++ 18 files changed, 287 insertions(+), 151 deletions(-) create mode 100644 tensorflow/python/keras/backend_config.py create mode 100644 tensorflow/python/keras/backend_config_test.py diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 9e08c766db..c4c403119c 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -82,6 +82,7 @@ py_library( srcs = ["backend.py"], srcs_version = "PY2AND3", deps = [ + ":backend_config", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:check_ops", @@ -117,6 +118,12 @@ py_library( ], ) +py_library( + name = "backend_config", + srcs = ["backend_config.py"], + srcs_version = "PY2AND3", +) + py_library( name = "engine", srcs = [ @@ -1108,6 +1115,18 @@ tf_py_test( ], ) +tf_py_test( + name = "backend_config_test", + size = "medium", + srcs = ["backend_config_test.py"], + additional_deps = [ + ":keras", + "//third_party/py/numpy", + "//tensorflow/python:client_testlib", + "//tensorflow/python:util", + ], +) + tf_py_test( name = "keras_parameterized_test", size = "small", diff --git a/tensorflow/python/keras/backend.py b/tensorflow/python/keras/backend.py index f357f0efc7..9677f12d34 100644 --- a/tensorflow/python/keras/backend.py +++ b/tensorflow/python/keras/backend.py @@ -40,6 +40,7 @@ from tensorflow.python.framework import func_graph from tensorflow.python.framework import ops from tensorflow.python.framework import sparse_tensor from tensorflow.python.framework import tensor_util +from tensorflow.python.keras import backend_config from tensorflow.python.ops import array_ops from tensorflow.python.ops import clip_ops from tensorflow.python.ops import control_flow_ops @@ -97,15 +98,6 @@ _DUMMY_EAGER_GRAPH = _DummyEagerGraph() # Change its value via `manual_variable_initialization(value)`. _MANUAL_VAR_INIT = False -# The type of float to use throughout a session. -_FLOATX = 'float32' - -# Epsilon fuzz factor used throughout the codebase. -_EPSILON = 1e-7 - -# Default image data format, one of "channels_last", "channels_first". -_IMAGE_DATA_FORMAT = 'channels_last' - # This list holds the available devices. # It is populated when `_get_available_gpus()` is called for the first time. # We assume our devices don't change henceforth. @@ -119,6 +111,14 @@ _GRAPH_VARIABLES = weakref.WeakKeyDictionary() # the graph. _GRAPH_TF_OPTIMIZERS = weakref.WeakKeyDictionary() +# The below functions are kept accessible from backend for compatibility. +epsilon = backend_config.epsilon +floatx = backend_config.floatx +image_data_format = backend_config.image_data_format +set_epsilon = backend_config.set_epsilon +set_floatx = backend_config.set_floatx +set_image_data_format = backend_config.set_image_data_format + @keras_export('keras.backend.backend') def backend(): @@ -132,87 +132,6 @@ def backend(): return 'tensorflow' -@keras_export('keras.backend.epsilon') -def epsilon(): - """Returns the value of the fuzz factor used in numeric expressions. - - Returns: - A float. - - Example: - ```python - >>> keras.backend.epsilon() - 1e-07 - ``` - """ - return _EPSILON - - -@keras_export('keras.backend.set_epsilon') -def set_epsilon(value): - """Sets the value of the fuzz factor used in numeric expressions. - - Arguments: - value: float. New value of epsilon. - - Example: - ```python - >>> from keras import backend as K - >>> K.epsilon() - 1e-07 - >>> K.set_epsilon(1e-05) - >>> K.epsilon() - 1e-05 - ``` - """ - global _EPSILON - _EPSILON = value - - -@keras_export('keras.backend.floatx') -def floatx(): - """Returns the default float type, as a string. - - E.g. 'float16', 'float32', 'float64'. - - Returns: - String, the current default float type. - - Example: - ```python - >>> keras.backend.floatx() - 'float32' - ``` - """ - return _FLOATX - - -@keras_export('keras.backend.set_floatx') -def set_floatx(value): - """Sets the default float type. - - Arguments: - value: String; 'float16', 'float32', or 'float64'. - - Example: - ```python - >>> from keras import backend as K - >>> K.floatx() - 'float32' - >>> K.set_floatx('float16') - >>> K.floatx() - 'float16' - ``` - - Raises: - ValueError: In case of invalid value. - """ - global _FLOATX - if value not in {'float16', 'float32', 'float64'}: - raise ValueError('Unknown floatx type: ' + str(value)) - _FLOATX = str(value) - - @keras_export('keras.backend.cast_to_floatx') def cast_to_floatx(x): """Cast a Numpy array to the default Keras float type. @@ -238,49 +157,7 @@ def cast_to_floatx(x): dtype('float32') ``` """ - return np.asarray(x, dtype=_FLOATX) - - -@keras_export('keras.backend.image_data_format') -def image_data_format(): - """Returns the default image data format convention. - - Returns: - A string, either `'channels_first'` or `'channels_last'` - - Example: - ```python - >>> keras.backend.image_data_format() - 'channels_first' - ``` - """ - return _IMAGE_DATA_FORMAT - - -@keras_export('keras.backend.set_image_data_format') -def set_image_data_format(data_format): - """Sets the value of the image data format convention. - - Arguments: - data_format: string. `'channels_first'` or `'channels_last'`. - - Example: - ```python - >>> from keras import backend as K - >>> K.image_data_format() - 'channels_first' - >>> K.set_image_data_format('channels_last') - >>> K.image_data_format() - 'channels_last' - ``` - - Raises: - ValueError: In case of invalid `data_format` value. - """ - global _IMAGE_DATA_FORMAT - if data_format not in {'channels_last', 'channels_first'}: - raise ValueError('Unknown data_format: ' + str(data_format)) - _IMAGE_DATA_FORMAT = str(data_format) + return np.asarray(x, dtype=floatx()) # A global dictionary mapping graph objects to an index of counters used diff --git a/tensorflow/python/keras/backend_config.py b/tensorflow/python/keras/backend_config.py new file mode 100644 index 0000000000..e7c63ac2c7 --- /dev/null +++ b/tensorflow/python/keras/backend_config.py @@ -0,0 +1,126 @@ +# 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. +# ============================================================================== +"""Keras backend config API.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.util.tf_export import keras_export + +# The type of float to use throughout a session. +_FLOATX = 'float32' + +# Epsilon fuzz factor used throughout the codebase. +_EPSILON = 1e-7 + +# Default image data format, one of "channels_last", "channels_first". +_IMAGE_DATA_FORMAT = 'channels_last' + + +@keras_export('keras.backend.epsilon') +def epsilon(): + """Returns the value of the fuzz factor used in numeric expressions. + + Returns: + A float. + + Example: + ```python + keras.backend.epsilon() >>>1e-07 + ``` + """ + return _EPSILON + + +@keras_export('keras.backend.set_epsilon') +def set_epsilon(value): + """Sets the value of the fuzz factor used in numeric expressions. + + Arguments: + value: float. New value of epsilon. + Example: ```python from keras import backend as K K.epsilon() >>> 1e-07 + K.set_epsilon(1e-05) K.epsilon() >>> 1e-05 ``` + """ + global _EPSILON + _EPSILON = value + + +@keras_export('keras.backend.floatx') +def floatx(): + """Returns the default float type, as a string. + + E.g. 'float16', 'float32', 'float64'. + + Returns: + String, the current default float type. + + Example: + ```python + keras.backend.floatx() >>> 'float32' + ``` + """ + return _FLOATX + + +@keras_export('keras.backend.set_floatx') +def set_floatx(value): + """Sets the default float type. + + Arguments: + value: String; 'float16', 'float32', or 'float64'. + Example: ```python from keras import backend as K K.floatx() >>> 'float32' + K.set_floatx('float16') K.floatx() >>> 'float16' ``` + + Raises: + ValueError: In case of invalid value. + """ + global _FLOATX + if value not in {'float16', 'float32', 'float64'}: + raise ValueError('Unknown floatx type: ' + str(value)) + _FLOATX = str(value) + + +@keras_export('keras.backend.image_data_format') +def image_data_format(): + """Returns the default image data format convention. + + Returns: + A string, either `'channels_first'` or `'channels_last'` + + Example: + ```python + keras.backend.image_data_format() >>> 'channels_first' + ``` + """ + return _IMAGE_DATA_FORMAT + + +@keras_export('keras.backend.set_image_data_format') +def set_image_data_format(data_format): + """Sets the value of the image data format convention. + + Arguments: + data_format: string. `'channels_first'` or `'channels_last'`. + Example: ```python from keras import backend as K K.image_data_format() >>> + 'channels_first' K.set_image_data_format('channels_last') + K.image_data_format() >>> 'channels_last' ``` + + Raises: + ValueError: In case of invalid `data_format` value. + """ + global _IMAGE_DATA_FORMAT + if data_format not in {'channels_last', 'channels_first'}: + raise ValueError('Unknown data_format: ' + str(data_format)) + _IMAGE_DATA_FORMAT = str(data_format) diff --git a/tensorflow/python/keras/backend_config_test.py b/tensorflow/python/keras/backend_config_test.py new file mode 100644 index 0000000000..59e003d81e --- /dev/null +++ b/tensorflow/python/keras/backend_config_test.py @@ -0,0 +1,55 @@ +# 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 backend_config.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python import keras +from tensorflow.python.framework import test_util +from tensorflow.python.platform import test + + +@test_util.run_all_in_graph_and_eager_modes +class BackendConfigTest(test.TestCase): + + def test_backend(self): + self.assertEqual(keras.backend.backend(), 'tensorflow') + + def test_espilon(self): + epsilon = 1e-2 + keras.backend_config.set_epsilon(epsilon) + self.assertEqual(keras.backend_config.epsilon(), epsilon) + keras.backend_config.set_epsilon(1e-7) + self.assertEqual(keras.backend_config.epsilon(), 1e-7) + + def test_floatx(self): + floatx = 'float64' + keras.backend_config.set_floatx(floatx) + self.assertEqual(keras.backend_config.floatx(), floatx) + keras.backend_config.set_floatx('float32') + self.assertEqual(keras.backend_config.floatx(), 'float32') + + def test_image_data_format(self): + image_data_format = 'channels_first' + keras.backend_config.set_image_data_format(image_data_format) + self.assertEqual(keras.backend_config.image_data_format(), + image_data_format) + keras.backend_config.set_image_data_format('channels_last') + self.assertEqual(keras.backend_config.image_data_format(), 'channels_last') + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/python/keras/backend_test.py b/tensorflow/python/keras/backend_test.py index 4b83f0bf66..e3f86ee2c0 100644 --- a/tensorflow/python/keras/backend_test.py +++ b/tensorflow/python/keras/backend_test.py @@ -99,24 +99,6 @@ class BackendUtilsTest(test.TestCase): def test_backend(self): self.assertEqual(keras.backend.backend(), 'tensorflow') - def test_espilon(self): - epsilon = 1e-2 - keras.backend.set_epsilon(epsilon) - self.assertEqual(keras.backend.epsilon(), epsilon) - keras.backend.set_epsilon(1e-7) - - def test_floatx(self): - floatx = 'float64' - keras.backend.set_floatx(floatx) - self.assertEqual(keras.backend.floatx(), floatx) - keras.backend.set_floatx('float32') - - def test_image_data_format(self): - image_data_format = 'channels_first' - keras.backend.set_image_data_format(image_data_format) - self.assertEqual(keras.backend.image_data_format(), image_data_format) - keras.backend.set_image_data_format('channels_last') - def test_get_reset_uids(self): self.assertEqual(keras.backend.get_uid('foo'), 1) self.assertEqual(keras.backend.get_uid('foo'), 2) diff --git a/tensorflow/python/keras/optimizer_v2/BUILD b/tensorflow/python/keras/optimizer_v2/BUILD index da757edc74..45afe2a134 100644 --- a/tensorflow/python/keras/optimizer_v2/BUILD +++ b/tensorflow/python/keras/optimizer_v2/BUILD @@ -35,6 +35,7 @@ py_library( "//tensorflow/python:variables", "//tensorflow/python/distribute:reduce_util", "//tensorflow/python/distribute:values", + "//tensorflow/python/keras:backend_config", ], ) diff --git a/tensorflow/python/keras/optimizer_v2/adadelta.py b/tensorflow/python/keras/optimizer_v2/adadelta.py index b6cba263bc..a3d5538ea8 100644 --- a/tensorflow/python/keras/optimizer_v2/adadelta.py +++ b/tensorflow/python/keras/optimizer_v2/adadelta.py @@ -20,6 +20,7 @@ from __future__ import print_function import numpy as np +from tensorflow.python.keras import backend_config from tensorflow.python.keras.optimizer_v2 import optimizer_v2 from tensorflow.python.training import training_ops from tensorflow.python.util.tf_export import keras_export @@ -90,6 +91,8 @@ class Adadelta(optimizer_v2.OptimizerV2): invocations of optimizer functions. @end_compatibility """ + if epsilon is None: + epsilon = backend_config.epsilon() super(Adadelta, self).__init__(name, **kwargs) self._set_hyper('learning_rate', kwargs.get('lr', learning_rate)) self._set_hyper('decay', self._initial_decay) diff --git a/tensorflow/python/keras/optimizer_v2/adadelta_test.py b/tensorflow/python/keras/optimizer_v2/adadelta_test.py index 442a1d3189..06ff975212 100644 --- a/tensorflow/python/keras/optimizer_v2/adadelta_test.py +++ b/tensorflow/python/keras/optimizer_v2/adadelta_test.py @@ -181,6 +181,15 @@ class AdadeltaOptimizerTest(test.TestCase): self.assertAllClose(self.evaluate(opt_2.lr), (1.0)) self.assertAllClose(self.evaluate(opt_3.lr), (0.1)) + def testConstructAdadeltaWithEpsilonValues(self): + opt = adadelta.Adadelta(epsilon=None) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-7) + + opt = adadelta.Adadelta(epsilon=1e-8) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-8) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/keras/optimizer_v2/adagrad.py b/tensorflow/python/keras/optimizer_v2/adagrad.py index 01c722521f..0840aa6fae 100644 --- a/tensorflow/python/keras/optimizer_v2/adagrad.py +++ b/tensorflow/python/keras/optimizer_v2/adagrad.py @@ -21,6 +21,7 @@ from __future__ import print_function import numpy as np from tensorflow.python.framework import ops +from tensorflow.python.keras import backend_config from tensorflow.python.keras.optimizer_v2 import optimizer_v2 from tensorflow.python.ops import array_ops from tensorflow.python.ops import init_ops @@ -89,6 +90,8 @@ class Adagrad(optimizer_v2.OptimizerV2): if initial_accumulator_value < 0.0: raise ValueError('initial_accumulator_value must be non-negative: %s' % initial_accumulator_value) + if epsilon is None: + epsilon = backend_config.epsilon() if epsilon < 1e-7: raise ValueError('epsilon must be larger than 1e-7: %s' % epsilon) super(Adagrad, self).__init__(name, **kwargs) diff --git a/tensorflow/python/keras/optimizer_v2/adagrad_test.py b/tensorflow/python/keras/optimizer_v2/adagrad_test.py index 1599a54745..864aefaf70 100644 --- a/tensorflow/python/keras/optimizer_v2/adagrad_test.py +++ b/tensorflow/python/keras/optimizer_v2/adagrad_test.py @@ -411,6 +411,19 @@ class AdagradOptimizerTest(test.TestCase): self.assertAllClose(self.evaluate(opt_2.lr), (1.0)) self.assertAllClose(self.evaluate(opt_3.lr), (0.1)) + def testConstructAdagradWithEpsilonValues(self): + opt = adagrad.Adagrad(epsilon=None) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-7) + + opt = adagrad.Adagrad(epsilon=1e-6) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-6) + + with self.assertRaisesRegexp(ValueError, + "epsilon must be larger than 1e-7"): + opt = adagrad.Adagrad(epsilon=1e-8) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/keras/optimizer_v2/adam.py b/tensorflow/python/keras/optimizer_v2/adam.py index 263004c9df..4fa7c73615 100644 --- a/tensorflow/python/keras/optimizer_v2/adam.py +++ b/tensorflow/python/keras/optimizer_v2/adam.py @@ -18,6 +18,7 @@ from __future__ import division from __future__ import print_function from tensorflow.python.framework import ops +from tensorflow.python.keras import backend_config from tensorflow.python.keras.optimizer_v2 import optimizer_v2 from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import math_ops @@ -131,6 +132,8 @@ class Adam(optimizer_v2.OptimizerV2): compatibility, recommended to use `learning_rate` instead. """ + if epsilon is None: + epsilon = backend_config.epsilon() super(Adam, self).__init__(name, **kwargs) self._set_hyper('learning_rate', kwargs.get('lr', learning_rate)) self._set_hyper('decay', self._initial_decay) diff --git a/tensorflow/python/keras/optimizer_v2/adam_test.py b/tensorflow/python/keras/optimizer_v2/adam_test.py index 837d0220e1..7918c09b7e 100644 --- a/tensorflow/python/keras/optimizer_v2/adam_test.py +++ b/tensorflow/python/keras/optimizer_v2/adam_test.py @@ -516,6 +516,15 @@ class AdamOptimizerTest(test.TestCase): self.assertAllClose(self.evaluate(opt_2.lr), (1.0)) self.assertAllClose(self.evaluate(opt_3.lr), (0.1)) + def testConstructAdamWithEpsilonValues(self): + opt = adam.Adam(epsilon=None) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-7) + + opt = adam.Adam(epsilon=1e-8) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-8) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/keras/optimizer_v2/adamax.py b/tensorflow/python/keras/optimizer_v2/adamax.py index 9a1b63f6c6..3102e28cff 100644 --- a/tensorflow/python/keras/optimizer_v2/adamax.py +++ b/tensorflow/python/keras/optimizer_v2/adamax.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function from tensorflow.python.framework import ops +from tensorflow.python.keras import backend_config from tensorflow.python.keras.optimizer_v2 import optimizer_v2 from tensorflow.python.ops import array_ops from tensorflow.python.ops import control_flow_ops @@ -95,6 +96,8 @@ class Adamax(optimizer_v2.OptimizerV2): allow time inverse decay of learning rate. `lr` is included for backward compatibility, recommended to use `learning_rate` instead. """ + if epsilon is None: + epsilon = backend_config.epsilon() super(Adamax, self).__init__(name, **kwargs) self._set_hyper('learning_rate', kwargs.get('lr', learning_rate)) self._set_hyper('decay', self._initial_decay) diff --git a/tensorflow/python/keras/optimizer_v2/adamax_test.py b/tensorflow/python/keras/optimizer_v2/adamax_test.py index fe658761c7..6934f1590e 100644 --- a/tensorflow/python/keras/optimizer_v2/adamax_test.py +++ b/tensorflow/python/keras/optimizer_v2/adamax_test.py @@ -375,6 +375,15 @@ class AdamaxOptimizerTest(test.TestCase): self.assertAllClose(self.evaluate(opt_2.lr), (1.0)) self.assertAllClose(self.evaluate(opt_3.lr), (0.1)) + def testConstructAdamaxWithEpsilonValues(self): + opt = adamax.Adamax(epsilon=None) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-7) + + opt = adamax.Adamax(epsilon=1e-8) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-8) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/keras/optimizer_v2/nadam.py b/tensorflow/python/keras/optimizer_v2/nadam.py index 4c94b1358a..d515f98725 100644 --- a/tensorflow/python/keras/optimizer_v2/nadam.py +++ b/tensorflow/python/keras/optimizer_v2/nadam.py @@ -18,6 +18,7 @@ from __future__ import division from __future__ import print_function from tensorflow.python.framework import ops +from tensorflow.python.keras import backend_config from tensorflow.python.keras.optimizer_v2 import optimizer_v2 from tensorflow.python.ops import array_ops from tensorflow.python.ops import control_flow_ops @@ -85,6 +86,8 @@ class Nadam(optimizer_v2.OptimizerV2): # Backwards compatiblity with keras NAdam optimizer. kwargs['decay'] = kwargs.pop('schedule_decay', 0.004) + if epsilon is None: + epsilon = backend_config.epsilon() super(Nadam, self).__init__(name, **kwargs) self._set_hyper('learning_rate', kwargs.get('lr', learning_rate)) self._set_hyper('decay', self._initial_decay) diff --git a/tensorflow/python/keras/optimizer_v2/nadam_test.py b/tensorflow/python/keras/optimizer_v2/nadam_test.py index b877a80cd8..8dd61956f6 100644 --- a/tensorflow/python/keras/optimizer_v2/nadam_test.py +++ b/tensorflow/python/keras/optimizer_v2/nadam_test.py @@ -178,6 +178,15 @@ class NadamOptimizerTest(test.TestCase): self.evaluate(variables.global_variables_initializer()) self.assertAllClose(self.evaluate(opt.decay), (0.2)) + def testConstructNAdamWithEpsilonValues(self): + opt = nadam.Nadam(epsilon=None) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-7) + + opt = nadam.Nadam(epsilon=1e-8) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-8) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/keras/optimizer_v2/rmsprop.py b/tensorflow/python/keras/optimizer_v2/rmsprop.py index d459d9811e..e55e6375a3 100644 --- a/tensorflow/python/keras/optimizer_v2/rmsprop.py +++ b/tensorflow/python/keras/optimizer_v2/rmsprop.py @@ -20,6 +20,7 @@ from __future__ import print_function import numpy as np from tensorflow.python.framework import ops +from tensorflow.python.keras import backend_config from tensorflow.python.keras.optimizer_v2 import optimizer_v2 from tensorflow.python.ops import array_ops from tensorflow.python.ops import control_flow_ops @@ -102,6 +103,8 @@ class RMSprop(optimizer_v2.OptimizerV2): allow time inverse decay of learning rate. `lr` is included for backward compatibility, recommended to use `learning_rate` instead. """ + if epsilon is None: + epsilon = backend_config.epsilon() super(RMSprop, self).__init__(name, **kwargs) self._set_hyper("learning_rate", kwargs.get("lr", learning_rate)) self._set_hyper("decay", self._initial_decay) diff --git a/tensorflow/python/keras/optimizer_v2/rmsprop_test.py b/tensorflow/python/keras/optimizer_v2/rmsprop_test.py index d4bc7ce50c..a9ddc2155a 100644 --- a/tensorflow/python/keras/optimizer_v2/rmsprop_test.py +++ b/tensorflow/python/keras/optimizer_v2/rmsprop_test.py @@ -471,6 +471,15 @@ class RMSpropOptimizerTest(test.TestCase): self.assertEqual( self.evaluate(opt.variables()[0]), self.evaluate(opt.iterations)) + def testConstructRMSpropWithEpsilonValues(self): + opt = rmsprop.RMSprop(epsilon=None) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-7) + + opt = rmsprop.RMSprop(epsilon=1e-8) + config = opt.get_config() + self.assertEqual(config["epsilon"], 1e-8) + if __name__ == "__main__": test.main() -- GitLab From 53c44a9ac4e69c788ef5b707d21c0f364eedb9a8 Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Thu, 17 Jan 2019 09:06:26 -0800 Subject: [PATCH 0856/2345] Make a real repro for IConstantLayer: we need to call getType() before getDimensions(), then if CreateConstantLayer() doesn't set the dtype, the test will fail. Before CreateConstantLayer() was added, graphs which trigger conversion of INT32 weights to IConstantLayer will crash the TRT conversion. This is a bug in TRT 5.0 and I has reported it to NVIDIA. CreateConstantLayer() fixed that by setting the dtype of the output ITensor of the IConstantLayer, but the added test could not reproduce the error when we remove the setting code. This change fix the test, so when removing the dtype setting logic the test will fail. Note that, after this fix, during conversion it'll report a lot of spammy logs but won't crash. Once NVIDIA fixed the TRT bug we should remove the dtype setting logic and the test should still pass. PiperOrigin-RevId: 229758150 --- tensorflow/contrib/tensorrt/convert/convert_nodes.cc | 7 ++++--- tensorflow/contrib/tensorrt/convert/convert_nodes.h | 3 +++ tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index a1c519d908..8b7279ad03 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -340,10 +340,11 @@ nvinfer1::ITensor* Converter::CreateConstantLayer( nvinfer1::IConstantLayer* layer = network()->addConstant(dims, trt_weights); if (!layer) return nullptr; const nvinfer1::DataType trt_dtype = trt_weights.type; - // NOTE(laigd): calling layer->setOutputType() does not work. This may be a - // bug and we should report to NVIDIA. - // layer->setOutputType(0, trt_dtype); nvinfer1::ITensor* trt_tensor = layer->getOutput(0); + // TODO(laigd): there is a bug in TensorRT 5.0 library that, if we don't set + // the data type below, it will always be kFLOAT regardless what the data type + // of the weights is. Once NVIDIA fixes this bug, we should remove the data + // type setting logic below and test should still pass. trt_tensor->setType(trt_dtype); return trt_tensor; } diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.h b/tensorflow/contrib/tensorrt/convert/convert_nodes.h index 25e1c42fdf..a02a52e430 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.h +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.h @@ -158,7 +158,10 @@ class OutputEdgeValidator { bool operator()(const tensorflow::Edge* out_edge) const; }; +string DebugString(const nvinfer1::DimensionType type); +string DebugString(const nvinfer1::DataType trt_dtype); string DebugString(const nvinfer1::Dims& dims); +string DebugString(const nvinfer1::Permutation& permutation, int len); string DebugString(const nvinfer1::ITensor& tensor); int64_t TrtDimsNumElements(const nvinfer1::Dims& dims); diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc index 36dbbe3f6f..f90cdac359 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc @@ -922,8 +922,10 @@ TEST_F(ConverterTest, CreateConstantLayer) { nvinfer1::ITensor* tensor = converter_->CreateConstantLayer(weights, GetTestDims({3, 10})); ASSERT_NE(nullptr, tensor); + EXPECT_EQ(TfDataTypeToTrt(dtype), tensor->getType()) + << "Expected " << DebugString(TfDataTypeToTrt(dtype)) << " vs. actual " + << DebugString(tensor->getType()); ExpectTrtDimsEqualsArray({3, 10}, tensor->getDimensions()); - EXPECT_EQ(TfDataTypeToTrt(dtype), tensor->getType()); } } -- GitLab From ac7f91ee3cd48c01f9ff253440e81c36a729526a Mon Sep 17 00:00:00 2001 From: Vojtech Bardiovsky Date: Thu, 17 Jan 2019 09:14:03 -0800 Subject: [PATCH 0857/2345] Make serialization work with kwargs. Additionally: 1) Unify the way how concrete functions and concrete functions from signatures are serialized. This can be achieved by using signature from the concrete_function.structured_input_signature field instead of constructing it manually. 2) Add a test for serializing a concrete function, then restoring it and calling with a positional argument. PiperOrigin-RevId: 229759347 --- tensorflow/python/eager/def_function_test.py | 18 +++++---- tensorflow/python/eager/function.py | 13 +++++-- .../eager/function_argument_naming_test.py | 8 ++-- tensorflow/python/framework/func_graph.py | 39 +++++++++++++++---- .../saved_model/function_deserialization.py | 32 ++++++++------- .../saved_model/function_serialization.py | 28 +++---------- tensorflow/python/saved_model/load_test.py | 39 +++++++++++++++++++ 7 files changed, 115 insertions(+), 62 deletions(-) diff --git a/tensorflow/python/eager/def_function_test.py b/tensorflow/python/eager/def_function_test.py index 256ef511a3..e0a82bb0be 100644 --- a/tensorflow/python/eager/def_function_test.py +++ b/tensorflow/python/eager/def_function_test.py @@ -247,10 +247,9 @@ class DefFunctionTest(test.TestCase): concrete = compute.get_concrete_function( tensor_spec.TensorSpec(None, dtypes.float32)) self.assertAllClose(4., concrete(constant_op.constant(2.))) - input_signature, _ = concrete.structured_input_signature - self.assertEqual( - tuple(input_signature), - (tensor_spec.TensorSpec(None, dtypes.float32),)) + signature_args, _ = concrete.structured_input_signature + self.assertEqual(signature_args, + (tensor_spec.TensorSpec(None, dtypes.float32),)) def test_serialization_signature_cache(self): @@ -261,12 +260,15 @@ class DefFunctionTest(test.TestCase): f(constant_op.constant([[3., 4.]]), constant_op.constant([2.])) f(constant_op.constant([[3, 4, 5]]), constant_op.constant([2])) + signatures_args = set() concrete_functions = f._list_all_concrete_functions_for_serialization() - signatures_for_serialization = [ - c.structured_input_signature[0] for c in concrete_functions - ] + for concrete_function in concrete_functions: + args, kwargs = concrete_function.structured_input_signature + signatures_args.add(args) + self.assertEqual(dict(), kwargs) + self.assertEqual( - set(signatures_for_serialization), + signatures_args, set(((tensor_spec.TensorSpec([1, 2], dtypes.float32), tensor_spec.TensorSpec([1], dtypes.float32)), (tensor_spec.TensorSpec([1, 3], dtypes.int32), diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 82dea51c29..d17c7020f3 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -1306,10 +1306,15 @@ class Function(object): arglen = len(args) else: arglen = len(self._input_signature) - arg_names = ( - self._function_spec.arg_names[:arglen] - + [self._function_spec.vararg_name] * - (arglen - len(self._function_spec.arg_names))) + base_arg_names = self._function_spec.arg_names[:arglen] + num_missing_args = arglen - len(self._function_spec.arg_names) + missing_arg_names = [self._function_spec.vararg_name] * num_missing_args + # Produce a list of missing args of the form ["arg_0", "arg_1", ...], + # where arg is based on the self._function_spec.vararg_name. + missing_arg_names = [ + "%s_%d" % (arg, i) for i, arg in enumerate(missing_arg_names) + ] + arg_names = base_arg_names + missing_arg_names graph_function = ConcreteFunction( func_graph_module.func_graph_from_py_func( self._name, diff --git a/tensorflow/python/eager/function_argument_naming_test.py b/tensorflow/python/eager/function_argument_naming_test.py index 9358c4fd07..08a50a8f51 100644 --- a/tensorflow/python/eager/function_argument_naming_test.py +++ b/tensorflow/python/eager/function_argument_naming_test.py @@ -220,10 +220,10 @@ class ArgumentNamingTests(test.TestCase, parameterized.TestCase): z=tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32), zz=tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32, name='cust')) self.assertEqual( - ['x', 'y', 'args', 'second_variadic', 'z', 'cust'], + ['x', 'y', 'args_1', 'second_variadic', 'z', 'cust'], [inp.op.name for inp in variadic_op.inputs]) self.assertEqual( - [b'x', b'y', b'args', b'second_variadic', b'z', b'cust'], + [b'x', b'y', b'args_1', b'second_variadic', b'z', b'cust'], [inp.op.get_attr('_user_specified_name') for inp in variadic_op.inputs]) @@ -244,10 +244,10 @@ class ArgumentNamingTests(test.TestCase, parameterized.TestCase): variadic_op = variadic_fn.get_concrete_function() self.assertIn(b'variadic_fn', variadic_op.name) self.assertEqual( - ['x', 'y', 'args', 'z'], + ['x', 'y', 'args_1', 'z'], [inp.op.name for inp in variadic_op.inputs]) self.assertEqual( - [b'x', b'y', b'args', b'z'], + [b'x', b'y', b'args_1', b'z'], [inp.op.get_attr('_user_specified_name') for inp in variadic_op.inputs]) diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index 5202e9b8f9..36f7aa67c7 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -63,11 +63,20 @@ class UnknownArgument(object): pass -def convert_structure_to_signature(structure): +# TODO(vbardiovsky): Remove this when nest is updated with new +# flatten_with_tuple_paths. +def flatten_with_tuple_paths(structure): + return list(zip(nest.yield_flat_paths(structure), nest.flatten(structure))) + + +def convert_structure_to_signature(structure, arg_names=None): """Convert a potentially nested structure to a signature. Args: - structure: Structure to convert. + structure: Structure to convert, where top level collection is a list or a + tuple. + arg_names: Optional list of arguments that has equal number of elements as + `structure` and is used for naming corresponding TensorSpecs. Returns: Identical structure that has TensorSpec objects instead of Tensors and @@ -84,8 +93,19 @@ def convert_structure_to_signature(structure): # We are using the flattened paths to name the TensorSpecs. We need an # explicit name for them downstream. - flattened_with_paths = nest.flatten_with_joined_string_paths(structure) - mapped = [encode_arg(arg, path) for path, arg in flattened_with_paths] + flattened = flatten_with_tuple_paths(structure) + if arg_names: + if len(arg_names) != len(structure): + raise ValueError( + "Passed in arg_names don't match actual signature (%s)." % arg_names) + # Replace all top-level names with their actual arg_names. If a path before + # was "(2,'a',1)", it will become "(arg_names[2],'a',1)". + flattened = [((arg_names[0],) + path[1:], arg) for path, arg in flattened] + + mapped = [ + encode_arg(arg, "/".join([str(p) for p in path])) + for path, arg in flattened + ] return nest.pack_sequence_as(structure, mapped) @@ -100,9 +120,9 @@ class FuncGraph(ops.Graph): inputs coming first. outputs: Tensors that will be returned by this function. The tensors are in this FuncGraph. - structured_input_signature: A possibly-nested python object that was - received by this function. Note that this structure might contain Python - `None`s. + 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. structured_outputs: A possibly-nested python object which will be returned by this function. The Tensors in this structure are the same as those of self.outputs. Note that this structure might contain Python `None`s. @@ -443,8 +463,11 @@ def func_graph_from_py_func(name, func_kwargs = _get_defun_inputs_from_kwargs(kwargs) # Convert all Tensors into TensorSpecs before saving the structured inputs. + # If storing pure concrete functions that are not called through polymorphic + # functions, we don't have access to FunctionSpec, so we need to call the + # TensorSpecs by their `arg_names` for later binding. func_graph.structured_input_signature = ( - convert_structure_to_signature(func_args), + convert_structure_to_signature(func_args, arg_names), convert_structure_to_signature(func_kwargs)) # Note: `nest.flatten` sorts by keys, as does `_deterministic_dict_values`. diff --git a/tensorflow/python/saved_model/function_deserialization.py b/tensorflow/python/saved_model/function_deserialization.py index 5479bbb355..ec5214824e 100644 --- a/tensorflow/python/saved_model/function_deserialization.py +++ b/tensorflow/python/saved_model/function_deserialization.py @@ -73,17 +73,23 @@ def _deserialize_function_spec(function_spec_proto, coder): def recreate_concrete_function(saved_concrete_function, concrete_functions): """Recreates a user-facing concrete function.""" + coder = nested_structure_coder.StructureCoder() + concrete_function = concrete_functions[saved_concrete_function.name] - input_signature = (saved_concrete_function.canonicalized_input_signature - .list_value.values) + input_signature = coder.decode_proto( + saved_concrete_function.canonicalized_input_signature) + input_signature_args, input_signature_kwargs = input_signature + if input_signature_kwargs: + raise ValueError("Restoring concrete function with non-empty kwargs (%s)." % + input_signature_kwargs) + # pylint: disable=protected-access # Set metadata required for the concrete function to accept keyword and # positional arguments in __call__. Normally this is set in # get_concrete_function. - concrete_function._arg_keywords = [ - spec.tensor_spec_value.name for spec in input_signature] + concrete_function._arg_keywords = [spec.name for spec in input_signature_args] # TODO(allenl): Should we preserve the number of allowed positional arguments? - concrete_function._num_positional_args = len(input_signature) + concrete_function._num_positional_args = len(input_signature_args) # pylint: enable=protected-access concrete_function.add_to_graph() return concrete_function @@ -120,25 +126,21 @@ def recreate_function(saved_function, concrete_functions): concrete_function.canonicalized_input_signature) try: - can_args, can_kwargs = function_spec.canonicalize_function_inputs( + canonicalized_inputs = function_spec.canonicalize_function_inputs( *args, **kwargs) - if can_kwargs: - # TODO(vbardiovsky): Enable this along with the structured input and - # structured output. - raise ValueError( - "Received keywords arguments that could not be bound: %s" % - kwargs) except ValueError: continue - if _inputs_compatible(can_args, + if _inputs_compatible(canonicalized_inputs, canonicalized_original_inputs): - flattened_inputs = nest.flatten(can_args) + flattened_inputs = nest.flatten(canonicalized_inputs) filtered_inputs = [t for t in flattened_inputs if _is_tensor(t)] return function_obj._call_flat(filtered_inputs) # pylint: disable=protected-access raise AssertionError( - "Could not find matching function to call for arguments: %s" % (args,)) + "Could not find matching function to call for args %r and kwargs %r" % + (args, kwargs)) + return restored_function diff --git a/tensorflow/python/saved_model/function_serialization.py b/tensorflow/python/saved_model/function_serialization.py index c79c61b60c..172ab41dc8 100644 --- a/tensorflow/python/saved_model/function_serialization.py +++ b/tensorflow/python/saved_model/function_serialization.py @@ -19,7 +19,6 @@ from __future__ import division from __future__ import print_function from tensorflow.python.framework import func_graph as func_graph_module -from tensorflow.python.framework import tensor_spec from tensorflow.python.platform import tf_logging as logging from tensorflow.python.saved_model import nested_structure_coder from tensorflow.python.saved_model import saved_object_graph_pb2 @@ -39,8 +38,7 @@ def _serialize_function_spec(function_spec, coder): return proto -def _serialize_concrete_function_with_signature( - concrete_function, node_ids, signature_args, coder): +def serialize_concrete_function(concrete_function, node_ids): """Build a SavedConcreteFunction.""" bound_inputs = [] try: @@ -53,10 +51,11 @@ def _serialize_concrete_function_with_signature( "captures tensor %s which is unsupported or not reachable from root.", concrete_function.name, capture) return None + coder = nested_structure_coder.StructureCoder() concrete_function_proto = saved_object_graph_pb2.SavedConcreteFunction() concrete_function_proto.name = concrete_function.name concrete_function_proto.canonicalized_input_signature.CopyFrom( - coder.encode_structure(signature_args)) + coder.encode_structure(concrete_function.structured_input_signature)) structured_outputs = func_graph_module.convert_structure_to_signature( concrete_function.structured_outputs) concrete_function_proto.output_signature.CopyFrom( @@ -65,20 +64,6 @@ def _serialize_concrete_function_with_signature( return concrete_function_proto -def serialize_concrete_function(concrete_function, node_ids): - """Build a standalone SavedConcreteFunction.""" - coder = nested_structure_coder.StructureCoder() - signature = [] - # Construct a flat list of TensorSpecs for this concrete function. On load - # we'll parse them and recreate the call metadata. - for input_tensor, input_keyword in zip( - concrete_function.inputs, concrete_function._arg_keywords): # pylint: disable=protected-access - signature.append(tensor_spec.TensorSpec( - input_tensor.shape, input_tensor.dtype, name=input_keyword)) - return _serialize_concrete_function_with_signature( - concrete_function, node_ids, signature, coder) - - def serialize_function(function, node_ids): """Build a SavedFunction proto.""" coder = nested_structure_coder.StructureCoder() @@ -89,11 +74,8 @@ def serialize_function(function, node_ids): all_concrete_functions = \ function._list_all_concrete_functions_for_serialization() # pylint: disable=protected-access for concrete_function in all_concrete_functions: - signature_args, signature_kwargs = \ - concrete_function.structured_input_signature - del signature_kwargs - concrete_function_proto = _serialize_concrete_function_with_signature( - concrete_function, node_ids, signature_args, coder) + concrete_function_proto = serialize_concrete_function( + concrete_function, node_ids) if concrete_function_proto is not None: proto.concrete_function.add().CopyFrom(concrete_function_proto) return proto diff --git a/tensorflow/python/saved_model/load_test.py b/tensorflow/python/saved_model/load_test.py index e3dcbda50a..280a26a0a5 100644 --- a/tensorflow/python/saved_model/load_test.py +++ b/tensorflow/python/saved_model/load_test.py @@ -284,6 +284,28 @@ class LoadTest(test.TestCase): self.assertEqual(7, imported.f(constant_op.constant(2)).numpy()) self.assertEqual(6, imported.f(constant_op.constant(1), defg=7.0).numpy()) + def test_additional_kwargs(self): + def func(x, training=False, **options): + del options + if training: + return 2 * x + else: + return 7 + + root = tracking.AutoCheckpointable() + root.f = def_function.function(func) + + x = constant_op.constant(10) + self.assertEqual(7, root.f(x, learning_rate=0.5, epochs=3).numpy()) + + imported = self.cycle(root) + + with self.assertRaisesRegexp(AssertionError, + "Could not find matching function to call.*"): + imported.f(x, learning_rate=0.5, epochs=4) + + self.assertEqual(7, imported.f(x, learning_rate=0.5, epochs=3).numpy()) + def test_member_function(self): class CheckpointableWithMember(tracking.AutoCheckpointable): @@ -438,6 +460,23 @@ class LoadTest(test.TestCase): self.assertAllEqual([2, 4, 6], imported.f(constant_op.constant([1, 2, 3])).numpy()) + def test_concrete_function_arg_names(self): + + @def_function.function( + input_signature=[tensor_spec.TensorSpec([None], dtypes.int32)]) + def func(x): + return 2 * x + + root = tracking.AutoCheckpointable() + root.f = func.get_concrete_function() + + self.assertAllEqual([2], root.f(constant_op.constant([1])).numpy()) + + imported = self.cycle(root) + + self.assertAllEqual([2, 4, 6], + imported.f(x=constant_op.constant([1, 2, 3])).numpy()) + def test_concrete_function_no_signature(self): @def_function.function def func(x): -- GitLab From 452907059d5844831b15d4202574a3b6be5d8b91 Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Thu, 17 Jan 2019 10:22:56 -0800 Subject: [PATCH 0858/2345] Cleanup: remove unused code. PiperOrigin-RevId: 229772701 --- tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc index f90cdac359..c8739b46c1 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc @@ -364,9 +364,6 @@ TEST(TRT_TensorOrWeights_Test, Basic) { EXPECT_EQ(false, ptr->is_tensor()); EXPECT_EQ(true, ptr->is_weights()); EXPECT_TRUE(TrtShapedWeightsEquals(weights, ptr->weights())); - - nvinfer1::Dims dims; - dims.nbDims = 0; ExpectTrtDimsEqualsArray({}, ptr->GetTrtDims()); } } -- GitLab From a8dbd3a680b06f617baef53dfad47a10e642f0ca Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Thu, 17 Jan 2019 10:31:28 -0800 Subject: [PATCH 0859/2345] Do not override `steps_per_epoch` if it is not None. Currently if you pass a tf.keras.preprocessing.image.ImageDataGenerator to fit, with a `steps_per_epoch`, your manual `steps_per_epoch` is overridden with the real value. Fix it to only override the value if the `steps_per_epoch` was not manually set. PiperOrigin-RevId: 229774626 --- tensorflow/python/keras/engine/training_generator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/keras/engine/training_generator.py b/tensorflow/python/keras/engine/training_generator.py index 23b5d7e8c5..b8defad8b2 100644 --- a/tensorflow/python/keras/engine/training_generator.py +++ b/tensorflow/python/keras/engine/training_generator.py @@ -410,7 +410,9 @@ def convert_to_generator_like(data, and may be `None` or `[None]`. batch_size: Used when creating a generator out of tuples of NumPy arrays or EagerTensors. - steps_per_epoch: Steps of the generator to run each epoch. + steps_per_epoch: Steps of the generator to run each epoch. If `None` the + number of steps will be read from the data (for + `keras.utils.data_utils.Sequence` types). epochs: Total number of epochs to run. shuffle: Whether the data should be shuffled. @@ -431,7 +433,8 @@ def convert_to_generator_like(data, if data_utils.is_generator_or_sequence(data) or isinstance( data, iterator_ops.EagerIterator): if isinstance(data, data_utils.Sequence): - steps_per_epoch = len(data) + if steps_per_epoch is None: + steps_per_epoch = len(data) return data, steps_per_epoch if isinstance(data, dataset_ops.DatasetV2): return dataset_ops.make_one_shot_iterator(data), steps_per_epoch -- GitLab From 5f98ba7eba6538f6cb389fa7326a9549dcd1299a Mon Sep 17 00:00:00 2001 From: Andr? Susano Pinto Date: Thu, 17 Jan 2019 10:34:59 -0800 Subject: [PATCH 0860/2345] Refactor registering gradient of ConcreteFunction. PiperOrigin-RevId: 229775288 --- tensorflow/python/eager/function.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index d17c7020f3..9c3e20f936 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -506,26 +506,25 @@ class ConcreteFunction(object): if context.executing_eagerly() or not self.outputs: outputs = self._inference_function.call(ctx, args) else: - if not self._gradient_name: - self._gradient_name = "PartitionedCall-%s" % ops.uid() - self._register_gradient(self._gradient_name) + self._register_gradient() with ops.get_default_graph().gradient_override_map( {"PartitionedCall": self._gradient_name, "StatefulPartitionedCall": self._gradient_name}): outputs = self._inference_function.call(ctx, args) return self._build_call_outputs(outputs) - def _register_gradient(self, name): - """Registers the gradient for this `ConcreteFunction` under the given name. + def _register_gradient(self): + """Registers the gradient for this `ConcreteFunction`. The gradient rewrites an inference call op to a forward call op, but does not modify a pre-existing forward call op. It then computes the gradient from the output's gradients and the side outputs of the forward op. - - Args: - name: The name to register the gradient as. """ - @ops.RegisterGradient(name) + if self._gradient_name: + return + self._gradient_name = "PartitionedCall-%s" % ops.uid() + + @ops.RegisterGradient(self._gradient_name) def _registered_grad_fn(op, *doutputs): # pylint: disable=unused-variable return self._grad_fn(op, *doutputs) @@ -724,9 +723,7 @@ class ConcreteFunction(object): ctx = context.context() - if not self._gradient_name: - self._gradient_name = "PartitionedCall-%s" % ops.uid() - self._register_gradient(self._gradient_name) + self._register_gradient() with ops.get_default_graph().gradient_override_map( {"PartitionedCall": self._gradient_name, "StatefulPartitionedCall": self._gradient_name}): @@ -773,9 +770,7 @@ class ConcreteFunction(object): """ ctx = context.context() - if not self._gradient_name: - self._gradient_name = "PartitionedCall-%s" % ops.uid() - self._register_gradient(self._gradient_name) + self._register_gradient() with ops.get_default_graph().gradient_override_map( {"PartitionedCall": self._gradient_name, "StatefulPartitionedCall": self._gradient_name}): -- GitLab From 0f39e8a552018eba3bd2916c0a72467617bebf88 Mon Sep 17 00:00:00 2001 From: Haoyu Zhang Date: Thu, 17 Jan 2019 10:41:18 -0800 Subject: [PATCH 0861/2345] Reuse existing dataset iterators in Keras for better memory efficiency. * In Keras model iteration, create only one iterator for training dataset and one iterator for validation datasets. * If training requires dataset to be reset at each epoch, reinitialize the training iterator; * If we run multiple validation passes between training epochs, reinitialize the validation iterator. Compared to recreating iterators, this approach reduces host memory usage and prevents memory leak. PiperOrigin-RevId: 229776545 --- .../engine/distributed_training_utils.py | 10 ++++- .../python/keras/engine/training_arrays.py | 38 ++++++++++++++++--- .../python/keras/engine/training_utils.py | 18 +++++++-- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/tensorflow/python/keras/engine/distributed_training_utils.py b/tensorflow/python/keras/engine/distributed_training_utils.py index 27ad6a5c0b..d13e0b6fa9 100644 --- a/tensorflow/python/keras/engine/distributed_training_utils.py +++ b/tensorflow/python/keras/engine/distributed_training_utils.py @@ -34,6 +34,7 @@ from tensorflow.python.keras import callbacks from tensorflow.python.keras import metrics as metrics_module from tensorflow.python.keras import optimizers from tensorflow.python.keras.optimizer_v2 import optimizer_v2 +from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import variables from tensorflow.python.platform import tf_logging as logging @@ -528,10 +529,15 @@ def list_to_tuple(maybe_list): def get_iterator(dataset, distribution_strategy): with distribution_strategy.scope(): iterator = distribution_strategy.make_dataset_iterator(dataset) - init_op = iterator.initialize() + initialize_iterator(iterator, distribution_strategy) + return iterator + + +def initialize_iterator(iterator, distribution_strategy): + with distribution_strategy.scope(): + init_op = control_flow_ops.group(iterator.initialize()) if not context.executing_eagerly(): K.get_session().run(init_op) - return iterator def _get_input_from_iterator(iterator, model): diff --git a/tensorflow/python/keras/engine/training_arrays.py b/tensorflow/python/keras/engine/training_arrays.py index 3b6a13839a..d0881c11a5 100644 --- a/tensorflow/python/keras/engine/training_arrays.py +++ b/tensorflow/python/keras/engine/training_arrays.py @@ -24,6 +24,7 @@ import functools import numpy as np from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.data.ops import iterator_ops from tensorflow.python.eager import context from tensorflow.python.framework import errors from tensorflow.python.keras import backend as K @@ -119,18 +120,22 @@ def model_iteration(model, # In case we were passed a dataset, we extract symbolic tensors from it. reset_dataset_after_each_epoch = False - original_dataset = None + input_iterator = None is_dataset = isinstance(inputs, (dataset_ops.DatasetV1, dataset_ops.DatasetV2)) # TODO(fchollet): consider moving `steps_per_epoch` inference to # _standardize_user_data and set reset_dataset_after_each_epoch as an # attribute on the dataset instance. if is_dataset: - original_dataset = inputs if steps_per_epoch is None: reset_dataset_after_each_epoch = True steps_per_epoch = training_utils.infer_steps_for_dataset( inputs, steps_per_epoch, epochs=epochs, steps_name=steps_name) + input_iterator = _get_iterator(inputs, model._distribution_strategy) + + val_iterator = None + if isinstance(val_inputs, (dataset_ops.DatasetV1, dataset_ops.DatasetV2)): + val_iterator = _get_iterator(val_inputs, model._distribution_strategy) if mode == ModeKeys.TRAIN: _print_train_info(inputs, val_inputs, steps_per_epoch, verbose) @@ -150,6 +155,7 @@ def model_iteration(model, convert_eager_tensors_to_numpy((inputs, targets, sample_weights)) # Prepare input data. + inputs = input_iterator or inputs ins = _prepare_feed_values(model, inputs, targets, sample_weights, mode) if not is_dataset: num_samples_or_steps = _get_num_samples_or_steps(ins, batch_size, @@ -230,7 +236,7 @@ def model_iteration(model, actual_inputs = ins() if callable(ins) else ins batch_outs = f(actual_inputs) except errors.OutOfRangeError: - if original_dataset is None: + if not is_dataset: # We ran out of batches while the user passed an iterator (legacy). logging.warning( 'Your dataset iterator ran out of data; ' @@ -332,6 +338,7 @@ def model_iteration(model, if (do_validation and training_utils.should_run_validation(validation_freq, epoch) and not callbacks.model.stop_training): + val_inputs = val_iterator or val_inputs val_results = model_iteration( model, val_inputs, @@ -348,15 +355,18 @@ def model_iteration(model, val_results = [val_results] epoch_logs = cbks.make_logs( model, epoch_logs, val_results, mode, prefix='val_') + if val_iterator and epoch < epochs - 1: + _reinitialize_iterator(val_iterator, model._distribution_strategy) if mode == ModeKeys.TRAIN: # Epochs only apply to `fit`. callbacks.on_epoch_end(epoch, epoch_logs) progbar.on_epoch_end(epoch, epoch_logs) - # Recreate dataset iterator for the next epoch. + # Reinitialize dataset iterator for the next epoch. if reset_dataset_after_each_epoch and epoch < epochs - 1: - ins = _prepare_feed_values(model, original_dataset, None, None, mode) + _reinitialize_iterator(input_iterator, model._distribution_strategy) + ins = _prepare_feed_values(model, input_iterator, None, None, mode) callbacks._call_end_hook(mode) @@ -430,7 +440,8 @@ def _prepare_feed_values(model, inputs, targets, sample_weights, mode): else: return get_distributed_inputs() - if isinstance(inputs, (dataset_ops.DatasetV1, dataset_ops.DatasetV2)): + if isinstance(inputs, (dataset_ops.DatasetV1, dataset_ops.DatasetV2, + iterator_ops.Iterator)): inputs, targets, sample_weights = model._standardize_user_data( inputs, extract_tensors_from_dataset=True) @@ -445,6 +456,21 @@ def _prepare_feed_values(model, inputs, targets, sample_weights, mode): return ins +def _get_iterator(inputs, distribution_strategy=None): + if distribution_strategy: + return distributed_training_utils.get_iterator( + inputs, distribution_strategy) + return training_utils.get_iterator(inputs) + + +def _reinitialize_iterator(iterator, distribution_strategy=None): + if distribution_strategy: + distributed_training_utils.initialize_iterator( + iterator, distribution_strategy) + else: + training_utils.initialize_iterator(iterator) + + def _make_execution_function(model, mode): """Makes function to run one step of model execution.""" if model._distribution_strategy: diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py index 3affb84147..7e5bc08e5e 100644 --- a/tensorflow/python/keras/engine/training_utils.py +++ b/tensorflow/python/keras/engine/training_utils.py @@ -1232,6 +1232,19 @@ def is_dataset_or_iterator(data): iterator_ops.Iterator)) +def get_iterator(dataset): + """Create and initialize an iterator from a dataset.""" + iterator = dataset_ops.make_initializable_iterator(dataset) + initialize_iterator(iterator) + return iterator + + +def initialize_iterator(iterator): + init_op = iterator.initializer + if not context.executing_eagerly(): + K.get_session().run(init_op) + + def extract_tensors_from_dataset(dataset): """Extract a tuple of tensors `inputs, targets, sample_weight` from a dataset. @@ -1241,10 +1254,7 @@ def extract_tensors_from_dataset(dataset): Returns: Tuple of tensors `x, y, weights`. `y` and `weights` entry may be None. """ - iterator = dataset_ops.make_initializable_iterator(dataset) - init_op = iterator.initializer - if not context.executing_eagerly(): - K.get_session().run(init_op) + iterator = get_iterator(dataset) inputs, targets, sample_weight = unpack_iterator_input(iterator) return inputs, targets, sample_weight -- GitLab From 887bcfa5116a11c1ee534b99cc1c118c29ff7e46 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Thu, 17 Jan 2019 10:51:03 -0800 Subject: [PATCH 0862/2345] [XLA] Fix a bug in dynamic parameter binding. PiperOrigin-RevId: 229778432 --- tensorflow/compiler/xla/service/dynamic_parameter_binding.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/dynamic_parameter_binding.cc b/tensorflow/compiler/xla/service/dynamic_parameter_binding.cc index 3d0eddb0d8..5549cccfa8 100644 --- a/tensorflow/compiler/xla/service/dynamic_parameter_binding.cc +++ b/tensorflow/compiler/xla/service/dynamic_parameter_binding.cc @@ -70,7 +70,7 @@ StatusOr DynamicParameterBinding::CreateFromProto( int64 target_param_num = binding.target_param_num(); ShapeIndex target_param_index(binding.target_param_index().begin(), binding.target_param_index().end()); - int64 target_dim_num = binding.target_param_num(); + int64 target_dim_num = binding.target_param_dim_num(); TF_RETURN_IF_ERROR( result.Bind(DynamicParameter{dynamic_param_num, dynamic_param_index}, -- GitLab From 8f275bdcede1de92ef2ec72324627ab5fc449a21 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 17 Jan 2019 10:54:21 -0800 Subject: [PATCH 0863/2345] Fixes bug preventing cond_v2 from working with gradient tapes inside functions. PiperOrigin-RevId: 229779015 --- .../python/kernel_tests/cond_v2_test.py | 22 +++++++++++++++++++ tensorflow/python/ops/cond_v2.py | 21 ++++++++++-------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/tensorflow/python/kernel_tests/cond_v2_test.py b/tensorflow/python/kernel_tests/cond_v2_test.py index 050da5ff6c..356c6f0a16 100644 --- a/tensorflow/python/kernel_tests/cond_v2_test.py +++ b/tensorflow/python/kernel_tests/cond_v2_test.py @@ -20,7 +20,9 @@ from __future__ import division from __future__ import print_function from tensorflow.core.protobuf import config_pb2 +from tensorflow.python.eager import backprop from tensorflow.python.eager import context +from tensorflow.python.eager import def_function from tensorflow.python.eager import function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes @@ -628,6 +630,26 @@ class CondV2Test(test.TestCase): # d2[x]/dx2 = 0 self.assertEqual(false_val, [0.0]) + def testGradientTapeOfCondWithResourceVariableInFunction(self): + with context.eager_mode(): + v = variables.Variable(2.) + + @def_function.function + def fnWithCond(): # pylint: disable=invalid-name + with backprop.GradientTape() as tape: + pred = constant_op.constant(True, dtype=dtypes.bool) + + def true_fn(): + return math_ops.pow(v, 3) + + def false_fn(): + return v + + cond = cond_v2.cond_v2(pred, true_fn, false_fn, name="cond") + return tape.gradient(cond, v) + + self.assertAllEqual(fnWithCond(), 12.0) + def testLowering(self): with ops.Graph().as_default() as g: with self.session(graph=g) as sess: diff --git a/tensorflow/python/ops/cond_v2.py b/tensorflow/python/ops/cond_v2.py index 5dca8e501b..6e8f2df39b 100644 --- a/tensorflow/python/ops/cond_v2.py +++ b/tensorflow/python/ops/cond_v2.py @@ -91,11 +91,13 @@ def cond_v2(pred, true_fn, false_fn, name="cond"): @ops.RegisterGradient("If") def _IfGrad(op, *grads): # pylint: disable=invalid-name """The gradient of an If op produced by cond_v2.""" - true_graph, false_graph = _get_func_graphs(op) + # Get the if operator (this logic handles the case where op is a MockOp) + if_op = op.outputs[0].op + true_graph, false_graph = _get_func_graphs(if_op) # Note: op.graph != ops.get_default_graph() when we are computing the gradient # of a nested cond. - assert true_graph.outer_graph == op.graph - assert false_graph.outer_graph == op.graph + assert true_graph.outer_graph == if_op.graph + assert false_graph.outer_graph == if_op.graph # Create grad functions that compute the gradient of the true/false forward # graphs. These functions will capture tensors from the forward pass @@ -140,11 +142,12 @@ def _IfGrad(op, *grads): # pylint: disable=invalid-name true_graph.name += "_rewritten" false_graph.name += "_rewritten" - op._set_func_attr("then_branch", util.create_new_tf_function(true_graph)) - op._set_func_attr("else_branch", util.create_new_tf_function(false_graph)) - op._set_type_list_attr("Tout", true_graph.output_types) - op._set_shape_list_attr("output_shapes", true_graph.output_shapes) - op._add_outputs( + if_op._set_func_attr("then_branch", util.create_new_tf_function(true_graph)) + if_op._set_func_attr("else_branch", + util.create_new_tf_function(false_graph)) + if_op._set_type_list_attr("Tout", true_graph.output_types) + if_op._set_shape_list_attr("output_shapes", true_graph.output_shapes) + if_op._add_outputs( [t.dtype for t in extra_true_outputs], [t.shape for t in extra_true_outputs]) @@ -153,7 +156,7 @@ def _IfGrad(op, *grads): # pylint: disable=invalid-name true_grad_inputs = _resolve_grad_inputs(true_graph, true_grad_graph) false_grad_inputs = _resolve_grad_inputs(false_graph, false_grad_graph) - outputs = _build_cond(op.inputs[0], true_grad_graph, false_grad_graph, + outputs = _build_cond(if_op.inputs[0], true_grad_graph, false_grad_graph, true_grad_inputs, false_grad_inputs) # The predicate has no gradient. -- GitLab From d0cc3477ff5e3d9dd71abb2656a2a2b5fb39d04c Mon Sep 17 00:00:00 2001 From: Russell Power Date: Thu, 17 Jan 2019 11:03:31 -0800 Subject: [PATCH 0864/2345] TPUEstimator: Add export_to_cpu option to enable/disable CPU export. Not all TPU ops are supported on the CPU (in particular some bfloat16 operations). These models will fail to export on the CPU. This CL adds a flag to allow users to disable CPU export in this case. PiperOrigin-RevId: 229780875 --- .../contrib/tpu/python/tpu/tpu_estimator.py | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py index 79340a9f21..022ac4152d 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py @@ -2143,6 +2143,7 @@ class TPUEstimator(estimator_lib.Estimator): 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): """Constructs an `TPUEstimator` instance. @@ -2187,9 +2188,11 @@ class TPUEstimator(estimator_lib.Estimator): 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 besides the one on CPU. 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. + 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 @@ -2264,6 +2267,7 @@ class TPUEstimator(estimator_lib.Estimator): self._config, train_batch_size, eval_batch_size, predict_batch_size, use_tpu, eval_on_tpu) + 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) @@ -2284,14 +2288,18 @@ class TPUEstimator(estimator_lib.Estimator): 'when `export_to_tpu` is `True`; Mode {} will be ignored ' 'for TPU.'.format(mode)) - (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 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 = { @@ -2299,15 +2307,20 @@ class TPUEstimator(estimator_lib.Estimator): } 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=False, + save_variables=save_variables, mode=mode, export_tags=export_tags, - check_variables=False)) + check_variables=check_variables)) def _call_model_fn(self, features, labels, mode, config): if mode == _REWRITE_FOR_INFERENCE_MODE: -- GitLab From 568b9e56038d2d2fe8927f9e0b538bf5e49116f6 Mon Sep 17 00:00:00 2001 From: Yu-Cheng Ling Date: Thu, 17 Jan 2019 11:03:53 -0800 Subject: [PATCH 0865/2345] Fix Toco IdentifyL2Normalization bugs PiperOrigin-RevId: 229780959 --- tensorflow/lite/build_def.bzl | 1 + tensorflow/lite/testing/generate_examples.py | 30 +++++++++++++++++++ .../identify_l2_normalization.cc | 16 +++------- tensorflow/lite/toco/tooling_util.cc | 2 +- tensorflow/lite/toco/tooling_util.h | 2 +- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/tensorflow/lite/build_def.bzl b/tensorflow/lite/build_def.bzl index 3c48b655f7..7e70503386 100644 --- a/tensorflow/lite/build_def.bzl +++ b/tensorflow/lite/build_def.bzl @@ -253,6 +253,7 @@ def generated_test_models(): "greater_equal", "sum", "l2norm", + "l2norm_shared_epsilon", "l2_pool", "leaky_relu", "less", diff --git a/tensorflow/lite/testing/generate_examples.py b/tensorflow/lite/testing/generate_examples.py index d2c275f787..12b5a8b210 100644 --- a/tensorflow/lite/testing/generate_examples.py +++ b/tensorflow/lite/testing/generate_examples.py @@ -1424,6 +1424,36 @@ def make_conv_tests(zip_path): make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) +# Note: This is a regression test for a bug (b/122651451) that Toco incorrectly +# erases the reduction indices array while it's shared with other ops. +def make_l2norm_shared_epsilon_tests(zip_path): + """Regression test for a bug (b/122651451).""" + + # Chose a set of parameters + test_parameters = [{ + "input_shape": [[5, 7]], + "dim": [1], + "epsilon": [1e-8], + }] + + def build_graph(parameters): + input_tensor = tf.placeholder( + dtype=tf.float32, name="input", shape=parameters["input_shape"]) + epsilon = tf.constant(parameters["epsilon"]) + out1 = tf.nn.l2_normalize(input_tensor, parameters["dim"], epsilon=epsilon) + out2 = tf.nn.l2_normalize(input_tensor, parameters["dim"], epsilon=epsilon) + out = out1 + out2 + return [input_tensor], [out] + + def build_inputs(parameters, sess, inputs, outputs): + input_values = create_tensor_data( + np.float32, parameters["input_shape"], min_value=-4, max_value=10) + return [input_values], sess.run( + outputs, feed_dict=dict(zip(inputs, [input_values]))) + + make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) + + # Note: This is a regression test for a bug (b/112436267) that Toco incorrectly # fuses weights when multiple Conv2D/FULLY_CONNECTED ops share the same constant # weight tensor. diff --git a/tensorflow/lite/toco/graph_transformations/identify_l2_normalization.cc b/tensorflow/lite/toco/graph_transformations/identify_l2_normalization.cc index dabd4bd209..3b7c88ac62 100644 --- a/tensorflow/lite/toco/graph_transformations/identify_l2_normalization.cc +++ b/tensorflow/lite/toco/graph_transformations/identify_l2_normalization.cc @@ -151,20 +151,12 @@ std::vector>::iterator FindOperator( // Erase the subgraph that is now replaced by L2Normalization model->operators.erase(FindOperator(model, square_op)); - model->EraseArray(sum_op->inputs[0]); - if (sum_op->inputs.size() > 1) { - model->EraseArray(sum_op->inputs[1]); - } - model->operators.erase(FindOperator(model, sum_op)); + DeleteOpAndArraysIfUnused(model, sum_op); if (add_op) { - model->EraseArray(add_op->inputs[0]); - model->EraseArray(add_op->inputs[1]); - model->operators.erase(FindOperator(model, add_op)); + DeleteOpAndArraysIfUnused(model, add_op); } - model->EraseArray(sqrt_or_rsqrt_op->inputs[0]); - model->operators.erase(FindOperator(model, sqrt_or_rsqrt_op)); - model->EraseArray(div_or_mul_op->inputs[1]); - model->operators.erase(FindOperator(model, div_or_mul_op)); + DeleteOpAndArraysIfUnused(model, sqrt_or_rsqrt_op); + DeleteOpAndArraysIfUnused(model, div_or_mul_op); *modified = true; return ::tensorflow::Status::OK(); } diff --git a/tensorflow/lite/toco/tooling_util.cc b/tensorflow/lite/toco/tooling_util.cc index 9c0e8067d8..2c98ff1d92 100644 --- a/tensorflow/lite/toco/tooling_util.cc +++ b/tensorflow/lite/toco/tooling_util.cc @@ -173,7 +173,7 @@ bool DeleteArrayIfUsedOnce(const string& array_name, Model* model) { return false; } -void DeleteOpAndArraysIfUnused(Model* model, Operator* op) { +void DeleteOpAndArraysIfUnused(Model* model, const Operator* op) { for (const string& array_name : op->inputs) { DeleteArrayIfUsedOnce(array_name, model); } diff --git a/tensorflow/lite/toco/tooling_util.h b/tensorflow/lite/toco/tooling_util.h index 53131824b5..517da784d0 100644 --- a/tensorflow/lite/toco/tooling_util.h +++ b/tensorflow/lite/toco/tooling_util.h @@ -72,7 +72,7 @@ bool DeleteArrayIfUsedOnce(const string& array_name, Model* model); // Deletes the op and any of its input and output arrays if they are unused // after the op has been deleted. -void DeleteOpAndArraysIfUnused(Model* model, Operator* op); +void DeleteOpAndArraysIfUnused(Model* model, const Operator* op); std::vector>::const_iterator FindOpWithOutput( const Model& model, const string& array_name); -- GitLab From 2db14a0ffcb3cd46211a0da8baae2be2afe02b45 Mon Sep 17 00:00:00 2001 From: Andy Ly Date: Thu, 17 Jan 2019 11:06:51 -0800 Subject: [PATCH 0866/2345] [Grappler] Add validation in MutableGraphView.DeleteNodes(). PiperOrigin-RevId: 229781551 --- tensorflow/core/grappler/BUILD | 1 + .../core/grappler/mutable_graph_view.cc | 132 +++++++++-- tensorflow/core/grappler/mutable_graph_view.h | 43 +++- .../core/grappler/mutable_graph_view_test.cc | 218 ++++++++++++++---- .../core/grappler/optimizers/data/BUILD | 10 + .../grappler/optimizers/data/filter_fusion.cc | 5 +- .../optimizers/data/graph_utils_test.cc | 14 +- .../optimizers/data/hoist_random_uniform.cc | 5 +- .../optimizers/data/make_numa_aware.cc | 5 +- .../optimizers/data/map_and_batch_fusion.cc | 5 +- .../optimizers/data/map_and_filter_fusion.cc | 5 +- .../grappler/optimizers/data/map_fusion.cc | 5 +- .../optimizers/data/map_parallelization.cc | 5 +- .../optimizers/data/map_vectorization.cc | 5 +- .../optimizers/data/noop_elimination.cc | 5 +- .../data/shuffle_and_repeat_fusion.cc | 5 +- 16 files changed, 379 insertions(+), 89 deletions(-) diff --git a/tensorflow/core/grappler/BUILD b/tensorflow/core/grappler/BUILD index bd95e89878..d5266247cf 100644 --- a/tensorflow/core/grappler/BUILD +++ b/tensorflow/core/grappler/BUILD @@ -211,6 +211,7 @@ cc_library( ":utils", "//tensorflow/core:graph", "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", diff --git a/tensorflow/core/grappler/mutable_graph_view.cc b/tensorflow/core/grappler/mutable_graph_view.cc index 2ba53a685d..e31eedf166 100644 --- a/tensorflow/core/grappler/mutable_graph_view.cc +++ b/tensorflow/core/grappler/mutable_graph_view.cc @@ -19,6 +19,7 @@ limitations under the License. #include #include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" #include "tensorflow/core/framework/graph.pb.h" @@ -29,6 +30,7 @@ limitations under the License. #include "tensorflow/core/grappler/utils.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/stringpiece.h" +#include "tensorflow/core/lib/gtl/map_util.h" #include "tensorflow/core/platform/types.h" namespace tensorflow { @@ -134,7 +136,8 @@ void MutableGraphView::AddAndDedupFanouts(NodeDef* node) { controlling_fanins.contains(input_node_name))); if (!gtl::InsertIfNotPresent(&fanins, input_node_name) && can_dedup_control) { - node->mutable_input()->SwapElements(pos, last_pos--); + node->mutable_input()->SwapElements(pos, last_pos); + --last_pos; } else { OutputPort output(nodes()[input_node_name], tensor_id.index()); @@ -506,10 +509,11 @@ bool MutableGraphView::RemoveRegularFaninInternal(NodeDef* node, auto fanouts_set = remove_input(fanin_port, i, /*update_max_port=*/false); fanouts_set->insert({node, curr_pos}); // Shift inputs to be retained. - mutable_inputs->SwapElements(i, curr_pos++); + mutable_inputs->SwapElements(i, curr_pos); + ++curr_pos; } else { // Skip inputs to be retained until first modification. - curr_pos++; + ++curr_pos; } } @@ -717,15 +721,103 @@ Status MutableGraphView::UpdateFanin(absl::string_view node_name, return Status::OK(); } -void MutableGraphView::DeleteNodes(const std::set& nodes_to_delete) { - // TODO(lyandy): Check if nodes have fanouts before deleting and return - // Status. - for (const string& node_name_to_delete : nodes_to_delete) - RemoveFaninsInternal(nodes().at(node_name_to_delete), - /*keep_controlling_fanins=*/false); - for (const string& node_name_to_delete : nodes_to_delete) +Status MutableGraphView::CheckNodesCanBeDeleted( + const absl::flat_hash_set& nodes_to_delete) { + std::vector missing_nodes; + std::vector nodes_with_fanouts; + for (const string& node_name_to_delete : nodes_to_delete) { + NodeDef* node = GetNode(node_name_to_delete); + if (node == nullptr) { + // Can't delete missing node. + missing_nodes.push_back(node_name_to_delete); + continue; + } + const int max_port = gtl::FindWithDefault(max_regular_output_port(), node, + Graph::kControlSlot); + for (int i = Graph::kControlSlot; i <= max_port; ++i) { + auto it = fanouts().find({node, i}); + bool has_retained_fanout = false; + if (it != fanouts().end()) { + for (const auto& fanout : it->second) { + // Check if fanouts are of nodes to be deleted, and if so, they can be + // ignored, as they will be removed also. + if (!nodes_to_delete.contains(fanout.node->name())) { + // Removing node will leave graph in an invalid state. + has_retained_fanout = true; + break; + } + } + } + if (has_retained_fanout) { + nodes_with_fanouts.push_back(node_name_to_delete); + break; + } + } + } + + // Error message can get quite long, so we only show the first 5 node names. + auto sort_and_sample = [](std::vector* s) { + constexpr int kMaxNodeNames = 5; + std::sort(s->begin(), s->end()); + if (s->size() > kMaxNodeNames) { + return absl::StrCat( + absl::StrJoin(s->begin(), s->begin() + kMaxNodeNames, ", "), ", ..."); + } + return absl::StrJoin(*s, ", "); + }; + + if (!missing_nodes.empty()) { + VLOG(1) << absl::Substitute("Attempting to delete missing node(s) [$0]", + sort_and_sample(&missing_nodes)); + } + if (!nodes_with_fanouts.empty()) { + std::vector input_node_names(nodes_to_delete.begin(), + nodes_to_delete.end()); + return errors::Internal(absl::Substitute( + "Can't delete node(s) with retained fanout(s) [$0] from node(s) [$1].", + sort_and_sample(&nodes_with_fanouts), + sort_and_sample(&input_node_names))); + } + + return Status::OK(); +} + +Status MutableGraphView::DeleteNodes( + const absl::flat_hash_set& nodes_to_delete) { + TF_RETURN_IF_ERROR(CheckNodesCanBeDeleted(nodes_to_delete)); + + // Find nodes in internal state and delete. + for (const string& node_name_to_delete : nodes_to_delete) { + NodeDef* node = GetNode(node_name_to_delete); + if (node != nullptr) { + RemoveFaninsInternal(node, /*keep_controlling_fanins=*/false); + RemoveFanoutsInternal(node); + } + } + for (const string& node_name_to_delete : nodes_to_delete) { nodes().erase(node_name_to_delete); - EraseNodesFromGraph(nodes_to_delete, graph()); + } + + // Find nodes in graph and delete by partitioning into nodes to retain and + // nodes to delete based on input set of nodes to delete by name. + // TODO(lyandy): Use a node name->idx hashmap if this is a performance + // bottleneck. + int pos = 0; + const int last_idx = graph()->node_size() - 1; + int last_pos = last_idx; + while (pos <= last_pos) { + if (nodes_to_delete.contains(graph()->node(pos).name())) { + graph()->mutable_node()->SwapElements(pos, last_pos); + --last_pos; + } else { + ++pos; + } + } + if (last_pos < last_idx) { + graph()->mutable_node()->DeleteSubrange(last_pos + 1, last_idx - last_pos); + } + + return Status::OK(); } void MutableGraphView::RemoveFaninsInternal(NodeDef* deleted_node, @@ -741,10 +833,22 @@ void MutableGraphView::RemoveFaninsInternal(NodeDef* deleted_node, input.node = deleted_node; input.port_id = IsTensorIdControlling(tensor_id) ? Graph::kControlSlot : i; - absl::flat_hash_set* fanouts_set = &fanouts()[fanin]; - fanouts_set->erase(input); - UpdateMaxRegularOutputPortForRemovedFanin(fanin, *fanouts_set); + auto it = fanouts().find(fanin); + if (it != fanouts().end()) { + absl::flat_hash_set* fanouts_set = &it->second; + fanouts_set->erase(input); + UpdateMaxRegularOutputPortForRemovedFanin(fanin, *fanouts_set); + } + } +} + +void MutableGraphView::RemoveFanoutsInternal(NodeDef* deleted_node) { + const int max_port = gtl::FindWithDefault(max_regular_output_port(), + deleted_node, Graph::kControlSlot); + for (int i = Graph::kControlSlot; i <= max_port; ++i) { + fanouts().erase({deleted_node, i}); } + max_regular_output_port().erase(deleted_node); } } // end namespace grappler diff --git a/tensorflow/core/grappler/mutable_graph_view.h b/tensorflow/core/grappler/mutable_graph_view.h index 59740f2c71..c62129bcad 100644 --- a/tensorflow/core/grappler/mutable_graph_view.h +++ b/tensorflow/core/grappler/mutable_graph_view.h @@ -101,6 +101,10 @@ class MutableGraphView : public internal::GraphViewInternal { // end up with: // // fanin -> Identity{N} -^> target node. + // + // If the control dependency being added is redundant (control dependency + // already exists or control dependency can be deduped from regular fanins), + // this will not result in an error and the node will not be modified. Status AddControllingFanin(absl::string_view node_name, const TensorId& fanin); @@ -108,29 +112,41 @@ class MutableGraphView : public internal::GraphViewInternal { // do not exist in the graph, nothing will be modified in the graph. If there // are multiple inputs that match the fanin, all of them will be removed. To // remove controlling fanins, use RemoveControllingFanin. + // + // If the fanin being removed doesn't exist in the node's inputs, this will + // not result in an error and the node will not be modified. Status RemoveRegularFanin(absl::string_view node_name, const TensorId& fanin); // Removes control dependency `fanin_node_name` from the target node named // `node_name`. If the node or fanin do not exist in the graph, nothing will // be modified in the graph. To remove regular fanins, use RemoveRegualrFanin. + // + // If the fanin being removed doesn't exist in the node's inputs, this will + // not result in an error and the node will not be modified. Status RemoveControllingFanin(absl::string_view node_name, absl::string_view fanin_node_name); // Removes all fanins from node `node_name`. Control dependencies will be // retained if keep_controlling_fanins is true. + // + // If no fanins are removed, this will not result in an error and the node + // will not be modified. Status RemoveAllFanins(absl::string_view node_name, bool keep_controlling_fanins); // Replaces all fanins `from_fanin` with `to_fanin` in node `node_name`. If // the fanins or node do not exist, nothing will be modified in the graph. // Control dependencies will be deduped. + // + // If the fanin being updated doesn't exist in the node's inputs, this will + // not result in an error and the node will not be modified. Status UpdateFanin(absl::string_view node_name, const TensorId& from_fanin, const TensorId& to_fanin); - // Deletes nodes from the graph. - // TODO(lyandy): Check if nodes have fanouts before deleting and return - // Status. - void DeleteNodes(const std::set& nodes_to_delete); + // Deletes nodes from the graph. If a node can't be safely removed, + // specifically if a node still has fanouts, an error will be returned. Nodes + // that can't be found are ignored. + Status DeleteNodes(const absl::flat_hash_set& nodes_to_delete); private: // Adds fanouts for fanins of node to graph, while deduping control @@ -163,11 +179,6 @@ class MutableGraphView : public internal::GraphViewInternal { // behavior is undefined. Status UpdateFanoutsInternal(NodeDef* from_node, NodeDef* to_node); - // Removes fanins of the deleted node from internal state. Control - // dependencies are retained iff keep_controlling_fanins is true. - void RemoveFaninsInternal(NodeDef* deleted_node, - bool keep_controlling_fanins); - // Adds fanin to node. If fanin is a control dependency, existing control // dependencies will be checked first before adding. Otherwise fanin will be // added after existing non control dependency inputs. @@ -179,6 +190,20 @@ class MutableGraphView : public internal::GraphViewInternal { // Removes controlling fanin `fanin_node` from node if such controlling fanin // exists. bool RemoveControllingFaninInternal(NodeDef* node, NodeDef* fanin_node); + + // Checks if nodes to be deleted are missing or have any fanouts that will + // remain in the graph. If node is removed in either case, the graph will + // enter an invalid state. + Status CheckNodesCanBeDeleted( + const absl::flat_hash_set& nodes_to_delete); + + // Removes fanins of the deleted node from internal state. Control + // dependencies are retained iff keep_controlling_fanins is true. + void RemoveFaninsInternal(NodeDef* deleted_node, + bool keep_controlling_fanins); + + // Removes fanouts of the deleted node from internal state. + void RemoveFanoutsInternal(NodeDef* deleted_node); }; } // end namespace grappler diff --git a/tensorflow/core/grappler/mutable_graph_view_test.cc b/tensorflow/core/grappler/mutable_graph_view_test.cc index b5bf33033c..acfaba5ddd 100644 --- a/tensorflow/core/grappler/mutable_graph_view_test.cc +++ b/tensorflow/core/grappler/mutable_graph_view_test.cc @@ -23,6 +23,7 @@ limitations under the License. #include "tensorflow/core/grappler/grappler_item.h" #include "tensorflow/core/grappler/inputs/trivial_test_graph_input_yielder.h" #include "tensorflow/core/grappler/utils.h" +#include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/platform/test.h" namespace tensorflow { @@ -154,7 +155,7 @@ TEST(MutableGraphViewTest, AddAndUpdateFanouts) { NodeDef* new_bar = graph.AddNode(NDef("new_bar", "NotImportant", {}, {})); - EXPECT_TRUE(graph.UpdateFanouts("bar", new_bar->name()).ok()); + TF_EXPECT_OK(graph.UpdateFanouts("bar", new_bar->name())); // Fanins and fanouts must be updated. CheckNode(graph, "bar", "NotImportant", "", {}, {}, {}); @@ -184,7 +185,7 @@ TEST(MutableGraphViewTest, AddAndUpdateFanoutsKeepControls) { NodeDef* new_bar = graph.AddNode(NDef("new_bar", "Identity", {"bar_1:2"})); - EXPECT_TRUE(graph.UpdateFanouts("bar_2", new_bar->name()).ok()); + TF_EXPECT_OK(graph.UpdateFanouts("bar_2", new_bar->name())); // Fanins and fanouts must be updated. CheckNode(graph, "bar_1", "Switch", "", {}, {}, {"bar_2", "new_bar"}); @@ -213,7 +214,7 @@ TEST(MutableGraphViewTest, AddAndUpdateFanoutsWithoutSelfLoops) { // `new_bar` reads the output of an original `bar` node. NodeDef* new_bar = graph.AddNode(NDef("new_bar", "NewBar", {"bar"}, {})); - EXPECT_TRUE(graph.UpdateFanouts("bar", new_bar->name()).ok()); + TF_EXPECT_OK(graph.UpdateFanouts("bar", new_bar->name())); // Fanins and fanouts must be updated. CheckNode(graph, "bar", "NotImportant", "", {}, {}, {"new_bar"}); @@ -266,7 +267,7 @@ TEST(MutableGraphViewTest, UpdateFanoutsToSwitchWithNoControlFromSwitch) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.UpdateFanouts("c", "b").ok()); + TF_EXPECT_OK(graph.UpdateFanouts("c", "b")); EXPECT_EQ(graph.graph()->node_size(), 5); @@ -882,10 +883,10 @@ TEST(MutableGraphViewTest, DedupControllingFaninsOnAddFanin) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.AddRegularFanin("b", {"a", 2}).ok()); + TF_EXPECT_OK(graph.AddRegularFanin("b", {"a", 2})); CheckNode(graph, "b", "NotImportant", "", {}, {"a:2"}, {}); - EXPECT_TRUE(graph.AddControllingFanin("c", {"a", Graph::kControlSlot}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("c", {"a", Graph::kControlSlot})); CheckNode(graph, "c", "NotImportant", "", {}, {"a:1"}, {}); CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b:0", "c:0"}); @@ -901,16 +902,16 @@ TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnAddFanin) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.AddRegularFanin("c", {"b", 2}).ok()); + TF_EXPECT_OK(graph.AddRegularFanin("c", {"b", 2})); CheckNode(graph, "c", "", "", {}, {"b:2"}, {}); - EXPECT_TRUE(graph.AddControllingFanin("c", {"b", Graph::kControlSlot}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("c", {"b", Graph::kControlSlot})); CheckNode(graph, "c", "", "", {}, {"b:2", "^b"}, {}); - EXPECT_TRUE(graph.AddControllingFanin("c", {"b", Graph::kControlSlot}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("c", {"b", Graph::kControlSlot})); CheckNode(graph, "c", "", "", {}, {"b:2", "^b"}, {}); - EXPECT_TRUE(graph.AddControllingFanin("d", {"b", Graph::kControlSlot}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("d", {"b", Graph::kControlSlot})); CheckNode(graph, "d", "", "", {}, {"^b"}, {}); - EXPECT_TRUE(graph.AddControllingFanin("d", {"b", Graph::kControlSlot}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("d", {"b", Graph::kControlSlot})); CheckNode(graph, "d", "", "", {}, {"^b"}, {}); CheckGraph(graph); @@ -925,7 +926,7 @@ TEST(MutableGraphViewTest, DedupControllingFaninsOnUpdateFanin) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.UpdateFanin("c", {"a", 1}, {"b", 2}).ok()); + TF_EXPECT_OK(graph.UpdateFanin("c", {"a", 1}, {"b", 2})); CheckNode(graph, "a", "NotImportant", "", {}, {}, {}); CheckNode(graph, "b", "NotImportant", "", {}, {}, {"c"}); @@ -943,17 +944,14 @@ TEST(MutableGraphViewTest, NoDedupControlFlowControllingFaninsOnUpdateFanin) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph - .UpdateFanin("d", {"b", Graph::kControlSlot}, - {"c", Graph::kControlSlot}) - .ok()); + TF_EXPECT_OK(graph.UpdateFanin("d", {"b", Graph::kControlSlot}, + {"c", Graph::kControlSlot})); CheckNode(graph, "d", "NotImportant", "", {}, {"c", "^c"}, {}); - EXPECT_TRUE(graph.UpdateFanin("e", {"b", 0}, {"c", 3}).ok()); + TF_EXPECT_OK(graph.UpdateFanin("e", {"b", 0}, {"c", 3})); CheckNode(graph, "e", "NotImportant", "", {}, {"c:3", "^c"}, {}); - EXPECT_TRUE( - graph.UpdateFanin("e", {"c", 3}, {"c", Graph::kControlSlot}).ok()); + TF_EXPECT_OK(graph.UpdateFanin("e", {"c", 3}, {"c", Graph::kControlSlot})); CheckNode(graph, "e", "NotImportant", "", {}, {"^c"}, {}); CheckGraph(graph); @@ -968,7 +966,7 @@ TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnAddFanin) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.AddRegularFanin("c", {"a", 3}).ok()); + TF_EXPECT_OK(graph.AddRegularFanin("c", {"a", 3})); CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b", "c"}); CheckNode(graph, "b", "NotImportant", "", {}, {"a:1"}, {"^c"}); @@ -986,7 +984,7 @@ TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnRemoveFanin) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.RemoveRegularFanin("c", {"a", 2}).ok()); + TF_EXPECT_OK(graph.RemoveRegularFanin("c", {"a", 2})); CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b"}); CheckNode(graph, "b", "NotImportant", "", {}, {"a:1"}, {}); CheckNode(graph, "c", "NotImportant", "", {}, {}, {}); @@ -1003,7 +1001,7 @@ TEST(MutableGraphViewTest, KeepMaxRegularOutputPortOnRemoveFanin) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.RemoveRegularFanin("b", {"a", 1}).ok()); + TF_EXPECT_OK(graph.RemoveRegularFanin("b", {"a", 1})); CheckNode(graph, "a", "NotImportant", "", {}, {}, {"c"}); CheckNode(graph, "b", "NotImportant", "", {}, {}, {}); @@ -1021,7 +1019,7 @@ TEST(MutableGraphViewTest, UpdateMaxRegularOutputPortOnUpdateFanin) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.UpdateFanin("c", {"a", 2}, {"b", 3}).ok()); + TF_EXPECT_OK(graph.UpdateFanin("c", {"a", 2}, {"b", 3})); CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b"}); CheckNode(graph, "b", "NotImportant", "", {}, {"a:1"}, {"c"}); @@ -1069,8 +1067,8 @@ TEST(MutableGraphViewTest, AddControllingFaninExistingControl) { /*funcs=*/{}); MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.AddControllingFanin("a", {"b", Graph::kControlSlot}).ok()); - EXPECT_TRUE(graph.AddControllingFanin("a", {"b", Graph::kControlSlot}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"b", Graph::kControlSlot})); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"b", Graph::kControlSlot})); ASSERT_EQ(graph.graph()->node_size(), 2); @@ -1087,8 +1085,8 @@ TEST(MutableGraphViewTest, AddControllingFaninNotSwitch) { /*funcs=*/{}); MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.AddControllingFanin("a", {"b", 2}).ok()); - EXPECT_TRUE(graph.AddControllingFanin("a", {"b", 2}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"b", 2})); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"b", 2})); ASSERT_EQ(graph.graph()->node_size(), 2); @@ -1127,8 +1125,8 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithIdentity) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.AddControllingFanin("a", {"switch", 0}).ok()); - EXPECT_TRUE(graph.AddControllingFanin("a", {"switch", 0}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"switch", 0})); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"switch", 0})); ASSERT_EQ(graph.graph()->node_size(), 3); @@ -1148,8 +1146,8 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithNoExistingIdentity) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.AddControllingFanin("a", {"switch", 0}).ok()); - EXPECT_TRUE(graph.AddControllingFanin("a", {"switch", 0}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"switch", 0})); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"switch", 0})); ASSERT_EQ(graph.graph()->node_size(), 3); @@ -1171,8 +1169,8 @@ TEST(MutableGraphViewTest, AddControllingFaninSwitchWithExistingAddedIdentity) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.AddControllingFanin("a", {"switch", 0}).ok()); - EXPECT_TRUE(graph.AddControllingFanin("a", {"switch", 0}).ok()); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"switch", 0})); + TF_EXPECT_OK(graph.AddControllingFanin("a", {"switch", 0})); ASSERT_EQ(graph.graph()->node_size(), 3); @@ -1279,7 +1277,7 @@ TEST(MutableGraphViewTest, RemoveControllingFaninMissing) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.RemoveControllingFanin("d", "c").ok()); + TF_EXPECT_OK(graph.RemoveControllingFanin("d", "c")); ASSERT_EQ(graph.graph()->node_size(), 4); @@ -1301,8 +1299,8 @@ TEST(MutableGraphViewTest, RemoveControllingFaninExisting) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.RemoveControllingFanin("d", "a").ok()); - EXPECT_TRUE(graph.RemoveControllingFanin("d", "a").ok()); + TF_EXPECT_OK(graph.RemoveControllingFanin("d", "a")); + TF_EXPECT_OK(graph.RemoveControllingFanin("d", "a")); ASSERT_EQ(graph.graph()->node_size(), 4); @@ -1323,8 +1321,8 @@ TEST(MutableGraphViewTest, RemoveControllingFaninOnRegularFanin) { MutableGraphView graph(&graph_def); - EXPECT_TRUE(graph.RemoveControllingFanin("c", "a").ok()); - EXPECT_TRUE(graph.RemoveControllingFanin("c", "b").ok()); + TF_EXPECT_OK(graph.RemoveControllingFanin("c", "a")); + TF_EXPECT_OK(graph.RemoveControllingFanin("c", "b")); ASSERT_EQ(graph.graph()->node_size(), 3); @@ -1370,7 +1368,7 @@ TEST(MutableGraphViewTest, DeleteNodes) { MutableGraphView graph(&graph_def); EXPECT_NE(graph.GetNode("foo_1"), nullptr); - graph.DeleteNodes({"foo_1"}); + TF_EXPECT_OK(graph.DeleteNodes({"foo_1"})); EXPECT_EQ(graph.graph()->node_size(), 3); EXPECT_EQ(graph.GetNode("foo_1"), nullptr); @@ -1382,6 +1380,148 @@ TEST(MutableGraphViewTest, DeleteNodes) { CheckGraph(graph); } +GraphDef SimpleDeleteNodeGraph() { + // Actual node.op() is not important in this test. + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"a:2"}), + NDef("c", "NotImportant", {"a:5", "^b"}), NDef("d", "NotImportant", {}), + NDef("e", "NotImportant", {"d:2"}), + NDef("f", "NotImportant", {"d:3", "^e"})}, + /*funcs=*/{}); + return graph_def; +} + +TEST(MutableGraphViewTest, DeleteNodesWithFanoutsBeingDeleted) { + GraphDef graph_def = SimpleDeleteNodeGraph(); + + MutableGraphView graph(&graph_def); + EXPECT_NE(graph.GetNode("a"), nullptr); + EXPECT_NE(graph.GetNode("b"), nullptr); + EXPECT_NE(graph.GetNode("c"), nullptr); + TF_EXPECT_OK(graph.DeleteNodes({"c", "a", "b"})); + + EXPECT_EQ(graph.graph()->node_size(), 3); + EXPECT_EQ(graph.GetNode("a"), nullptr); + EXPECT_EQ(graph.GetNode("b"), nullptr); + EXPECT_EQ(graph.GetNode("c"), nullptr); + + CheckNode(graph, "d", "NotImportant", "", {}, {}, {"e", "f"}); + CheckNode(graph, "e", "NotImportant", "", {}, {"d:2"}, {"^f"}); + CheckNode(graph, "f", "NotImportant", "", {}, {"d:3", "^e"}, {}); + + CheckGraph(graph); +} + +TEST(MutableGraphViewTest, DeleteMissingNodes) { + GraphDef graph_def = SimpleDeleteNodeGraph(); + + MutableGraphView graph(&graph_def); + + EXPECT_EQ(graph.GetNode("g"), nullptr); + EXPECT_EQ(graph.GetNode("h"), nullptr); + TF_EXPECT_OK(graph.DeleteNodes({"g", "h"})); + + EXPECT_EQ(graph.graph()->node_size(), 6); + EXPECT_EQ(graph.GetNode("g"), nullptr); + EXPECT_EQ(graph.GetNode("h"), nullptr); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b", "c"}); + CheckNode(graph, "b", "NotImportant", "", {}, {"a:2"}, {"^c"}); + CheckNode(graph, "c", "NotImportant", "", {}, {"a:5", "^b"}, {}); + CheckNode(graph, "d", "NotImportant", "", {}, {}, {"e", "f"}); + CheckNode(graph, "e", "NotImportant", "", {}, {"d:2"}, {"^f"}); + CheckNode(graph, "f", "NotImportant", "", {}, {"d:3", "^e"}, {}); + + CheckGraph(graph); +} + +TEST(MutableGraphViewTest, DeleteMissingNodesAndNodesWithFanoutsBeingDeleted) { + GraphDef graph_def = SimpleDeleteNodeGraph(); + + MutableGraphView graph(&graph_def); + + EXPECT_NE(graph.GetNode("d"), nullptr); + EXPECT_NE(graph.GetNode("e"), nullptr); + EXPECT_NE(graph.GetNode("f"), nullptr); + TF_EXPECT_OK(graph.DeleteNodes({"d", "e", "f", "g", "h"})); + + EXPECT_EQ(graph.graph()->node_size(), 3); + EXPECT_EQ(graph.GetNode("d"), nullptr); + EXPECT_EQ(graph.GetNode("e"), nullptr); + EXPECT_EQ(graph.GetNode("f"), nullptr); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b", "c"}); + CheckNode(graph, "b", "NotImportant", "", {}, {"a:2"}, {"^c"}); + CheckNode(graph, "c", "NotImportant", "", {}, {"a:5", "^b"}, {}); + + CheckGraph(graph); +} + +TEST(MutableGraphViewTest, DeleteNodesWithError) { + GraphDef graph_def = SimpleDeleteNodeGraph(); + + MutableGraphView graph(&graph_def); + + Status s = graph.DeleteNodes({"b", "a"}); + EXPECT_FALSE(s.ok()); + string error_msg = + "Can't delete node(s) with retained fanout(s) [a, b] from node(s) [a, " + "b]."; + EXPECT_EQ(s.error_message(), error_msg); + + EXPECT_EQ(graph.graph()->node_size(), 6); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b", "c"}); + CheckNode(graph, "b", "NotImportant", "", {}, {"a:2"}, {"^c"}); + CheckNode(graph, "c", "NotImportant", "", {}, {"a:5", "^b"}, {}); + CheckNode(graph, "d", "NotImportant", "", {}, {}, {"e", "f"}); + CheckNode(graph, "e", "NotImportant", "", {}, {"d:2"}, {"^f"}); + CheckNode(graph, "f", "NotImportant", "", {}, {"d:3", "^e"}, {}); + + CheckGraph(graph); +} + +TEST(MutableGraphViewTest, DeleteNodesWithLargeError) { + // Actual node.op() is not important in this test. + GraphDef graph_def = test::function::GDef( + {NDef("a", "NotImportant", {}, {}), NDef("b", "NotImportant", {"a:2"}), + NDef("c", "NotImportant", {"^b"}), NDef("d", "NotImportant", {"c:6"}), + NDef("e", "NotImportant", {"d:2"}), + NDef("f", "NotImportant", {"d:3", "^e"}), + NDef("g", "NotImportant", {"f"}), NDef("h", "NotImportant", {"a"}), + NDef("i", "NotImportant", {"b"}), NDef("j", "NotImportant", {"c"}), + NDef("k", "NotImportant", {"d"}), NDef("l", "NotImportant", {"e"}), + NDef("m", "NotImportant", {"f"})}, + /*funcs=*/{}); + + MutableGraphView graph(&graph_def); + + Status s = graph.DeleteNodes({"a", "b", "c", "d", "e", "f"}); + EXPECT_FALSE(s.ok()); + string error_msg = + "Can't delete node(s) with retained fanout(s) [a, b, c, d, e, ...] from " + "node(s) [a, b, c, d, e, ...]."; + EXPECT_EQ(s.error_message(), error_msg); + + EXPECT_EQ(graph.graph()->node_size(), 13); + + CheckNode(graph, "a", "NotImportant", "", {}, {}, {"b", "h"}); + CheckNode(graph, "b", "NotImportant", "", {}, {"a:2"}, {"^c", "i"}); + CheckNode(graph, "c", "NotImportant", "", {}, {"^b"}, {"d", "j"}); + CheckNode(graph, "d", "NotImportant", "", {}, {"c:6"}, {"e", "f", "k"}); + CheckNode(graph, "e", "NotImportant", "", {}, {"d:2"}, {"^f", "l"}); + CheckNode(graph, "f", "NotImportant", "", {}, {"d:3", "^e"}, {"g", "m"}); + CheckNode(graph, "g", "NotImportant", "", {}, {"f"}, {}); + CheckNode(graph, "h", "NotImportant", "", {}, {"a"}, {}); + CheckNode(graph, "i", "NotImportant", "", {}, {"b"}, {}); + CheckNode(graph, "j", "NotImportant", "", {}, {"c"}, {}); + CheckNode(graph, "k", "NotImportant", "", {}, {"d"}, {}); + CheckNode(graph, "l", "NotImportant", "", {}, {"e"}, {}); + CheckNode(graph, "m", "NotImportant", "", {}, {"f"}, {}); + + CheckGraph(graph); +} + } // namespace } // namespace grappler } // namespace tensorflow diff --git a/tensorflow/core/grappler/optimizers/data/BUILD b/tensorflow/core/grappler/optimizers/data/BUILD index 413588b15c..fe59dfef15 100644 --- a/tensorflow/core/grappler/optimizers/data/BUILD +++ b/tensorflow/core/grappler/optimizers/data/BUILD @@ -38,6 +38,7 @@ cc_library( ":graph_utils", ":fusion_utils", ":optimizer_base", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core:lib", "//tensorflow/core/grappler:grappler_item", @@ -193,6 +194,7 @@ cc_library( ":function_utils", ":graph_utils", ":optimizer_base", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core:lib", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:grappler_item", @@ -261,6 +263,7 @@ cc_library( deps = [ ":graph_utils", ":optimizer_base", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:op_types", @@ -324,6 +327,7 @@ cc_library( deps = [ ":graph_utils", ":optimizer_base", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core:lib", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:grappler_item", @@ -358,6 +362,7 @@ cc_library( ":graph_utils", ":fusion_utils", ":optimizer_base", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core:lib", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:grappler_item", @@ -396,6 +401,7 @@ cc_library( ":graph_utils", ":fusion_utils", ":optimizer_base", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core:lib", "//tensorflow/core/grappler:grappler_item", @@ -434,6 +440,7 @@ cc_library( deps = [ ":graph_utils", ":optimizer_base", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:op_types", @@ -470,6 +477,7 @@ cc_library( ":graph_utils", ":optimizer_base", ":vectorization_utils", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core:lib", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:grappler_item", @@ -526,6 +534,7 @@ cc_library( deps = [ ":graph_utils", ":optimizer_base", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core:lib", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:grappler_item", @@ -571,6 +580,7 @@ cc_library( deps = [ ":graph_utils", ":optimizer_base", + "@com_google_absl//absl/container:flat_hash_set", "//tensorflow/core:lib", "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:grappler_item", diff --git a/tensorflow/core/grappler/optimizers/data/filter_fusion.cc b/tensorflow/core/grappler/optimizers/data/filter_fusion.cc index 25dc10806b..7a20b8042b 100644 --- a/tensorflow/core/grappler/optimizers/data/filter_fusion.cc +++ b/tensorflow/core/grappler/optimizers/data/filter_fusion.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/filter_fusion.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/grappler/clusters/cluster.h" @@ -66,7 +67,7 @@ Status FilterFusion::OptimizeAndCollectStats(Cluster* cluster, *output = sorted_old_graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; FunctionLibraryDefinition function_library(OpRegistry::Global(), output->library()); @@ -125,7 +126,7 @@ Status FilterFusion::OptimizeAndCollectStats(Cluster* cluster, stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/data/graph_utils_test.cc b/tensorflow/core/grappler/optimizers/data/graph_utils_test.cc index 5c0f03dca8..3b6d223fd3 100644 --- a/tensorflow/core/grappler/optimizers/data/graph_utils_test.cc +++ b/tensorflow/core/grappler/optimizers/data/graph_utils_test.cc @@ -109,7 +109,7 @@ TEST(GraphUtilsTest, ContainsGraphNodeWithName) { AddNode("A", "OpA", {}, {}, &graph); EXPECT_TRUE(ContainsGraphNodeWithName("A", *graph.graph())); - graph.DeleteNodes({"A"}); + EXPECT_TRUE(graph.DeleteNodes({"A"}).ok()); EXPECT_TRUE(!ContainsGraphNodeWithName("A", *graph.graph())); } @@ -131,7 +131,7 @@ TEST(GraphUtilsTest, ContainsNodeWithOp) { AddNode("A", "OpA", {}, {}, &graph); EXPECT_TRUE(ContainsNodeWithOp("OpA", *graph.graph())); - graph.DeleteNodes({"A"}); + EXPECT_TRUE(graph.DeleteNodes({"A"}).ok()); EXPECT_TRUE(!ContainsNodeWithOp("OpA", *graph.graph())); } @@ -143,7 +143,7 @@ TEST(GraphUtilsTest, FindGraphNodeWithName) { AddNode("A", "OpA", {}, {}, &graph); EXPECT_NE(FindGraphNodeWithName("A", *graph.graph()), -1); - graph.DeleteNodes({"A"}); + EXPECT_TRUE(graph.DeleteNodes({"A"}).ok()); EXPECT_EQ(FindGraphNodeWithName("A", *graph.graph()), -1); } @@ -164,10 +164,10 @@ TEST(GraphUtilsTest, FindGraphNodeWithOp) { AddNode("A", "OpA", {}, {}, &graph); AddNode("B", "OpB", {"A"}, {}, &graph); - AddNode("A2", "OpA", {"B"}, {}, &graph); + AddNode("A2", "OpA", {"A"}, {}, &graph); EXPECT_EQ(FindGraphNodeWithOp("OpA", *graph.graph()), 0); - graph.DeleteNodes({"B"}); + EXPECT_TRUE(graph.DeleteNodes({"B"}).ok()); EXPECT_EQ(FindGraphNodeWithOp("OpB", *graph.graph()), -1); EXPECT_EQ(FindGraphNodeWithName("A2", *graph.graph()), 1); } @@ -186,7 +186,7 @@ TEST(GraphUtilsTest, FindAllGraphNodesWithOp) { EXPECT_EQ(result_indices.at(0), 0); EXPECT_EQ(result_indices.at(1), 2); - graph.DeleteNodes({"A2"}); + EXPECT_TRUE(graph.DeleteNodes({"A2"}).ok()); std::vector result_indices_new = FindAllGraphNodesWithOp("OpA", *graph.graph()); EXPECT_EQ(result_indices_new.size(), 1); @@ -201,7 +201,7 @@ TEST(GraphUtilsTest, SetUniqueGraphNodeName) { NodeDef* node2 = AddNode("", "A", {}, {}, &graph); EXPECT_NE(node1->name(), node2->name()); - graph.DeleteNodes({node1->name()}); + EXPECT_TRUE(graph.DeleteNodes({node1->name()}).ok()); NodeDef* node3 = AddNode("", "A", {}, {}, &graph); EXPECT_NE(node2->name(), node3->name()); } diff --git a/tensorflow/core/grappler/optimizers/data/hoist_random_uniform.cc b/tensorflow/core/grappler/optimizers/data/hoist_random_uniform.cc index 237e4b63e3..496277780f 100644 --- a/tensorflow/core/grappler/optimizers/data/hoist_random_uniform.cc +++ b/tensorflow/core/grappler/optimizers/data/hoist_random_uniform.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/hoist_random_uniform.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/op_def.pb.h" @@ -227,7 +228,7 @@ Status HoistRandomUniform::OptimizeAndCollectStats(Cluster* cluster, *output = item.graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; FunctionLibraryDefinition function_library(OpRegistry::Global(), item.graph.library()); @@ -277,7 +278,7 @@ Status HoistRandomUniform::OptimizeAndCollectStats(Cluster* cluster, stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/data/make_numa_aware.cc b/tensorflow/core/grappler/optimizers/data/make_numa_aware.cc index d8ea9adfb7..221f4c2525 100644 --- a/tensorflow/core/grappler/optimizers/data/make_numa_aware.cc +++ b/tensorflow/core/grappler/optimizers/data/make_numa_aware.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/make_numa_aware.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/grappler/clusters/cluster.h" #include "tensorflow/core/grappler/grappler_item.h" @@ -43,7 +44,7 @@ Status MakeNumaAware::OptimizeAndCollectStats(Cluster* cluster, OptimizationStats* stats) { *output = item.graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; for (const NodeDef& node : item.graph.node()) { if (node.op() != "ExperimentalMapAndBatchDataset") continue; @@ -53,7 +54,7 @@ Status MakeNumaAware::OptimizeAndCollectStats(Cluster* cluster, nodes_to_delete.insert(node.name()); stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.cc b/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.cc index 3aeac021e6..5d26d1abe4 100644 --- a/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.cc +++ b/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/grappler/clusters/cluster.h" @@ -104,7 +105,7 @@ Status MapAndBatchFusion::OptimizeAndCollectStats(Cluster* cluster, OptimizationStats* stats) { *output = item.graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; for (const NodeDef& node : item.graph.node()) { if (node.op() != "BatchDataset" && node.op() != "BatchDatasetV2") { continue; @@ -131,7 +132,7 @@ Status MapAndBatchFusion::OptimizeAndCollectStats(Cluster* cluster, stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/data/map_and_filter_fusion.cc b/tensorflow/core/grappler/optimizers/data/map_and_filter_fusion.cc index 1ed4eae4ba..e257683b35 100644 --- a/tensorflow/core/grappler/optimizers/data/map_and_filter_fusion.cc +++ b/tensorflow/core/grappler/optimizers/data/map_and_filter_fusion.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/map_and_filter_fusion.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/grappler/clusters/cluster.h" @@ -103,7 +104,7 @@ Status MapAndFilterFusion::OptimizeAndCollectStats(Cluster* cluster, *output = sorted_old_graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; FunctionLibraryDefinition function_library(OpRegistry::Global(), item.graph.library()); auto get_map_node = [](const NodeDef& node) -> const NodeDef* { @@ -168,7 +169,7 @@ Status MapAndFilterFusion::OptimizeAndCollectStats(Cluster* cluster, stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/data/map_fusion.cc b/tensorflow/core/grappler/optimizers/data/map_fusion.cc index 66f8404c05..ce41f7069c 100644 --- a/tensorflow/core/grappler/optimizers/data/map_fusion.cc +++ b/tensorflow/core/grappler/optimizers/data/map_fusion.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/map_fusion.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/grappler/clusters/cluster.h" @@ -86,7 +87,7 @@ Status MapFusion::OptimizeAndCollectStats(Cluster* cluster, *output = sorted_old_graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; FunctionLibraryDefinition function_library(OpRegistry::Global(), item.graph.library()); @@ -147,7 +148,7 @@ Status MapFusion::OptimizeAndCollectStats(Cluster* cluster, stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/data/map_parallelization.cc b/tensorflow/core/grappler/optimizers/data/map_parallelization.cc index d8f01d720a..57e541cde6 100644 --- a/tensorflow/core/grappler/optimizers/data/map_parallelization.cc +++ b/tensorflow/core/grappler/optimizers/data/map_parallelization.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/map_parallelization.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/grappler/clusters/cluster.h" #include "tensorflow/core/grappler/grappler_item.h" @@ -73,7 +74,7 @@ Status MapParallelization::OptimizeAndCollectStats(Cluster* cluster, OptimizationStats* stats) { *output = item.graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; FunctionLibraryDefinition function_library(OpRegistry::Global(), item.graph.library()); auto get_map_node = [](const NodeDef& node) -> const NodeDef* { @@ -97,7 +98,7 @@ Status MapParallelization::OptimizeAndCollectStats(Cluster* cluster, stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/data/map_vectorization.cc b/tensorflow/core/grappler/optimizers/data/map_vectorization.cc index 848642041b..4c9e1f31e7 100644 --- a/tensorflow/core/grappler/optimizers/data/map_vectorization.cc +++ b/tensorflow/core/grappler/optimizers/data/map_vectorization.cc @@ -16,6 +16,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/map_vectorization.h" #include "tensorflow/core/grappler/optimizers/data/vectorization_utils.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/tensor.pb.h" // NOLINT @@ -218,7 +219,7 @@ Status MapVectorization::OptimizeAndCollectStats(Cluster* cluster, OptimizationStats* stats) { *output = item.graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; for (const NodeDef& node : item.graph.node()) { // Find Map->Batch nodes. @@ -274,7 +275,7 @@ Status MapVectorization::OptimizeAndCollectStats(Cluster* cluster, nodes_to_delete.insert(batch_node.name()); stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/data/noop_elimination.cc b/tensorflow/core/grappler/optimizers/data/noop_elimination.cc index e474fddcc0..851bbbdc1a 100644 --- a/tensorflow/core/grappler/optimizers/data/noop_elimination.cc +++ b/tensorflow/core/grappler/optimizers/data/noop_elimination.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/noop_elimination.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/grappler/clusters/cluster.h" @@ -76,7 +77,7 @@ Status NoOpElimination::OptimizeAndCollectStats(Cluster* cluster, OptimizationStats* stats) { *output = item.graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; for (const NodeDef& node : item.graph.node()) { if (!IsNoOp(node, graph)) continue; @@ -87,7 +88,7 @@ Status NoOpElimination::OptimizeAndCollectStats(Cluster* cluster, stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.cc b/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.cc index f30b2230ff..ff64ff1adb 100644 --- a/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.cc +++ b/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.h" +#include "absl/container/flat_hash_set.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/grappler/clusters/cluster.h" @@ -39,7 +40,7 @@ Status ShuffleAndRepeatFusion::OptimizeAndCollectStats( OptimizationStats* stats) { *output = item.graph; MutableGraphView graph(output); - std::set nodes_to_delete; + absl::flat_hash_set nodes_to_delete; auto make_shuffle_and_repeat_node = [&output](const NodeDef& shuffle_node, const NodeDef& repeat_node) { @@ -95,7 +96,7 @@ Status ShuffleAndRepeatFusion::OptimizeAndCollectStats( stats->num_changes++; } - graph.DeleteNodes(nodes_to_delete); + TF_RETURN_IF_ERROR(graph.DeleteNodes(nodes_to_delete)); return Status::OK(); } -- GitLab From fa016bec45651ecdd789dbb197bca65ea3ab80d4 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Thu, 17 Jan 2019 11:09:33 -0800 Subject: [PATCH 0867/2345] [XLA] Actually test the dynamic dimension inference for select-and-scatter. PiperOrigin-RevId: 229782019 --- .../dynamic_dimension_inference_test.cc | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/xla/service/dynamic_dimension_inference_test.cc b/tensorflow/compiler/xla/service/dynamic_dimension_inference_test.cc index 1dd196821c..b42e67b4bb 100644 --- a/tensorflow/compiler/xla/service/dynamic_dimension_inference_test.cc +++ b/tensorflow/compiler/xla/service/dynamic_dimension_inference_test.cc @@ -62,6 +62,17 @@ class DynamicDimensionInferenceTest : public HloTestBase { return module_->AddEmbeddedComputation(embedded_builder.Build()); } + HloComputation* GetGe() { + auto embedded_builder = HloComputation::Builder("ge"); + auto lhs = embedded_builder.AddInstruction(HloInstruction::CreateParameter( + 0, ShapeUtil::MakeShape(F32, {}), "lhs")); + auto rhs = embedded_builder.AddInstruction(HloInstruction::CreateParameter( + 1, ShapeUtil::MakeShape(F32, {}), "rhs")); + embedded_builder.AddInstruction(HloInstruction::CreateBinary( + ShapeUtil::MakeShape(PRED, {}), HloOpcode::kGe, lhs, rhs)); + return module_->AddEmbeddedComputation(embedded_builder.Build()); + } + std::unique_ptr module_; std::unique_ptr inference_; const Shape scalar_shape_ = ShapeUtil::MakeShape(S32, {}); @@ -487,7 +498,7 @@ TEST_F(DynamicDimensionInferenceTest, SelectAndScatterTest) { // Test the ability to trace select and scatter batch dimensions. auto builder = HloComputation::Builder(TestName()); auto input_shape = ShapeUtil::MakeShape(F32, {2, 4, 4}); - auto output_shape = ShapeUtil::MakeShape(F32, {2, 2, 2}); + auto source_shape = ShapeUtil::MakeShape(F32, {2, 2, 2}); Window window; // First dimension is unchanged. @@ -514,22 +525,26 @@ TEST_F(DynamicDimensionInferenceTest, SelectAndScatterTest) { /*parameter_number=*/0, input_shape, "A")); auto* size_param = builder.AddInstruction(HloInstruction::CreateParameter( /*parameter_number=*/1, scalar_shape_, "size_param")); + auto* source = builder.AddInstruction(HloInstruction::CreateParameter( + /*parameter_number=*/2, source_shape, "B")); auto init = builder.AddInstruction( HloInstruction::CreateConstant(LiteralUtil::CreateR0(0.0))); - auto* reduce_window = - builder.AddInstruction(HloInstruction::CreateReduceWindow( - output_shape, a_param, init, window, GetAdd())); + auto* sns = builder.AddInstruction(HloInstruction::CreateSelectAndScatter( + input_shape, a_param, GetGe(), window, source, init, GetAdd())); module_->AddEntryComputation(builder.Build()); TF_CHECK_OK(module_->dynamic_parameter_binding().Bind( DynamicParameterBinding::DynamicParameter{1, {}}, DynamicParameterBinding::DynamicDimension{0, {}, 0})); + TF_CHECK_OK(module_->dynamic_parameter_binding().Bind( + DynamicParameterBinding::DynamicParameter{1, {}}, + DynamicParameterBinding::DynamicDimension{2, {}, 0})); TF_ASSERT_OK(RunInference()); - EXPECT_EQ(inference_->GetDynamicSize(reduce_window, {}, 0), size_param); + EXPECT_EQ(inference_->GetDynamicSize(sns, {}, 0), size_param); } } // namespace -- GitLab From 7462a8fcd7c526a00fa62036cde53f5d989c5a45 Mon Sep 17 00:00:00 2001 From: Michael Kuperstein Date: Thu, 17 Jan 2019 11:49:03 -0800 Subject: [PATCH 0868/2345] [XLA] Remove the old HloInstruction CreateDS/CreateDUS interfaces. Migrate all remaining use to thes new Span interfaces. Note that this does not enforce using R0 indices - it's still possible to pass a span with a single R1 element. That happens in practice when going through the deprecated version of the XlaBuilder API. PiperOrigin-RevId: 229790190 --- .../xla/service/buffer_liveness_test.cc | 8 +-- .../cpu/cpu_instruction_fusion_test.cc | 51 ++++++++----------- .../compiler/xla/service/hlo_instruction.cc | 49 +++++------------- .../compiler/xla/service/hlo_instruction.h | 10 ---- .../service/tuple_points_to_analysis_test.cc | 16 +++--- tensorflow/compiler/xla/tests/fusion_test.cc | 4 +- 6 files changed, 47 insertions(+), 91 deletions(-) diff --git a/tensorflow/compiler/xla/service/buffer_liveness_test.cc b/tensorflow/compiler/xla/service/buffer_liveness_test.cc index 46218e6499..23b9af0281 100644 --- a/tensorflow/compiler/xla/service/buffer_liveness_test.cc +++ b/tensorflow/compiler/xla/service/buffer_liveness_test.cc @@ -638,10 +638,10 @@ class FusedDynamicUpdateSliceLivenessTest : public BufferLivenessTest { } // Create a DynamicUpdateSlice instruction of tuple element 1 with 'update'. auto starts = builder.AddInstruction( - HloInstruction::CreateConstant(LiteralUtil::CreateR1({2}))); + HloInstruction::CreateConstant(LiteralUtil::CreateR0(2))); auto dynamic_update_slice = builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( - data_shape, gte1, update, starts)); + data_shape, gte1, update, {starts})); // Create output tuple. builder.AddInstruction( HloInstruction::CreateTuple({gte0, dynamic_update_slice})); @@ -794,10 +794,10 @@ class DynamicUpdateSliceLivenessTest : public BufferLivenessTest { } // Create a DynamicUpdateSlice instruction of tuple element 1 with 'update'. auto starts = builder.AddInstruction( - HloInstruction::CreateConstant(LiteralUtil::CreateR1({2}))); + HloInstruction::CreateConstant(LiteralUtil::CreateR0(2))); auto dynamic_update_slice = builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( - data_shape, gte1, update, starts)); + data_shape, gte1, update, {starts})); // Create output tuple. auto tuple_root = builder.AddInstruction( HloInstruction::CreateTuple({gte0, dynamic_update_slice})); diff --git a/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc b/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc index 86cc72dd7c..c4bde837e5 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion_test.cc @@ -390,10 +390,8 @@ TEST_F(OpcodeFusionTest, DynamicSlice_Negate) { HloInstruction::CreateParameter(0, param_shape, "param")); HloInstruction* param1 = builder.AddInstruction( HloInstruction::CreateParameter(1, slice_shape, "starts")); - HloInstruction* dynamic_slice2 = - builder.AddInstruction(HloInstruction::CreateDynamicSlice( - result_shape, param0, - std::initializer_list({param1}), {2})); + HloInstruction* dynamic_slice2 = builder.AddInstruction( + HloInstruction::CreateDynamicSlice(result_shape, param0, {param1}, {2})); builder.AddInstruction(HloInstruction::CreateUnary( result_shape, HloOpcode::kNegate, dynamic_slice2)); @@ -591,49 +589,40 @@ TEST_F(OpcodeFusionTest, MessOfFusibleNodes) { Shape full_shape = ShapeUtil::MakeShape(F32, {4, 100, 10, 100, 50}); - auto loop_idx = builder.AddInstruction(HloInstruction::CreateReshape( - ShapeUtil::MakeShape(S32, {1}), - builder.AddInstruction(HloInstruction::CreateParameter( - 0, ShapeUtil::MakeShape(S32, {}), "param0")))); - + auto loop_idx = builder.AddInstruction(HloInstruction::CreateParameter( + 0, ShapeUtil::MakeShape(S32, {}), "param0")); auto param1 = builder.AddInstruction(HloInstruction::CreateParameter( - 1, ShapeUtil::MakeShape(S32, {1}), "param1")); - auto concat = builder.AddInstruction(HloInstruction::CreateConcatenate( - ShapeUtil::MakeShape(S32, {5}), - {loop_idx, param1, param1, param1, param1}, /*dimension=*/0)); + 1, ShapeUtil::MakeShape(S32, {}), "param1")); - auto idx_choice = builder.AddInstruction(HloInstruction::CreateDynamicSlice( - ShapeUtil::MakeShape(S32, {1}), - builder.AddInstruction(HloInstruction::CreateParameter( - 2, ShapeUtil::MakeShape(S32, {4}), "param2")), - loop_idx, - /*slice_sizes=*/{1})); - - PaddingConfig padding_config; - padding_config.add_dimensions()->set_edge_padding_high(4); - auto pad = builder.AddInstruction(HloInstruction::CreatePad( - ShapeUtil::MakeShape(S32, {5}), idx_choice, - builder.AddInstruction( - HloInstruction::CreateConstant(LiteralUtil::CreateR0(0))), - padding_config)); + auto idx_choice = builder.AddInstruction(HloInstruction::CreateReshape( + ShapeUtil::MakeShape(S32, {}), + builder.AddInstruction(HloInstruction::CreateDynamicSlice( + ShapeUtil::MakeShape(S32, {1}), + builder.AddInstruction(HloInstruction::CreateParameter( + 2, ShapeUtil::MakeShape(S32, {4}), "param2")), + {loop_idx}, + /*slice_sizes=*/{1})))); + auto zero = builder.AddInstruction( + HloInstruction::CreateConstant(LiteralUtil::CreateR0(0))); auto slice = builder.AddInstruction(HloInstruction::CreateDynamicSlice( ShapeUtil::MakeShape(F32, {1, 100, 10, 100, 50}), builder.AddInstruction(HloInstruction::CreateParameter( 3, ShapeUtil::MakeShape(F32, {100, 100, 10, 100, 50}), "param3")), - pad, /*slice_sizes=*/{1, 100, 10, 100, 50})); + {idx_choice, zero, zero, zero, zero}, + /*slice_sizes=*/{1, 100, 10, 100, 50})); builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( full_shape, builder.AddInstruction( HloInstruction::CreateParameter(4, full_shape, "param4")), - slice, concat)); + slice, {loop_idx, param1, param1, param1, param1})); module->AddEntryComputation(builder.Build()); RunFusionAndCheckOpcodesWereFused( module.get(), - {HloOpcode::kConcatenate, HloOpcode::kPad, HloOpcode::kDynamicSlice, - HloOpcode::kDynamicSlice, HloOpcode::kDynamicUpdateSlice, + {HloOpcode::kDynamicSlice, HloOpcode::kDynamicSlice, + HloOpcode::kDynamicUpdateSlice, HloOpcode::kReshape, HloOpcode::kParameter, HloOpcode::kParameter, HloOpcode::kParameter, HloOpcode::kParameter, HloOpcode::kParameter, HloOpcode::kParameter}); } diff --git a/tensorflow/compiler/xla/service/hlo_instruction.cc b/tensorflow/compiler/xla/service/hlo_instruction.cc index 029d170317..3c92554ad4 100644 --- a/tensorflow/compiler/xla/service/hlo_instruction.cc +++ b/tensorflow/compiler/xla/service/hlo_instruction.cc @@ -458,21 +458,17 @@ StatusOr> HloInstruction::CreateFromProto( << "DynamicSlice instruction should have at least 1 operands but " "sees " << proto.operand_ids_size(); - if (proto.operand_ids_size() == 2 && operands(1)->shape().rank() == 1) { - // TODO(b/118437727): Old form, remove this path. - instruction = - CreateDynamicSlice(shape, operands(0), operands(1), slice_sizes); - } else { - // New form + // TODO(b/118437727): Old form, make the check unconditional. + if (proto.operand_ids_size() != 2 || operands(1)->shape().rank() != 1) { auto expected_operands = 1 + operands(0)->shape().rank(); TF_RET_CHECK(proto.operand_ids_size() == expected_operands) << "DynamicSlice instruction should have " << expected_operands << " operands, but has " << proto.operand_ids_size(); - const auto& operand_vector = all_operands(); - instruction = CreateDynamicSlice( - shape, operands(0), absl::MakeSpan(operand_vector).subspan(1), - slice_sizes); } + const auto& operand_vector = all_operands(); + instruction = CreateDynamicSlice( + shape, operands(0), absl::MakeSpan(operand_vector).subspan(1), + slice_sizes); break; } case HloOpcode::kDynamicUpdateSlice: { @@ -480,22 +476,19 @@ StatusOr> HloInstruction::CreateFromProto( << "DynamicUpdateSlice instruction should have at least 2 operands " "but sees " << proto.operand_ids_size(); - if (proto.operand_ids_size() == 3 && operands(2)->shape().rank() == 1) { - // TODO(b/118437727): Old form, remove this path. - instruction = CreateDynamicUpdateSlice(shape, operands(0), operands(1), - operands(2)); - } else { - // New form + // TODO(b/118437727): Old form, make the check unconditional. + if (proto.operand_ids_size() != 3 || operands(2)->shape().rank() != 1) { auto expected_operands = 2 + operands(0)->shape().rank(); TF_RET_CHECK(proto.operand_ids_size() == expected_operands) << "DynamicUpdateSlice instruction should have " << expected_operands << " operands, but has " << proto.operand_ids_size(); - const auto& operand_vector = all_operands(); - instruction = - CreateDynamicUpdateSlice(shape, operands(0), operands(1), - absl::MakeSpan(operand_vector).subspan(2)); } + const auto& operand_vector = all_operands(); + instruction = + CreateDynamicUpdateSlice(shape, operands(0), operands(1), + absl::MakeSpan(operand_vector).subspan(2)); + break; } case HloOpcode::kGather: { @@ -947,13 +940,6 @@ HloInstruction::CreateAddDependency(HloInstruction* data_operand, limit_indices, strides); } -/* static */ std::unique_ptr HloInstruction::CreateDynamicSlice( - const Shape& shape, HloInstruction* operand, HloInstruction* start_indices, - absl::Span slice_sizes) { - return absl::make_unique( - shape, operand, start_indices, slice_sizes); -} - /* static */ std::unique_ptr HloInstruction::CreateDynamicSlice( const Shape& shape, HloInstruction* operand, absl::Span start_indices, @@ -962,15 +948,6 @@ HloInstruction::CreateAddDependency(HloInstruction* data_operand, shape, operand, start_indices, slice_sizes); } -/* static */ std::unique_ptr -HloInstruction::CreateDynamicUpdateSlice(const Shape& shape, - HloInstruction* operand, - HloInstruction* update, - HloInstruction* start_indices) { - return absl::make_unique( - shape, operand, update, start_indices); -} - /* static */ std::unique_ptr HloInstruction::CreateDynamicUpdateSlice( const Shape& shape, HloInstruction* operand, HloInstruction* update, diff --git a/tensorflow/compiler/xla/service/hlo_instruction.h b/tensorflow/compiler/xla/service/hlo_instruction.h index 789ace6a26..2c29b6c243 100644 --- a/tensorflow/compiler/xla/service/hlo_instruction.h +++ b/tensorflow/compiler/xla/service/hlo_instruction.h @@ -556,11 +556,6 @@ class HloInstruction { // Creates a slice instruction, where the first operand is sliced by // start indices specified in the second operand, and by size specified in // 'slice_sizes'. - ABSL_DEPRECATED("Use span-of-indices form instead") - static std::unique_ptr CreateDynamicSlice( - const Shape& shape, HloInstruction* operand, - HloInstruction* start_indices, absl::Span slice_sizes); - // Same as above, but expects a span of scalar start indices. static std::unique_ptr CreateDynamicSlice( const Shape& shape, HloInstruction* operand, absl::Span start_indices, @@ -568,11 +563,6 @@ class HloInstruction { // Creates a dynamic update slice instruction, which updates a slice // of 'operand' with 'update' and 'start_indices'. - ABSL_DEPRECATED("Use span-of-indices form instead") - static std::unique_ptr CreateDynamicUpdateSlice( - const Shape& shape, HloInstruction* operand, HloInstruction* update, - HloInstruction* start_indices); - // Same as above, but expects a span of scalar start indices. static std::unique_ptr CreateDynamicUpdateSlice( const Shape& shape, HloInstruction* operand, HloInstruction* update, absl::Span start_indices); diff --git a/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc b/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc index 7599e1e6ad..fd5759e442 100644 --- a/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc +++ b/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc @@ -623,7 +623,7 @@ class FusionPointsToAnalysisTest : public TuplePointsToAnalysisTest { void Run(const bool add_additional_gte0_user) { Shape input_shape = ShapeUtil::MakeShape(F32, {8}); Shape update_shape = ShapeUtil::MakeShape(F32, {3}); - Shape starts_shape = ShapeUtil::MakeShape(S32, {1}); + Shape starts_shape = ShapeUtil::MakeShape(S32, {}); Shape tuple_shape = ShapeUtil::MakeTupleShape({input_shape, update_shape, starts_shape}); @@ -657,7 +657,7 @@ class FusionPointsToAnalysisTest : public TuplePointsToAnalysisTest { HloInstruction::CreateGetTupleElement(starts_shape, tuple_param0, 2)); // Update 'input' with 'update' at dynamic 'starts' indices. builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( - input_shape, input, update, starts)); + input_shape, input, update, {starts})); // Build computation and add it to module as entry computation. BuildModule(builder.Build()); @@ -882,12 +882,12 @@ TEST_F(DoesNotUseOperandBufferTest, FusedDynamicUpdateSlice) { // Create a DynamicUpdateSlice instruction of tuple element 1. auto starts = builder.AddInstruction( - HloInstruction::CreateConstant(LiteralUtil::CreateR1({2}))); + HloInstruction::CreateConstant(LiteralUtil::CreateR0(2))); auto update = builder.AddInstruction(HloInstruction::CreateConstant( LiteralUtil::CreateR1({2.f, 2.f, 2.f}))); auto dynamic_update_slice = builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( - data_shape, gte1, update, starts)); + data_shape, gte1, update, {starts})); builder.AddInstruction( HloInstruction::CreateTuple({gte0, dynamic_update_slice})); @@ -976,12 +976,12 @@ TEST_F(CanShareOperandBufferWithUserTest, FusedDynamicUpdateSlice) { // Create a DynamicUpdateSlice instruction of tuple element 1. auto starts = builder.AddInstruction( - HloInstruction::CreateConstant(LiteralUtil::CreateR1({2}))); + HloInstruction::CreateConstant(LiteralUtil::CreateR0(2))); auto update = builder.AddInstruction(HloInstruction::CreateConstant( LiteralUtil::CreateR1({2.f, 2.f, 2.f}))); auto dynamic_update_slice = builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( - data_shape, gte1, update, starts)); + data_shape, gte1, update, {starts})); builder.AddInstruction( HloInstruction::CreateTuple({gte0, dynamic_update_slice})); @@ -1003,7 +1003,7 @@ TEST_F(CanShareOperandBufferWithUserTest, DynamicUpdateSliceCanShare) { Shape data_shape = ShapeUtil::MakeShape(F32, {8}); Shape update_shape = ShapeUtil::MakeShape(F32, {4}); - Shape starts_shape = ShapeUtil::MakeShape(S32, {1}); + Shape starts_shape = ShapeUtil::MakeShape(S32, {}); auto data = builder.AddInstruction( HloInstruction::CreateParameter(0, data_shape, "data")); auto update = builder.AddInstruction( @@ -1011,7 +1011,7 @@ TEST_F(CanShareOperandBufferWithUserTest, DynamicUpdateSliceCanShare) { auto starts = builder.AddInstruction( HloInstruction::CreateParameter(2, starts_shape, "starts")); auto dus = builder.AddInstruction(HloInstruction::CreateDynamicUpdateSlice( - data_shape, data, update, starts)); + data_shape, data, update, {starts})); BuildModuleAndRunAnalysis(builder.Build()); diff --git a/tensorflow/compiler/xla/tests/fusion_test.cc b/tensorflow/compiler/xla/tests/fusion_test.cc index d1fddf9d6b..2178c9b3f3 100644 --- a/tensorflow/compiler/xla/tests/fusion_test.cc +++ b/tensorflow/compiler/xla/tests/fusion_test.cc @@ -523,10 +523,10 @@ XLA_TEST_F(FusionTest, DynamicSliceNegate) { auto const0 = builder.AddInstruction(HloInstruction::CreateConstant( LiteralUtil::CreateR1({1, 2, 3, 4}))); auto const1 = builder.AddInstruction( - HloInstruction::CreateConstant(LiteralUtil::CreateR1({1}))); + HloInstruction::CreateConstant(LiteralUtil::CreateR0(1))); auto dynamic_slice2 = builder.AddInstruction(HloInstruction::CreateDynamicSlice( - ShapeUtil::MakeShape(S32, {2}), const0, const1, {2})); + ShapeUtil::MakeShape(S32, {2}), const0, {const1}, {2})); auto negate3 = builder.AddInstruction(HloInstruction::CreateUnary( ShapeUtil::MakeShape(S32, {2}), HloOpcode::kNegate, dynamic_slice2)); hlo_module->AddEntryComputation(builder.Build()) -- GitLab From 5c08b1f021e0a40fece750b5ebc0cf85b3281265 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 17 Jan 2019 11:52:56 -0800 Subject: [PATCH 0869/2345] Remove alias redirection for RBE. PiperOrigin-RevId: 229790895 --- tensorflow/opensource_only.files | 3 - third_party/gpus/crosstool/remote.BUILD.tpl | 10 -- third_party/gpus/cuda/remote.BUILD.tpl | 110 -------------------- third_party/gpus/cuda_configure.bzl | 17 ++- third_party/tensorrt/build_defs.bzl.tpl | 4 +- third_party/tensorrt/remote.BUILD.tpl | 7 -- third_party/tensorrt/tensorrt_configure.bzl | 11 +- 7 files changed, 14 insertions(+), 148 deletions(-) delete mode 100644 third_party/gpus/crosstool/remote.BUILD.tpl delete mode 100644 third_party/gpus/cuda/remote.BUILD.tpl delete mode 100644 third_party/tensorrt/remote.BUILD.tpl diff --git a/tensorflow/opensource_only.files b/tensorflow/opensource_only.files index 30dfce3c83..5f1ec1d673 100644 --- a/tensorflow/opensource_only.files +++ b/tensorflow/opensource_only.files @@ -82,7 +82,6 @@ tensorflow/third_party/gpus/crosstool/clang/bin/crosstool_wrapper_driver_is_not_ tensorflow/third_party/gpus/crosstool/CROSSTOOL.tpl tensorflow/third_party/gpus/crosstool/CROSSTOOL_hipcc.tpl tensorflow/third_party/gpus/crosstool/LICENSE -tensorflow/third_party/gpus/crosstool/remote.BUILD.tpl tensorflow/third_party/gpus/crosstool/windows/msvc_wrapper_for_nvcc.py.tpl tensorflow/third_party/gpus/crosstool/BUILD.tpl tensorflow/third_party/gpus/crosstool/BUILD @@ -90,7 +89,6 @@ tensorflow/third_party/gpus/cuda/LICENSE tensorflow/third_party/gpus/cuda/BUILD.tpl tensorflow/third_party/gpus/cuda/BUILD.windows.tpl tensorflow/third_party/gpus/cuda/cuda_config.h.tpl -tensorflow/third_party/gpus/cuda/remote.BUILD.tpl tensorflow/third_party/gpus/cuda/BUILD tensorflow/third_party/gpus/cuda/build_defs.bzl.tpl tensorflow/third_party/gpus/rocm/rocm_config.h.tpl @@ -194,7 +192,6 @@ tensorflow/third_party/tensorrt/BUILD tensorflow/third_party/tensorrt/build_defs.bzl.tpl tensorflow/third_party/tensorrt/BUILD.tpl tensorflow/third_party/tensorrt/tensorrt_configure.bzl -tensorflow/third_party/tensorrt/remote.BUILD.tpl tensorflow/third_party/kafka/config.patch tensorflow/third_party/kafka/BUILD tensorflow/third_party/android/BUILD diff --git a/third_party/gpus/crosstool/remote.BUILD.tpl b/third_party/gpus/crosstool/remote.BUILD.tpl deleted file mode 100644 index b2316331db..0000000000 --- a/third_party/gpus/crosstool/remote.BUILD.tpl +++ /dev/null @@ -1,10 +0,0 @@ -# Description: -# Template for crosstool Build file to use a pre-generated config. -licenses(["restricted"]) - -package(default_visibility = ["//visibility:public"]) - -alias( - name = "toolchain", - actual = "%{remote_cuda_repo}:toolchain", -) diff --git a/third_party/gpus/cuda/remote.BUILD.tpl b/third_party/gpus/cuda/remote.BUILD.tpl deleted file mode 100644 index 100c7bb7c4..0000000000 --- a/third_party/gpus/cuda/remote.BUILD.tpl +++ /dev/null @@ -1,110 +0,0 @@ -# Description: -# Template for cuda Build file to use a pre-generated config. -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"], -) - -alias( - name = "cuda_headers", - actual = "%{remote_cuda_repo}/cuda:cuda_headers", -) - -alias( - name = "cudart_static", - actual = "%{remote_cuda_repo}/cuda:cudart_static", -) - -alias( - name = "cuda_driver", - actual = "%{remote_cuda_repo}/cuda:cuda_driver", -) - -alias( - name = "cudart", - actual = "%{remote_cuda_repo}/cuda:cudart", -) - -alias( - name = "cublas", - actual = "%{remote_cuda_repo}/cuda:cublas", -) - -alias( - name = "cusolver", - actual = "%{remote_cuda_repo}/cuda:cusolver", -) - -alias( - name = "cudnn", - actual = "%{remote_cuda_repo}/cuda:cudnn", -) - -alias( - name = "cudnn_header", - actual = "%{remote_cuda_repo}/cuda:cudnn_header", -) - -alias( - name = "cufft", - actual = "%{remote_cuda_repo}/cuda:cufft", -) - -alias( - name = "curand", - actual = "%{remote_cuda_repo}/cuda:curand", -) - -alias( - name = "cuda", - actual = "%{remote_cuda_repo}/cuda:cuda", -) - -alias( - name = "cupti_headers", - actual = "%{remote_cuda_repo}/cuda:cupti_headers", -) - -alias( - name = "cupti_dsos", - actual = "%{remote_cuda_repo}/cuda:cupti_dsos", -) - -alias( - name = "libdevice_root", - actual = "%{remote_cuda_repo}/cuda:libdevice_root", -) diff --git a/third_party/gpus/cuda_configure.bzl b/third_party/gpus/cuda_configure.bzl index 1988c4ae96..beacf2f45a 100644 --- a/third_party/gpus/cuda_configure.bzl +++ b/third_party/gpus/cuda_configure.bzl @@ -1492,17 +1492,16 @@ def _create_remote_cuda_repository(repository_ctx, remote_config_repo): ), }, ) - _tpl( - repository_ctx, - "cuda:remote.BUILD", - { - "%{remote_cuda_repo}": remote_config_repo, - }, + repository_ctx.template( "cuda/BUILD", + Label(remote_config_repo + "/cuda/BUILD"), + {}, + ) + repository_ctx.template( + "crosstool/BUILD", + Label(remote_config_repo + "/crosstool/BUILD"), + {}, ) - _tpl(repository_ctx, "crosstool:remote.BUILD", { - "%{remote_cuda_repo}": remote_config_repo, - }, "crosstool/BUILD") def _cuda_autoconf_impl(repository_ctx): diff --git a/third_party/tensorrt/build_defs.bzl.tpl b/third_party/tensorrt/build_defs.bzl.tpl index 0dc3a7ba2d..6d00513827 100644 --- a/third_party/tensorrt/build_defs.bzl.tpl +++ b/third_party/tensorrt/build_defs.bzl.tpl @@ -2,6 +2,4 @@ def if_tensorrt(if_true, if_false=[]): """Tests whether TensorRT was enabled during the configure process.""" - if %{tensorrt_is_configured}: - return if_true - return if_false + return %{if_tensorrt} diff --git a/third_party/tensorrt/remote.BUILD.tpl b/third_party/tensorrt/remote.BUILD.tpl deleted file mode 100644 index 7598e7aa4b..0000000000 --- a/third_party/tensorrt/remote.BUILD.tpl +++ /dev/null @@ -1,7 +0,0 @@ -licenses(["notice"]) # Apache 2.0 - -package(default_visibility = ["//visibility:public"]) - -alias(name="LICENSE", actual = "%{target}:LICENSE") -alias(name = "tensorrt_headers", actual = "%{target}:tensorrt_headers") -alias(name = "nv_infer", actual = "%{target}:nv_infer") diff --git a/third_party/tensorrt/tensorrt_configure.bzl b/third_party/tensorrt/tensorrt_configure.bzl index d1e116d9dd..7cf845d5cc 100644 --- a/third_party/tensorrt/tensorrt_configure.bzl +++ b/third_party/tensorrt/tensorrt_configure.bzl @@ -143,7 +143,7 @@ def _tpl(repository_ctx, tpl, substitutions): def _create_dummy_repository(repository_ctx): """Create a dummy TensorRT repository.""" - _tpl(repository_ctx, "build_defs.bzl", {"%{tensorrt_is_configured}": "False"}) + _tpl(repository_ctx, "build_defs.bzl", {"%{if_tensorrt}": "if_false"}) substitutions = { "%{tensorrt_genrules}": "", "%{tensorrt_headers}": "", @@ -158,11 +158,10 @@ def _tensorrt_configure_impl(repository_ctx): """Implementation of the tensorrt_configure repository rule.""" if _TF_TENSORRT_CONFIG_REPO in repository_ctx.os.environ: # Forward to the pre-configured remote repository. - repository_ctx.template("BUILD", Label("//third_party/tensorrt:remote.BUILD.tpl"), { - "%{target}": repository_ctx.os.environ[_TF_TENSORRT_CONFIG_REPO], - }) + remote_config_repo = repository_ctx.os.environ[_TF_TENSORRT_CONFIG_REPO] + repository_ctx.template("BUILD", Label(remote_config_repo + "/BUILD"), {}) # Set up config file. - _tpl(repository_ctx, "build_defs.bzl", {"%{tensorrt_is_configured}": "True"}) + _tpl(repository_ctx, "build_defs.bzl", {"%{if_tensorrt}": "if_true"}) return if _TENSORRT_INSTALL_PATH not in repository_ctx.os.environ: @@ -210,7 +209,7 @@ def _tensorrt_configure_impl(repository_ctx): )) # Set up config file. - _tpl(repository_ctx, "build_defs.bzl", {"%{tensorrt_is_configured}": "True"}) + _tpl(repository_ctx, "build_defs.bzl", {"%{if_tensorrt}": "if_true"}) # Set up BUILD file. substitutions = { -- GitLab From 2435a1875b574f5d299b1dee431ab5ceccd6132f Mon Sep 17 00:00:00 2001 From: Revan Sopher Date: Thu, 17 Jan 2019 11:54:37 -0800 Subject: [PATCH 0870/2345] Switch out unittest for absltest, and point app.py to absl's app.py to resolve flag conflict. PiperOrigin-RevId: 229791193 --- tensorflow/python/BUILD | 6 +- tensorflow/python/platform/app.py | 98 ++---------------------- tensorflow/python/platform/googletest.py | 12 +-- tensorflow/tools/pip_package/BUILD | 5 ++ 4 files changed, 22 insertions(+), 99 deletions(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index b61c33f319..e0648085c3 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -213,6 +213,7 @@ py_library( ":pywrap_tensorflow", ":util", "//tensorflow/core:protos_all_py", + "@absl_py//absl:app", "@absl_py//absl/flags", "@six_archive//:six", ], @@ -233,7 +234,10 @@ py_library( name = "platform_test", srcs = ["platform/googletest.py"], srcs_version = "PY2AND3", - deps = [":platform_benchmark"], + deps = [ + ":platform_benchmark", + "@absl_py//absl/testing:absltest", + ], ) tf_py_test( diff --git a/tensorflow/python/platform/app.py b/tensorflow/python/platform/app.py index 7b917235c0..303b70ff57 100644 --- a/tensorflow/python/platform/app.py +++ b/tensorflow/python/platform/app.py @@ -18,109 +18,23 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import errno as _errno import sys as _sys +from absl.app import run as _run + from tensorflow.python.platform import flags from tensorflow.python.util.tf_export import tf_export -def _usage(shorthelp): - """Writes __main__'s docstring to stdout with some help text. - - Args: - shorthelp: bool, if True, prints only flags from the main module, - rather than all flags. - """ - doc = _sys.modules['__main__'].__doc__ - if not doc: - doc = '\nUSAGE: %s [flags]\n' % _sys.argv[0] - doc = flags.text_wrap(doc, indent=' ', firstline_indent='') - else: - # Replace all '%s' with sys.argv[0], and all '%%' with '%'. - num_specifiers = doc.count('%') - 2 * doc.count('%%') - try: - doc %= (_sys.argv[0],) * num_specifiers - except (OverflowError, TypeError, ValueError): - # Just display the docstring as-is. - pass - if shorthelp: - flag_str = flags.FLAGS.main_module_help() - else: - flag_str = str(flags.FLAGS) - try: - _sys.stdout.write(doc) - if flag_str: - _sys.stdout.write('\nflags:\n') - _sys.stdout.write(flag_str) - _sys.stdout.write('\n') - except IOError as e: - # We avoid printing a huge backtrace if we get EPIPE, because - # "foo.par --help | less" is a frequent use case. - if e.errno != _errno.EPIPE: - raise - - -class _HelpFlag(flags.BooleanFlag): - """Special boolean flag that displays usage and raises SystemExit.""" - NAME = 'help' - SHORT_NAME = 'h' - - def __init__(self): - super(_HelpFlag, self).__init__( - self.NAME, False, 'show this help', short_name=self.SHORT_NAME) - - def parse(self, arg): - if arg: - _usage(shorthelp=True) - print() - print('Try --helpfull to get a list of all flags.') - _sys.exit(1) - - -class _HelpshortFlag(_HelpFlag): - """--helpshort is an alias for --help.""" - NAME = 'helpshort' - SHORT_NAME = None - - -class _HelpfullFlag(flags.BooleanFlag): - """Display help for flags in main module and all dependent modules.""" - - def __init__(self): - super(_HelpfullFlag, self).__init__('helpfull', False, 'show full help') - - def parse(self, arg): - if arg: - _usage(shorthelp=False) - _sys.exit(1) - - -_define_help_flags_called = False - - -def _define_help_flags(): - global _define_help_flags_called - if not _define_help_flags_called: - flags.DEFINE_flag(_HelpFlag()) - flags.DEFINE_flag(_HelpfullFlag()) - flags.DEFINE_flag(_HelpshortFlag()) - _define_help_flags_called = True +def _parse_flags_tolerate_undef(argv): + """Parse args, returning any unknown flags (ABSL defaults to crashing).""" + return flags.FLAGS(_sys.argv if argv is None else argv, known_only=True) @tf_export(v1=['app.run']) def run(main=None, argv=None): """Runs the program with an optional 'main' function and 'argv' list.""" - # Define help flags. - _define_help_flags() - - # Parse known flags. - argv = flags.FLAGS(_sys.argv if argv is None else argv, known_only=True) - main = main or _sys.modules['__main__'].main - # Call the main function, passing through any arguments - # to the final program. - _sys.exit(main(argv)) - + _run(main=main, argv=argv, flags_parser=_parse_flags_tolerate_undef) diff --git a/tensorflow/python/platform/googletest.py b/tensorflow/python/platform/googletest.py index fe4b0d0d37..802721e34b 100644 --- a/tensorflow/python/platform/googletest.py +++ b/tensorflow/python/platform/googletest.py @@ -13,7 +13,7 @@ # limitations under the License. # ============================================================================== -"""Imports unittest as a replacement for testing.pybase.googletest.""" +"""Imports absltest as a replacement for testing.pybase.googletest.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -26,7 +26,7 @@ import tempfile # go/tf-wildcard-import # pylint: disable=wildcard-import -from unittest import * +from absl.testing.absltest import * # pylint: enable=wildcard-import from tensorflow.python.framework import errors @@ -41,7 +41,7 @@ from tensorflow.python.util.tf_export import tf_export Benchmark = benchmark.TensorFlowBenchmark # pylint: disable=invalid-name -unittest_main = main +absltest_main = main # We keep a global variable in this module to make sure we create the temporary # directory only once per test binary invocation. @@ -51,7 +51,7 @@ _googletest_temp_dir = '' # pylint: disable=invalid-name # pylint: disable=undefined-variable def g_main(argv): - """Delegate to unittest.main after redefining testLoader.""" + """Delegate to absltest.main after redefining testLoader.""" if 'TEST_SHARD_STATUS_FILE' in os.environ: try: f = None @@ -67,7 +67,7 @@ def g_main(argv): if ('TEST_TOTAL_SHARDS' not in os.environ or 'TEST_SHARD_INDEX' not in os.environ): - return unittest_main(argv=argv) + return absltest_main(argv=argv) total_shards = int(os.environ['TEST_TOTAL_SHARDS']) shard_index = int(os.environ['TEST_SHARD_INDEX']) @@ -87,7 +87,7 @@ def g_main(argv): # Override getTestCaseNames base_loader.getTestCaseNames = getShardedTestCaseNames - unittest_main(argv=argv, testLoader=base_loader) + absltest_main(argv=argv, testLoader=base_loader) # Redefine main to allow running benchmarks diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index c51b45a49c..3c1a9280a2 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -146,7 +146,11 @@ filegroup( "//third_party/eigen3:LICENSE", "//third_party/fft2d:LICENSE", "//third_party/hadoop:LICENSE.txt", + "@absl_py//absl:LICENSE", + "@absl_py//absl/logging:LICENSE", "@absl_py//absl/flags:LICENSE", + "@absl_py//absl/testing:LICENSE", + "@absl_py//absl/third_party/unittest3_backport:LICENSE", "@arm_neon_2_x86_sse//:LICENSE", "@astor_archive//:LICENSE", "@boringssl//:LICENSE", @@ -155,6 +159,7 @@ filegroup( "@curl//:COPYING", "@double_conversion//:LICENSE", "@eigen_archive//:COPYING.MPL2", + "@enum34_archive//:LICENSE", "@farmhash_archive//:COPYING", "@fft2d//:fft/readme.txt", "@flatbuffers//:LICENSE.txt", -- GitLab From f5465080e9d3b2544c5b0fe364070d55b469e516 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Thu, 17 Jan 2019 11:56:22 -0800 Subject: [PATCH 0871/2345] Device Assignment support in TPUStrategy for StatefulRNN PiperOrigin-RevId: 229791509 --- .../contrib/distribute/python/tpu_strategy.py | 56 +++++++++++++++++-- .../tpu/python/tpu/device_assignment.py | 3 + 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/tensorflow/contrib/distribute/python/tpu_strategy.py b/tensorflow/contrib/distribute/python/tpu_strategy.py index b9d3924a24..f55e6f0b20 100644 --- a/tensorflow/contrib/distribute/python/tpu_strategy.py +++ b/tensorflow/contrib/distribute/python/tpu_strategy.py @@ -25,6 +25,8 @@ import copy import functools 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 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 @@ -58,6 +60,8 @@ def initialize_tpu_system(cluster_resolver=None): 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 = resolver_lib.TPUClusterResolver("") @@ -68,8 +72,9 @@ def initialize_tpu_system(cluster_resolver=None): with ops.Graph().as_default(): with session_lib.Session(config=session_config, target=master) as sess: - sess.run([tpu.initialize_system()]) + 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): @@ -149,6 +154,7 @@ class TPUStrategy(distribute_lib.DistributionStrategy): def __init__(self, tpu_cluster_resolver=None, steps_per_run=None, + device_assignment=None, **kwargs): """Initializes the TPUStrategy object. @@ -160,10 +166,13 @@ class TPUStrategy(distribute_lib.DistributionStrategy): 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. **kwargs: Additional experimental flags. Will be removed in future. """ super(TPUStrategy, self).__init__(TPUExtended( - self, tpu_cluster_resolver, steps_per_run)) + self, tpu_cluster_resolver, steps_per_run, device_assignment)) self._disable_training_loop_on_host = False if len(kwargs) > 1: @@ -188,7 +197,8 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended): def __init__(self, container_strategy, tpu_cluster_resolver=None, - steps_per_run=None): + steps_per_run=None, + device_assignment=None): super(TPUExtended, self).__init__(container_strategy) if tpu_cluster_resolver is None: @@ -201,6 +211,21 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended): 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. @@ -604,15 +629,34 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended): @property def num_hosts(self): - return self._tpu_metadata.num_hosts + 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): - return self._tpu_metadata.num_of_cores_per_host + 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): - return self._tpu_metadata.num_cores + 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): diff --git a/tensorflow/contrib/tpu/python/tpu/device_assignment.py b/tensorflow/contrib/tpu/python/tpu/device_assignment.py index 6906501ecf..3313dc749c 100644 --- a/tensorflow/contrib/tpu/python/tpu/device_assignment.py +++ b/tensorflow/contrib/tpu/python/tpu/device_assignment.py @@ -25,6 +25,9 @@ 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 = {} -- GitLab From 5f640305f37a58970f7070ecc074ada1ec14107b Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Thu, 17 Jan 2019 11:58:19 -0800 Subject: [PATCH 0872/2345] Disable nnapi_delegate_test portable tests PiperOrigin-RevId: 229791853 --- tensorflow/lite/delegates/nnapi/BUILD | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/delegates/nnapi/BUILD b/tensorflow/lite/delegates/nnapi/BUILD index dda3c02567..63f6da1fa6 100644 --- a/tensorflow/lite/delegates/nnapi/BUILD +++ b/tensorflow/lite/delegates/nnapi/BUILD @@ -24,7 +24,11 @@ tf_cc_test( name = "nnapi_delegate_test", size = "small", srcs = ["nnapi_delegate_test.cc"], - tags = ["tflite_not_portable_ios"], + tags = [ + # TODO(b/122987564): Enable on Android after resolving API 27 failures. + "tflite_not_portable_android", + "tflite_not_portable_ios", + ], deps = [ ":nnapi_delegate", "//tensorflow/lite:framework", -- GitLab From 1e8a3d9e1fc542bca285682dd0fb9971ac3262d3 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Thu, 17 Jan 2019 12:06:17 -0800 Subject: [PATCH 0873/2345] Adds v2 binary crossentropy metric. PiperOrigin-RevId: 229793647 --- tensorflow/python/keras/metrics.py | 66 +++++++++++++ tensorflow/python/keras/metrics_test.py | 120 ++++++++++++++++++++++++ 2 files changed, 186 insertions(+) diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index d6010e3cb0..d3bd523d29 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -2411,6 +2411,72 @@ class MeanTensor(Metric): K.set_value(v, np.zeros(self._shape.as_list())) +class BinaryCrossentropy(MeanMetricWrapper): + """Computes the binary crossentropy between `y_true` and `y_pred`. + + Usage: + + ```python + m = tf.keras.metrics.BinaryCrossentropy() + m.update_state([1., 0., 1., 0.], [1., 1., 1., 0.]) + + # EPSILON = 1e-7, y = y_true, y` = y_pred, Y_MAX = 0.9999999 + # y` = clip_ops.clip_by_value(output, EPSILON, 1. - EPSILON) + # y` = [Y_MAX, Y_MAX, Y_MAX, EPSILON] + + # Metric = -(y log(y` + EPSILON) + (1 - y) log(1 - y` + EPSILON)) + # = [-log(Y_MAX + EPSILON), -log(1 - Y_MAX + EPSILON), + # -log(Y_MAX + EPSILON), -log(1)] + # = [(0 + 15.33) / 2, (0 + 0) / 2] + # Reduced metric = 7.665 / 2 + + print('Final result: ', m.result().numpy()) # Final result: 3.833 + ``` + + Usage with tf.keras API: + + ```python + model = keras.models.Model(inputs, outputs) + model.compile( + 'sgd', + loss='mse', + metrics=[tf.keras.metrics.BinaryCrossentropy()]) + ``` + """ + + def __init__(self, + name='binary_crossentropy', + dtype=None, + from_logits=False, + label_smoothing=0): + """Creates a `BinaryCrossentropy` instance. + + Args: + name: (Optional) string name of the metric instance. + dtype: (Optional) data type of the metric result. + from_logits: (Optional )Whether output is expected to be a logits tensor. + By default, we consider that output encodes a probability distribution. + label_smoothing: (Optional) Float in [0, 1]. When > 0, label values are + smoothed, meaning the confidence on label values are relaxed. + e.g. `label_smoothing=0.2` means that we will use a value of `0.1` for + label `0` and `0.9` for label `1`" + """ + label_smoothing = ops.convert_to_tensor(label_smoothing, dtype=K.floatx()) + + super(BinaryCrossentropy, self).__init__( + binary_crossentropy, + name, + dtype=dtype, + from_logits=from_logits, + label_smoothing=label_smoothing) + + @classmethod + def from_config(cls, config): + if 'fn' in config: + config.pop('fn') + return super(BinaryCrossentropy, cls).from_config(config) + + def accuracy(y_true, y_pred): y_pred.get_shape().assert_is_compatible_with(y_true.get_shape()) if y_true.dtype != y_pred.dtype: diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index bd94132a9a..4e755dd301 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -1200,6 +1200,126 @@ class MeanTensorTest(keras_parameterized.TestCase): np.full((4, 3), 4)) +@test_util.run_all_in_graph_and_eager_modes +class BinaryCrossentropyTest(test.TestCase): + + def test_config(self): + bce_obj = metrics.BinaryCrossentropy( + name='bce', dtype=dtypes.int32, label_smoothing=0.2) + self.assertEqual(bce_obj.name, 'bce') + self.assertEqual(bce_obj._dtype, dtypes.int32) + + old_config = bce_obj.get_config() + self.assertAllClose(self.evaluate(old_config['label_smoothing']), 0.2, 1e-3) + + # Check save and restore config + bce_obj2 = metrics.BinaryCrossentropy.from_config(old_config) + self.assertEqual(bce_obj2.name, 'bce') + self.assertEqual(bce_obj2._dtype, dtypes.int32) + new_config = bce_obj2.get_config() + self.assertAllClose(self.evaluate(new_config['label_smoothing']), 0.2, 1e-3) + + def test_unweighted(self): + bce_obj = metrics.BinaryCrossentropy() + self.evaluate(variables.variables_initializer(bce_obj.variables)) + y_true = np.asarray([1, 0, 1, 0]).reshape([2, 2]) + y_pred = np.asarray([1, 1, 1, 0], dtype=np.float32).reshape([2, 2]) + result = bce_obj(y_true, y_pred) + + # EPSILON = 1e-7, y = y_true, y` = y_pred, Y_MAX = 0.9999999 + # y` = clip_ops.clip_by_value(output, EPSILON, 1. - EPSILON) + # y` = [Y_MAX, Y_MAX, Y_MAX, EPSILON] + + # Metric = -(y log(y` + EPSILON) + (1 - y) log(1 - y` + EPSILON)) + # = [-log(Y_MAX + EPSILON), -log(1 - Y_MAX + EPSILON), + # -log(Y_MAX + EPSILON), -log(1)] + # = [(0 + 15.33) / 2, (0 + 0) / 2] + # Reduced metric = 7.665 / 2 + + self.assertAllClose(self.evaluate(result), 3.833, atol=1e-3) + + def test_unweighted_with_logits(self): + bce_obj = metrics.BinaryCrossentropy(from_logits=True) + self.evaluate(variables.variables_initializer(bce_obj.variables)) + + y_true = constant_op.constant([[1, 0, 1], [0, 1, 1]]) + y_pred = constant_op.constant([[100.0, -100.0, 100.0], + [100.0, 100.0, -100.0]]) + result = bce_obj(y_true, y_pred) + + # Metric = max(x, 0) - x * z + log(1 + exp(-abs(x))) + # (where x = logits and z = y_true) + # = [((100 - 100 * 1 + log(1 + exp(-100))) + + # (0 + 100 * 0 + log(1 + exp(-100))) + + # (100 - 100 * 1 + log(1 + exp(-100))), + # ((100 - 100 * 0 + log(1 + exp(-100))) + + # (100 - 100 * 1 + log(1 + exp(-100))) + + # (0 + 100 * 1 + log(1 + exp(-100))))] + # = [(0 + 0 + 0) / 3, 200 / 3] + # Reduced metric = (0 + 66.666) / 2 + + self.assertAllClose(self.evaluate(result), 33.333, atol=1e-3) + + def test_weighted(self): + bce_obj = metrics.BinaryCrossentropy() + self.evaluate(variables.variables_initializer(bce_obj.variables)) + y_true = np.asarray([1, 0, 1, 0]).reshape([2, 2]) + y_pred = np.asarray([1, 1, 1, 0], dtype=np.float32).reshape([2, 2]) + sample_weight = constant_op.constant([1.5, 2.]) + result = bce_obj(y_true, y_pred, sample_weight=sample_weight) + + # EPSILON = 1e-7, y = y_true, y` = y_pred, Y_MAX = 0.9999999 + # y` = clip_ops.clip_by_value(output, EPSILON, 1. - EPSILON) + # y` = [Y_MAX, Y_MAX, Y_MAX, EPSILON] + + # Metric = -(y log(y` + EPSILON) + (1 - y) log(1 - y` + EPSILON)) + # = [-log(Y_MAX + EPSILON), -log(1 - Y_MAX + EPSILON), + # -log(Y_MAX + EPSILON), -log(1)] + # = [(0 + 15.33) / 2, (0 + 0) / 2] + # Weighted metric = [7.665 * 1.5, 0] + # Reduced metric = 7.665 * 1.5 / (1.5 + 2) + + self.assertAllClose(self.evaluate(result), 3.285, atol=1e-3) + + def test_weighted_from_logits(self): + bce_obj = metrics.BinaryCrossentropy(from_logits=True) + self.evaluate(variables.variables_initializer(bce_obj.variables)) + y_true = constant_op.constant([[1, 0, 1], [0, 1, 1]]) + y_pred = constant_op.constant([[100.0, -100.0, 100.0], + [100.0, 100.0, -100.0]]) + sample_weight = constant_op.constant([2., 2.5]) + result = bce_obj(y_true, y_pred, sample_weight=sample_weight) + + # Metric = max(x, 0) - x * z + log(1 + exp(-abs(x))) + # (where x = logits and z = y_true) + # = [(0 + 0 + 0) / 3, 200 / 3] + # Weighted metric = [0, 66.666 * 2.5] + # Reduced metric = 66.666 * 2.5 / (2 + 2.5) + + self.assertAllClose(self.evaluate(result), 37.037, atol=1e-3) + + def test_label_smoothing(self): + logits = constant_op.constant(((100., -100., -100.))) + y_true = constant_op.constant(((1, 0, 1))) + label_smoothing = 0.1 + # Metric: max(x, 0) - x * z + log(1 + exp(-abs(x))) + # (where x = logits and z = y_true) + # Label smoothing: z' = z * (1 - L) + 0.5L + # After label smoothing, label 1 becomes 1 - 0.5L + # label 0 becomes 0.5L + # Applying the above two fns to the given input: + # (100 - 100 * (1 - 0.5 L) + 0 + + # 0 + 100 * (0.5 L) + 0 + + # 0 + 100 * (1 - 0.5 L) + 0) * (1/3) + # = (100 + 50L) * 1/3 + bce_obj = metrics.BinaryCrossentropy( + from_logits=True, label_smoothing=label_smoothing) + self.evaluate(variables.variables_initializer(bce_obj.variables)) + result = bce_obj(y_true, logits) + expected_value = (100.0 + 50.0 * label_smoothing) / 3.0 + self.assertAllClose(expected_value, self.evaluate(result), atol=1e-3) + + def _get_model(compile_metrics): model_layers = [ layers.Dense(3, activation='relu', kernel_initializer='ones'), -- GitLab From f6b3d83c1112686658c0e6d34c04eef545240056 Mon Sep 17 00:00:00 2001 From: Alex Ilchenko Date: Thu, 17 Jan 2019 12:15:41 -0800 Subject: [PATCH 0874/2345] Add TPU support for resize using nearest neighbor PiperOrigin-RevId: 229795283 --- tensorflow/compiler/tests/image_ops_test.py | 115 +++++- .../tf2xla/kernels/image_resize_ops.cc | 331 ++++++++++-------- 2 files changed, 304 insertions(+), 142 deletions(-) diff --git a/tensorflow/compiler/tests/image_ops_test.py b/tensorflow/compiler/tests/image_ops_test.py index 0e2d840418..12741c4d4a 100644 --- a/tensorflow/compiler/tests/image_ops_test.py +++ b/tensorflow/compiler/tests/image_ops_test.py @@ -403,6 +403,117 @@ class AdjustSaturationTest(xla_test.XLATestCase): self.assertAllClose(y_fused, y_baseline, rtol=2e-5, atol=1e-5) +class ResizeNearestNeighborTest(xla_test.XLATestCase): + # TODO(ilch): Wrap each test with `for dtype in self.float_types:` + # Some work to understand how that should be done was presented here: + # cl/227850213 + + def _assertForwardOpMatchesExpected(self, + image_np, + target_shape, + expected=None, + large_tolerance=False, + align_corners=True): + if expected is None: + self.fail("expected must be specified") + with self.cached_session() as sess, self.test_scope(): + image = array_ops.placeholder(image_np.dtype) + resized = gen_image_ops.resize_nearest_neighbor( + image, target_shape, align_corners=align_corners) + out = sess.run(resized, {image: image_np[np.newaxis, :, :, np.newaxis]}) + if large_tolerance: + self.assertAllClose( + expected[np.newaxis, :, :, np.newaxis], out, rtol=0.03, atol=0.1) + else: + self.assertAllClose(expected[np.newaxis, :, :, np.newaxis], out) + + def testAlignCorners2x2To1x1(self): + self._assertForwardOpMatchesExpected( + np.array([[1, 2], [3, 4]], dtype=np.float32), [1, 1], + expected=np.array([[1]], dtype=np.float32)) + + def testAlignCorners1x1To2x2(self): + self._assertForwardOpMatchesExpected( + np.array([[1]], dtype=np.float32), [2, 2], + expected=np.array([[1, 1], [1, 1]], dtype=np.float32)) + + def testAlignCorners1x1To3x3(self): + self._assertForwardOpMatchesExpected( + np.array([[1]], dtype=np.float32), [3, 3], + expected=np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.float32)) + + def testAlignCorners2x2To3x3(self): + self._assertForwardOpMatchesExpected( + np.array([[1, 2], [3, 4]], dtype=np.float32), [3, 3], + expected=np.array([[1, 2, 2], [3, 4, 4], [3, 4, 4]], dtype=np.float32)) + + def testAlignCorners2x2To4x4(self): + self._assertForwardOpMatchesExpected( + np.array([[1, 2], [3, 4]], dtype=np.float32), [4, 4], + expected=np.array( + [[1, 1, 2, 2], [1, 1, 2, 2], [3, 3, 4, 4], [3, 3, 4, 4]], + dtype=np.float32)) + + def testAlignCorners3x3To2x2(self): + self._assertForwardOpMatchesExpected( + np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32), [2, 2], + expected=np.array([[1, 3], [7, 9]], dtype=np.float32)) + + def testAlignCorners4x4To3x3(self): + self._assertForwardOpMatchesExpected( + np.array( + [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], + dtype=np.float32), [3, 3], + expected=np.array([[1, 3, 4], [9, 11, 12], [13, 15, 16]], + dtype=np.float32)) + + def testAlignCorners3x3To4x4(self): + self._assertForwardOpMatchesExpected( + np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32), [4, 4], + expected=np.array( + [[1, 2, 2, 3], [4, 5, 5, 6], [4, 5, 5, 6], [7, 8, 8, 9]], + dtype=np.float32)) + + def testAlignCorners3x3To6x6(self): + self._assertForwardOpMatchesExpected( + np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32), [6, 6], + expected=np.array( + [[1, 1, 2, 2, 3, 3], [1, 1, 2, 2, 3, 3], [4, 4, 5, 5, 6, 6], + [4, 4, 5, 5, 6, 6], [7, 7, 8, 8, 9, 9], [7, 7, 8, 8, 9, 9]], + dtype=np.float32)) + + def testAlignCorners3x3To9x9(self): + # The expected matrix might look uneven in terms of how many of each number + # there is, but this is an artifact of doing the dilation and convolution + # iteratively. The behavior is less esoteric in the 3x3To12x12 case below. + self._assertForwardOpMatchesExpected( + np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32), [9, 9], + expected=np.array( + [[1, 2, 2, 2, 2, 3, 3, 3, 3], [4, 5, 5, 5, 5, 6, 6, 6, 6], + [4, 5, 5, 5, 5, 6, 6, 6, 6], [4, 5, 5, 5, 5, 6, 6, 6, 6], + [4, 5, 5, 5, 5, 6, 6, 6, 6], [7, 8, 8, 8, 8, 9, 9, 9, 9], + [7, 8, 8, 8, 8, 9, 9, 9, 9], [7, 8, 8, 8, 8, 9, 9, 9, 9], + [7, 8, 8, 8, 8, 9, 9, 9, 9]], + dtype=np.float32)) + + def testAlignCorners3x3To12x12(self): + self._assertForwardOpMatchesExpected( + np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32), [12, 12], + expected=np.array([[1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3], + [1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3], + [1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3], + [4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6], + [4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6], + [4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6], + [4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6], + [4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6], + [4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6], + [7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9], + [7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9], + [7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9]], + dtype=np.float32)) + + class ResizeBilinearTest(xla_test.XLATestCase): def _assertForwardOpMatchesExpected(self, @@ -444,14 +555,14 @@ class ResizeBilinearTest(xla_test.XLATestCase): self.assertAllCloseAccordingToType(expected[np.newaxis, :, :, np.newaxis], out) - def testAlignCorners1x2To3x2(self): + def testAlignCorners1x2To3x3(self): for dtype in self.float_types: self._assertForwardOpMatchesExpected( np.array([[1, 2]], dtype=dtype), [3, 3], expected=np.array([[1, 1.5, 2], [1, 1.5, 2], [1, 1.5, 2]], dtype=np.float32)) - def testAlignCorners1x2To3x2Grad(self): + def testAlignCorners1x2To3x3Grad(self): for dtype in self.float_types: self._assertBackwardOpMatchesExpected( np.array([[1, 2], [3, 4], [5, 6]], dtype=np.float32), diff --git a/tensorflow/compiler/tf2xla/kernels/image_resize_ops.cc b/tensorflow/compiler/tf2xla/kernels/image_resize_ops.cc index 5a10c52ba8..b96d45316f 100644 --- a/tensorflow/compiler/tf2xla/kernels/image_resize_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/image_resize_ops.cc @@ -72,10 +72,10 @@ namespace { // from in_size to out_size. struct ResizeConvolutionDims { // Size of the kernel to use. - std::vector kernel_size; + std::vector kernel_size; // k // Stride of the convolution to use. - std::vector stride; + std::vector stride; // S }; ResizeConvolutionDims ComputeResizeConvolutionParameters( absl::Span in_size, absl::Span out_size, @@ -117,8 +117,10 @@ ResizeConvolutionDims ComputeResizeConvolutionParameters( // + dims.stride * (out_size - 1) int64 CalculateUpperPadding(int64 in_size, int64 out_size, int64 kernel_size, int64 stride) { - return (2 * kernel_size - 1) + (out_size - 1) * stride - (kernel_size - 1) - - 1 - (kernel_size * (in_size - 1)); + int64 padding = (2 * kernel_size - 1) + (out_size - 1) * stride - + (kernel_size - 1) - 1 - (kernel_size * (in_size - 1)); + + return padding; } // Form a 2D convolution kernel like: @@ -132,7 +134,7 @@ int64 CalculateUpperPadding(int64 in_size, int64 out_size, int64 kernel_size, // If the 2D kernel would be very large, the 1D kernel can be applied once in // each dimension due to the symmetry of the kernel along all axis to reduce the // computational intensity. -xla::XlaOp Make1DKernel(xla::XlaBuilder* builder, int64 n) { +xla::XlaOp MakeBilinear1DKernel(xla::XlaBuilder* builder, int64 n) { std::vector kernel(n * 2 - 1); for (int64 i = 0; i < n; ++i) { float v = (i + 1.0f) / n; @@ -142,43 +144,64 @@ xla::XlaOp Make1DKernel(xla::XlaBuilder* builder, int64 n) { return xla::ConstantR1(builder, kernel); } +// Unlike the bilinear kernel, which is triangular, the nearest neighbor +// kernel is a square. For example, a 1D kernel with n=3 would look like +// [0 1 1 1 0] +// and n=4 would look like +// [0 0 1 1 1 1 0]. +// Note that in the second case, the kernel is not symmetric and we default +// to the right (because an existing non TPU kernel +// for nearest neighbor resize already chose to default to the right, +// so we want to be consistent). +xla::XlaOp MakeNearestNeighbor1DKernel(xla::XlaBuilder* builder, int64 n) { + std::vector kernel(n * 2 - 1, 0.0f); + std::fill(&kernel[n / 2], &kernel[(3 * n) / 2], 1.0f); + + return xla::ConstantR1(builder, kernel); +} + // Kernels with more than 16 spatial elements are considered intense and the -// kernel should applied to each dimension independently. +// kernel should be applied to each dimension independently. const int64 kMax2DKernelSize = 16; -xla::XlaOp MakeBilinearResizeKernel(xla::XlaBuilder* builder, - absl::Span kernel_size, - int64 channels) { +xla::XlaOp MakeGeneralResizeKernel(xla::XlaBuilder* builder, + absl::Span kernel_size, + int64 channels, bool is_kernel_bilinear) { + auto make_kernel_func = + is_kernel_bilinear ? MakeBilinear1DKernel : MakeNearestNeighbor1DKernel; + auto depthwise_kernel = xla::Broadcast( xla::Zero(builder, xla::F32), {(2 * kernel_size[0] - 1), (2 * kernel_size[1] - 1), channels, 1}); return xla::Mul( - xla::Add(depthwise_kernel, Make1DKernel(builder, kernel_size[1]), + xla::Add(depthwise_kernel, make_kernel_func(builder, kernel_size[1]), /*broadcast_dimensions=*/{1}), - Make1DKernel(builder, kernel_size[0]), + make_kernel_func(builder, kernel_size[0]), /*broadcast_dimensions=*/{0}); } -xla::XlaOp MakeBilinearResizeKernelInDim(xla::XlaBuilder* builder, - absl::Span kernel_size, - int64 channels, int64 dim) { +xla::XlaOp MakeGeneralResizeKernelInDim(xla::XlaBuilder* builder, + absl::Span kernel_size, + int64 channels, int64 dim, + bool is_kernel_bilinear) { + auto make_kernel_func = + is_kernel_bilinear ? MakeBilinear1DKernel : MakeNearestNeighbor1DKernel; + auto depthwise_kernel = xla::Broadcast(xla::Zero(builder, xla::F32), {dim == 0 ? (2 * kernel_size[0] - 1) : 1, dim == 1 ? (2 * kernel_size[1] - 1) : 1, channels, 1}); - return xla::Add(depthwise_kernel, Make1DKernel(builder, kernel_size[dim]), + return xla::Add(depthwise_kernel, make_kernel_func(builder, kernel_size[dim]), /*broadcast_dimensions=*/{dim}); } -xla::XlaOp ResizeUsingDilationAndConvolution(xla::XlaBuilder* builder, - const xla::XlaOp& input, - const int num_spatial_dims, - std::vector in_size, - std::vector out_size, - const int64 channels, - const bool align_corners) { - // Picture for a 1x3 to 1x4 resize: +xla::XlaOp ResizeUsingDilationAndConvolution( + xla::XlaBuilder* builder, const xla::XlaOp& input, + const int num_spatial_dims, std::vector in_size, + std::vector out_size, const int64 channels, const bool align_corners, + bool is_kernel_bilinear) { + // Picture for a 1x3 to 1x4 bilinear resize: // stride = 2, kernel size = 3 // Input: // 3 6 9 @@ -264,8 +287,8 @@ xla::XlaOp ResizeUsingDilationAndConvolution(xla::XlaBuilder* builder, // Split convolutions into independent dimensions if they would be a very // large kernel. if (dims.kernel_size[0] * dims.kernel_size[1] < kMax2DKernelSize) { - xla::XlaOp kernel = - MakeBilinearResizeKernel(builder, dims.kernel_size, channels); + xla::XlaOp kernel = MakeGeneralResizeKernel(builder, dims.kernel_size, + channels, is_kernel_bilinear); output = xla::ConvGeneralDilated(input_data, kernel, dims.stride, /*padding=*/ @@ -275,8 +298,8 @@ xla::XlaOp ResizeUsingDilationAndConvolution(xla::XlaBuilder* builder, /*rhs_dilation=*/{1, 1}, dimension_numbers, /*feature_group_count=*/channels); } else { - xla::XlaOp kernel0 = - MakeBilinearResizeKernelInDim(builder, dims.kernel_size, channels, 0); + xla::XlaOp kernel0 = MakeGeneralResizeKernelInDim( + builder, dims.kernel_size, channels, 0, is_kernel_bilinear); output = xla::ConvGeneralDilated( input_data, kernel0, {dims.stride[0], 1}, /*padding=*/ @@ -284,8 +307,8 @@ xla::XlaOp ResizeUsingDilationAndConvolution(xla::XlaBuilder* builder, /*lhs_dilation=*/{dims.kernel_size[0], 1}, /*rhs_dilation=*/{1, 1}, dimension_numbers, /*feature_group_count=*/channels); - xla::XlaOp kernel1 = - MakeBilinearResizeKernelInDim(builder, dims.kernel_size, channels, 1); + xla::XlaOp kernel1 = MakeGeneralResizeKernelInDim( + builder, dims.kernel_size, channels, 1, is_kernel_bilinear); output = xla::ConvGeneralDilated( output, kernel1, {1, dims.stride[1]}, /*padding=*/ @@ -306,13 +329,11 @@ xla::XlaOp ResizeUsingDilationAndConvolution(xla::XlaBuilder* builder, return output; } -xla::XlaOp ResizeUsingDilationAndConvolutionGradOp(xla::XlaBuilder* builder, - const xla::XlaOp& grad, - const int num_spatial_dims, - std::vector in_size, - std::vector grad_size, - const int64 channels, - const bool align_corners) { +xla::XlaOp ResizeUsingDilationAndConvolutionGradOp( + xla::XlaBuilder* builder, const xla::XlaOp& grad, + const int num_spatial_dims, std::vector in_size, + std::vector grad_size, const int64 channels, + const bool align_corners, bool is_kernel_bilinear) { ResizeConvolutionDims dims = ComputeResizeConvolutionParameters(in_size, grad_size, align_corners); @@ -332,8 +353,8 @@ xla::XlaOp ResizeUsingDilationAndConvolutionGradOp(xla::XlaBuilder* builder, dimension_numbers.set_kernel_output_feature_dimension(num_spatial_dims); xla::XlaOp output; if (dims.kernel_size[0] * dims.kernel_size[1] < kMax2DKernelSize) { - xla::XlaOp kernel = - MakeBilinearResizeKernel(builder, dims.kernel_size, channels); + xla::XlaOp kernel = MakeGeneralResizeKernel(builder, dims.kernel_size, + channels, is_kernel_bilinear); // Broadcast the input kernel where the forward op expanded from a size == 1 // dimension to a size > 1 dimension. This has the effect of summing the @@ -355,14 +376,14 @@ xla::XlaOp ResizeUsingDilationAndConvolutionGradOp(xla::XlaBuilder* builder, /*rhs_dilation=*/{1, 1}, dimension_numbers, /*feature_group_count=*/channels); } else { - xla::XlaOp kernel0 = - MakeBilinearResizeKernelInDim(builder, dims.kernel_size, channels, 0); - xla::XlaOp kernel1 = - MakeBilinearResizeKernelInDim(builder, dims.kernel_size, channels, 1); - - // Broadcast the input kernel where the forward op expanded from a size == 1 - // dimension to a size > 1 dimension. This has the effect of summing the - // gradient contributions in that dimension. + xla::XlaOp kernel0 = MakeGeneralResizeKernelInDim( + builder, dims.kernel_size, channels, 0, is_kernel_bilinear); + xla::XlaOp kernel1 = MakeGeneralResizeKernelInDim( + builder, dims.kernel_size, channels, 1, is_kernel_bilinear); + + // Broadcast the input kernel where the forward op expanded from a + // size == 1 dimension to a size > 1 dimension. This has the effect of + // summing the gradient contributions in that dimension. if (in_size[0] == 1 && grad_size[0] > 1) { kernel0 = xla::Add(kernel0, xla::ConstantR1(builder, grad_size[0], 0), @@ -407,109 +428,139 @@ xla::XlaOp ResizeUsingDilationAndConvolutionGradOp(xla::XlaBuilder* builder, return output; } -class ResizeBilinearOp : public XlaOpKernel { - public: - explicit ResizeBilinearOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) { - OP_REQUIRES_OK(ctx, ctx->GetAttr("align_corners", &align_corners_)); +void GeneralCompile(XlaOpKernelContext* ctx, bool align_corners_, + bool is_kernel_bilinear) { + xla::XlaBuilder* b = ctx->builder(); + + TensorShape input_shape = ctx->InputShape(0); + OP_REQUIRES(ctx, input_shape.dims() == 4, + errors::InvalidArgument("input must be 4-dimensional", + input_shape.DebugString())); + // First dimension always assumed to be batch + const int64 batch = input_shape.dim_size(0); + std::vector in_size = {input_shape.dim_size(1), + input_shape.dim_size(2)}; + // Last/4th dimension always assumed to be num channels + const int64 channels = input_shape.dim_size(3); + OP_REQUIRES(ctx, in_size[0] > 0 && in_size[1] > 0, + errors::InvalidArgument("input size must be positive, got [", + in_size[0], ",", in_size[1], "]")); + + std::vector out_size; + OP_REQUIRES_OK(ctx, ctx->ConstantInputAsIntVector(1, &out_size)); + OP_REQUIRES(ctx, out_size.size() == 2, + errors::InvalidArgument("output size must be length 2, got ", + out_size.size())); + OP_REQUIRES(ctx, out_size[0] > 0 && out_size[1] > 0, + errors::InvalidArgument("output size must be positive, got [", + out_size[0], ",", out_size[1], "]")); + + const int num_spatial_dims = 2; + + xla::XlaOp input = ctx->Input(0); + + // If in_size[i] > 1 and out_size[i] == 1, slice out the first input in + // dimension i. + bool slice_input = false; + for (int i = 0; i < num_spatial_dims; ++i) { + if (in_size[i] > 1 && out_size[i] == 1) { + // If in_size[i] > 1 but out_size[i] == 1, then we slice out the first + // entry before resizing. + slice_input = true; + in_size[i] = 1; + } + } + if (slice_input) { + input = xla::Slice(input, {0, 0, 0, 0}, + {batch, in_size[0], in_size[1], channels}, {1, 1, 1, 1}); } - void Compile(XlaOpKernelContext* ctx) override { - xla::XlaBuilder* b = ctx->builder(); + // Output is always type float. + input = xla::ConvertElementType(input, xla::F32); + + // Special Case: + // Instead of doing a ResizeUsingDilationAndConvolution directly, + // while (out_size[0]-1) = c * 2^x * (in_size[0]-1) for x>1 c>1, resize the + // image to 2*(in_size[0]-1)+1 x-times and then resize by scale c(int here). + // Instead of resizing directly we resize it iteratively. + // + // Since bilinear resize can be broken down as 2 sequential linear + // operations along different dimensions. + // Given sufficient numerical stability and a cxd is same as resizing axb -> exf -> cxd. + // This does not work in the case of align_corners_=false because of special + // padding requirements that cause multiple resizes to be very different + // from a single resize. + // + // This makes the convolutions kernels smaller and the operation faster. + xla::XlaOp output = input; + while (in_size != out_size) { + if (in_size[0] != 1 && in_size[1] != 1) { + std::vector k = { + (static_cast(out_size[0]) - 1) / ((in_size[0] - 1) * 2), + (static_cast(out_size[1]) - 1) / ((in_size[1] - 1) * 2)}; + if ((k[0] == std::floor(k[0])) && (k[1] == std::floor(k[1])) && + k[0] > 1 && k[1] > 1 && align_corners_) { + std::vector next_out_size = {(in_size[0] - 1) * 2 + 1, + (in_size[1] - 1) * 2 + 1}; + output = ResizeUsingDilationAndConvolution( + b, input, num_spatial_dims, in_size, next_out_size, channels, + align_corners_, is_kernel_bilinear); + input = output; + in_size = next_out_size; + } else { + output = ResizeUsingDilationAndConvolution( + b, input, num_spatial_dims, in_size, out_size, channels, + align_corners_, is_kernel_bilinear); + in_size = out_size; + } + } else { + output = ResizeUsingDilationAndConvolution( + b, input, num_spatial_dims, in_size, out_size, channels, + align_corners_, is_kernel_bilinear); + in_size = out_size; + } + } - TensorShape input_shape = ctx->InputShape(0); - OP_REQUIRES(ctx, input_shape.dims() == 4, - errors::InvalidArgument("input must be 4-dimensional", - input_shape.DebugString())); - const int64 batch = input_shape.dim_size(0); - std::vector in_size = {input_shape.dim_size(1), - input_shape.dim_size(2)}; - const int64 channels = input_shape.dim_size(3); - OP_REQUIRES(ctx, in_size[0] > 0 && in_size[1] > 0, - errors::InvalidArgument("input size must be positive, got [", - in_size[0], ",", in_size[1], "]")); + ctx->SetOutput(0, output); +} - std::vector out_size; - OP_REQUIRES_OK(ctx, ctx->ConstantInputAsIntVector(1, &out_size)); - OP_REQUIRES(ctx, out_size.size() == 2, - errors::InvalidArgument("output size must be length 2, got ", - out_size.size())); - OP_REQUIRES(ctx, out_size[0] > 0 && out_size[1] > 0, - errors::InvalidArgument("output size must be positive, got [", - out_size[0], ",", out_size[1], "]")); +class ResizeNearestNeighborOp : public XlaOpKernel { + public: + explicit ResizeNearestNeighborOp(OpKernelConstruction* ctx) + : XlaOpKernel(ctx) { + OP_REQUIRES_OK(ctx, ctx->GetAttr("align_corners", &align_corners_)); + OP_REQUIRES( + ctx, align_corners_ == true, + errors::Unimplemented("ResizeNearestNeighbor with align_corners=False " + "is not yet implemented")); + } - const int num_spatial_dims = 2; + void Compile(XlaOpKernelContext* ctx) override { + GeneralCompile(ctx, align_corners_, is_kernel_bilinear_); + } - xla::XlaOp input = ctx->Input(0); + private: + bool align_corners_ = true; + bool is_kernel_bilinear_ = false; +}; - // If in_size[i] > 1 and out_size[i] == 1, slice out the first input in - // dimension i. - bool slice_input = false; - for (int i = 0; i < num_spatial_dims; ++i) { - if (in_size[i] > 1 && out_size[i] == 1) { - // If in_size[i] > 1 but out_size[i] == 1, then we slice out the first - // entry before resizing. - slice_input = true; - in_size[i] = 1; - } - } - if (slice_input) { - input = - xla::Slice(input, {0, 0, 0, 0}, - {batch, in_size[0], in_size[1], channels}, {1, 1, 1, 1}); - } +REGISTER_XLA_OP(Name("ResizeNearestNeighbor").CompileTimeConstantInput("size"), + ResizeNearestNeighborOp); - // Output is always type float. - input = xla::ConvertElementType(input, xla::F32); - - // Special Case: - // Instead of doing a ResizeUsingDilationAndConvolution directly, - // while (out_size[0]-1) = c * 2^x * (in_size[0]-1) for x>1 c>1, resize the - // image to 2*(in_size[0]-1)+1 x-times and then resize by scale c(int here). - // Instead of resizing directly we resize it iteratively. - // - // Since bilinear resize can be broken down as 2 sequential linear - // operations along different dimensions. - // Given sufficient numerical stability and a cxd is same as resizing axb -> exf -> cxd. - // This does not work in the case of align_corners_=false because of special - // padding requirements that cause multiple resizes to be very different - // from a single resize. - // - // This makes the convolutions kernels smaller and the operation faster. - xla::XlaOp output = input; - while (in_size != out_size) { - if (in_size[0] != 1 && in_size[1] != 1) { - std::vector k = { - (static_cast(out_size[0]) - 1) / ((in_size[0] - 1) * 2), - (static_cast(out_size[1]) - 1) / ((in_size[1] - 1) * 2)}; - if ((k[0] == std::floor(k[0])) && (k[1] == std::floor(k[1])) && - k[0] > 1 && k[1] > 1 && align_corners_) { - std::vector next_out_size = {(in_size[0] - 1) * 2 + 1, - (in_size[1] - 1) * 2 + 1}; - output = ResizeUsingDilationAndConvolution(b, input, num_spatial_dims, - in_size, next_out_size, - channels, align_corners_); - input = output; - in_size = next_out_size; - } else { - output = ResizeUsingDilationAndConvolution(b, input, num_spatial_dims, - in_size, out_size, - channels, align_corners_); - in_size = out_size; - } - } else { - output = ResizeUsingDilationAndConvolution(b, input, num_spatial_dims, - in_size, out_size, channels, - align_corners_); - in_size = out_size; - } - } +class ResizeBilinearOp : public XlaOpKernel { + public: + explicit ResizeBilinearOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) { + OP_REQUIRES_OK(ctx, ctx->GetAttr("align_corners", &align_corners_)); + } - ctx->SetOutput(0, output); + void Compile(XlaOpKernelContext* ctx) override { + GeneralCompile(ctx, align_corners_, is_kernel_bilinear_); } private: - bool align_corners_; + bool align_corners_ = true; + bool is_kernel_bilinear_ = true; }; REGISTER_XLA_OP(Name("ResizeBilinear").CompileTimeConstantInput("size"), @@ -581,19 +632,19 @@ class ResizeBilinearGradOp : public XlaOpKernel { (in_size[1] - 1) * 2 + 1}; output = ResizeUsingDilationAndConvolutionGradOp( b, grad, num_spatial_dims, in_size, next_grad_size, channels, - align_corners_); + align_corners_, true); grad = output; in_size = next_grad_size; } else { output = ResizeUsingDilationAndConvolutionGradOp( b, grad, num_spatial_dims, in_size, grad_size, channels, - align_corners_); + align_corners_, true); in_size = grad_size; } } else { output = ResizeUsingDilationAndConvolutionGradOp( b, grad, num_spatial_dims, in_size, grad_size, channels, - align_corners_); + align_corners_, true); in_size = grad_size; } } -- GitLab From 66ae27d95e18f1abd728dfbc0ce4c876b8a4f219 Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Thu, 17 Jan 2019 12:26:18 -0800 Subject: [PATCH 0875/2345] TensorScatter* ops should forward the correct input, and set output when forwarding. PiperOrigin-RevId: 229796887 --- tensorflow/core/kernels/scatter_nd_op.cc | 4 +++- tensorflow/python/kernel_tests/scatter_nd_ops_test.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/scatter_nd_op.cc b/tensorflow/core/kernels/scatter_nd_op.cc index 1b1c59cf34..50e4c66b7e 100644 --- a/tensorflow/core/kernels/scatter_nd_op.cc +++ b/tensorflow/core/kernels/scatter_nd_op.cc @@ -197,7 +197,7 @@ class TensorScatterOp : public OpKernel { } std::unique_ptr forwarded_input = c->forward_input( - 2, 0, input.dtype(), shape, DEVICE_MEMORY, AllocatorAttributes()); + 0, 0, input.dtype(), shape, DEVICE_MEMORY, AllocatorAttributes()); if (forwarded_input == nullptr) { // We were not able to forward the input, so we deep copy the tensor and @@ -215,6 +215,8 @@ class TensorScatterOp : public OpKernel { OP_REQUIRES_OK(c, functor::DoScatterNd( c, indices, updates, shape, forwarded_input.get(), false /*allocate*/)); + + c->set_output(0, *forwarded_input); } } }; diff --git a/tensorflow/python/kernel_tests/scatter_nd_ops_test.py b/tensorflow/python/kernel_tests/scatter_nd_ops_test.py index f30b5315aa..88f7b27b77 100644 --- a/tensorflow/python/kernel_tests/scatter_nd_ops_test.py +++ b/tensorflow/python/kernel_tests/scatter_nd_ops_test.py @@ -24,6 +24,7 @@ import numpy as np from tensorflow.python.client import session 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 @@ -750,6 +751,16 @@ class ScatterNdTensorTest(test.TestCase): self.assertLess(err_added_wrt_updates, 2e-4) self.assertLess(err_subbed_wrt_updates, 2e-4) + def testTensorScatterUpdateWithForwarding(self): + @def_function.function + def _TestFn(): + indices = constant_op.constant([[4], [3], [1], [7]]) + updates = constant_op.constant([9, 10, 11, 12], dtype=dtypes.float32) + t = array_ops.ones([8], dtype=dtypes.float32) + + return array_ops.tensor_scatter_update(t, indices, updates) + + self.assertAllEqual(_TestFn(), [1, 11, 1, 10, 9, 1, 1, 12]) if __name__ == "__main__": test.main() -- GitLab From bc7cf8062dc88b55130bc9b786707fca22c75206 Mon Sep 17 00:00:00 2001 From: Alan Chiao Date: Thu, 17 Jan 2019 12:32:26 -0800 Subject: [PATCH 0876/2345] Replace runtime memory alignment checks with debug time checks. average 7.5% performance improvement across different models with hybrid inference. PiperOrigin-RevId: 229797803 --- .../lite/kernels/internal/optimized/neon_tensor_utils.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/lite/kernels/internal/optimized/neon_tensor_utils.cc b/tensorflow/lite/kernels/internal/optimized/neon_tensor_utils.cc index 3e7b6c22be..730d9b662a 100644 --- a/tensorflow/lite/kernels/internal/optimized/neon_tensor_utils.cc +++ b/tensorflow/lite/kernels/internal/optimized/neon_tensor_utils.cc @@ -145,8 +145,8 @@ void NeonMatrixBatchVectorMultiplyAccumulate( // Load 16 8-bit values from the row and vector, each, to operate on. // Here the assumption is that each buffer is 4-byte aligned. Otherwise, // performance may suffer significantly. - TFLITE_CHECK_EQ((uintptr_t)(&row_ptr[col]) & (kWeightsPerUint32 - 1), - 0); + TFLITE_DCHECK_EQ( // NOLINT + (uintptr_t)(&row_ptr[col]) & (kWeightsPerUint32 - 1), 0); const int8x16_t s1_8x16 = vld1q_s8((const int8_t*)(aligned_vec + col)); const int8x16_t s2_8x16 = vld1q_s8((const int8_t*)(row_ptr + col)); // Multiply the low bits (i.e. the lower 8 8bit numbers in the @@ -174,8 +174,8 @@ void NeonMatrixBatchVectorMultiplyAccumulate( // Load 8 8-bit values from the row and column each to operate on. // Here the assumption is that each buffer is 4-bytes aligned. // Otherwise, performance may suffer significantly. - TFLITE_CHECK_EQ((uintptr_t)(&row_ptr[col]) & (kWeightsPerUint32 - 1), - 0); + TFLITE_DCHECK_EQ( // NOLINT + (uintptr_t)(&row_ptr[col]) & (kWeightsPerUint32 - 1), 0); const int8x8_t s1_8x8 = vld1_s8((const int8_t*)(aligned_vec + col)); const int8x8_t s2_8x8 = vld1_s8((const int8_t*)(row_ptr + col)); const int16x8_t prod_16x8 = vmull_s8(s1_8x8, s2_8x8); -- GitLab From a851fa125b0ddaf91e64a279f568d8ff01f6f76f Mon Sep 17 00:00:00 2001 From: Yanan Cao Date: Thu, 17 Jan 2019 12:33:39 -0800 Subject: [PATCH 0877/2345] Add a warning for dump_graph when dirname is not specified PiperOrigin-RevId: 229798002 --- tensorflow/core/util/dump_graph.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tensorflow/core/util/dump_graph.cc b/tensorflow/core/util/dump_graph.cc index 523d37ecc2..d275e076f8 100644 --- a/tensorflow/core/util/dump_graph.cc +++ b/tensorflow/core/util/dump_graph.cc @@ -84,6 +84,10 @@ string WriteTextProtoToUniqueFile(Env* env, const string& name, dir = getenv("TF_DUMP_GRAPH_PREFIX"); } if (!dir) { + LOG(WARNING) + << "Failed to dump " << name << " because dump location is not " + << " specified through either TF_DUMP_GRAPH_PREFIX environment " + << "variable or function argument."; return "(TF_DUMP_GRAPH_PREFIX not specified)"; } Status status = env->RecursivelyCreateDir(dir); -- GitLab From 155385b86579489f92c0f5643c8af97364fe5d24 Mon Sep 17 00:00:00 2001 From: Reed Wanderman-Milne Date: Thu, 17 Jan 2019 12:38:07 -0800 Subject: [PATCH 0878/2345] Automated rollback of commit 7e090f6a5412fd5f22b5b75ca7ae7844c3d025e9 PiperOrigin-RevId: 229798646 --- tensorflow/core/kernels/unicode_script_op.cc | 3 --- third_party/icu/BUILD.bazel | 1 - 2 files changed, 4 deletions(-) diff --git a/tensorflow/core/kernels/unicode_script_op.cc b/tensorflow/core/kernels/unicode_script_op.cc index f01bb52293..085e397eba 100644 --- a/tensorflow/core/kernels/unicode_script_op.cc +++ b/tensorflow/core/kernels/unicode_script_op.cc @@ -17,9 +17,6 @@ limitations under the License. #include "unicode/uscript.h" // TF:icu #include "tensorflow/core/framework/op_kernel.h" -// ICU codemap data is linked statically. -#define U_STATIC_IMPLEMENTATION - namespace tensorflow { class UnicodeScriptOp : public OpKernel { diff --git a/third_party/icu/BUILD.bazel b/third_party/icu/BUILD.bazel index 399962d249..36d6b9006b 100644 --- a/third_party/icu/BUILD.bazel +++ b/third_party/icu/BUILD.bazel @@ -44,7 +44,6 @@ cc_library( ]), copts = [ "-DU_COMMON_IMPLEMENTATION", - "-DU_STATIC_IMPLEMENTATION", "-DU_HAVE_STD_ATOMICS", ] + select({ ":android": [ -- GitLab From 7b690b1687fb506ad9e7faabe5095bb266a0f68f Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Thu, 17 Jan 2019 12:42:39 -0800 Subject: [PATCH 0879/2345] Return the task_type if the Cluster Resolver is in "grpc" mode PiperOrigin-RevId: 229799270 --- .../python/distribute/cluster_resolver/tpu_cluster_resolver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py index 72b9990701..635948fd19 100644 --- a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py @@ -378,7 +378,8 @@ class TPUClusterResolver(ClusterResolver): return self.master() def get_job_name(self): - if self._shouldResolve(): + if (self._shouldResolve() or + self._tpu.startswith(compat.as_bytes('grpc://'))): return self.task_type def cluster_spec(self): -- GitLab From 094ae1c04d3c021e7e0048c91204a04bb08d3eb0 Mon Sep 17 00:00:00 2001 From: Mihai Maruseac Date: Thu, 17 Jan 2019 12:48:01 -0800 Subject: [PATCH 0880/2345] Remove useless comments. PiperOrigin-RevId: 229800138 --- tensorflow/core/lib/gif/gif_io.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/lib/gif/gif_io.cc b/tensorflow/core/lib/gif/gif_io.cc index 3fad8c8b14..24aca854eb 100644 --- a/tensorflow/core/lib/gif/gif_io.cc +++ b/tensorflow/core/lib/gif/gif_io.cc @@ -94,8 +94,8 @@ uint8* Decode(const void* srcdata, int datasize, } const int num_frames = gif_file->ImageCount; - const int width = max_frame_width; // gif_file->SWidth; - const int height = max_frame_height; // gif_file->SHeight; + const int width = max_frame_width; + const int height = max_frame_height; const int channel = 3; uint8* const dstdata = allocate_output(num_frames, width, height, channel); -- GitLab From 76c2a2512c3fefe065415e338a98c1043acbb131 Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Thu, 17 Jan 2019 12:48:12 -0800 Subject: [PATCH 0881/2345] Use more specific assertions from absl base class. PiperOrigin-RevId: 229800181 --- tensorflow/python/module/module_test.py | 66 ++++++++++++------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/tensorflow/python/module/module_test.py b/tensorflow/python/module/module_test.py index e7c736696e..d5d36a7bbc 100644 --- a/tensorflow/python/module/module_test.py +++ b/tensorflow/python/module/module_test.py @@ -143,64 +143,64 @@ class VariableTrackingTest(test.TestCase): def test_variables(self): m = RecursiveModule(3) - self.assertEqual(m.variables, (m.w, m.child.w, m.child.child.w)) - self.assertEqual(m.child.variables, (m.child.w, m.child.child.w)) - self.assertEqual(m.child.child.variables, (m.child.child.w,)) + self.assertCountEqual(m.variables, (m.w, m.child.w, m.child.child.w)) + self.assertCountEqual(m.child.variables, (m.child.w, m.child.child.w)) + self.assertCountEqual(m.child.child.variables, (m.child.child.w,)) def test_owned_variables(self): m = RecursiveModule(3) - self.assertEqual(m.owned_variables, (m.w,)) - self.assertEqual(m.child.owned_variables, (m.child.w,)) - self.assertEqual(m.child.child.owned_variables, (m.child.child.w,)) + self.assertCountEqual(m.owned_variables, (m.w,)) + self.assertCountEqual(m.child.owned_variables, (m.child.w,)) + self.assertCountEqual(m.child.child.owned_variables, (m.child.child.w,)) def test_trainable_variables(self): m = RecursiveModule(3) - self.assertEqual(m.trainable_variables, - (m.w, m.child.w, m.child.child.w)) - self.assertEqual(m.child.trainable_variables, - (m.child.w, m.child.child.w)) - self.assertEqual(m.child.child.trainable_variables, (m.child.child.w,)) + self.assertCountEqual(m.trainable_variables, + (m.w, m.child.w, m.child.child.w)) + self.assertCountEqual(m.child.trainable_variables, + (m.child.w, m.child.child.w)) + self.assertCountEqual(m.child.child.trainable_variables, (m.child.child.w,)) def test_trainable_variables_ignores_non_trainable(self): m = RecursiveModule(3, trainable=False) - self.assertEqual(len(m.trainable_variables), 0) - self.assertEqual(len(m.child.trainable_variables), 0) - self.assertEqual(len(m.child.child.trainable_variables), 0) + self.assertEmpty(m.trainable_variables) + self.assertEmpty(m.child.trainable_variables) + self.assertEmpty(m.child.child.trainable_variables) def test_owned_trainable_variables(self): m = RecursiveModule(3) - self.assertEqual(m.owned_trainable_variables, (m.w,)) - self.assertEqual(m.child.owned_trainable_variables, (m.child.w,)) - self.assertEqual(m.child.child.owned_trainable_variables, - (m.child.child.w,)) + self.assertCountEqual(m.owned_trainable_variables, (m.w,)) + self.assertCountEqual(m.child.owned_trainable_variables, (m.child.w,)) + self.assertCountEqual(m.child.child.owned_trainable_variables, + (m.child.child.w,)) def test_owned_trainable_variables_ignores_non_trainable(self): m = RecursiveModule(3, trainable=False) - self.assertEqual(len(m.owned_trainable_variables), 0) - self.assertEqual(len(m.child.owned_trainable_variables), 0) - self.assertEqual(len(m.child.child.owned_trainable_variables), 0) + self.assertEmpty(m.owned_trainable_variables) + self.assertEmpty(m.child.owned_trainable_variables) + self.assertEmpty(m.child.child.owned_trainable_variables) class ModuleTrackingTest(test.TestCase): def test_owned_submodules(self): m = RecursiveModule(3) - self.assertEqual(list(m.owned_submodules), [m.child]) - self.assertEqual(list(m.child.owned_submodules), [m.child.child]) - self.assertEqual(list(m.child.child.owned_submodules), []) + self.assertCountEqual(m.owned_submodules, [m.child]) + self.assertCountEqual(m.child.owned_submodules, [m.child.child]) + self.assertEmpty(list(m.child.child.owned_submodules)) def test_submodules(self): m = RecursiveModule(3) - self.assertEqual(list(m.submodules), [m.child, m.child.child]) - self.assertEqual(list(m.child.submodules), [m.child.child]) - self.assertEqual(list(m.child.child.submodules), []) + self.assertCountEqual(m.submodules, [m.child, m.child.child]) + self.assertCountEqual(m.child.submodules, [m.child.child]) + self.assertEmpty(list(m.child.child.submodules)) def test_non_ctor_submodule(self): m = TreeModule() leaf1 = m.new_leaf() - self.assertEqual(set(m.submodules), {leaf1}) + self.assertCountEqual(m.submodules, (leaf1,)) leaf2 = m.new_leaf() - self.assertEqual(set(m.submodules), {leaf1, leaf2}) + self.assertCountEqual(m.submodules, (leaf1, leaf2)) class CommonErrorsTest(test.TestCase): @@ -323,12 +323,12 @@ class WalkTest(test.TestCase): parent = SimpleModule() child = parent.c - self.assertEqual( - list(module.walk(parent, predicate=IS_MEMBER)), + self.assertCountEqual( + module.walk(parent, predicate=IS_MEMBER), [parent.a[0], parent.a[1], parent.z]) - self.assertEqual( - list(module.walk(parent, recurse_if=IS_MODULE, predicate=IS_MEMBER)), + self.assertCountEqual( + module.walk(parent, recurse_if=IS_MODULE, predicate=IS_MEMBER), [parent.a[0], parent.a[1], parent.z, child.a[0], child.a[1], child.z]) -- GitLab From aed554b573df4387e47c8f9e7d4ff52963c6e89b Mon Sep 17 00:00:00 2001 From: Mihai Maruseac Date: Thu, 17 Jan 2019 12:48:46 -0800 Subject: [PATCH 0882/2345] Prevent OOMs in too large JPG multi-channel images. PiperOrigin-RevId: 229800275 --- tensorflow/core/lib/jpeg/jpeg_mem.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/lib/jpeg/jpeg_mem.cc b/tensorflow/core/lib/jpeg/jpeg_mem.cc index f7a359eb5b..9e7d1e6410 100644 --- a/tensorflow/core/lib/jpeg/jpeg_mem.cc +++ b/tensorflow/core/lib/jpeg/jpeg_mem.cc @@ -157,7 +157,8 @@ uint8* UncompressLow(const void* srcdata, FewerArgsForCompiler* argball) { jpeg_calc_output_dimensions(&cinfo); int64 total_size = static_cast(cinfo.output_height) * - static_cast(cinfo.output_width); + static_cast(cinfo.output_width) * + static_cast(cinfo.num_components); // Some of the internal routines do not gracefully handle ridiculously // large images, so fail fast. if (cinfo.output_width <= 0 || cinfo.output_height <= 0) { -- GitLab From 05bef43400c83ca6d27617e27f1661e14f5f05e8 Mon Sep 17 00:00:00 2001 From: Saurabh Saxena Date: Thu, 17 Jan 2019 12:52:46 -0800 Subject: [PATCH 0883/2345] Do not constant fold nodes with DT_VARIANT type outputs in XLA. XLA does not support Const nodes of type Variant. It needs to see the source ops for the Variant operations to be able to build its own representation. PiperOrigin-RevId: 229801068 --- .../jit/encapsulate_subgraphs_pass.cc | 28 +++++++++++- .../compiler/tests/tensor_list_ops_test.py | 29 ++++++++++--- tensorflow/compiler/tf2xla/xla_compiler.cc | 28 +++++++++++- tensorflow/core/common_runtime/function.cc | 10 ++++- tensorflow/core/common_runtime/function.h | 3 ++ .../core/common_runtime/graph_optimizer.cc | 10 ++++- .../core/common_runtime/graph_optimizer.h | 43 ++++++++++++------- 7 files changed, 123 insertions(+), 28 deletions(-) diff --git a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc index 03aba97bbe..a21e083131 100644 --- a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc +++ b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc @@ -2535,7 +2535,33 @@ Status EncapsulateSubgraphsPass::Run( std::vector* input_permutation, std::vector* output_permutation, NodeDef* node) { // Optimize the subgraph. - OptimizeGraph(flr, subgraph); + // Do not constant fold nodes that output DT_VARIANT type tensors. + // XLA does not support Const nodes of Variant type since it needs + // to know the original ops to be able to compile them to the relevant + // XLA form. + // TODO(srbs): This filter is a little conservative. E.g. a subgraph of + // the form: + // Const + // | + // EmptyTensorList -> TensorListPushBack -> TensorListPopBack -> Op + // | + // (Discard popped list) + // + // Would have been reduced to "Const -> Op" without this filter. + // However since we are only allowed to specify the filter at the "Node" + // level there is no good way to allow the above behavior. So we + // disallow any sort of constant folding on Variant nodes for now. + auto cf_consider_fn = [](const Node* n) { + for (const auto& output_arg : n->op_def().output_arg()) { + if (output_arg.type() == DT_VARIANT) { + return false; + } + } + return true; + }; + GraphOptimizer::Options graph_optimizer_options; + graph_optimizer_options.cf_consider_fn = cf_consider_fn; + OptimizeGraph(flr, subgraph, graph_optimizer_options); const int num_args = input_permutation->size(); std::vector const_args(num_args); diff --git a/tensorflow/compiler/tests/tensor_list_ops_test.py b/tensorflow/compiler/tests/tensor_list_ops_test.py index 5c079d595c..3b48aed60c 100644 --- a/tensorflow/compiler/tests/tensor_list_ops_test.py +++ b/tensorflow/compiler/tests/tensor_list_ops_test.py @@ -48,24 +48,39 @@ class ListOpsTest(xla_test.XLATestCase): def testPushPop(self): with self.cached_session() as sess, self.test_scope(): - num = array_ops.placeholder(dtypes.int32) l = list_ops.tensor_list_reserve( - element_shape=(7, 15), num_elements=num, element_dtype=dtypes.float32) + element_shape=(7, 15), num_elements=10, element_dtype=dtypes.float32) l = list_ops.tensor_list_push_back( l, constant_op.constant(1.0, shape=(7, 15))) l = list_ops.tensor_list_push_back( l, constant_op.constant(2.0, shape=(7, 15))) l, e2 = list_ops.tensor_list_pop_back(l, element_dtype=dtypes.float32) _, e1 = list_ops.tensor_list_pop_back(l, element_dtype=dtypes.float32) - self.assertAllEqual(sess.run(e2, {num: 10}), 2.0 * np.ones((7, 15))) - self.assertAllEqual(sess.run(e1, {num: 10}), 1.0 * np.ones((7, 15))) + self.assertAllEqual(sess.run(e2), 2.0 * np.ones((7, 15))) + self.assertAllEqual(sess.run(e1), 1.0 * np.ones((7, 15))) + + def testDoNotConstantFoldVariants(self): + with self.cached_session() as sess, self.test_scope(): + val = array_ops.placeholder(dtype=dtypes.float32) + l = list_ops.tensor_list_reserve( + element_shape=(7, 15), num_elements=10, element_dtype=dtypes.float32) + # Note: Pushing a Placeholder will force the constant folding code + # to build a Const node with a DT_VARIANT output. This tests that XLA + # passes a cf_consider_fn which prevent folding such nodes. + l = list_ops.tensor_list_push_back( + l, array_ops.fill(value=val, dims=(7, 15))) + l = list_ops.tensor_list_push_back( + l, constant_op.constant(2.0, shape=(7, 15))) + l, e2 = list_ops.tensor_list_pop_back(l, element_dtype=dtypes.float32) + _, e1 = list_ops.tensor_list_pop_back(l, element_dtype=dtypes.float32) + self.assertAllEqual(sess.run(e2, {val: 1.0}), 2.0 * np.ones((7, 15))) + self.assertAllEqual(sess.run(e1, {val: 1.0}), 1.0 * np.ones((7, 15))) def testPushPopSeparateLists(self): with self.cached_session() as sess, self.test_scope(): - num = array_ops.placeholder(dtypes.int32) l = list_ops.tensor_list_reserve( element_shape=scalar_shape(), - num_elements=num, + num_elements=20, element_dtype=dtypes.float32) l = list_ops.tensor_list_push_back(l, constant_op.constant(1.0)) l2 = list_ops.tensor_list_push_back(l, constant_op.constant(2.0)) @@ -75,7 +90,7 @@ class ListOpsTest(xla_test.XLATestCase): l2, e22 = list_ops.tensor_list_pop_back(l2, element_dtype=dtypes.float32) l3, e31 = list_ops.tensor_list_pop_back(l3, element_dtype=dtypes.float32) l3, e32 = list_ops.tensor_list_pop_back(l3, element_dtype=dtypes.float32) - result = sess.run([e11, [e21, e22], [e31, e32]], {num: 20}) + result = sess.run([e11, [e21, e22], [e31, e32]]) self.assertEqual(result, [1.0, [2.0, 1.0], [3.0, 1.0]]) def testEmptyTensorList(self): diff --git a/tensorflow/compiler/tf2xla/xla_compiler.cc b/tensorflow/compiler/tf2xla/xla_compiler.cc index ee461a3c07..15fd265686 100644 --- a/tensorflow/compiler/tf2xla/xla_compiler.cc +++ b/tensorflow/compiler/tf2xla/xla_compiler.cc @@ -462,8 +462,34 @@ std::unique_ptr XlaCompiler::GetGraph(const FunctionBody* fbody) { opts.set_do_function_inlining(true); opts.set_do_constant_folding(true); GraphOptimizer optimizer(opts); + // Do not constant fold nodes that output DT_VARIANT type tensors. + // XLA does not support Const nodes of Variant type since it needs + // to know the original ops to be able to compile them to the relevant + // XLA form. + // TODO(srbs): This filter is a little conservative. E.g. a subgraph of + // the form: + // Const + // | + // EmptyTensorList -> TensorListPushBack -> TensorListPopBack -> Op + // | + // (Discard popped list) + // + // Would have been reduced to "Const -> Op" without this filter. + // However since we are only allowed to specify the filter at the "Node" + // level there is no good way to allow the above behavior. So we + // disallow any sort of constant folding on Variant nodes for now. + auto cf_consider_fn = [](const Node* n) { + for (const auto& output_arg : n->op_def().output_arg()) { + if (output_arg.type() == DT_VARIANT) { + return false; + } + } + return true; + }; + GraphOptimizer::Options graph_optimizer_options; + graph_optimizer_options.cf_consider_fn = cf_consider_fn; optimizer.Optimize(flib_runtime_, flib_runtime_->env(), - /*device=*/nullptr, &graph, /*shape_map=*/nullptr); + /*device=*/nullptr, &graph, graph_optimizer_options); return graph; } diff --git a/tensorflow/core/common_runtime/function.cc b/tensorflow/core/common_runtime/function.cc index 3df09eac29..82bbb7e689 100644 --- a/tensorflow/core/common_runtime/function.cc +++ b/tensorflow/core/common_runtime/function.cc @@ -786,13 +786,19 @@ void DumpGraph(StringPiece label, const Graph* g) { } } -void OptimizeGraph(FunctionLibraryRuntime* lib, std::unique_ptr* g) { +void OptimizeGraph(FunctionLibraryRuntime* lib, std::unique_ptr* g, + const GraphOptimizer::Options& graph_optimizer_options) { OptimizerOptions opts; opts.set_do_common_subexpression_elimination(true); opts.set_do_function_inlining(true); opts.set_do_constant_folding(true); GraphOptimizer optimizer(opts); - optimizer.Optimize(lib, lib->env(), lib->device(), g, /*shape_map=*/nullptr); + optimizer.Optimize(lib, lib->env(), lib->device(), g, + graph_optimizer_options); +} + +void OptimizeGraph(FunctionLibraryRuntime* lib, std::unique_ptr* g) { + OptimizeGraph(lib, g, GraphOptimizer::Options()); } namespace { diff --git a/tensorflow/core/common_runtime/function.h b/tensorflow/core/common_runtime/function.h index eeca66f5d0..df884f7577 100644 --- a/tensorflow/core/common_runtime/function.h +++ b/tensorflow/core/common_runtime/function.h @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/core/common_runtime/device.h" #include "tensorflow/core/common_runtime/device_mgr.h" +#include "tensorflow/core/common_runtime/graph_optimizer.h" #include "tensorflow/core/common_runtime/process_function_library_runtime.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/graph/graph.h" @@ -133,6 +134,8 @@ void DumpGraph(StringPiece label, const Graph* g); // OptimizeGraph mutates **g extensively and replaces '*g' with a // complete copy. Therefore, the caller should not keep any references // to nodes *g. +void OptimizeGraph(FunctionLibraryRuntime* lib, std::unique_ptr* g, + const GraphOptimizer::Options& graph_optimizer_options); void OptimizeGraph(FunctionLibraryRuntime* lib, std::unique_ptr* g); // Convert the Graph of a function to a GraphDef. diff --git a/tensorflow/core/common_runtime/graph_optimizer.cc b/tensorflow/core/common_runtime/graph_optimizer.cc index 37a979a8f1..7905944fb1 100644 --- a/tensorflow/core/common_runtime/graph_optimizer.cc +++ b/tensorflow/core/common_runtime/graph_optimizer.cc @@ -38,8 +38,7 @@ void GraphOptimizer::Optimize( std::unique_ptr* graph, const std::unordered_map>* shape_map, - const std::function& cse_consider_fn, - const std::function& cf_consider_fn) { + const NodePredicate& cse_consider_fn, const NodePredicate& cf_consider_fn) { Graph* g = graph->get(); DumpGraph("Initial", g); @@ -103,4 +102,11 @@ void GraphOptimizer::Optimize( DumpGraph("ReCopy", graph->get()); } +void GraphOptimizer::Optimize(FunctionLibraryRuntime* runtime, Env* env, + Device* device, std::unique_ptr* graph, + const Options& options) { + Optimize(runtime, env, device, graph, options.shape_map, + options.cse_consider_fn, options.cf_consider_fn); +} + } // end namespace tensorflow diff --git a/tensorflow/core/common_runtime/graph_optimizer.h b/tensorflow/core/common_runtime/graph_optimizer.h index 789cc56942..05150608f0 100644 --- a/tensorflow/core/common_runtime/graph_optimizer.h +++ b/tensorflow/core/common_runtime/graph_optimizer.h @@ -26,6 +26,28 @@ namespace tensorflow { class GraphOptimizer { public: + using NodePredicate = std::function; + + struct Options { + // If not null it maps from nodes in graph to partially-known + // shapes of their outputs, and may be used, e.g., in the constant folding + // pass. The use of shape_map implies that the mapping from node name to the + // vector of partial shapes of its outputs is stable, i.e., no optimization + // pass may replace a node with a different node of the same name that has a + // different number of outputs, or outputs with different known shapes. + // TODO(b/65453533) introduce a unique way to name nodes in a graph. + std::unordered_map>* shape_map = + nullptr; + + // If not null then only nodes for which cse_consider_fn returns true will + // be considered for CSE. + NodePredicate cse_consider_fn = nullptr; + + // If not null then only nodes for which cf_consider_fn returns true will be + // considered for CF. + NodePredicate cf_consider_fn = nullptr; + }; + GraphOptimizer(const OptimizerOptions& opts); ~GraphOptimizer(); @@ -34,26 +56,17 @@ class GraphOptimizer { // on which the 'graph' will execute. It's passed to the optimizers // so that they can respect constraints if any, that should be // respected. - // - // If shape_map is not null it maps from nodes in graph to partially-known - // shapes of their outputs, and may be used, e.g., in the constant folding - // pass. The use of shape_map implies that the mapping from node name to the - // vector of partial shapes of its outputs is stable, i.e., no optimization - // pass may replace a node with a different node of the same name that has a - // different number of outputs, or outputs with different known shapes. - // TODO(b/65453533) introduce a unique way to name nodes in a graph. - // - // If cse_consider_fn is not null then only nodes for which cse_consider_fn - // returns true will be considered for CSE. - // If cf_consider_fn is not null then only nodes for which cf_consider_fn - // returns true will be considered for CF. + void Optimize(FunctionLibraryRuntime* runtime, Env* env, Device* device, + std::unique_ptr* graph, + const Options& graph_optimizer_options); + // DEPRECATED: Consider passing a GraphOptimizer::Options object instead. void Optimize( FunctionLibraryRuntime* runtime, Env* env, Device* device, std::unique_ptr* graph, const std::unordered_map>* shape_map, - const std::function& cse_consider_fn = nullptr, - const std::function& cf_consider_fn = nullptr); + const NodePredicate& cse_consider_fn = nullptr, + const NodePredicate& cf_consider_fn = nullptr); const OptimizerOptions& options() { return opts_; } -- GitLab From c1ccc03a08211dddc409759bfab22ed9af59078c Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Thu, 17 Jan 2019 12:53:51 -0800 Subject: [PATCH 0884/2345] [tf.data] Disabling default optimizations in benchmarks. This CL: - makes sure all benchmarks disable default optimizations - removes redundant print statements - various minor fixes PiperOrigin-RevId: 229801289 --- .../python/data/benchmarks/batch_benchmark.py | 13 +-- .../data/benchmarks/filter_benchmark.py | 10 +- .../from_tensor_slices_benchmark.py | 26 +++--- .../data/benchmarks/list_files_benchmark.py | 8 +- .../python/data/benchmarks/map_benchmark.py | 21 ++--- .../python/data/benchmarks/range_benchmark.py | 17 ++-- .../benchmarks/autotune_benchmark.py | 16 +--- .../benchmarks/csv_dataset_benchmark.py | 5 +- .../benchmarks/map_and_batch_benchmark.py | 91 +++++++------------ .../benchmarks/map_vectorization_benchmark.py | 31 +++---- .../benchmarks/matching_files_benchmark.py | 10 +- .../benchmarks/optimize_benchmark.py | 9 +- .../parallel_interleave_benchmark.py | 6 +- .../rejection_resample_benchmark.py | 3 + .../benchmarks/unbatch_benchmark.py | 10 +- 15 files changed, 117 insertions(+), 159 deletions(-) diff --git a/tensorflow/python/data/benchmarks/batch_benchmark.py b/tensorflow/python/data/benchmarks/batch_benchmark.py index e063849f70..0ccf5c57d1 100644 --- a/tensorflow/python/data/benchmarks/batch_benchmark.py +++ b/tensorflow/python/data/benchmarks/batch_benchmark.py @@ -42,6 +42,9 @@ class BatchBenchmark(test.Benchmark): dataset = dataset_ops.Dataset.from_tensors(sparse_placeholder).repeat( ).batch(batch_size_placeholder) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_initializable_iterator(dataset) next_element = iterator.get_next() @@ -72,13 +75,11 @@ class BatchBenchmark(test.Benchmark): median_wall_time = np.median(deltas) / 100.0 - print("Batch sparse dataset non-zeros per row: %d batch_size: %d " - "wall time: %f" - % (non_zeros_per_row, batch_size, median_wall_time)) self.report_benchmark( - iters=10000, wall_time=median_wall_time, - name="batch_sparse_dataset_nnz_%d_batch_size_%d" % ( - non_zeros_per_row, batch_size)) + iters=10000, + wall_time=median_wall_time, + name="sparse_num_elements_%d_batch_size_%d" % + (non_zeros_per_row, batch_size)) if __name__ == "__main__": diff --git a/tensorflow/python/data/benchmarks/filter_benchmark.py b/tensorflow/python/data/benchmarks/filter_benchmark.py index a6d86fe221..e0ecf19e11 100644 --- a/tensorflow/python/data/benchmarks/filter_benchmark.py +++ b/tensorflow/python/data/benchmarks/filter_benchmark.py @@ -36,6 +36,9 @@ class FilterBenchmark(test.Benchmark): with ops.Graph().as_default(): dataset = ( dataset_ops.Dataset.from_tensors(True).repeat(None).filter(predicate)) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_one_shot_iterator(dataset) next_element = iterator.get_next() @@ -51,12 +54,7 @@ class FilterBenchmark(test.Benchmark): deltas.append(end - start) median_wall_time = np.median(deltas) / 100 - print("Filter dataset using %s. Median wall time: %f" % - (name, median_wall_time)) - self.report_benchmark( - iters=100, - wall_time=median_wall_time, - name=name) + self.report_benchmark(iters=100, wall_time=median_wall_time, name=name) def benchmarkSimpleFunction(self): self._benchmark(array_ops.identity, "simple_function") diff --git a/tensorflow/python/data/benchmarks/from_tensor_slices_benchmark.py b/tensorflow/python/data/benchmarks/from_tensor_slices_benchmark.py index d7f1a4e7af..4e5559ddba 100644 --- a/tensorflow/python/data/benchmarks/from_tensor_slices_benchmark.py +++ b/tensorflow/python/data/benchmarks/from_tensor_slices_benchmark.py @@ -41,6 +41,9 @@ class FromTensorSlicesBenchmark(test.Benchmark): dataset = ( dataset_ops.Dataset.from_tensor_slices(input_data) .repeat(num_epochs + 1).batch(batch_size)) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_initializable_iterator(dataset) next_element = iterator.get_next() @@ -59,9 +62,6 @@ class FromTensorSlicesBenchmark(test.Benchmark): pass median_wall_time = np.median(deltas) - print("Slice/repeat/batch with sess.run() input size: %d batch size: %d " - "Median wall time per element: %f" % (input_size, batch_size, - median_wall_time)) self.report_benchmark( iters=len(deltas), wall_time=median_wall_time, @@ -77,6 +77,9 @@ class FromTensorSlicesBenchmark(test.Benchmark): dataset = ( dataset_ops.Dataset.from_tensor_slices(input_data) .repeat(num_epochs + 1).batch(batch_size)) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_initializable_iterator(dataset) next_element = iterator.get_next() @@ -96,10 +99,6 @@ class FromTensorSlicesBenchmark(test.Benchmark): pass median_wall_time = np.median(deltas) - print( - "Slice/repeat/batch with callable input size: %d batch size: %d Median" - " wall time per element: %f" % (input_size, batch_size, - median_wall_time)) self.report_benchmark( iters=len(deltas), wall_time=median_wall_time, @@ -116,6 +115,9 @@ class FromTensorSlicesBenchmark(test.Benchmark): dataset = ( dataset_ops.Dataset.from_tensor_slices(input_data.reshape(100, 100)) .repeat(num_epochs + 1)) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_initializable_iterator(dataset) next_element = iterator.get_next() @@ -135,9 +137,6 @@ class FromTensorSlicesBenchmark(test.Benchmark): pass median_wall_time = np.median(deltas) - print("Reshape/slice/repeat with callable input size: %d batch size: %d " - "Median wall time per element: %f" % (input_size, batch_size, - median_wall_time)) self.report_benchmark( iters=len(deltas), wall_time=median_wall_time, @@ -154,6 +153,9 @@ class FromTensorSlicesBenchmark(test.Benchmark): dataset = ( dataset_ops.Dataset.from_tensor_slices(input_data).batch(batch_size) .cache().repeat(num_epochs + 1)) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_initializable_iterator(dataset) next_element = iterator.get_next() @@ -173,10 +175,6 @@ class FromTensorSlicesBenchmark(test.Benchmark): pass median_wall_time = np.median(deltas) - print( - "Slice/batch/cache/repeat with callable input size: %d batch size: %d " - "Median wall time per element: %f" - % (input_size, batch_size, median_wall_time)) self.report_benchmark( iters=len(deltas), wall_time=median_wall_time, diff --git a/tensorflow/python/data/benchmarks/list_files_benchmark.py b/tensorflow/python/data/benchmarks/list_files_benchmark.py index 0dc2147112..70f8eeec9e 100644 --- a/tensorflow/python/data/benchmarks/list_files_benchmark.py +++ b/tensorflow/python/data/benchmarks/list_files_benchmark.py @@ -58,6 +58,9 @@ class ListFilesBenchmark(test.Benchmark): for _ in range(iters): with ops.Graph().as_default(): dataset = dataset_ops.Dataset.list_files(patterns) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) next_element = dataset.make_one_shot_iterator().get_next() with session.Session() as sess: sub_deltas = [] @@ -71,11 +74,6 @@ class ListFilesBenchmark(test.Benchmark): break deltas.append(sub_deltas) median_deltas = np.median(deltas, axis=0) - print('Nested directory size (width*depth): %d*%d Median wall time: ' - '%fs (read first filename), %fs (read second filename), avg %fs' - ' (read %d more filenames)' % - (width, depth, median_deltas[0], median_deltas[1], - np.average(median_deltas[2:]), len(median_deltas) - 2)) self.report_benchmark( iters=iters, wall_time=np.sum(median_deltas), diff --git a/tensorflow/python/data/benchmarks/map_benchmark.py b/tensorflow/python/data/benchmarks/map_benchmark.py index 65d945cdae..b620eaaed5 100644 --- a/tensorflow/python/data/benchmarks/map_benchmark.py +++ b/tensorflow/python/data/benchmarks/map_benchmark.py @@ -38,17 +38,14 @@ class MapBenchmark(test.Benchmark): if mode == "general": map_fn = lambda x: x + 1 use_inter_op_parallelism = True - print_label = "" benchmark_label = "" if mode == "single-threaded": map_fn = lambda x: x + 1 use_inter_op_parallelism = False - print_label = " (single threaded mode)" benchmark_label = "_single_threaded" if mode == "short-circuit": map_fn = lambda x: x use_inter_op_parallelism = True # should not have any significance - print_label = " (short circuit mode)" benchmark_label = "_short_circuit" with ops.Graph().as_default(): @@ -58,6 +55,9 @@ class MapBenchmark(test.Benchmark): dataset, map_fn, use_inter_op_parallelism=use_inter_op_parallelism) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_one_shot_iterator(dataset) next_element = iterator.get_next() @@ -73,13 +73,10 @@ class MapBenchmark(test.Benchmark): deltas.append(end - start) median_wall_time = np.median(deltas) / 100 - print("Map dataset chain length%s: %d Median wall time: %f" % - (print_label, chain_length, median_wall_time)) self.report_benchmark( iters=1000, wall_time=median_wall_time, - name="map_dataset_chain_length_%d%s" % (chain_length, - benchmark_label)) + name="chain_length_%d%s" % (chain_length, benchmark_label)) def benchmarkMapFanOut(self): fan_outs = [1, 2, 5, 10, 20, 50, 100] @@ -88,17 +85,14 @@ class MapBenchmark(test.Benchmark): if mode == "general": map_fn = lambda *xs: [x + 1 for x in xs] use_inter_op_parallelism = True - print_label = "" benchmark_label = "" if mode == "single-threaded": map_fn = lambda *xs: [x + 1 for x in xs] use_inter_op_parallelism = False - print_label = " (single threaded mode)" benchmark_label = "_single_threaded" if mode == "short-circuit": map_fn = lambda *xs: xs use_inter_op_parallelism = True # should not have any significance - print_label = " (short circuit mode)" benchmark_label = "_short_circuit" with ops.Graph().as_default(): @@ -108,6 +102,9 @@ class MapBenchmark(test.Benchmark): dataset, map_fn, use_inter_op_parallelism=use_inter_op_parallelism) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_one_shot_iterator(dataset) next_element = iterator.get_next() @@ -123,12 +120,10 @@ class MapBenchmark(test.Benchmark): deltas.append(end - start) median_wall_time = np.median(deltas) / 100 - print("Map dataset fan out%s: %d Median wall time: %f" % - (print_label, fan_out, median_wall_time)) self.report_benchmark( iters=1000, wall_time=median_wall_time, - name="map_dataset_fan_out_%d%s" % (fan_out, benchmark_label)) + name="fan_out_%d%s" % (fan_out, benchmark_label)) if __name__ == "__main__": diff --git a/tensorflow/python/data/benchmarks/range_benchmark.py b/tensorflow/python/data/benchmarks/range_benchmark.py index a5020e2873..375ff339a8 100644 --- a/tensorflow/python/data/benchmarks/range_benchmark.py +++ b/tensorflow/python/data/benchmarks/range_benchmark.py @@ -31,14 +31,16 @@ class RangeBenchmark(test.Benchmark): def _benchmarkRangeHelper(self, modeling_enabled): num_elements = 10000000 if modeling_enabled else 50000000 - options = dataset_ops.Options() - options.experimental_autotune = modeling_enabled # Use `Dataset.skip()` and `Dataset.take()` to perform the iteration in # C++, and focus on the minimal overheads (excluding Python invocation # costs). dataset = dataset_ops.Dataset.range(num_elements).skip( - num_elements - 1).take(1).with_options(options) + num_elements - 1).take(1) + options = dataset_ops.Options() + options.experimental_autotune = modeling_enabled + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_initializable_iterator(dataset) next_element = iterator.get_next() @@ -54,11 +56,10 @@ class RangeBenchmark(test.Benchmark): end = time.time() time_per_element = (end - start) / num_elements - print("Average time per element (%s modeling): %f nanoseconds" % ( - "with" if modeling_enabled else "without", time_per_element * 1e9)) - self.report_benchmark(iters=num_elements, wall_time=time_per_element, - name="benchmark_tf_data_dataset_range%s" - % ("_with_modeling" if modeling_enabled else "")) + self.report_benchmark( + iters=num_elements, + wall_time=time_per_element, + name="modeling_%s" % ("on" if modeling_enabled else "off")) def benchmarkRange(self): for modeling_enabled in [False, True]: diff --git a/tensorflow/python/data/experimental/benchmarks/autotune_benchmark.py b/tensorflow/python/data/experimental/benchmarks/autotune_benchmark.py index 391b6711e9..bda7d38792 100644 --- a/tensorflow/python/data/experimental/benchmarks/autotune_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/autotune_benchmark.py @@ -60,9 +60,6 @@ class AutotuneBenchmark(test.Benchmark): end = time.time() deltas.append(end - start) - print("%f (median), %f (mean), %f (stddev), %f (min), %f (max)\n" % - (np.median(deltas), np.mean(deltas), np.std(deltas), np.min(deltas), - np.max(deltas))) self.report_benchmark( iters=10000, wall_time=np.median(deltas), @@ -87,6 +84,7 @@ class AutotuneBenchmark(test.Benchmark): batch_size=batch_size)) options = dataset_ops.Options() options.experimental_autotune = autotune + options.experimental_optimization.apply_default_optimizations = False dataset = dataset.with_options(options) iterator = dataset_ops.make_one_shot_iterator(dataset) get_next = iterator.get_next() @@ -101,10 +99,6 @@ class AutotuneBenchmark(test.Benchmark): end = time.time() deltas.append(end - start) - print("%f (median), %f (mean), %f (stddev), %f (min), %f (max)\n" % - (np.median(deltas), np.mean(deltas), np.std(deltas), np.min(deltas), - np.max(deltas))) - self.report_benchmark( iters=1000, wall_time=np.median(deltas), @@ -128,6 +122,7 @@ class AutotuneBenchmark(test.Benchmark): num_parallel_calls=optimization.AUTOTUNE) options = dataset_ops.Options() options.experimental_autotune = autotune + options.experimental_optimization.apply_default_optimizations = False dataset = dataset.with_options(options) iterator = dataset_ops.make_one_shot_iterator(dataset) get_next = iterator.get_next() @@ -142,9 +137,6 @@ class AutotuneBenchmark(test.Benchmark): end = time.time() deltas.append(end - start) - print("%f (median), %f (mean), %f (stddev), %f (min), %f (max)\n" % - (np.median(deltas), np.mean(deltas), np.std(deltas), np.min(deltas), - np.max(deltas))) self.report_benchmark( iters=10000, wall_time=np.median(deltas), @@ -190,6 +182,7 @@ class AutotuneBenchmark(test.Benchmark): dataset = dataset.map(f2, num_parallel_calls=optimization.AUTOTUNE) options = dataset_ops.Options() options.experimental_autotune = autotune + options.experimental_optimization.apply_default_optimizations = False dataset = dataset.with_options(options) iterator = dataset_ops.make_one_shot_iterator(dataset) get_next = iterator.get_next() @@ -204,9 +197,6 @@ class AutotuneBenchmark(test.Benchmark): end = time.time() deltas.append(end - start) - print("%f (median), %f (mean), %f (stddev), %f (min), %f (max)\n" % - (np.median(deltas), np.mean(deltas), np.std(deltas), np.min(deltas), - np.max(deltas))) self.report_benchmark( iters=1000, wall_time=np.median(deltas), diff --git a/tensorflow/python/data/experimental/benchmarks/csv_dataset_benchmark.py b/tensorflow/python/data/experimental/benchmarks/csv_dataset_benchmark.py index 03345ce4e6..2e91e08c79 100644 --- a/tensorflow/python/data/experimental/benchmarks/csv_dataset_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/csv_dataset_benchmark.py @@ -63,6 +63,9 @@ class CsvDatasetBenchmark(test.Benchmark): def _runBenchmark(self, dataset, num_cols, prefix): dataset = dataset.skip(self._num_per_iter - 1) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) deltas = [] for _ in range(10): next_element = dataset_ops.make_one_shot_iterator(dataset).get_next() @@ -79,8 +82,6 @@ class CsvDatasetBenchmark(test.Benchmark): deltas.append(end - start) # Median wall time per CSV record read and decoded median_wall_time = np.median(deltas) / self._num_per_iter - print('%s num_cols: %d Median wall time: %f' % (prefix, num_cols, - median_wall_time)) self.report_benchmark( iters=self._num_per_iter, wall_time=median_wall_time, diff --git a/tensorflow/python/data/experimental/benchmarks/map_and_batch_benchmark.py b/tensorflow/python/data/experimental/benchmarks/map_and_batch_benchmark.py index b17f2bcd12..4b7c173786 100644 --- a/tensorflow/python/data/experimental/benchmarks/map_and_batch_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/map_and_batch_benchmark.py @@ -26,7 +26,6 @@ import numpy as np from tensorflow.core.protobuf import config_pb2 from tensorflow.python.client import session from tensorflow.python.data.experimental.ops import batching -from tensorflow.python.data.experimental.ops.optimization_options import OptimizationOptions from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes @@ -41,7 +40,7 @@ _NUMPY_RANDOM_SEED = 42 class MapAndBatchBenchmark(test.Benchmark): """Benchmarks for `tf.data.experimental.map_and_batch()`.""" - def benchmarkMapAndBatchDense(self): + def benchmarkMapAndBatch(self): """Measures the performance of parallelized batching.""" shapes = [(), (10,), (10, 10), (10, 10, 10), (224, 224, 3)] batch_size_values = [1, 32, 64, 128, 1024] @@ -55,6 +54,9 @@ class MapAndBatchBenchmark(test.Benchmark): dataset = dataset.apply(batching.map_and_batch( lambda _: dense_value, batch_size_placeholder)) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_initializable_iterator(dataset) next_element = iterator.get_next() @@ -88,13 +90,9 @@ class MapAndBatchBenchmark(test.Benchmark): median_wall_time = np.median(deltas) / 100.0 iters = len(deltas) * 100 - print("Map and batch dense dataset shape: %r batch_size: %d " - "wall time: %f (%d iters)" - % (shape, batch_size, median_wall_time, iters)) self.report_benchmark( iters=iters, wall_time=median_wall_time, - name="benchmark_batch_dense_dataset_nnz_%d_batch_size_%d" % ( - np.prod(shape), batch_size)) + name="num_elements_%d_batch_size_%d" % (np.prod(shape), batch_size)) def benchmarkMapAndBatchChainingVersusFusing(self): """Compares the performance of chaining and fusing map and batch. @@ -128,49 +126,25 @@ class MapAndBatchBenchmark(test.Benchmark): def benchmark(label, series): """Runs benchmark the given series.""" - print("%s:" % label) - - def make_base_dataset(element_size): + def make_dataset(element_size, num_calls, batch_size): # pylint: disable=missing-docstring k = 1024 * 1024 x = constant_op.constant(np.random.rand(element_size, 4 * k)) y = constant_op.constant(np.random.rand(4 * k, 1)) - return dataset_ops.Dataset.range(1000000000000).map(lambda _: (x, y)) + dataset = dataset_ops.Dataset.range(1000000000000).map(lambda _: (x, y)) + dataset = dataset.map( + math_ops.matmul, + num_parallel_calls=num_calls).batch(batch_size=batch_size) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + return dataset.with_options(options) for num_calls, inter_op, element_size, batch_size in series: - num_iters = 1024 // ( (element_size * batch_size) // min(num_calls, inter_op)) - fused_dataset = make_base_dataset(element_size) - fused_dataset = fused_dataset.map( - math_ops.matmul, - num_parallel_calls=num_calls).batch(batch_size=batch_size) - - fused_iterator = dataset_ops.make_one_shot_iterator(fused_dataset) - fused_get_next = fused_iterator.get_next() - - fused_deltas = [] - with session.Session( - config=config_pb2.ConfigProto( - inter_op_parallelism_threads=inter_op, - use_per_session_threads=True)) as sess: - - for _ in range(5): - sess.run(fused_get_next.op) - for _ in range(num_iters): - start = time.time() - sess.run(fused_get_next.op) - end = time.time() - fused_deltas.append(end - start) - - # `map_and_batch_fusion` is optimized by default. To get the chained - # dataset, with have to disable it. - options = dataset_ops.Options() - options.experimental_optimization = OptimizationOptions() - options.experimental_optimization.map_and_batch_fusion = False - chained_dataset = fused_dataset.with_options(options) + # By default the chained map().batch() calls will not be fused. + chained_dataset = make_dataset(element_size, num_calls, batch_size) chained_iterator = dataset_ops.make_one_shot_iterator(chained_dataset) chained_get_next = chained_iterator.get_next() - chained_deltas = [] with session.Session( config=config_pb2.ConfigProto( @@ -184,27 +158,32 @@ class MapAndBatchBenchmark(test.Benchmark): end = time.time() chained_deltas.append(end - start) - print( - "batch size: %d, num parallel calls: %d, inter-op parallelism: %d, " - "element size: %d, num iters: %d\nchained wall time: %f (median), " - "%f (mean), %f (stddev), %f (min), %f (max)\n fused wall time: " - "%f (median), %f (mean), %f (stddev), %f (min), %f (max)\n " - "chained/fused: %.2fx (median), %.2fx (mean)" % - (batch_size, num_calls, inter_op, element_size, num_iters, - np.median(chained_deltas), np.mean(chained_deltas), - np.std(chained_deltas), np.min(chained_deltas), - np.max(chained_deltas), np.median(fused_deltas), - np.mean(fused_deltas), np.std(fused_deltas), np.min(fused_deltas), - np.max(fused_deltas), - np.median(chained_deltas) / np.median(fused_deltas), - np.mean(chained_deltas) / np.mean(fused_deltas))) - self.report_benchmark( iters=num_iters, wall_time=np.median(chained_deltas), name=name("chained", label, num_calls, inter_op, element_size, batch_size)) + # Apply an option to the default dataset that will fuse map().batch(). + options = dataset_ops.Options() + options.experimental_optimization.map_and_batch_fusion = True + fused_dataset = chained_dataset.with_options(options) + fused_iterator = dataset_ops.make_one_shot_iterator(fused_dataset) + fused_get_next = fused_iterator.get_next() + fused_deltas = [] + with session.Session( + config=config_pb2.ConfigProto( + inter_op_parallelism_threads=inter_op, + use_per_session_threads=True)) as sess: + + for _ in range(5): + sess.run(fused_get_next.op) + for _ in range(num_iters): + start = time.time() + sess.run(fused_get_next.op) + end = time.time() + fused_deltas.append(end - start) + self.report_benchmark( iters=num_iters, wall_time=np.median(fused_deltas), diff --git a/tensorflow/python/data/experimental/benchmarks/map_vectorization_benchmark.py b/tensorflow/python/data/experimental/benchmarks/map_vectorization_benchmark.py index a60ba0a857..50e3a5c469 100644 --- a/tensorflow/python/data/experimental/benchmarks/map_vectorization_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/map_vectorization_benchmark.py @@ -24,7 +24,6 @@ import numpy as np from tensorflow.core.example import example_pb2 from tensorflow.core.example import feature_pb2 from tensorflow.python.client import session -from tensorflow.python.data.experimental.ops import optimization_options from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.util import nest from tensorflow.python.framework import constant_op @@ -115,31 +114,27 @@ class MapVectorizationBenchmark(test.Benchmark): def _compare(self, input_dataset, map_fn, batch_size, input_size, str_id): num_elems = int(np.sum([np.prod(x) for x in input_size])) - name_template = "{}__batch_size_{}_input_element_size_{}_{}" + name_template = "{}_batch_size_{}_input_element_size_{}_{}" - base_dataset = input_dataset.map(map_fn).batch(batch_size) + unoptimized_dataset = input_dataset.map(map_fn).batch(batch_size) options = dataset_ops.Options() - opt_options = optimization_options.OptimizationOptions() - # Disable default map_and_batch_fusion optimization - opt_options.map_and_batch_fusion = False - options.experimental_optimization = opt_options - base_dataset = base_dataset.with_options(options) + options.experimental_optimization.apply_default_optimizations = False + unoptimized_dataset = unoptimized_dataset.with_options(options) + unoptimized_next = dataset_ops.make_one_shot_iterator( + unoptimized_dataset).get_next() - unoptimized_op = dataset_ops.make_one_shot_iterator(base_dataset).get_next() - - optimized_options = dataset_ops.Options() - opt_options = optimization_options.OptimizationOptions() - opt_options.map_vectorization = True - optimized_options.experimental_optimization = opt_options - optimized = base_dataset.with_options(optimized_options) - optimized_op = dataset_ops.make_one_shot_iterator(optimized).get_next() + options = dataset_ops.Options() + options.experimental_optimization.map_vectorization = True + optimized_dataset = unoptimized_dataset.with_options(options) + optimized_next = dataset_ops.make_one_shot_iterator( + optimized_dataset).get_next() unoptimized_time = self._run( - unoptimized_op, + unoptimized_next, name=name_template.format(str_id, batch_size, num_elems, "unoptimized")) optimized_time = self._run( - optimized_op, + optimized_next, name=name_template.format(str_id, batch_size, num_elems, "optimized")) print("Batch size: {}\n" diff --git a/tensorflow/python/data/experimental/benchmarks/matching_files_benchmark.py b/tensorflow/python/data/experimental/benchmarks/matching_files_benchmark.py index c53f8dd7c5..cb5bf2946d 100644 --- a/tensorflow/python/data/experimental/benchmarks/matching_files_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/matching_files_benchmark.py @@ -60,6 +60,9 @@ class MatchingFilesBenchmark(test.Benchmark): for _ in range(iters): with ops.Graph().as_default(): dataset = matching_files.MatchingFilesDataset(patterns) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) next_element = dataset_ops.make_one_shot_iterator(dataset).get_next() with session.Session() as sess: @@ -75,11 +78,6 @@ class MatchingFilesBenchmark(test.Benchmark): deltas.append(sub_deltas) median_deltas = np.median(deltas, axis=0) - print('Nested directory size (width*depth): %d*%d Median wall time: ' - '%fs (read first filename), %fs (read second filename), avg %fs' - ' (read %d more filenames)' % - (width, depth, median_deltas[0], median_deltas[1], - np.average(median_deltas[2:]), len(median_deltas) - 2)) self.report_benchmark( iters=iters, wall_time=np.sum(median_deltas), @@ -92,7 +90,7 @@ class MatchingFilesBenchmark(test.Benchmark): (len(median_deltas) - 2): np.average(median_deltas[2:]) }, - name='dataset_nested_directory(%d*%d)' % + name='nested_directory(%d*%d)' % (width, depth)) shutil.rmtree(tmp_dir, ignore_errors=True) diff --git a/tensorflow/python/data/experimental/benchmarks/optimize_benchmark.py b/tensorflow/python/data/experimental/benchmarks/optimize_benchmark.py index 1bbee5e7a3..395a529f85 100644 --- a/tensorflow/python/data/experimental/benchmarks/optimize_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/optimize_benchmark.py @@ -47,6 +47,7 @@ class OptimizationBenchmark(test.Benchmark): dataset = dataset.map(lambda x: x) if optimize_dataset: options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False options.experimental_optimization.map_fusion = True dataset = dataset.with_options(options) @@ -66,8 +67,6 @@ class OptimizationBenchmark(test.Benchmark): median_wall_time = np.median(deltas) / 100 opt_mark = "opt" if optimize_dataset else "noopt" - print("Map dataset {} chain length: {} Median wall time: {}".format( - opt_mark, chain_length, median_wall_time)) self.report_benchmark( iters=100, wall_time=median_wall_time, @@ -90,6 +89,7 @@ class OptimizationBenchmark(test.Benchmark): lambda x: math_ops.greater_equal(x - 5, 0)) if optimize_dataset: options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False options.experimental_optimization.map_and_filter_fusion = True dataset = dataset.with_options(options) iterator = dataset_ops.make_one_shot_iterator(dataset) @@ -108,8 +108,6 @@ class OptimizationBenchmark(test.Benchmark): median_wall_time = np.median(deltas) / 100 opt_mark = "opt" if optimize_dataset else "noopt" - print("Map and filter dataset {} chain length: {} Median wall time: {}" - .format(opt_mark, chain_length, median_wall_time)) self.report_benchmark( iters=100, wall_time=median_wall_time, @@ -131,6 +129,7 @@ class OptimizationBenchmark(test.Benchmark): dataset = dataset.filter(lambda x: math_ops.greater_equal(x - 5, 0)) if optimize_dataset: options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False options.experimental_optimization.filter_fusion = True dataset = dataset.with_options(options) @@ -150,8 +149,6 @@ class OptimizationBenchmark(test.Benchmark): median_wall_time = np.median(deltas) / 100 opt_mark = "opt" if optimize_dataset else "no-opt" - print("Filter dataset {} chain length: {} Median wall time: {}".format( - opt_mark, chain_length, median_wall_time)) self.report_benchmark( iters=1000, wall_time=median_wall_time, diff --git a/tensorflow/python/data/experimental/benchmarks/parallel_interleave_benchmark.py b/tensorflow/python/data/experimental/benchmarks/parallel_interleave_benchmark.py index f5108e5ff4..37375af27f 100644 --- a/tensorflow/python/data/experimental/benchmarks/parallel_interleave_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/parallel_interleave_benchmark.py @@ -56,8 +56,10 @@ class ParallelInterleaveBenchmark(test.Benchmark): def _benchmark(self, dataset_fn, iters, num_elements): with ops.Graph().as_default(): - iterator = dataset_ops.make_one_shot_iterator(dataset_fn()) - next_element = iterator.get_next() + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset_fn().with_options(options) + next_element = dataset_ops.make_one_shot_iterator(dataset).get_next() with session.Session() as sess: deltas = [] for _ in range(iters): diff --git a/tensorflow/python/data/experimental/benchmarks/rejection_resample_benchmark.py b/tensorflow/python/data/experimental/benchmarks/rejection_resample_benchmark.py index a64f7ecb00..9a8ac7ef65 100644 --- a/tensorflow/python/data/experimental/benchmarks/rejection_resample_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/rejection_resample_benchmark.py @@ -39,6 +39,9 @@ def _time_resampling(data_np, target_dist, init_dist, num_to_sample): # pylint: initial_dist=init_dist, seed=142)) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) get_next = dataset_ops.make_one_shot_iterator(dataset).get_next() with session.Session() as sess: diff --git a/tensorflow/python/data/experimental/benchmarks/unbatch_benchmark.py b/tensorflow/python/data/experimental/benchmarks/unbatch_benchmark.py index 6f80df50b8..3f5b9b9130 100644 --- a/tensorflow/python/data/experimental/benchmarks/unbatch_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/unbatch_benchmark.py @@ -42,6 +42,9 @@ class UnbatchBenchmark(test.Benchmark): dataset = dataset.batch(batch_size_placeholder) dataset = dataset.apply(batching.unbatch()) dataset = dataset.skip(elems_per_trial) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_initializable_iterator(dataset) next_element = iterator.get_next() @@ -58,8 +61,6 @@ class UnbatchBenchmark(test.Benchmark): deltas.append((end - start) / elems_per_trial) median_wall_time = np.median(deltas) - print("Unbatch (native) batch size: %d Median wall time per element:" - " %f microseconds" % (batch_size, median_wall_time * 1e6)) self.report_benchmark( iters=10000, wall_time=median_wall_time, @@ -78,6 +79,9 @@ class UnbatchBenchmark(test.Benchmark): dataset = dataset.batch(batch_size_placeholder) dataset = dataset.flat_map(dataset_ops.Dataset.from_tensor_slices) dataset = dataset.skip(elems_per_trial) + options = dataset_ops.Options() + options.experimental_optimization.apply_default_optimizations = False + dataset = dataset.with_options(options) iterator = dataset_ops.make_initializable_iterator(dataset) next_element = iterator.get_next() @@ -94,8 +98,6 @@ class UnbatchBenchmark(test.Benchmark): deltas.append((end - start) / elems_per_trial) median_wall_time = np.median(deltas) - print("Unbatch (unfused) batch size: %d Median wall time per element:" - " %f microseconds" % (batch_size, median_wall_time * 1e6)) self.report_benchmark( iters=10000, wall_time=median_wall_time, -- GitLab From f602445c23feb67f2e35256ffdf24e090195fab6 Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Thu, 17 Jan 2019 12:59:15 -0800 Subject: [PATCH 0885/2345] [tf.data] Replacing IteratorStateMetadata with a simpler alternative. Currently, serialization of iterator state uses the `IteratorStateMetadata` proto, which AFAICT is only used to convert a map to and from string. This CL replaces the proto-based conversion with a simpler mechanism. This change is motivated by making it possible to move the VariantTensorDataReader and VariantTensorDataWriter into a header file and depending on protos from TensorFlow header files is discouraged (c.f. b/62899350). PiperOrigin-RevId: 229802166 --- .../contrib/cmake/tf_core_framework.cmake | 1 - tensorflow/core/BUILD | 1 - tensorflow/core/framework/iterator.proto | 18 -------- tensorflow/core/kernels/data/iterator_ops.cc | 41 ++++++------------- 4 files changed, 13 insertions(+), 48 deletions(-) delete mode 100644 tensorflow/core/framework/iterator.proto diff --git a/tensorflow/contrib/cmake/tf_core_framework.cmake b/tensorflow/contrib/cmake/tf_core_framework.cmake index 9ae5831f47..d8d1cc3aa2 100644 --- a/tensorflow/contrib/cmake/tf_core_framework.cmake +++ b/tensorflow/contrib/cmake/tf_core_framework.cmake @@ -147,7 +147,6 @@ set(tf_proto_text_srcs "tensorflow/core/framework/function.proto" "tensorflow/core/framework/graph.proto" "tensorflow/core/framework/graph_transfer_info.proto" - "tensorflow/core/framework/iterator.proto" "tensorflow/core/framework/kernel_def.proto" "tensorflow/core/framework/log_memory.proto" "tensorflow/core/framework/node_def.proto" diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 6c9afc51e9..084db7a0fd 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -181,7 +181,6 @@ COMMON_PROTO_SRCS = [ "framework/function.proto", "framework/graph.proto", "framework/graph_transfer_info.proto", - "framework/iterator.proto", "framework/kernel_def.proto", "framework/log_memory.proto", "framework/node_def.proto", diff --git a/tensorflow/core/framework/iterator.proto b/tensorflow/core/framework/iterator.proto deleted file mode 100644 index f015342e13..0000000000 --- a/tensorflow/core/framework/iterator.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto3"; - -package tensorflow; -option cc_enable_arenas = true; -option java_outer_classname = "IteratorProtos"; -option java_multiple_files = true; -option java_package = "org.tensorflow.util"; -option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; - -// Protocol buffer representing the metadata for an iterator's state stored -// as a Variant tensor. -message IteratorStateMetadata { - // A user-specified version string. - string version = 1; - - // Keys for tensors in the VariantTensorDataProto. - repeated string keys = 2; -} diff --git a/tensorflow/core/kernels/data/iterator_ops.cc b/tensorflow/core/kernels/data/iterator_ops.cc index a8c3f7f77f..da497a5f72 100644 --- a/tensorflow/core/kernels/data/iterator_ops.cc +++ b/tensorflow/core/kernels/data/iterator_ops.cc @@ -20,7 +20,6 @@ limitations under the License. #include "tensorflow/core/common_runtime/threadpool_device.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/function_handle_cache.h" -#include "tensorflow/core/framework/iterator.pb.h" #include "tensorflow/core/framework/partial_tensor_shape.h" #include "tensorflow/core/framework/resource_op_kernel.h" #include "tensorflow/core/framework/stats_aggregator.h" @@ -277,12 +276,19 @@ class IteratorResource : public ResourceBase { namespace { +constexpr char kDelimiter[] = "@@"; + // Helper class for reading data from a VariantTensorData object. class VariantTensorDataReader : public IteratorStateReader { public: explicit VariantTensorDataReader(const VariantTensorData* data) : data_(data) { - PreProcess(); + string metadata; + data_->get_metadata(&metadata); + auto keys = str_util::Split(metadata, kDelimiter, str_util::SkipEmpty()); + for (size_t i = 0; i < keys.size(); ++i) { + map_[keys[i]] = i; + } } // Returns OK iff the initialization was successful, i.e., @@ -306,21 +312,6 @@ class VariantTensorDataReader : public IteratorStateReader { } private: - void PreProcess() { - string metadata; - data_->get_metadata(&metadata); - IteratorStateMetadata proto; - if (!proto.ParseFromString(metadata)) { - status_ = errors::Internal("Error parsing IteratorStateMetadata."); - return; - } - size_t num_entries = proto.keys_size(); - CHECK_EQ(num_entries, data_->tensors_size()); - for (size_t i = 0; i < num_entries; i++) { - map_[proto.keys(i)] = i; - } - } - template Status ReadScalarInternal(StringPiece key, T* val) { if (map_.find(string(key)) == map_.end()) { @@ -361,11 +352,10 @@ class VariantTensorDataWriter : public IteratorStateWriter { return WriteTensorInternal(key, val); } - // Writes the metadata to `data_`. Status Flush() { string metadata; - if (!metadata_proto_.SerializeToString(&metadata)) { - return errors::Internal("Unable to serialize IteratorStateMetadata."); + for (size_t i = 0; i < keys_.size(); ++i) { + strings::StrAppend(&metadata, kDelimiter, keys_[i]); } data_->set_metadata(metadata); return Status::OK(); @@ -380,19 +370,14 @@ class VariantTensorDataWriter : public IteratorStateWriter { } Status WriteTensorInternal(StringPiece key, const Tensor& val) { - // Write key to the metadata proto. This gets written to `data_` - // when `Flush()` is called. We do this lazily to avoid multiple - // serialization calls. - metadata_proto_.add_keys(string(key)); - - // Update tensors. + DCHECK_EQ(key.find(kDelimiter), string::npos); + keys_.push_back(string(key)); *(data_->add_tensors()) = val; return Status::OK(); } VariantTensorData* data_; - // TODO(srbs): Set the version string. - IteratorStateMetadata metadata_proto_; + std::vector keys_; }; // Wrapper for encoding/decoding the iterator state stored in a Variant tensor. -- GitLab From 1757ebeaa21b356b954fa8f54e1d0796a8e60dc6 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Thu, 17 Jan 2019 13:01:06 -0800 Subject: [PATCH 0886/2345] Add oss-serial tag to elastic_optimizer_test This requires allocating many ports, and attaching to them later. Even with portserver, this is a flaky action when multiple tests execute on the same environment. PiperOrigin-RevId: 229802480 --- tensorflow/contrib/opt/BUILD | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/contrib/opt/BUILD b/tensorflow/contrib/opt/BUILD index 0446e823d9..12320d9e45 100644 --- a/tensorflow/contrib/opt/BUILD +++ b/tensorflow/contrib/opt/BUILD @@ -319,6 +319,9 @@ tf_py_test( "//tensorflow/python:framework_for_generated_wrappers", "//third_party/py/numpy", ], + tags = [ + "oss_serial", + ], ) tf_py_test( -- GitLab From 26cb1cd0e402b179650bba742caa4796f12da963 Mon Sep 17 00:00:00 2001 From: Suharsh Sivakumar Date: Thu, 17 Jan 2019 13:10:15 -0800 Subject: [PATCH 0887/2345] Add support for per channel quantization to C API. Also add future proof quantization params structure. PiperOrigin-RevId: 229804132 --- tensorflow/lite/c/c_api_internal.c | 37 ++++++++++++ tensorflow/lite/c/c_api_internal.h | 71 ++++++++++++++++++++++-- tensorflow/lite/c/c_api_internal_test.cc | 21 +++++++ 3 files changed, 124 insertions(+), 5 deletions(-) diff --git a/tensorflow/lite/c/c_api_internal.c b/tensorflow/lite/c/c_api_internal.c index 2923dbad4e..29dba15c63 100644 --- a/tensorflow/lite/c/c_api_internal.c +++ b/tensorflow/lite/c/c_api_internal.c @@ -70,6 +70,20 @@ TfLiteIntArray* TfLiteIntArrayCopy(const TfLiteIntArray* src) { void TfLiteIntArrayFree(TfLiteIntArray* a) { free(a); } +int TfLiteFloatArrayGetSizeInBytes(int size) { + static TfLiteFloatArray dummy; + return sizeof(dummy) + sizeof(dummy.data[0]) * size; +} + +TfLiteFloatArray* TfLiteFloatArrayCreate(int size) { + TfLiteFloatArray* ret = + (TfLiteFloatArray*)malloc(TfLiteFloatArrayGetSizeInBytes(size)); + ret->size = size; + return ret; +} + +void TfLiteFloatArrayFree(TfLiteFloatArray* a) { free(a); } + void TfLiteTensorDataFree(TfLiteTensor* t) { if (t->allocation_type == kTfLiteDynamic && t->data.raw) { free(t->data.raw); @@ -77,10 +91,30 @@ void TfLiteTensorDataFree(TfLiteTensor* t) { t->data.raw = NULL; } +void TfLiteQuantizationFree(TfLiteTensor* t) { + if (t->quantization.type == kTfLiteAffineQuantization) { + TfLiteAffineQuantization* q_params = + (TfLiteAffineQuantization*)(t->quantization.params); + if (q_params->scale) { + TfLiteFloatArrayFree(q_params->scale); + q_params->scale = NULL; + } + if (q_params->zero_point) { + TfLiteIntArrayFree(q_params->zero_point); + q_params->zero_point = NULL; + } + free(q_params); + } + t->quantization.params = NULL; + t->quantization.type = kTfLiteNoQuantization; +} + void TfLiteTensorFree(TfLiteTensor* t) { TfLiteTensorDataFree(t); if (t->dims) TfLiteIntArrayFree(t->dims); t->dims = NULL; + + TfLiteQuantizationFree(t); } void TfLiteTensorReset(TfLiteType type, const char* name, TfLiteIntArray* dims, @@ -98,6 +132,9 @@ void TfLiteTensorReset(TfLiteType type, const char* name, TfLiteIntArray* dims, tensor->allocation_type = allocation_type; tensor->allocation = allocation; tensor->is_variable = is_variable; + + tensor->quantization.type = kTfLiteNoQuantization; + tensor->quantization.params = NULL; } void TfLiteTensorRealloc(size_t num_bytes, TfLiteTensor* tensor) { diff --git a/tensorflow/lite/c/c_api_internal.h b/tensorflow/lite/c/c_api_internal.h index 1b1bc6db8f..31f483370c 100644 --- a/tensorflow/lite/c/c_api_internal.h +++ b/tensorflow/lite/c/c_api_internal.h @@ -98,8 +98,32 @@ int TfLiteIntArrayEqualsArray(TfLiteIntArray* a, int b_size, int b_data[]); // You are expected to free memory with TfLiteIntArrayFree TfLiteIntArray* TfLiteIntArrayCopy(const TfLiteIntArray* src); -// Free memory of array `v`. -void TfLiteIntArrayFree(TfLiteIntArray* v); +// Free memory of array `a`. +void TfLiteIntArrayFree(TfLiteIntArray* a); + +// Fixed size list of floats. Used for per-channel quantization. +typedef struct { + int size; +// gcc 6.1+ have a bug where flexible members aren't properly handled +// https://github.com/google/re2/commit/b94b7cd42e9f02673cd748c1ac1d16db4052514c +#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ == 6 && \ + __GNUC_MINOR__ >= 1 + float data[0]; +#else + float data[]; +#endif +} TfLiteFloatArray; + +// Given the size (number of elements) in a TfLiteFloatArray, calculate its size +// in bytes. +int TfLiteFloatArrayGetSizeInBytes(int size); + +// Create a array of a given `size` (uninitialized entries). +// This returns a pointer, that you must free using TfLiteFloatArrayFree(). +TfLiteFloatArray* TfLiteFloatArrayCreate(int size); + +// Free memory of array `a`. +void TfLiteFloatArrayFree(TfLiteFloatArray* a); // Since we must not depend on any libraries, define a minimal subset of // error macros while avoiding names that have pre-conceived meanings like @@ -185,14 +209,48 @@ typedef enum { // Return the name of a given type, for error reporting purposes. const char* TfLiteTypeGetName(TfLiteType type); +// SupportedQuantizationTypes. +typedef enum { + // No quantization. + kTfLiteNoQuantization = 0, + // Affine quantization (with support for per-channel quantization). + // Corresponds to TfLiteAffineQuantization. + kTfLiteAffineQuantization = 1, +} TfLiteQuantizationType; + +// Structure specifying the quantization used by the tensor, if-any. +typedef struct { + // The type of quantization held by params. + TfLiteQuantizationType type; + // Holds a reference to one of the quantization param structures specified + // below. + void* params; +} TfLiteQuantization; + +// Legacy. Will be deprecated in favor of TfLiteAffineQuantization. +// If per-layer quantization is specified this field will still be populated in +// addition to TfLiteAffineQuantization. // Parameters for asymmetric quantization. Quantized values can be converted // back to float using: -// real_value = scale * (quantized_value - zero_point); +// real_value = scale * (quantized_value - zero_point) typedef struct { float scale; int32_t zero_point; } TfLiteQuantizationParams; +// Parameters for asymmetric quantization across a dimension (i.e per output +// channel quantization). +// quantized_dimension specifies which dimension the scales and zero_points +// correspond to. +// For a particular value in quantized_dimension, quantized values can be +// converted back to float using: +// real_value = scale * (quantized_value - zero_point) +typedef struct { + TfLiteFloatArray* scale; + TfLiteIntArray* zero_point; + int32_t quantized_dimension; +} TfLiteAffineQuantization; + // A union of pointers that points to memory for a given tensor. typedef union { int* i32; @@ -274,12 +332,15 @@ typedef struct { // True if the tensor is a variable. bool is_variable; + + // Quantization information. Replaces params field above. + TfLiteQuantization quantization; } TfLiteTensor; -// Free data memory of tensor `t`; +// Free data memory of tensor `t`. void TfLiteTensorDataFree(TfLiteTensor* t); -// Free memory of tensor `t`; +// Free memory of tensor `t`. void TfLiteTensorFree(TfLiteTensor* t); // Set all of a tensor's fields (and free any previously allocated data). diff --git a/tensorflow/lite/c/c_api_internal_test.cc b/tensorflow/lite/c/c_api_internal_test.cc index acf0dfc5be..d01cf63a3e 100644 --- a/tensorflow/lite/c/c_api_internal_test.cc +++ b/tensorflow/lite/c/c_api_internal_test.cc @@ -65,6 +65,13 @@ TEST(IntArray, TestIntArrayEqual) { TfLiteIntArrayFree(d); } +TEST(FloatArray, TestFloatArrayCreate) { + TfLiteFloatArray* a = TfLiteFloatArrayCreate(0); + TfLiteFloatArray* b = TfLiteFloatArrayCreate(3); + TfLiteFloatArrayFree(a); + TfLiteFloatArrayFree(b); +} + TEST(Types, TestTypeNames) { auto type_name = [](TfLiteType t) { return std::string(TfLiteTypeGetName(t)); @@ -81,6 +88,20 @@ TEST(Types, TestTypeNames) { EXPECT_EQ(type_name(kTfLiteString), "STRING"); } +TEST(Quantization, TestQuantizationFree) { + TfLiteTensor t; + // Set these values, otherwise TfLiteTensorFree has uninitialized values. + t.allocation_type = kTfLiteArenaRw; + t.dims = nullptr; + t.quantization.type = kTfLiteAffineQuantization; + auto* params = reinterpret_cast( + malloc(sizeof(TfLiteAffineQuantization))); + params->scale = TfLiteFloatArrayCreate(3); + params->zero_point = TfLiteIntArrayCreate(3); + t.quantization.params = reinterpret_cast(params); + TfLiteTensorFree(&t); +} + } // namespace tflite int main(int argc, char** argv) { -- GitLab From f132194cf910343d4e1c3aedfad45f7b76fc5f61 Mon Sep 17 00:00:00 2001 From: Russell Power Date: Thu, 17 Jan 2019 13:24:19 -0800 Subject: [PATCH 0888/2345] SessionRef: add a timeout to avoid blocking Session::Close() forever. Some operations/sub-systems do not respect TF cancellation operations. This can result in Session:Close() hanging when waiting for these operations to finish. This CL adds a default timeout of 60 seconds to ensure we always return control back to the client eventually. PiperOrigin-RevId: 229806493 --- tensorflow/python/BUILD | 1 + tensorflow/python/client/session_ref.cc | 31 +++++++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index e0648085c3..d17c57b05f 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -4067,6 +4067,7 @@ cc_library( "//tensorflow/core:master_proto_cc", "//tensorflow/core:protos_all_cc", "//tensorflow/core:replay_log_proto_cc", + "@com_google_absl//absl/time", ], ) diff --git a/tensorflow/python/client/session_ref.cc b/tensorflow/python/client/session_ref.cc index 6639cf506e..895624f592 100644 --- a/tensorflow/python/client/session_ref.cc +++ b/tensorflow/python/client/session_ref.cc @@ -18,6 +18,9 @@ limitations under the License. #include #include +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/io/path.h" #include "tensorflow/core/lib/io/record_writer.h" #include "tensorflow/core/lib/strings/stringprintf.h" @@ -477,6 +480,8 @@ Status SessionRef::ReleaseCallable(CallableHandle handle) { LOG_AND_RUN_OPERATION(ReleaseCallable, handle); } +static const absl::Duration kMaxCloseWaitTime = absl::Seconds(60); + Status SessionRef::Close(const RunOptions& run_options) { TF_RETURN_IF_ERROR(CheckNotClosed()); mutex_lock l(run_lock_); @@ -487,8 +492,17 @@ Status SessionRef::Close(const RunOptions& run_options) { status = session_->Close(run_options); } session_.reset(); - while (run_count_ > 0) { - run_finished_.wait(l); + + // Wait no more than kMaxCloseWaitTime for all pending operations to finish. + absl::Time start_time = absl::Now(); + absl::Duration wait_time = start_time + kMaxCloseWaitTime - absl::Now(); + while (run_count_ > 0 && wait_time > absl::ZeroDuration()) { + if (run_finished_.wait_for(l, absl::ToChronoMilliseconds(wait_time)) == + std::cv_status::timeout) { + status.Update(errors::DeadlineExceeded("Timeout closing session.")); + return status; + } + wait_time = start_time + kMaxCloseWaitTime - absl::Now(); } return status; } @@ -503,8 +517,17 @@ Status SessionRef::Close() { status = session_->Close(); } session_.reset(); - while (run_count_ > 0) { - run_finished_.wait(l); + + // Wait no more than kMaxCloseWaitTime for all pending operations to finish. + absl::Time start_time = absl::Now(); + absl::Duration wait_time = start_time + kMaxCloseWaitTime - absl::Now(); + while (run_count_ > 0 && wait_time > absl::ZeroDuration()) { + if (run_finished_.wait_for(l, absl::ToChronoMilliseconds(wait_time)) == + std::cv_status::timeout) { + status.Update(errors::DeadlineExceeded("Timeout closing session.")); + return status; + } + wait_time = start_time + kMaxCloseWaitTime - absl::Now(); } return status; } -- GitLab From 8879131afb5e0470279a838f4b47bb969a361cf2 Mon Sep 17 00:00:00 2001 From: Mingming Liu Date: Thu, 17 Jan 2019 13:30:56 -0800 Subject: [PATCH 0889/2345] Use Sampler to record warm up latency distribution (by status). PiperOrigin-RevId: 229807679 --- tensorflow/cc/saved_model/loader.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/cc/saved_model/loader.cc b/tensorflow/cc/saved_model/loader.cc index aaf06a74c6..10f7abf09e 100644 --- a/tensorflow/cc/saved_model/loader.cc +++ b/tensorflow/cc/saved_model/loader.cc @@ -52,8 +52,8 @@ auto* load_latency_by_stage = monitoring::Sampler<2>::New( "model_path", "stage", }, - // Scale of 10, power of 1.5 with bucket count 36 (~20 minutes). - monitoring::Buckets::Exponential(10, 1.5, 36)); + // Scale of 10, power of 1.8 with bucket count 33 (~20 minutes). + monitoring::Buckets::Exponential(10, 1.8, 33)); constexpr char kLoadAttemptFail[] = "fail"; constexpr char kLoadAttemptSuccess[] = "success"; -- GitLab From 3fa896d2aa9f67f88c825c3a05eff2cf01b5546a Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Thu, 17 Jan 2019 13:33:33 -0800 Subject: [PATCH 0890/2345] Revert the changes to TRTEngineOp's OpDef, so existing models won't get broken. PiperOrigin-RevId: 229808206 --- .../contrib/tensorrt/convert/convert_graph.cc | 12 +++++----- .../contrib/tensorrt/convert/convert_graph.h | 5 ++-- .../contrib/tensorrt/convert/convert_nodes.h | 2 +- .../tensorrt/convert/trt_optimization_pass.cc | 6 ++--- .../contrib/tensorrt/kernels/trt_engine_op.cc | 17 +++++++------ .../contrib/tensorrt/kernels/trt_engine_op.h | 2 +- .../contrib/tensorrt/ops/trt_engine_op.cc | 5 +++- .../contrib/tensorrt/python/trt_convert.py | 24 +++++++++---------- .../tensorrt/python/trt_convert_test.py | 7 +++--- .../contrib/tensorrt/test/test_tftrt.py | 6 ++--- .../test/tf_trt_integration_test_base.py | 8 +++---- 11 files changed, 47 insertions(+), 47 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index 87c2d367f8..e2350f69a6 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -239,7 +239,7 @@ tensorflow::Status ConvertGraphDefToTensorRT( const std::vector& output_names, size_t max_batch_size, size_t max_workspace_size_bytes, tensorflow::GraphDef* new_graph_def, int precision_mode, int minimum_segment_size, bool is_dyn_op, - int max_cached_engines, std::vector cached_engine_batch_sizes, + int max_cached_engines, std::vector cached_engine_batches, bool use_calibration) { // Create GrapplerItem. tensorflow::grappler::GrapplerItem item; @@ -301,9 +301,9 @@ tensorflow::Status ConvertGraphDefToTensorRT( TF_RETURN_IF_ERROR(GetPrecisionModeName( precision_mode, parameters["precision_mode"].mutable_s())); parameters["maximum_cached_engines"].set_i(max_cached_engines); - if (!cached_engine_batch_sizes.empty()) { - auto list = parameters["cached_engine_batch_sizes"].mutable_list(); - for (const int batch : cached_engine_batch_sizes) { + if (!cached_engine_batches.empty()) { + auto list = parameters["cached_engine_batches"].mutable_list(); + for (const int batch : cached_engine_batches) { list->add_i(batch); } } @@ -689,7 +689,7 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, } if (info.engine_type == EngineInfo::EngineType::TRTStatic && - !info.cached_engine_batch_sizes.empty()) { + !info.cached_engine_batches.empty()) { LOG(WARNING) << "Cached engine batches are ignored for static engines"; } tensorflow::NodeDef trt_node; @@ -983,7 +983,7 @@ tensorflow::Status ConvertAfterShapes(ConversionParams& params) { ? EngineInfo::EngineType::TRTDynamic : EngineInfo::EngineType::TRTStatic); curr_engine.use_calibration = params.use_calibration; - curr_engine.cached_engine_batch_sizes = params.cached_engine_batch_sizes; + curr_engine.cached_engine_batches = params.cached_engine_batches; curr_engine.maximum_cached_engines = params.max_cached_engines; StrAppend(&curr_engine.engine_name, "TRTEngineOp_", t); status = RegisterSegmentFunctionToFunctionLibrary( diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.h b/tensorflow/contrib/tensorrt/convert/convert_graph.h index 5616bbefd9..1f39f56f63 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.h +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.h @@ -82,7 +82,7 @@ struct ConversionParams { bool fixed_input_size; // Assume non-batch ranks of input tensors are fixed int max_cached_engines; // maximum number of cached engines bool use_calibration; - std::vector cached_engine_batch_sizes; // list of cached engines + std::vector cached_engine_batches; // list of cached engines }; // This method extracts calibration information from the resource managers @@ -101,8 +101,7 @@ tensorflow::Status ConvertGraphDefToTensorRT( size_t max_workspace_size_bytes, tensorflow::GraphDef* new_graph_def, int precision_mode = 1, int minimum_segment_size = 3, bool is_dyn_op = false, int max_cached_engines = 1, - std::vector cached_engine_batch_sizes = {}, - bool use_calibration = true); + std::vector cached_engine_batches = {}, bool use_calibration = true); // Method to call from optimization pass tensorflow::Status ConvertAfterShapes(ConversionParams& params); diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.h b/tensorflow/contrib/tensorrt/convert/convert_nodes.h index a02a52e430..4ea5775f04 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.h +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.h @@ -108,7 +108,7 @@ struct EngineInfo { EngineType engine_type; int64 max_workspace_size_bytes; int maximum_cached_engines; - std::vector cached_engine_batch_sizes; + std::vector cached_engine_batches; int precision_mode; bool use_calibration; }; diff --git a/tensorflow/contrib/tensorrt/convert/trt_optimization_pass.cc b/tensorflow/contrib/tensorrt/convert/trt_optimization_pass.cc index e7ca126f55..d57f2300f8 100644 --- a/tensorflow/contrib/tensorrt/convert/trt_optimization_pass.cc +++ b/tensorflow/contrib/tensorrt/convert/trt_optimization_pass.cc @@ -50,8 +50,8 @@ tensorflow::Status TRTOptimizationPass::Init( if (params.count("is_dynamic_op")) { is_dynamic_op_ = params.at("is_dynamic_op").b(); } - if (params.count("cached_engine_batch_sizes")) { - auto batch_vec = params.at("cached_engine_batch_sizes").list(); + if (params.count("cached_engine_batches")) { + auto batch_vec = params.at("cached_engine_batches").list(); batches_.reserve(batch_vec.i_size()); for (const auto i : batch_vec.i()) { batches_.push_back(i); @@ -258,7 +258,7 @@ tensorflow::Status TRTOptimizationPass::Optimize( cp.graph_properties = &static_graph_properties; cp.cluster = cluster; cp.is_dyn_op = is_dynamic_op_; - cp.cached_engine_batch_sizes = batches_; + cp.cached_engine_batches = batches_; cp.max_cached_engines = max_cached_batches_; cp.use_calibration = use_calibration_; auto status = tensorflow::tensorrt::convert::ConvertAfterShapes(cp); diff --git a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc index f089f2c1cc..7548c8ccda 100644 --- a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc +++ b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc @@ -136,13 +136,12 @@ TRTEngineOp::TRTEngineOp(OpKernelConstruction* context) native_func_ = tensorflow::kInvalidHandle; OP_REQUIRES_OK(context, context->GetAttr("max_cached_engines_count", &max_cached_engines_)); - OP_REQUIRES_OK(context, context->GetAttr("cached_engine_batch_sizes", - &cached_engine_batch_sizes_)); - std::sort(cached_engine_batch_sizes_.begin(), - cached_engine_batch_sizes_.end()); + OP_REQUIRES_OK(context, context->GetAttr("cached_engine_batches", + &cached_engine_batches_)); + std::sort(cached_engine_batches_.begin(), cached_engine_batches_.end()); if (VLOG_IS_ON(1)) { string s("Engine Batches= "); - for (auto i : cached_engine_batch_sizes_) { + for (auto i : cached_engine_batches_) { StrAppend(&s, i, " "); } VLOG(1) << s; @@ -247,7 +246,7 @@ bool TRTEngineOp::GetCompatibleCachedEngine( // Output shape will always be the same as the input but we will overwrite the // batch size. *engine_input_shapes = actual_input_shapes; - for (const int cached_batch_size : cached_engine_batch_sizes_) { + for (const int cached_batch_size : cached_engine_batches_) { // Check if compatible: batch <= cached batch. // // TODO(laigd): here it only compare the first dim a.k.a the batch size, @@ -495,12 +494,12 @@ EngineContext* TRTEngineOp::GetEngine( // exact shape and possibly create a new engine if it is not in cache. if (!matched_successfully) { engine_input_shapes = input_shapes; - if (!cached_engine_batch_sizes_.empty()) { - // If user has explicitly defined cached_engine_batch_sizes, we should + if (!cached_engine_batches_.empty()) { + // If user has explicitly defined cached_engine_batches, we should // warn them that their input was non-compatible (batch size too high) LOG(WARNING) << "No compatible cached engine was found for batch size: " << batch_size << ". A new engine will be created."; - cached_engine_batch_sizes_.push_back(batch_size); + cached_engine_batches_.push_back(batch_size); } } diff --git a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h index c9b325cfd2..7f0f05aa0a 100644 --- a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h +++ b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h @@ -104,7 +104,7 @@ class TRTEngineOp : public AsyncOpKernel { bool calibration_mode_; // Batches of the cached engines - std::vector cached_engine_batch_sizes_; + std::vector cached_engine_batches_; // Maximum number of cached engines int max_cached_engines_; diff --git a/tensorflow/contrib/tensorrt/ops/trt_engine_op.cc b/tensorflow/contrib/tensorrt/ops/trt_engine_op.cc index 73cc046bc3..b84d2fe0b8 100644 --- a/tensorflow/contrib/tensorrt/ops/trt_engine_op.cc +++ b/tensorflow/contrib/tensorrt/ops/trt_engine_op.cc @@ -28,6 +28,9 @@ namespace shape_inference { extern Status TRTEngineOpShapeInference(InferenceContext* c); } +// NOTE: please try NOT to add/modify/remove attributes or inputs/outputs to the +// list below, this will break backward compatibility! +// // TODO(laigd): consider making this op stateful. The only problem is it uses TF // function which has to be stateless, but we can use function library as the // key to cache the instantiated functions for different executor subgraphs. @@ -40,7 +43,7 @@ REGISTER_OP("TRTEngineOp") .Attr("OutT: list({int8,float16,float32,int32})") .Attr("static_engine: bool = true") .Attr("fixed_input_size: bool = true") - .Attr("cached_engine_batch_sizes: list(int) >= 0 = []") + .Attr("cached_engine_batches: list(int) >= 0 = []") .Attr("max_cached_engines_count: int = 1") .Attr("workspace_size_bytes: int") .Attr("precision_mode: {'FP32', 'FP16', 'INT8'}") diff --git a/tensorflow/contrib/tensorrt/python/trt_convert.py b/tensorflow/contrib/tensorrt/python/trt_convert.py index 8de17347f8..49d72232aa 100644 --- a/tensorflow/contrib/tensorrt/python/trt_convert.py +++ b/tensorflow/contrib/tensorrt/python/trt_convert.py @@ -77,7 +77,7 @@ def get_tensorrt_rewriter_config(rewriter_config=None, minimum_segment_size=3, is_dynamic_op=False, maximum_cached_engines=1, - cached_engine_batch_sizes=None, + cached_engine_batches=None, use_calibration=True): """Returns a RewriterConfig proto for TRT transformation. @@ -97,7 +97,7 @@ def get_tensorrt_rewriter_config(rewriter_config=None, If the number of cached engines is already at max but none of them can serve the input, the TRTEngineOp will fall back to run the TF function based on which the TRTEngineOp is created. - cached_engine_batch_sizes: a list of batch sizes used to create cached + cached_engine_batches: a list of batch sizes used to create cached engines, only used when is_dynamic_op is True. The length of the list should be <= maximum_cached_engines, and the dynamic TRT op will use this list to determine the batch sizes of the cached engines, instead @@ -150,14 +150,14 @@ def get_tensorrt_rewriter_config(rewriter_config=None, "max_workspace_size_bytes"].i = max_workspace_size_bytes optimizer.parameter_map["precision_mode"].s = _to_bytes(precision_mode) optimizer.parameter_map["maximum_cached_engines"].i = maximum_cached_engines - if cached_engine_batch_sizes: - if not isinstance(cached_engine_batch_sizes, list): - raise TypeError("cached_engine_batch_sizes should be a list.") - if len(cached_engine_batch_sizes) > maximum_cached_engines: - raise ValueError("cached_engine_batch_sizes should not contain more than " + if cached_engine_batches: + if not isinstance(cached_engine_batches, list): + raise TypeError("cached_engine_batches should be a list.") + if len(cached_engine_batches) > maximum_cached_engines: + raise ValueError("cached_engine_batches should not contain more than " "maximum_cached_engines items.") - optimizer.parameter_map["cached_engine_batch_sizes"].list.i.extend( - cached_engine_batch_sizes) + optimizer.parameter_map["cached_engine_batches"].list.i.extend( + cached_engine_batches) optimizer.parameter_map["use_calibration"].b = use_calibration return rewriter_config_with_trt @@ -170,7 +170,7 @@ def create_inference_graph(input_graph_def, minimum_segment_size=3, is_dynamic_op=False, maximum_cached_engines=1, - cached_engine_batch_sizes=None, + cached_engine_batches=None, use_calibration=True, input_saved_model_dir=None, input_saved_model_tags=None, @@ -197,7 +197,7 @@ def create_inference_graph(input_graph_def, If the number of cached engines is already at max but none of them can serve the input, the TRTEngineOp will fall back to run the TF function based on which the TRTEngineOp is created. - cached_engine_batch_sizes: a list of batch sizes used to create cached + cached_engine_batches: a list of batch sizes used to create cached engines, only used when is_dynamic_op is True. The length of the list should be <= maximum_cached_engines, and the dynamic TRT op will use this list to determine the batch sizes of the cached engines, instead @@ -361,7 +361,7 @@ def create_inference_graph(input_graph_def, rewriter_config_with_trt = get_tensorrt_rewriter_config( rewriter_config, max_batch_size, max_workspace_size_bytes, precision_mode, minimum_segment_size, is_dynamic_op, maximum_cached_engines, - cached_engine_batch_sizes, use_calibration) + cached_engine_batches, use_calibration) session_config_with_trt.graph_options.rewrite_options.CopyFrom( rewriter_config_with_trt) diff --git a/tensorflow/contrib/tensorrt/python/trt_convert_test.py b/tensorflow/contrib/tensorrt/python/trt_convert_test.py index 9562704aeb..3ef18e3e15 100644 --- a/tensorflow/contrib/tensorrt/python/trt_convert_test.py +++ b/tensorflow/contrib/tensorrt/python/trt_convert_test.py @@ -57,7 +57,7 @@ class TrtConvertTest(test_util.TensorFlowTestCase): minimum_segment_size=10, is_dynamic_op=True, maximum_cached_engines=2, - cached_engine_batch_sizes=[1, 128]) + cached_engine_batches=[1, 128]) self.assertEqual(["constfold", "layout", "constfold"], rewriter_cfg.optimizers) self.assertEqual(rewriter_config_pb2.RewriterConfig.ONE, @@ -71,7 +71,7 @@ class TrtConvertTest(test_util.TensorFlowTestCase): for key in [ "minimum_segment_size", "max_batch_size", "is_dynamic_op", "max_workspace_size_bytes", "precision_mode", "maximum_cached_engines", - "cached_engine_batch_sizes" + "cached_engine_batches" ]: self.assertTrue(key in trt_optimizer.parameter_map) self.assertEqual(10, trt_optimizer.parameter_map["minimum_segment_size"].i) @@ -84,8 +84,7 @@ class TrtConvertTest(test_util.TensorFlowTestCase): trt_optimizer.parameter_map["precision_mode"].s) self.assertEqual(2, trt_optimizer.parameter_map["maximum_cached_engines"].i) self.assertEqual( - [1, 128], - trt_optimizer.parameter_map["cached_engine_batch_sizes"].list.i) + [1, 128], trt_optimizer.parameter_map["cached_engine_batches"].list.i) def _GetConfigProto(self): """Get ConfigProto for session creation.""" diff --git a/tensorflow/contrib/tensorrt/test/test_tftrt.py b/tensorflow/contrib/tensorrt/test/test_tftrt.py index d26f260086..090aa8bdb0 100644 --- a/tensorflow/contrib/tensorrt/test/test_tftrt.py +++ b/tensorflow/contrib/tensorrt/test/test_tftrt.py @@ -191,7 +191,7 @@ def user(multi_engine, minimum_segment_size=2, # minimum number of nodes in an engine is_dynamic_op=False, maximum_cached_engines=1, - cached_engine_batch_sizes=[]) + cached_engine_batches=[]) o1 = run_graph(orig_graph, dummy_input) o2 = run_graph(trt_graph, dummy_input) o3 = run_graph(trt_graph, dummy_input) @@ -206,7 +206,7 @@ def user(multi_engine, minimum_segment_size=2, # minimum number of nodes in an engine is_dynamic_op=False, maximum_cached_engines=1, - cached_engine_batch_sizes=[]) + cached_engine_batches=[]) int8_calib_gdef = trt.create_inference_graph( input_graph_def=orig_graph, outputs=["output"], @@ -216,7 +216,7 @@ def user(multi_engine, minimum_segment_size=2, # minimum number of nodes in an engine is_dynamic_op=False, maximum_cached_engines=1, - cached_engine_batch_sizes=[]) + cached_engine_batches=[]) o4 = run_graph(fp16_graph, dummy_input) _ = run_calibration(int8_calib_gdef, dummy_input) int8_graph = trt.calib_graph_to_infer_graph(int8_calib_gdef) diff --git a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py index 4f47f80e73..db43581404 100644 --- a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py +++ b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py @@ -61,7 +61,7 @@ RunParams = namedtuple("RunParams", [ ConversionParams = namedtuple("ConversionParams", [ "max_batch_size", "max_workspace_size_bytes", "precision_mode", "minimum_segment_size", "is_dynamic_op", "maximum_cached_engines", - "cached_engine_batch_sizes", "rewriter_config", "use_calibration" + "cached_engine_batches", "rewriter_config", "use_calibration" ]) PRECISION_MODES = ["FP32", "FP16", "INT8"] @@ -186,7 +186,7 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): minimum_segment_size=2, is_dynamic_op=run_params.dynamic_engine, maximum_cached_engines=1, - cached_engine_batch_sizes=None, + cached_engine_batches=None, rewriter_config=None, use_calibration=run_params.use_calibration) @@ -266,7 +266,7 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): conversion_params.minimum_segment_size, conversion_params.is_dynamic_op, conversion_params.maximum_cached_engines, - conversion_params.cached_engine_batch_sizes, + conversion_params.cached_engine_batches, conversion_params.use_calibration) graph_options = config_pb2.GraphOptions(rewrite_options=rewriter_cfg) @@ -369,7 +369,7 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): minimum_segment_size=conversion_params.minimum_segment_size, is_dynamic_op=conversion_params.is_dynamic_op, maximum_cached_engines=conversion_params.maximum_cached_engines, - cached_engine_batch_sizes=conversion_params.cached_engine_batch_sizes, + cached_engine_batches=conversion_params.cached_engine_batches, use_calibration=conversion_params.use_calibration, session_config=config_for_trt) -- GitLab From 741fb41f3cafc5750b3d1f6ab3ba366b8cf93b34 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Thu, 17 Jan 2019 13:49:30 -0800 Subject: [PATCH 0891/2345] [TF:XLA] Bump open source llvm revision to r351428 PiperOrigin-RevId: 229811121 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 6fcccd4147..ad73b19ff1 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -512,11 +512,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 = "2027c5043ea3458376fbf4263aaab3b257736b9e4a1e9e9984f15e245177979d", - strip_prefix = "llvm-82a2c96ee81a617918d9bcc0fbe9af860e437f5f", + sha256 = "986122dee6053db98d4cf8a60efedd6d94e20c557cc97299a0c7d33b9efae12d", + strip_prefix = "llvm-329f768b5fc380a4bfa327396f108a8d8f33e77b", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/82a2c96ee81a617918d9bcc0fbe9af860e437f5f.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/82a2c96ee81a617918d9bcc0fbe9af860e437f5f.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/329f768b5fc380a4bfa327396f108a8d8f33e77b.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/329f768b5fc380a4bfa327396f108a8d8f33e77b.tar.gz", ], ) -- GitLab From 252aa8b5661559377e86b4763980f59f214b9eee Mon Sep 17 00:00:00 2001 From: Bairen Yi Date: Thu, 17 Jan 2019 22:48:05 +0000 Subject: [PATCH 0892/2345] Address review comments in #24864. Signed-off-by: Bairen Yi --- tensorflow/contrib/gdr/gdr_worker.cc | 6 +++--- tensorflow/contrib/gdr/gdr_worker.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/gdr/gdr_worker.cc b/tensorflow/contrib/gdr/gdr_worker.cc index 29ee4c9375..a2425557da 100644 --- a/tensorflow/contrib/gdr/gdr_worker.cc +++ b/tensorflow/contrib/gdr/gdr_worker.cc @@ -44,13 +44,13 @@ GdrWorker::GdrWorker(WorkerEnv* worker_env, const ConfigProto& config, RemoteMemoryManager* remote_memory_manager) : GrpcWorker(worker_env, config), remote_memory_manager_(remote_memory_manager), - recv_tensor_recent_request_ids_(100000) {} + recent_request_ids_(100000) {} void GdrWorker::GrpcRecvTensorAsync(CallOptions* opts, const RecvTensorRequest* request, ::grpc::ByteBuffer* response, StatusCallback done) { - Status s = recv_tensor_recent_request_ids_.TrackUnique( + Status s = recent_request_ids_.TrackUnique( request->request_id(), "RecvTensor (GdrWorker)", *request); if (!s.ok()) { done(s); @@ -152,7 +152,7 @@ void GdrWorker::GrpcRecvTensorAsync(CallOptions* opts, void GdrWorker::RecvBufAsync(CallOptions* opts, const RecvBufRequest* request, RecvBufResponse* response, StatusCallback done) { // This is an RDMA enabled implementation augmenting grpc. - Status s = recv_tensor_recent_request_ids_.TrackUnique( + Status s = recent_request_ids_.TrackUnique( request->request_id(), "RecvBuf (GdrWorker)", *request); if (!s.ok()) { done(s); diff --git a/tensorflow/contrib/gdr/gdr_worker.h b/tensorflow/contrib/gdr/gdr_worker.h index 7af03d16fc..9a85cfd426 100644 --- a/tensorflow/contrib/gdr/gdr_worker.h +++ b/tensorflow/contrib/gdr/gdr_worker.h @@ -44,7 +44,7 @@ class GdrWorker : public GrpcWorker { private: RemoteMemoryManager* remote_memory_manager_; // Not owned - RecentRequestIds recv_tensor_recent_request_ids_; + RecentRequestIds recent_request_ids_; }; } // namespace tensorflow -- GitLab From 793d66993c0210fd22eeda7c7048c379521ebd29 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 17 Jan 2019 13:52:51 -0800 Subject: [PATCH 0893/2345] internal changes PiperOrigin-RevId: 229811646 --- tensorflow/python/autograph/impl/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/autograph/impl/api.py b/tensorflow/python/autograph/impl/api.py index 6633441e9f..122ea1726b 100644 --- a/tensorflow/python/autograph/impl/api.py +++ b/tensorflow/python/autograph/impl/api.py @@ -35,9 +35,9 @@ from tensorflow.python.autograph.operators import py_builtins from tensorflow.python.autograph.pyct import compiler from tensorflow.python.autograph.pyct import inspect_utils from tensorflow.python.autograph.utils import py_func -from tensorflow.python.util import nest from tensorflow.python.framework import tensor_util from tensorflow.python.platform import tf_logging as logging +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 @@ -263,7 +263,7 @@ def converted_call(f, owner, options, *args, **kwargs): partial_types = (f.__class__,) else: - NotImplementedError('unknown callable type "%s"' % type(f)) + raise NotImplementedError('unknown callable type "%s"' % type(f)) arg_values = tf_inspect.getcallargs(arg_map_target, *args, **kwargs) arg_types = {} -- GitLab From 8dacf726e788afadf7b8a7ab366f66c10fb2c0c4 Mon Sep 17 00:00:00 2001 From: Russell Power Date: Thu, 17 Jan 2019 13:58:28 -0800 Subject: [PATCH 0894/2345] TPUEstimator: always force dequeue operations to execute. If a hostcall does not use the result of an outfeed_dequeue operation, the resulting values remains on the outfeed. This eventually stalls the training process. This CL executes all outfeed_dequeue operations explicitly when running hostcalls to ensure the outfeed is drained. PiperOrigin-RevId: 229812586 --- .../contrib/tpu/python/tpu/tpu_estimator.py | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py index 022ac4152d..27c8ccf6bd 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py @@ -124,6 +124,16 @@ def _is_iterable(obj): 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: @@ -1802,6 +1812,10 @@ class _OutfeedHostCall(object): 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: @@ -1809,6 +1823,14 @@ class _OutfeedHostCall(object): 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. # @@ -1842,7 +1864,7 @@ class _OutfeedHostCall(object): # The user-provided eval_metrics[1] is a dict. dequeue_ops = dict(zip(self._tensor_keys[name], dequeue_ops)) try: - ret[name] = self._host_fns[name](**dequeue_ops) + 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 ' @@ -1850,8 +1872,10 @@ class _OutfeedHostCall(object): 'function\'s arguments', name, e, name) raise else: - ret[name] = self._host_fns[name](*dequeue_ops) + 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 -- GitLab From 1cb48000f4c8e22bb444920a999942f8c50d17a9 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 3 Jan 2019 15:39:40 -0800 Subject: [PATCH 0895/2345] Move VariantTensorDataWriter and VariantTensorDataReader to dataset_utils --- tensorflow/core/kernels/data/dataset_utils.cc | 94 +++++++++++++++++++ tensorflow/core/kernels/data/dataset_utils.h | 49 ++++++++++ 2 files changed, 143 insertions(+) diff --git a/tensorflow/core/kernels/data/dataset_utils.cc b/tensorflow/core/kernels/data/dataset_utils.cc index 4d92d314d3..aba991c080 100644 --- a/tensorflow/core/kernels/data/dataset_utils.cc +++ b/tensorflow/core/kernels/data/dataset_utils.cc @@ -141,5 +141,99 @@ Status VerifyShapesCompatible(const std::vector& expected, return Status::OK(); } +Status VariantTensorDataReader::status() const { return status_; } + +Status VariantTensorDataReader::ReadScalar(StringPiece key, int64* val) { + return ReadScalarInternal(key, val); +} + +Status VariantTensorDataReader::ReadScalar(StringPiece key, string* val) { + return ReadScalarInternal(key, val); +} + +Status VariantTensorDataReader::ReadTensor(StringPiece key, Tensor* val) { + return ReadTensorInternal(key, val); +} + +bool VariantTensorDataReader::Contains(StringPiece key) { + return map_.find(string(key)) != map_.end(); +} + +void VariantTensorDataReader::PreProcess() { + string metadata; + data_->get_metadata(&metadata); + IteratorStateMetadata proto; + if (!proto.ParseFromString(metadata)) { + status_ = errors::Internal("Error parsing IteratorStateMetadata."); + return; + } + size_t num_entries = proto.keys_size(); + CHECK_EQ(num_entries, data_->tensors_size()); + for (size_t i = 0; i < num_entries; i++) { + map_[proto.keys(i)] = i; + } +} + +template +Status VariantTensorDataReader::ReadScalarInternal(StringPiece key, T* val) { + if (map_.find(string(key)) == map_.end()) { + return errors::NotFound(key); + } + *val = data_->tensors(map_[string(key)]).scalar()(); + return Status::OK(); +} + +Status VariantTensorDataReader::ReadTensorInternal(StringPiece key, + Tensor* val) { + if (map_.find(string(key)) == map_.end()) { + return errors::NotFound(key); + } + *val = data_->tensors(map_[string(key)]); + return Status::OK(); +} + +Status VariantTensorDataWriter::WriteScalar(StringPiece key, const int64 val) { + return WriteScalarInternal(key, val); +} + +Status VariantTensorDataWriter::WriteScalar(StringPiece key, + const string& val) { + return WriteScalarInternal(key, val); +} + +Status VariantTensorDataWriter::WriteTensor(StringPiece key, + const Tensor& val) { + return WriteTensorInternal(key, val); +} + +Status VariantTensorDataWriter::Flush() { + string metadata; + if (!metadata_proto_.SerializeToString(&metadata)) { + return errors::Internal("Unable to serialize IteratorStateMetadata."); + } + data_->set_metadata(metadata); + return Status::OK(); +} + +template +Status VariantTensorDataWriter::WriteScalarInternal(StringPiece key, + const T& val) { + Tensor val_t = Tensor(DataTypeToEnum::v(), TensorShape({})); + val_t.scalar()() = val; + return WriteTensorInternal(key, val_t); +} + +Status VariantTensorDataWriter::WriteTensorInternal(StringPiece key, + const Tensor& val) { + // Write key to the metadata proto. This gets written to `data_` + // when `Flush()` is called. We do this lazily to avoid multiple + // serialization calls. + metadata_proto_.add_keys(string(key)); + + // Update tensors. + *(data_->add_tensors()) = val; + return Status::OK(); +} + } // namespace data } // namespace tensorflow diff --git a/tensorflow/core/kernels/data/dataset_utils.h b/tensorflow/core/kernels/data/dataset_utils.h index 23a3d93ed1..b2a19ac869 100644 --- a/tensorflow/core/kernels/data/dataset_utils.h +++ b/tensorflow/core/kernels/data/dataset_utils.h @@ -16,6 +16,7 @@ limitations under the License. #define TENSORFLOW_CORE_KERNELS_DATA_DATASET_UTILS_H_ #include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/framework/iterator.pb.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/kernels/data/captured_function.h" @@ -57,6 +58,54 @@ Status VerifyTypesMatch(const DataTypeVector& expected, Status VerifyShapesCompatible(const std::vector& expected, const std::vector& received); +// Helper class for reading data from a VariantTensorData object. +class VariantTensorDataReader : public IteratorStateReader { + public: + explicit VariantTensorDataReader(const VariantTensorData* data) + : data_(data) { + PreProcess(); + } + + // Returns OK iff the initialization was successful, i.e., + // pre-processing did not have errors. + Status status() const; + Status ReadScalar(StringPiece key, int64* val) override; + Status ReadScalar(StringPiece key, string* val) override; + Status ReadTensor(StringPiece key, Tensor* val) override; + bool Contains(StringPiece key) override; + + private: + void PreProcess(); + template + Status ReadScalarInternal(StringPiece key, T* val); + Status ReadTensorInternal(StringPiece key, Tensor* val); + std::map map_; + const VariantTensorData* data_; // Not owned. + Status status_; +}; + +// Helper class for writing data to a VariantTensorData object. +class VariantTensorDataWriter : public IteratorStateWriter { + public: + // Does not take ownership of data. + explicit VariantTensorDataWriter(VariantTensorData* data) : data_(data) {} + Status WriteScalar(StringPiece key, const int64 val) override; + Status WriteScalar(StringPiece key, const string& val) override; + Status WriteTensor(StringPiece key, const Tensor& val) override; + + // Writes the metadata to `data_`. + Status Flush(); + + private: + template + Status WriteScalarInternal(StringPiece key, const T& val); + Status WriteTensorInternal(StringPiece key, const Tensor& val); + VariantTensorData* data_; + + // TODO(srbs): Set the version string. + IteratorStateMetadata metadata_proto_; +}; + } // namespace data } // namespace tensorflow -- GitLab From 0762324b237eca697b376e3e5d067d761717230e Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 3 Jan 2019 22:37:28 -0800 Subject: [PATCH 0896/2345] Add the test cases for VariantTensorDataWriter and VariantTensorDataReader --- tensorflow/core/kernels/data/BUILD | 1 + .../core/kernels/data/dataset_utils_test.cc | 44 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index fab1ec8b6e..dcbdb74691 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -39,6 +39,7 @@ tf_cc_test( srcs = ["dataset_utils_test.cc"], deps = [ ":dataset_utils", + "//tensorflow/core:framework", "//tensorflow/core:test", "//tensorflow/core:test_main", ], diff --git a/tensorflow/core/kernels/data/dataset_utils_test.cc b/tensorflow/core/kernels/data/dataset_utils_test.cc index 43295b8ebb..de5dc59bb1 100644 --- a/tensorflow/core/kernels/data/dataset_utils_test.cc +++ b/tensorflow/core/kernels/data/dataset_utils_test.cc @@ -13,15 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#include "tensorflow/core/framework/variant.h" #include "tensorflow/core/kernels/data/dataset_utils.h" - +#include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/platform/test.h" namespace tensorflow { namespace data { namespace { -TEST(DatasetUtils, ComputeMoveVector) { +TEST(DatasetUtilsTest, ComputeMoveVector) { struct TestCase { std::vector indices; std::vector expected; @@ -41,6 +42,45 @@ TEST(DatasetUtils, ComputeMoveVector) { } } +TEST(DatasetUtilsTest, VariantTensorData_Writer_Reader) { + VariantTensorData data; + + // Basic test cases. + VariantTensorDataWriter writer(&data); + TF_ASSERT_OK(writer.WriteScalar("Int64", 24)); + TF_ASSERT_OK(writer.WriteScalar("", "Empty_Key")); + Tensor input_tensor(DT_FLOAT, {1}); + input_tensor.flat()(0) = 2.0f; + TF_ASSERT_OK(writer.WriteTensor("Tensor", input_tensor)); + TF_ASSERT_OK(writer.Flush()); + + VariantTensorDataReader reader(&data); + EXPECT_OK(reader.status()); + int64 val_int64; + TF_ASSERT_OK(reader.ReadScalar("Int64", &val_int64)); + EXPECT_EQ(val_int64, 24); + string val_string; + TF_ASSERT_OK(reader.ReadScalar("", &val_string)); + EXPECT_EQ(val_string, "Empty_Key"); + Tensor val_tensor; + TF_ASSERT_OK(reader.ReadTensor("Tensor", &val_tensor)); + EXPECT_EQ(input_tensor.NumElements(), val_tensor.NumElements()); + EXPECT_EQ(input_tensor.flat()(0), val_tensor.flat()(0)); + + // Test the non-existing key for VariantTensorDataReader. + EXPECT_EQ(error::NOT_FOUND, + reader.ReadScalar("Non_Existing_Key", &val_int64).code()); + EXPECT_EQ(error::NOT_FOUND, + reader.ReadScalar("Non_Existing_Key", &val_string).code()); + EXPECT_EQ(error::NOT_FOUND, + reader.ReadTensor("Non_Existing_Key", &val_tensor).code()); + + // Test the invalid parameter for the constructor of VariantTensorDataReader. + data.metadata_ = "Invalid Metadata"; + VariantTensorDataReader reader2(&data); + EXPECT_EQ(error::INTERNAL, reader2.status().code()); +} + } // namespace } // namespace data } // namespace tensorflow -- GitLab From 55c6645ec7c65ebc6ce9e036052a915b66c2fc46 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 4 Jan 2019 10:00:48 -0800 Subject: [PATCH 0897/2345] Use error::InvalidArgument to replace CHECK_EQ in VariantTensorDataReader::PreProcess --- tensorflow/core/kernels/data/dataset_utils.cc | 7 ++++++- tensorflow/core/kernels/data/dataset_utils_test.cc | 13 ++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/kernels/data/dataset_utils.cc b/tensorflow/core/kernels/data/dataset_utils.cc index aba991c080..cb0a183c3b 100644 --- a/tensorflow/core/kernels/data/dataset_utils.cc +++ b/tensorflow/core/kernels/data/dataset_utils.cc @@ -168,7 +168,12 @@ void VariantTensorDataReader::PreProcess() { return; } size_t num_entries = proto.keys_size(); - CHECK_EQ(num_entries, data_->tensors_size()); + if (num_entries != data_->tensors_size()) { + status_ = errors::InvalidArgument("Unmatched number of keys and tensors: ", + num_entries, " vs. ", + data_->tensors_size()); + return; + } for (size_t i = 0; i < num_entries; i++) { map_[proto.keys(i)] = i; } diff --git a/tensorflow/core/kernels/data/dataset_utils_test.cc b/tensorflow/core/kernels/data/dataset_utils_test.cc index de5dc59bb1..2cc5d357b0 100644 --- a/tensorflow/core/kernels/data/dataset_utils_test.cc +++ b/tensorflow/core/kernels/data/dataset_utils_test.cc @@ -75,10 +75,17 @@ TEST(DatasetUtilsTest, VariantTensorData_Writer_Reader) { EXPECT_EQ(error::NOT_FOUND, reader.ReadTensor("Non_Existing_Key", &val_tensor).code()); - // Test the invalid parameter for the constructor of VariantTensorDataReader. - data.metadata_ = "Invalid Metadata"; - VariantTensorDataReader reader2(&data); + // Test the invalid metadata for VariantTensorDataReader. + VariantTensorData data_invalid_meta(data); + data_invalid_meta.metadata_ = "Invalid Metadata"; + VariantTensorDataReader reader2(&data_invalid_meta); EXPECT_EQ(error::INTERNAL, reader2.status().code()); + + // Test the unmatched number of keys and tensors for VariantTensorDataReader. + VariantTensorData data_unmatched_entries(data); + data_unmatched_entries.tensors_.push_back(Tensor(DT_INT64, {1})); + VariantTensorDataReader reader3(&data_unmatched_entries); + EXPECT_EQ(error::INVALID_ARGUMENT, reader3.status().code()); } } // namespace -- GitLab From 7bcbe79420f52f9fbee7d43327fb2edab088bb78 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 4 Jan 2019 11:08:56 -0800 Subject: [PATCH 0898/2345] Separate the big test to small tests and address the clang-format issues --- tensorflow/core/kernels/data/dataset_utils.cc | 6 +-- .../core/kernels/data/dataset_utils_test.cc | 43 +++++++++++-------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/tensorflow/core/kernels/data/dataset_utils.cc b/tensorflow/core/kernels/data/dataset_utils.cc index cb0a183c3b..8ac74af6ba 100644 --- a/tensorflow/core/kernels/data/dataset_utils.cc +++ b/tensorflow/core/kernels/data/dataset_utils.cc @@ -169,9 +169,9 @@ void VariantTensorDataReader::PreProcess() { } size_t num_entries = proto.keys_size(); if (num_entries != data_->tensors_size()) { - status_ = errors::InvalidArgument("Unmatched number of keys and tensors: ", - num_entries, " vs. ", - data_->tensors_size()); + status_ = errors::InvalidArgument( + "Unmatched number of keys and tensors: ", num_entries, " vs. ", + data_->tensors_size()); return; } for (size_t i = 0; i < num_entries; i++) { diff --git a/tensorflow/core/kernels/data/dataset_utils_test.cc b/tensorflow/core/kernels/data/dataset_utils_test.cc index 2cc5d357b0..b503e58c1b 100644 --- a/tensorflow/core/kernels/data/dataset_utils_test.cc +++ b/tensorflow/core/kernels/data/dataset_utils_test.cc @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/core/framework/variant.h" #include "tensorflow/core/kernels/data/dataset_utils.h" +#include "tensorflow/core/framework/variant.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/platform/test.h" @@ -42,10 +42,8 @@ TEST(DatasetUtilsTest, ComputeMoveVector) { } } -TEST(DatasetUtilsTest, VariantTensorData_Writer_Reader) { +TEST(DatasetUtilsTest, VariantTensorDataRoundtrip) { VariantTensorData data; - - // Basic test cases. VariantTensorDataWriter writer(&data); TF_ASSERT_OK(writer.WriteScalar("Int64", 24)); TF_ASSERT_OK(writer.WriteScalar("", "Empty_Key")); @@ -66,26 +64,35 @@ TEST(DatasetUtilsTest, VariantTensorData_Writer_Reader) { TF_ASSERT_OK(reader.ReadTensor("Tensor", &val_tensor)); EXPECT_EQ(input_tensor.NumElements(), val_tensor.NumElements()); EXPECT_EQ(input_tensor.flat()(0), val_tensor.flat()(0)); +} - // Test the non-existing key for VariantTensorDataReader. +TEST(DatasetUtilsTest, VariantTensorDataNonExistentKey) { + VariantTensorData data; + VariantTensorDataReader reader(&data); + TF_ASSERT_OK(reader.status()); + int64 val_int64; + string val_string; + Tensor val_tensor; EXPECT_EQ(error::NOT_FOUND, - reader.ReadScalar("Non_Existing_Key", &val_int64).code()); + reader.ReadScalar("NonExistentKey", &val_int64).code()); EXPECT_EQ(error::NOT_FOUND, - reader.ReadScalar("Non_Existing_Key", &val_string).code()); + reader.ReadScalar("NonExistentKey", &val_string).code()); EXPECT_EQ(error::NOT_FOUND, - reader.ReadTensor("Non_Existing_Key", &val_tensor).code()); + reader.ReadTensor("NonExistentKey", &val_tensor).code()); +} - // Test the invalid metadata for VariantTensorDataReader. - VariantTensorData data_invalid_meta(data); - data_invalid_meta.metadata_ = "Invalid Metadata"; - VariantTensorDataReader reader2(&data_invalid_meta); - EXPECT_EQ(error::INTERNAL, reader2.status().code()); +TEST(DatasetUtilsTest, VariantTensorDataInvalidMetadata) { + VariantTensorData data; + data.metadata_ = "Invalid Metadata"; + VariantTensorDataReader reader(&data); + EXPECT_EQ(error::INTERNAL, reader.status().code()); +} - // Test the unmatched number of keys and tensors for VariantTensorDataReader. - VariantTensorData data_unmatched_entries(data); - data_unmatched_entries.tensors_.push_back(Tensor(DT_INT64, {1})); - VariantTensorDataReader reader3(&data_unmatched_entries); - EXPECT_EQ(error::INVALID_ARGUMENT, reader3.status().code()); +TEST(DatasetUtilsTest, VariantTensorDataUnmatchedKeys) { + VariantTensorData data; + data.tensors_.push_back(Tensor(DT_INT64, {1})); + VariantTensorDataReader reader(&data); + EXPECT_EQ(error::INVALID_ARGUMENT, reader.status().code()); } } // namespace -- GitLab From cc1713c0c0cf20972c34c447fe4afc28a7f6ce34 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 4 Jan 2019 13:01:38 -0800 Subject: [PATCH 0899/2345] Change the indentation --- tensorflow/core/kernels/data/dataset_utils.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/kernels/data/dataset_utils.cc b/tensorflow/core/kernels/data/dataset_utils.cc index 8ac74af6ba..67a0b1165a 100644 --- a/tensorflow/core/kernels/data/dataset_utils.cc +++ b/tensorflow/core/kernels/data/dataset_utils.cc @@ -169,9 +169,9 @@ void VariantTensorDataReader::PreProcess() { } size_t num_entries = proto.keys_size(); if (num_entries != data_->tensors_size()) { - status_ = errors::InvalidArgument( - "Unmatched number of keys and tensors: ", num_entries, " vs. ", - data_->tensors_size()); + status_ = + errors::InvalidArgument("Unmatched number of keys and tensors: ", + num_entries, " vs. ", data_->tensors_size()); return; } for (size_t i = 0; i < num_entries; i++) { -- GitLab From abf9518088797e505ead96aedebd4d71b7e03b4d Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Thu, 17 Jan 2019 14:02:43 -0800 Subject: [PATCH 0900/2345] Disable InterpreterTest.java for now and fix missing include. PiperOrigin-RevId: 229813499 --- tensorflow/lite/java/BUILD | 3 +++ tensorflow/lite/models/smartreply/ops/predict.cc | 1 + 2 files changed, 4 insertions(+) diff --git a/tensorflow/lite/java/BUILD b/tensorflow/lite/java/BUILD index bd9d8f4806..a539a0cf77 100644 --- a/tensorflow/lite/java/BUILD +++ b/tensorflow/lite/java/BUILD @@ -145,6 +145,9 @@ java_test( "//tensorflow/lite:testdata/multi_add_flex.bin", ], javacopts = JAVACOPTS, + tags = [ + "no_mac", # TODO(b/122888913): libtensorflowlite_test_jni broke on mac. + ], test_class = "org.tensorflow.lite.InterpreterTest", visibility = ["//visibility:private"], deps = [ diff --git a/tensorflow/lite/models/smartreply/ops/predict.cc b/tensorflow/lite/models/smartreply/ops/predict.cc index bb2ed4a315..24b7d54897 100644 --- a/tensorflow/lite/models/smartreply/ops/predict.cc +++ b/tensorflow/lite/models/smartreply/ops/predict.cc @@ -28,6 +28,7 @@ limitations under the License. // #include +#include #include #include -- GitLab From f986acda003966de4efed0e2fe4854a2b6dc776f Mon Sep 17 00:00:00 2001 From: Andy Ly Date: Thu, 17 Jan 2019 14:05:09 -0800 Subject: [PATCH 0901/2345] [Grappler] Exclude Atan2 from IsUnaryElementWise. PiperOrigin-RevId: 229814062 --- tensorflow/core/grappler/op_types.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index e06580cebf..f7931c615c 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -713,7 +713,6 @@ bool IsUnaryElementWise(const NodeDef& node) { "Asin", "Asinh", "Atan", - "Atan2", "Atanh", "Ceil", "ComplexAbs", -- GitLab From 703ab9db70f61ab26d7379614149a07ef3e8db37 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Thu, 17 Jan 2019 14:22:45 -0800 Subject: [PATCH 0902/2345] Make tolerance more reasonable on _testOptimizersCompat... PiperOrigin-RevId: 229817236 --- tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py b/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py index 374ba60460..8069703b7a 100644 --- a/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py +++ b/tensorflow/python/keras/optimizer_v2/optimizer_v2_test.py @@ -533,8 +533,10 @@ class OptimizersCompatibilityTest(keras_parameterized.TestCase): hist_1 = model_v1.fit(x, y, batch_size=5, epochs=1, shuffle=False) hist_2 = model_v2.fit(x, y, batch_size=5, epochs=1, shuffle=False) - self.assertAllClose(model_v1.get_weights(), model_v2.get_weights()) - self.assertAllClose(hist_1.history['loss'], hist_2.history['loss']) + self.assertAllClose(model_v1.get_weights(), model_v2.get_weights(), + rtol=1e-5, atol=1e-5) + self.assertAllClose(hist_1.history['loss'], hist_2.history['loss'], + rtol=1e-5, atol=1e-5) def testAdadeltaCompatibility(self): opt_v1 = optimizers.Adadelta(lr=0.01) -- GitLab From 779f2f83fa07866ca34b6aff1a45ff5b09bfcfbd Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Thu, 17 Jan 2019 14:22:48 -0800 Subject: [PATCH 0903/2345] Minor fix to error message; NFC PiperOrigin-RevId: 229817246 --- tensorflow/core/graph/control_flow.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/graph/control_flow.cc b/tensorflow/core/graph/control_flow.cc index 8e1e56d29b..66237a3497 100644 --- a/tensorflow/core/graph/control_flow.cc +++ b/tensorflow/core/graph/control_flow.cc @@ -59,7 +59,7 @@ Status ValidateControlFlowInfo(const Graph* graph, "Invalid loop structure: Mismatched parent frames for \"", cf.frame_name, "\": \"", parent->name, "\" vs \"", frame.parent->name, "\". The node giving this error: ", FormatNodeForError(*node), - "This is an internal bug, please file a bug report with " + ". This is an internal bug, please file a bug report with " "instructions on how to reproduce the error."); } if (IsLoopCond(node)) { -- GitLab From 22bbbb2280f1ce9a04ab67cdb1f63d85da6f33b3 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Thu, 17 Jan 2019 14:28:59 -0800 Subject: [PATCH 0904/2345] Make tolerance larger for cudnn comparison PiperOrigin-RevId: 229818348 --- .../contrib/cudnn_rnn/python/kernel_tests/cudnn_rnn_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/cudnn_rnn/python/kernel_tests/cudnn_rnn_test.py b/tensorflow/contrib/cudnn_rnn/python/kernel_tests/cudnn_rnn_test.py index 7e1b4062ce..ca92c31236 100644 --- a/tensorflow/contrib/cudnn_rnn/python/kernel_tests/cudnn_rnn_test.py +++ b/tensorflow/contrib/cudnn_rnn/python/kernel_tests/cudnn_rnn_test.py @@ -1023,7 +1023,7 @@ class CudnnRNNTestCompatibleRNNCells(test_util.TensorFlowTestCase): outputs_v, output_state_v = sess.run( [outputs, output_state], feed_dict={cell_inputs: inference_input}) - self.assertAllClose(cudnn_outputs_v, outputs_v, atol=2e-5, rtol=2e-5) + self.assertAllClose(cudnn_outputs_v, outputs_v, atol=1e-4, rtol=2e-4) (cudnn_output_h_v,) = cudnn_output_states_v self.assertAllClose(cudnn_output_h_v, output_state_v, atol=2e-5, rtol=2e-5) -- GitLab From 98984347b66d0642d042841e47e571cdd2191104 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Thu, 17 Jan 2019 14:39:06 -0800 Subject: [PATCH 0905/2345] Disable broken tests for 1.13 PiperOrigin-RevId: 229820397 --- tensorflow/lite/build_def.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/lite/build_def.bzl b/tensorflow/lite/build_def.bzl index 7e70503386..5e6b13a46a 100644 --- a/tensorflow/lite/build_def.bzl +++ b/tensorflow/lite/build_def.bzl @@ -327,6 +327,7 @@ def generated_test_models_failing(conversion_mode): if conversion_mode == "toco-flex": return [ "lstm", # TODO(b/117510976): Restore when lstm flex conversion works. + "unroll_batch_matmul", # TODO(b/123030774): Fails in 1.13 tests. ] return [] -- GitLab From ee0cc9c80dfbc52ce63dea5e9bd9d789bdbb4893 Mon Sep 17 00:00:00 2001 From: Wenhao Jia Date: Thu, 17 Jan 2019 14:42:11 -0800 Subject: [PATCH 0906/2345] Internal change PiperOrigin-RevId: 229820977 --- tensorflow/compiler/jit/xla_device.cc | 78 +++++++--------------- tensorflow/compiler/jit/xla_device.h | 38 ++--------- tensorflow/core/common_runtime/device.h | 19 +++++- tensorflow/core/common_runtime/executor.cc | 54 ++++++++++++++- tensorflow/core/framework/op_kernel.h | 22 ++++++ 5 files changed, 120 insertions(+), 91 deletions(-) diff --git a/tensorflow/compiler/jit/xla_device.cc b/tensorflow/compiler/jit/xla_device.cc index 77cd2f4462..e2397f6fcb 100644 --- a/tensorflow/compiler/jit/xla_device.cc +++ b/tensorflow/compiler/jit/xla_device.cc @@ -219,9 +219,6 @@ XlaDevice::XlaDevice(const SessionOptions& session_options, XlaDevice::~XlaDevice() { VLOG(1) << "Destroying XLA device " << jit_device_name_ << " " << this; mutex_lock lock(mu_); - while (outstanding_asynchronous_operations_ > 0) { - outstanding_asynchronous_operations_cv_.wait(lock); - } if (device_context_) { device_context_->Unref(); } @@ -398,12 +395,6 @@ Status XlaDevice::Sync() { if (!stream) return Status::OK(); Status status = stream->BlockHostUntilDone(); - { - mutex_lock lock(mu_); - while (outstanding_asynchronous_operations_ > 0) { - outstanding_asynchronous_operations_cv_.wait(lock); - } - } TF_RETURN_IF_ERROR(status); if (!stream->ok()) { return errors::Internal("XlaDevice::Sync() failed."); @@ -412,6 +403,8 @@ Status XlaDevice::Sync() { return Status::OK(); } +// TODO(b/112409994): This is no longer necessary. Consolidate it with the +// synchronous version. void XlaDevice::Sync(const DoneCallback& done) { VLOG(1) << "XlaDevice::Sync (asynchronous)"; std::shared_ptr stream; @@ -424,14 +417,20 @@ void XlaDevice::Sync(const DoneCallback& done) { return; } + // The call to ThenEnqueueOnBackgroundThread below enqueues a host callback at + // the end of the stream, after everything that has already been enqueued + // there at this moment. When the host callback is called, everything before + // it must have already finished, and the host callback will then place the + // task below onto a background thread. (See the implementation of + // ThenEnqueueOnBackgroundThread for details.) Therefore, when the done + // callback is finally called from that background thread, we know for sure + // that everything enqueued onto the stream (i.e., the device) at this very + // moment--when ThenEnqueueOnBackgroundThread is called--will have finished. + // This achieves a device-wide sync. stream->ThenEnqueueOnBackgroundThread( [this, stream, done](se::StreamExecutor*) { tracing::ScopedActivity activity("XlaDevice::Sync::Callback", /*is_expensive=*/true); - mutex_lock lock(mu_); - while (outstanding_asynchronous_operations_ > 0) { - outstanding_asynchronous_operations_cv_.wait(lock); - } done(stream->ok() ? Status::OK() : errors::Internal("XlaDevice::Sync() failed.")); }); @@ -470,57 +469,26 @@ Status XlaDevice::MakeTensorFromProto(const TensorProto& tensor_proto, return status; } -void XlaDevice::SetRequiresSyncOnCompletion(bool sync_on_completion) { +void XlaDevice::SetAllowsSyncOnCompletion(bool sync_on_completion) { mutex_lock lock(mu_); sync_on_completion_ = sync_on_completion; } -bool XlaDevice::RequiresSyncOnCompletion() const { +bool XlaDevice::AllowsSyncOnCompletion() const { mutex_lock lock(mu_); return sync_on_completion_; } -XlaDevice::AsynchronousOperationHandle::AsynchronousOperationHandle( - XlaDevice* device) - : device_(device) { - mutex_lock lock(device_->mu_); - ++device_->outstanding_asynchronous_operations_; -} - -XlaDevice::AsynchronousOperationHandle::~AsynchronousOperationHandle() { - if (device_) { - mutex_lock lock(device_->mu_); - --device_->outstanding_asynchronous_operations_; - device_->outstanding_asynchronous_operations_cv_.notify_all(); +Status XlaDevice::CurrentStatus() { + std::shared_ptr stream; + { + mutex_lock lock(mu_); + stream = stream_; } -} - -XlaDevice::AsynchronousOperationHandle::AsynchronousOperationHandle( - const XlaDevice::AsynchronousOperationHandle& other) - : device_(other.device_) { - mutex_lock lock(device_->mu_); - ++device_->outstanding_asynchronous_operations_; -} - -XlaDevice::AsynchronousOperationHandle::AsynchronousOperationHandle( - XlaDevice::AsynchronousOperationHandle&& other) - : device_(other.device_) { - other.device_ = nullptr; -} - -XlaDevice::AsynchronousOperationHandle& XlaDevice::AsynchronousOperationHandle:: -operator=(const XlaDevice::AsynchronousOperationHandle& other) { - device_ = other.device_; - mutex_lock lock(device_->mu_); - ++device_->outstanding_asynchronous_operations_; - return *this; -} - -XlaDevice::AsynchronousOperationHandle& XlaDevice::AsynchronousOperationHandle:: -operator=(XlaDevice::AsynchronousOperationHandle&& other) { - device_ = other.device_; - other.device_ = nullptr; - return *this; + if (!stream) { + return Status::OK(); + } + return stream->ok() ? Status::OK() : errors::Internal("XlaDevice is not OK."); } XlaDeviceOpRegistrations* RegisterXlaDeviceKernels(const char* device, diff --git a/tensorflow/compiler/jit/xla_device.h b/tensorflow/compiler/jit/xla_device.h index 45f18ac9ee..e35a1c7d29 100644 --- a/tensorflow/compiler/jit/xla_device.h +++ b/tensorflow/compiler/jit/xla_device.h @@ -167,35 +167,14 @@ class XlaDevice : public LocalDevice { Status UseGpuDeviceInfo() LOCKS_EXCLUDED(mu_); // Instructs this XlaDevice to return 'sync_on_completion' for - // RequiresSyncOnCompletion(). - void SetRequiresSyncOnCompletion(bool sync_on_completion) LOCKS_EXCLUDED(mu_); + // AllowsSyncOnCompletion(). + void SetAllowsSyncOnCompletion(bool sync_on_completion) LOCKS_EXCLUDED(mu_); - bool RequiresSyncOnCompletion() const override LOCKS_EXCLUDED(mu_); + bool AllowsSyncOnCompletion() const override LOCKS_EXCLUDED(mu_); - // A simple RAII handle. On construction the device's - // outstanding_asynchronous_operations_ field is incremented; on destruction - // it is decremented. - class AsynchronousOperationHandle { - public: - AsynchronousOperationHandle(XlaDevice* device); - ~AsynchronousOperationHandle(); - AsynchronousOperationHandle(const AsynchronousOperationHandle& other); - AsynchronousOperationHandle(AsynchronousOperationHandle&& other); - AsynchronousOperationHandle& operator=( - const AsynchronousOperationHandle& other); - AsynchronousOperationHandle& operator=(AsynchronousOperationHandle&& other); - - private: - XlaDevice* device_ = nullptr; - }; - - AsynchronousOperationHandle CreateAsynchronousOperationHandle() { - return AsynchronousOperationHandle(this); - } + Status CurrentStatus() override LOCKS_EXCLUDED(mu_); private: - friend class AsynchronousOperationHandle; - xla::LocalClient* client() const; Allocator* GetAllocatorLocked(AllocatorAttributes attr) EXCLUSIVE_LOCKS_REQUIRED(mu_); @@ -255,14 +234,9 @@ class XlaDevice : public LocalDevice { // Thread pool used for running closures std::unique_ptr thread_pool_; - // True if the device requires XlaDevice::Sync to be called on completion + // True if the device allows XlaDevice::Sync to be called on completion // regardless of status. - bool sync_on_completion_ GUARDED_BY(mu_) = false; - - // Count of outstanding asynchronous operations which must be zero on Sync() - // completion. - int64 outstanding_asynchronous_operations_ GUARDED_BY(mu_) = 0; - condition_variable outstanding_asynchronous_operations_cv_; + bool sync_on_completion_ GUARDED_BY(mu_) = true; // Set of devices to use. This controls which of the devices on the given // platform will have resources allocated. For GPUs this will be diff --git a/tensorflow/core/common_runtime/device.h b/tensorflow/core/common_runtime/device.h index 8dfbb21eda..5a0ef28ff2 100644 --- a/tensorflow/core/common_runtime/device.h +++ b/tensorflow/core/common_runtime/device.h @@ -44,6 +44,7 @@ limitations under the License. #include "tensorflow/core/framework/types.h" #include "tensorflow/core/graph/graph.h" #include "tensorflow/core/graph/types.h" +#include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/macros.h" #include "tensorflow/core/platform/types.h" @@ -122,9 +123,21 @@ class Device : public DeviceBase { // version. virtual void Sync(const DoneCallback& done); - // Override this to return true for devices that require a Sync() call before - // session completion. - virtual bool RequiresSyncOnCompletion() const { return false; } + // On session completion, the executor may call Device::Sync() depending on + // flag settings. Override this to return false for devices that don't allow + // such calls. Instead, these devices must use other mechanisms (such as + // num_deferred_ops) to ensure the device has finished processing necessary + // work at session completion. + // + // Devices that override this function must also implement CurrentStatus. + virtual bool AllowsSyncOnCompletion() const { return true; } + + // This is used in conjunction with AllowsSyncOnCompletion to allow the + // executor to get execution result status at session completion. + virtual Status CurrentStatus() { + return errors::Unimplemented( + "CurrentStatus is not supported on this device."); + } // Optionally modify the device's GraphDef before execution. // diff --git a/tensorflow/core/common_runtime/executor.cc b/tensorflow/core/common_runtime/executor.cc index 07c8c4a5d4..d068bbf1e4 100644 --- a/tensorflow/core/common_runtime/executor.cc +++ b/tensorflow/core/common_runtime/executor.cc @@ -1276,6 +1276,11 @@ class ExecutorState { std::atomic_int_fast32_t num_outstanding_ops_; + // Available via OpKernelContext to every OpKernel invocation. + mutex num_deferred_ops_mu_; + condition_variable num_deferred_ops_cv_; + int64 num_deferred_ops_ GUARDED_BY(num_deferred_ops_mu_) = 0; + mutex mu_; Status status_ GUARDED_BY(mu_); @@ -1631,6 +1636,15 @@ void ExecutorState::Process(TaggedNode tagged_node, int64 scheduled_nsec) { params.input_alloc_attrs = &input_alloc_attrs; params.runner = &runner_; params.stats_collector = stats_collector_; + 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_--; + num_deferred_ops_cv_.notify_all(); + }; Status s; NodeExecStatsInterface* stats = nullptr; @@ -2416,7 +2430,45 @@ void ExecutorState::Finish() { CHECK(done_cb != nullptr); Device* device = impl_->params_.device; - if ((sync_on_finish_ && status.ok()) || device->RequiresSyncOnCompletion()) { + // There are several potential race conditions below. To name a few: + // 1. Even if the device's status is OK at the precise moment when + // num_deferred_ops_ reaches 0, it could go bad before device->CurrentStatus() + // is called below, caused by work enqueued onto the same device by other + // concurrent ExecutorState objects. + // 2. Some implementations of Device::CurrentStatus, such as + // XlaDevice::CurrentStatus, may be inherently racy because it releases the + // device mutex after a stream pointer is acquired and before the stream is + // queried for status. + // 3. It's the same for some implementations of Device::Sync, such as + // XlaDevice::Sync. + // + // However, these race conditions are acceptable because a stream (and + // therefore an XlaDevice) can only go from OK to not-OK, never the opposite, + // which means we will at worst report errors when there isn't any, never the + // opposite. + + // If inc_num_deferred_ops_function has ever been called, ExecutorState must + // wait for all corresponding dec_num_deferred_ops_function calls to happen + // regardless of status. This ensures that dec_num_deferred_ops_function can + // safely use ExecutorState's resources. + { + mutex_lock lock(num_deferred_ops_mu_); + while (num_deferred_ops_ > 0) { + num_deferred_ops_cv_.wait(lock); + } + } + + // An early exit for devices don't allow sync on completion. Ops that run on + // these devices should have used num_deferred_ops correctly to ensure the + // device has finished all relevant work at this point. + if (!device->AllowsSyncOnCompletion()) { + status.Update(device->CurrentStatus()); + delete this; + runner([=]() { done_cb(status); }); + return; + } + + if (sync_on_finish_ && status.ok()) { // Block until the device has finished all queued operations. For // devices like GPUs that continue to execute Ops after their Compute // methods have completed, this ensures that control is not returned to diff --git a/tensorflow/core/framework/op_kernel.h b/tensorflow/core/framework/op_kernel.h index aa07cbd380..06b90964ad 100644 --- a/tensorflow/core/framework/op_kernel.h +++ b/tensorflow/core/framework/op_kernel.h @@ -646,6 +646,10 @@ class OpKernelContext { static const int kNoReservation = -1; // Values in [0,...) represent reservations for the indexed output. const int* forward_from_array = nullptr; + + // For tracking actively running deferred ops. + std::function inc_num_deferred_ops_function = []() {}; + std::function dec_num_deferred_ops_function = []() {}; }; // params must outlive the OpKernelContext. @@ -1172,6 +1176,24 @@ class OpKernelContext { bool input_is_ref(int index) const; + // Used by OpKernel implementations to track actively running deferred ops. + // + // A deferred op is one whose Compute method returns (or whose ComputeAsync + // method invokes the callback) when work is scheduled onto a device. At that + // point, we don't know when the work will actually complete (or if it has + // already completed) on the device. These functions allow the executor to + // track the status of deferred ops and act accordingly. + // + // Deferred OpKernel implementations must use these methods to get two + // functions. It then must call these two functions in pairs, before and after + // device execution, respectively. + TF_MUST_USE_RESULT std::function inc_num_deferred_ops_function() { + return params_->inc_num_deferred_ops_function; + } + TF_MUST_USE_RESULT std::function dec_num_deferred_ops_function() { + return params_->dec_num_deferred_ops_function; + } + private: Allocator* get_allocator(AllocatorAttributes attr); -- GitLab From b797012b39f89c45f13336b36c6b73e9ad93d815 Mon Sep 17 00:00:00 2001 From: Scott Zhu Date: Thu, 17 Jan 2019 14:46:46 -0800 Subject: [PATCH 0907/2345] Add AttentionMechanismV2 that follows Keras API style. 1. Adding new base class for AttentionMechanism that use keras.layer as parent. 2. All the new AttentionMechanismV2 will accept tensors from call() instead of as __init__ parameters. 3. memory_length has been update to memory_mask in v2 which is more align with the keras API. 4. _luong_score() and _bahdanau_score() has been updated to not create variable by itself. All the variable are now passed in as parameters. 5. All the existing unit tests are still passing. I haven't add new unit test for v2 class yet, will add them once I update the AttentionWrapper to v2. PiperOrigin-RevId: 229821776 --- .../seq2seq/python/ops/attention_wrapper.py | 962 ++++++++++++++++-- 1 file changed, 851 insertions(+), 111 deletions(-) diff --git a/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py b/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py index 60ec3efffe..31c62d5849 100644 --- a/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py +++ b/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py @@ -28,6 +28,7 @@ from tensorflow.contrib.framework.python.framework import tensor_util from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape +from tensorflow.python.keras.engine.base_layer import Layer from tensorflow.python.layers import base as layers_base from tensorflow.python.layers import core as layers_core from tensorflow.python.ops import array_ops @@ -72,77 +73,6 @@ class AttentionMechanism(object): raise NotImplementedError -def _prepare_memory(memory, memory_sequence_length, check_inner_dims_defined): - """Convert to tensor and possibly mask `memory`. - - Args: - memory: `Tensor`, shaped `[batch_size, max_time, ...]`. - memory_sequence_length: `int32` `Tensor`, shaped `[batch_size]`. - check_inner_dims_defined: Python boolean. If `True`, the `memory` - argument's shape is checked to ensure all but the two outermost - dimensions are fully defined. - - Returns: - A (possibly masked), checked, new `memory`. - - Raises: - ValueError: If `check_inner_dims_defined` is `True` and not - `memory.shape[2:].is_fully_defined()`. - """ - memory = nest.map_structure( - lambda m: ops.convert_to_tensor(m, name="memory"), memory) - if memory_sequence_length is not None: - memory_sequence_length = ops.convert_to_tensor( - memory_sequence_length, name="memory_sequence_length") - if check_inner_dims_defined: - def _check_dims(m): - if not m.get_shape()[2:].is_fully_defined(): - raise ValueError("Expected memory %s to have fully defined inner dims, " - "but saw shape: %s" % (m.name, m.get_shape())) - nest.map_structure(_check_dims, memory) - if memory_sequence_length is None: - seq_len_mask = None - else: - seq_len_mask = array_ops.sequence_mask( - memory_sequence_length, - maxlen=array_ops.shape(nest.flatten(memory)[0])[1], - dtype=nest.flatten(memory)[0].dtype) - seq_len_batch_size = ( - tensor_shape.dimension_value(memory_sequence_length.shape[0]) - or array_ops.shape(memory_sequence_length)[0]) - def _maybe_mask(m, seq_len_mask): - rank = m.get_shape().ndims - rank = rank if rank is not None else array_ops.rank(m) - extra_ones = array_ops.ones(rank - 2, dtype=dtypes.int32) - m_batch_size = tensor_shape.dimension_value( - m.shape[0]) or array_ops.shape(m)[0] - if memory_sequence_length is not None: - message = ("memory_sequence_length and memory tensor batch sizes do not " - "match.") - with ops.control_dependencies([ - check_ops.assert_equal( - seq_len_batch_size, m_batch_size, message=message)]): - seq_len_mask = array_ops.reshape( - seq_len_mask, - array_ops.concat((array_ops.shape(seq_len_mask), extra_ones), 0)) - return m * seq_len_mask - else: - return m - return nest.map_structure(lambda m: _maybe_mask(m, seq_len_mask), memory) - - -def _maybe_mask_score(score, memory_sequence_length, score_mask_value): - if memory_sequence_length is None: - return score - message = ("All values in memory_sequence_length must greater than zero.") - with ops.control_dependencies( - [check_ops.assert_positive(memory_sequence_length, message=message)]): - score_mask = array_ops.sequence_mask( - memory_sequence_length, maxlen=array_ops.shape(score)[1]) - score_mask_values = score_mask_value * array_ops.ones_like(score) - return array_ops.where(score_mask, score, score_mask_values) - - class _BaseAttentionMechanism(AttentionMechanism): """A base AttentionMechanism class providing common functionality. @@ -205,12 +135,14 @@ class _BaseAttentionMechanism(AttentionMechanism): self._memory_layer.dtype).as_numpy_dtype(-np.inf) self._probability_fn = lambda score, prev: ( # pylint:disable=g-long-lambda probability_fn( - _maybe_mask_score(score, memory_sequence_length, score_mask_value), + _maybe_mask_score(score, + memory_sequence_length=memory_sequence_length, + score_mask_value=score_mask_value), prev)) with ops.name_scope( name, "BaseAttentionMechanismInit", nest.flatten(memory)): self._values = _prepare_memory( - memory, memory_sequence_length, + memory, memory_sequence_length=memory_sequence_length, check_inner_dims_defined=check_inner_dims_defined) self._keys = ( self.memory_layer(self._values) if self.memory_layer # pylint: disable=not-callable @@ -286,6 +218,199 @@ class _BaseAttentionMechanism(AttentionMechanism): return self.initial_alignments(batch_size, dtype) +class _BaseAttentionMechanismV2(AttentionMechanism, Layer): + """A base AttentionMechanism class providing common functionality. + + Common functionality includes: + 1. Storing the query and memory layers. + 2. Preprocessing and storing the memory. + + Note that this layer only support Keras functional API since it takes multiple + input tensors, which is not available in sequential model. + """ + + def __init__(self, + probability_fn, + query_layer=None, + memory_layer=None, + **kwargs): + """Construct base AttentionMechanism class. + + Args: + probability_fn: A `callable`. Converts the score and previous alignments + to probabilities. Its signature should be: + `probabilities = probability_fn(score, state)`. + query_layer: (optional): Instance of `tf.keras.Layer`. The layer's depth + must match the depth of `memory_layer`. If `query_layer` is not + provided, the shape of `query` must match that of `memory_layer`. + memory_layer: (optional): Instance of `tf.keras.Layer`. The layer's + depth must match the depth of `query_layer`. + If `memory_layer` is not provided, the shape of `memory` must match + that of `query_layer`. + **kwargs: Dictionary that contains other common arguments for layer + creation. + """ + if (query_layer is not None + and not isinstance(query_layer, layers_base.Layer)): + raise TypeError( + "query_layer is not a Layer: %s" % type(query_layer).__name__) + if (memory_layer is not None + and not isinstance(memory_layer, layers_base.Layer)): + raise TypeError( + "memory_layer is not a Layer: %s" % type(memory_layer).__name__) + self.query_layer = query_layer + self.memory_layer = memory_layer + if self.memory_layer is not None and "dtype" not in kwargs: + kwargs["dtype"] = self.memory_layer.dtype + super(_BaseAttentionMechanismV2, self).__init__(**kwargs) + if not callable(probability_fn): + raise TypeError("probability_fn must be callable, saw type: %s" % + type(probability_fn).__name__) + self.probability_fn = probability_fn + + self.keys = None + self.values = None + self.batch_size = None + self._memory_initialized = False + self._check_inner_dims_defined = True + + def build(self, input_shape): + if self.query_layer is not None: + self.query_layer.build(input_shape) + if self.memory_layer is not None: + self.memory_layer.build(input_shape) + # dtype of the layer is known at this moment, create the score_mask_value if + # needed. + self.score_mask_value = dtypes.as_dtype(self.dtype).as_numpy_dtype(-np.inf) + self.built = True + + def _setup_memory(self, memory, memory_mask=None): + """Pre-process the memory before actually query the memory. + + This should only be called once at the first invocation of call(). + + Args: + memory: The memory to query; usually the output of an RNN encoder. This + tensor should be shaped `[batch_size, max_time, ...]`. + memory_mask: The boolean tensor with shape `[batch_size, max_time]`. For + any value equal to False, the corresponding value in memory should be + ignored. + """ + if self._memory_initialized: + raise ValueError("The memory for the attention has already been setup.") + with ops.name_scope( + self.name, "BaseAttentionMechanismInit", nest.flatten(memory)): + self.values = _prepare_memory( + memory, memory_mask=memory_mask, + check_inner_dims_defined=self._check_inner_dims_defined) + if self.memory_layer is not None: + self.keys = self.memory_layer(self.values) + else: + self.keys = self.values + self.batch_size = ( + tensor_shape.dimension_value(self.keys.shape[0]) or + array_ops.shape(self.keys)[0]) + self.alignments_size = (tensor_shape.dimension_value(self.keys.shape[1]) + or array_ops.shape(self.keys)[1]) + if memory_mask is not None: + self.probability_fn = lambda score, prev: ( # pylint:disable=g-long-lambda + self.probability_fn(_maybe_mask_score( + score, self.score_mask_value, memory_mask=memory_mask), prev)) + self._memory_initialized = True + + def call(self, inputs, mask=None, **kwargs): + """Base method to calculate the attention score. + + Args: + inputs: a list of tensor that contains `query`, `state`, and `memory`. + `query` is the tensor of dtype matching `memory` and shape + `[batch_size, query_depth]`. + `state` is the tensor of dtype matching `memory` and shape + `[batch_size, alignments_size]`. (`alignments_size` is memory's + `max_time`). + `memory` is the memory to query; usually the output of an RNN encoder. + This tensor should be shaped `[batch_size, max_time, feature]`. + mask: optional bool tensor with shape `[batch, max_time]` for the mask of + memory. If it is not None, the corresponding item of the memory should + be filtered out during calculation. + **kwargs: Dict, other keyword arguments for the call method. + """ + query, state, memory, memory_mask = self._process_inputs(inputs, mask) + if not self._memory_initialized: + self._setup_memory(memory, memory_mask=memory_mask) + return self.calculate_attention(query, state) + + def calculate_attention(self, query, state): + raise NotImplementedError( + "calculate_attention need to be implemented by subclasses.") + + def get_config(self): + config = {} + # Since the probability_fn is likely to be a wrapped function, the child + # class should preserve the original function and how its wrapped. + + if self.query_layer is not None: + config["query_layer"] = { + "class_name": self.query_layer.__class__.__name__, + "config": self.query_layer.get_config(), + } + if self.memory_layer is not None: + config["memory_layer"] = { + "class_name": self.memory_layer.__class__.__name__, + "config": self.memory_layer.get_config(), + } + base_config = super(_BaseAttentionMechanismV2, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + def _process_inputs(self, inputs, mask): + if len(inputs) != 3: + raise ValueError( + "Expect to have 3 inputs for attention, got %d" % len(inputs)) + query, state, memory = inputs + return query, state, memory, mask + + def _process_probability_fn(self, func_name): + """Helper method to retrieve the probably function by string input.""" + valid_probability_fns = { + "softmax": nn_ops.softmax, + "hardmax": hardmax, + } + if func_name not in valid_probability_fns.keys(): + raise ValueError("Invalid probability function: %s, options are %s" % + (func_name, valid_probability_fns.keys())) + return valid_probability_fns[func_name] + + @classmethod + def deserialize_inner_layer_from_config(cls, config, custom_objects): + """Helper method that reconstruct the query and memory from the config. + + In the get_config() method, the query and memory layer configs are + serialized into dict for persistence, this method perform the reverse action + to reconstruct the layer from the config. + + Args: + config: dict, the configs that will be used to reconstruct the object. + custom_objects: dict mapping class names (or function names) of custom + (non-Keras) objects to class/functions. + Returns: + config: dict, the config with layer instance created, which is ready to be + used as init parameters. + """ + # Reconstruct the query and memory layer for parent class. + from tensorflow.python.keras.layers import deserialize as deserialize_layer # pylint: disable=g-import-not-at-top + query_layer_config = config.pop("query_layer", None) + if query_layer_config: + query_layer = deserialize_layer(query_layer_config, + custom_objects=custom_objects) + config["query_layer"] = query_layer + memory_layer_config = config.pop("memory_layer", None) + if memory_layer_config: + memory_layer = deserialize_layer(memory_layer_config, + custom_objects=custom_objects) + config["memory_layer"] = memory_layer + return config + + def _luong_score(query, keys, scale): """Implements Luong-style (multiplicative) scoring function. @@ -304,7 +429,7 @@ def _luong_score(query, keys, scale): Args: query: Tensor, shape `[batch_size, num_units]` to compare to keys. keys: Processed memory, shape `[batch_size, max_time, num_units]`. - scale: Whether to apply a scale to the score function. + scale: the optional tensor to scale the attention score. Returns: A `[batch_size, max_time]` tensor of unnormalized score values. @@ -320,7 +445,6 @@ def _luong_score(query, keys, scale): "Query (%s) has units: %s. Keys (%s) have units: %s. " "Perhaps you need to set num_units to the keys' dimension (%s)?" % (query, depth, keys, key_units, key_units)) - dtype = query.dtype # Reshape from [batch_size, depth] to [batch_size, 1, depth] # for matmul. @@ -338,12 +462,8 @@ def _luong_score(query, keys, scale): score = math_ops.matmul(query, keys, transpose_b=True) score = array_ops.squeeze(score, [1]) - if scale: - # Scalar used in weight scaling - g = variable_scope.get_variable( - "attention_g", dtype=dtype, - initializer=init_ops.ones_initializer, shape=()) - score = g * score + if scale is not None: + score = scale * score return score @@ -354,8 +474,8 @@ class LuongAttention(_BaseAttentionMechanism): as described in: Minh-Thang Luong, Hieu Pham, Christopher D. Manning. - "Effective Approaches to Attention-based Neural Machine Translation." - EMNLP 2015. https://arxiv.org/abs/1508.04025 + [Effective Approaches to Attention-based Neural Machine Translation. + EMNLP 2015.](https://arxiv.org/abs/1508.04025) The second is the scaled form inspired partly by the normalized form of Bahdanau attention. @@ -429,13 +549,121 @@ class LuongAttention(_BaseAttentionMechanism): `max_time`). """ with variable_scope.variable_scope(None, "luong_attention", [query]): - score = _luong_score(query, self._keys, self._scale) + attention_g = None + if self._scale: + attention_g = variable_scope.get_variable( + "attention_g", dtype=query.dtype, + initializer=init_ops.ones_initializer, shape=()) + score = _luong_score(query, self._keys, attention_g) alignments = self._probability_fn(score, state) next_state = alignments return alignments, next_state -def _bahdanau_score(processed_query, keys, normalize): +class LuongAttentionV2(_BaseAttentionMechanismV2): + """Implements Luong-style (multiplicative) attention scoring. + + This attention has two forms. The first is standard Luong attention, + as described in: + + Minh-Thang Luong, Hieu Pham, Christopher D. Manning. + [Effective Approaches to Attention-based Neural Machine Translation. + EMNLP 2015.](https://arxiv.org/abs/1508.04025) + + The second is the scaled form inspired partly by the normalized form of + Bahdanau attention. + + To enable the second form, construct the object with parameter + `scale=True`. + """ + + def __init__(self, + units, + scale=False, + probability_fn="softmax", + dtype=None, + name="LuongAttention", + **kwargs): + """Construct the AttentionMechanism mechanism. + + Args: + units: The depth of the attention mechanism. + scale: Python boolean. Whether to scale the energy term. + probability_fn: (optional) string, the name of function to convert the + attention score to probabilities. The default is `softmax` which is + `tf.nn.softmax`. Other options is `hardmax`, which is hardmax() within + this module. Any other value will result intovalidation error. Default + to use `softmax`. + dtype: The data type for the memory layer of the attention mechanism. + name: Name to use when creating ops. + **kwargs: Dictionary that contains other common arguments for layer + creation. + """ + # For LuongAttention, we only transform the memory layer; thus + # num_units **must** match expected the query depth. + self.probability_fn_name = probability_fn + probability_fn = self._process_probability_fn(self.probability_fn_name) + wrapped_probability_fn = lambda score, _: probability_fn(score) + if dtype is None: + dtype = dtypes.float32 + super(LuongAttentionV2, self).__init__( + query_layer=None, + memory_layer=layers_core.Dense( + units, name="memory_layer", use_bias=False, dtype=dtype), + probability_fn=wrapped_probability_fn, + name=name, + dtype=dtype, + **kwargs) + self.units = units + self.scale = scale + + def build(self, input_shape): + super(LuongAttentionV2, self).build(input_shape) + if self.scale: + self.scale_weight = self.add_weight( + "attention_g", initializer=init_ops.ones_initializer, shape=()) + else: + self.scale_weight = None + self.built = True + + def calculate_attention(self, query, state): + """Score the query based on the keys and values. + + Args: + query: Tensor of dtype matching `self.values` and shape + `[batch_size, query_depth]`. + state: Tensor of dtype matching `self.values` and shape + `[batch_size, alignments_size]` + (`alignments_size` is memory's `max_time`). + + Returns: + alignments: Tensor of dtype matching `self.values` and shape + `[batch_size, alignments_size]` (`alignments_size` is memory's + `max_time`). + """ + score = _luong_score(query, self.keys, self.scale_weight) + alignments = self.probability_fn(score, state) + next_state = alignments + return alignments, next_state + + def get_config(self): + config = { + "units": self.units, + "scale": self.scale, + "probability_fn": self.probability_fn_name, + } + base_config = super(LuongAttentionV2, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + @classmethod + def from_config(cls, config, custom_objects=None): + config = _BaseAttentionMechanismV2.deserialize_inner_layer_from_config( + config, custom_objects=custom_objects) + return cls(**config) + + +def _bahdanau_score(processed_query, keys, attention_v, + attention_g=None, attention_b=None): """Implements Bahdanau-style (additive) scoring function. This attention has two forms. The first is Bhandanau attention, @@ -453,41 +681,28 @@ def _bahdanau_score(processed_query, keys, normalize): Training of Deep Neural Networks." https://arxiv.org/abs/1602.07868 - To enable the second form, set `normalize=True`. + To enable the second form, set please pass in attention_g and attention_b. Args: processed_query: Tensor, shape `[batch_size, num_units]` to compare to keys. keys: Processed memory, shape `[batch_size, max_time, num_units]`. - normalize: Whether to normalize the score function. + attention_v: Tensor, shape `[num_units]`. + attention_g: Optional scalar tensor for normalization. + attention_b: Optional tensor with shape `[num_units]` for normalization. Returns: A `[batch_size, max_time]` tensor of unnormalized score values. """ - dtype = processed_query.dtype - # Get the number of hidden units from the trailing dimension of keys - num_units = tensor_shape.dimension_value( - keys.shape[2]) or array_ops.shape(keys)[2] # Reshape from [batch_size, ...] to [batch_size, 1, ...] for broadcasting. processed_query = array_ops.expand_dims(processed_query, 1) - v = variable_scope.get_variable( - "attention_v", [num_units], dtype=dtype) - if normalize: - # Scalar used in weight normalization - g = variable_scope.get_variable( - "attention_g", dtype=dtype, - initializer=init_ops.constant_initializer(math.sqrt((1. / num_units))), - shape=()) - # Bias added prior to the nonlinearity - b = variable_scope.get_variable( - "attention_b", [num_units], dtype=dtype, - initializer=init_ops.zeros_initializer()) - # normed_v = g * v / ||v|| - normed_v = g * v * math_ops.rsqrt( - math_ops.reduce_sum(math_ops.square(v))) + if attention_g is not None and attention_b is not None: + normed_v = attention_g * attention_v * math_ops.rsqrt( + math_ops.reduce_sum(math_ops.square(attention_v))) return math_ops.reduce_sum( - normed_v * math_ops.tanh(keys + processed_query + b), [2]) + normed_v * math_ops.tanh(keys + processed_query + attention_b), [2]) else: - return math_ops.reduce_sum(v * math_ops.tanh(keys + processed_query), [2]) + return math_ops.reduce_sum( + attention_v * math_ops.tanh(keys + processed_query), [2]) class BahdanauAttention(_BaseAttentionMechanism): @@ -578,12 +793,145 @@ class BahdanauAttention(_BaseAttentionMechanism): """ with variable_scope.variable_scope(None, "bahdanau_attention", [query]): processed_query = self.query_layer(query) if self.query_layer else query - score = _bahdanau_score(processed_query, self._keys, self._normalize) + attention_v = variable_scope.get_variable( + "attention_v", [self._num_units], dtype=query.dtype) + if not self._normalize: + attention_g = None + attention_b = None + else: + attention_g = variable_scope.get_variable( + "attention_g", dtype=query.dtype, + initializer=init_ops.constant_initializer( + math.sqrt((1. / self._num_units))), + shape=()) + attention_b = variable_scope.get_variable( + "attention_b", [self._num_units], dtype=query.dtype, + initializer=init_ops.zeros_initializer()) + + score = _bahdanau_score(processed_query, self._keys, attention_v, + attention_g=attention_g, attention_b=attention_b) alignments = self._probability_fn(score, state) next_state = alignments return alignments, next_state +class BahdanauAttentionV2(_BaseAttentionMechanismV2): + """Implements Bahdanau-style (additive) attention. + + This attention has two forms. The first is Bahdanau attention, + as described in: + + Dzmitry Bahdanau, Kyunghyun Cho, Yoshua Bengio. + "Neural Machine Translation by Jointly Learning to Align and Translate." + ICLR 2015. https://arxiv.org/abs/1409.0473 + + The second is the normalized form. This form is inspired by the + weight normalization article: + + Tim Salimans, Diederik P. Kingma. + "Weight Normalization: A Simple Reparameterization to Accelerate + Training of Deep Neural Networks." + https://arxiv.org/abs/1602.07868 + + To enable the second form, construct the object with parameter + `normalize=True`. + """ + + def __init__(self, + units, + normalize=False, + probability_fn="softmax", + dtype=None, + name="BahdanauAttention", + **kwargs): + """Construct the Attention mechanism. + + Args: + units: The depth of the query mechanism. + normalize: Python boolean. Whether to normalize the energy term. + probability_fn: (optional) string, the name of function to convert the + attention score to probabilities. The default is `softmax` which is + `tf.nn.softmax`. Other options is `hardmax`, which is hardmax() within + this module. Any other value will result into validation error. Default + to use `softmax`. + dtype: The data type for the query and memory layers of the attention + mechanism. + name: Name to use when creating ops. + **kwargs: Dictionary that contains other common arguments for layer + creation. + """ + self.probability_fn_name = probability_fn + probability_fn = self._process_probability_fn(self.probability_fn_name) + wrapped_probability_fn = lambda score, _: probability_fn(score) + if dtype is None: + dtype = dtypes.float32 + super(BahdanauAttentionV2, self).__init__( + query_layer=layers_core.Dense( + units, name="query_layer", use_bias=False, dtype=dtype), + memory_layer=layers_core.Dense( + units, name="memory_layer", use_bias=False, dtype=dtype), + probability_fn=wrapped_probability_fn, + name=name, + dtype=dtype, + **kwargs) + self.units = units + self.normalize = normalize + + def build(self, input_shape): + super(BahdanauAttentionV2, self).build(input_shape) + self.attention_v = self.add_weight( + "attention_v", [self.units], dtype=self.dtype) + if self.normalize: + self.attention_g = self.add_weight( + "attention_g", initializer=init_ops.constant_initializer( + math.sqrt((1. / self.units))), shape=()) + self.attention_b = self.add_weight( + "attention_b", shape=[self.units], + initializer=init_ops.zeros_initializer()) + else: + self.attention_g = None + self.attention_b = None + self.built = True + + def calculate_attention(self, query, state): + """Score the query based on the keys and values. + + Args: + query: Tensor of dtype matching `self.values` and shape + `[batch_size, query_depth]`. + state: Tensor of dtype matching `self.values` and shape + `[batch_size, alignments_size]` + (`alignments_size` is memory's `max_time`). + + Returns: + alignments: Tensor of dtype matching `self.values` and shape + `[batch_size, alignments_size]` (`alignments_size` is memory's + `max_time`). + """ + processed_query = self.query_layer(query) if self.query_layer else query + score = _bahdanau_score(processed_query, self.keys, self.attention_v, + attention_g=self.attention_g, + attention_b=self.attention_b) + alignments = self.probability_fn(score, state) + next_state = alignments + return alignments, next_state + + def get_config(self): + config = { + "units": self.units, + "normalize": self.normalize, + "probability_fn": self.probability_fn_name, + } + base_config = super(BahdanauAttentionV2, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + @classmethod + def from_config(cls, config, custom_objects=None): + config = _BaseAttentionMechanismV2.deserialize_inner_layer_from_config( + config, custom_objects=custom_objects) + return cls(**config) + + def safe_cumprod(x, *args, **kwargs): """Computes cumprod of x in logspace using cumsum to avoid underflow. @@ -766,6 +1114,34 @@ class _BaseMonotonicAttentionMechanism(_BaseAttentionMechanism): dtype=dtype) +class _BaseMonotonicAttentionMechanismV2(_BaseAttentionMechanismV2): + """Base attention mechanism for monotonic attention. + + Simply overrides the initial_alignments function to provide a dirac + distribution, which is needed in order for the monotonic attention + distributions to have the correct behavior. + """ + + def initial_alignments(self, batch_size, dtype): + """Creates the initial alignment values for the monotonic attentions. + + Initializes to dirac distributions, i.e. [1, 0, 0, ...memory length..., 0] + for all entries in the batch. + + Args: + batch_size: `int32` scalar, the batch_size. + dtype: The `dtype`. + + Returns: + A `dtype` tensor shaped `[batch_size, alignments_size]` + (`alignments_size` is the values' `max_time`). + """ + max_time = self._alignments_size + return array_ops.one_hot( + array_ops.zeros((batch_size,), dtype=dtypes.int32), max_time, + dtype=dtype) + + class BahdanauMonotonicAttention(_BaseMonotonicAttentionMechanism): """Monotonic attention mechanism with Bahadanau-style energy function. @@ -860,7 +1236,22 @@ class BahdanauMonotonicAttention(_BaseMonotonicAttentionMechanism): with variable_scope.variable_scope( None, "bahdanau_monotonic_attention", [query]): processed_query = self.query_layer(query) if self.query_layer else query - score = _bahdanau_score(processed_query, self._keys, self._normalize) + attention_v = variable_scope.get_variable( + "attention_v", [self._num_units], dtype=query.dtype) + if not self._normalize: + attention_g = None + attention_b = None + else: + attention_g = variable_scope.get_variable( + "attention_g", dtype=query.dtype, + initializer=init_ops.constant_initializer( + math.sqrt((1. / self._num_units))), + shape=()) + attention_b = variable_scope.get_variable( + "attention_b", [self._num_units], dtype=query.dtype, + initializer=init_ops.zeros_initializer()) + score = _bahdanau_score(processed_query, self._keys, attention_v, + attention_g=attention_g, attention_b=attention_b) score_bias = variable_scope.get_variable( "attention_score_bias", dtype=processed_query.dtype, initializer=self._score_bias_init) @@ -870,6 +1261,140 @@ class BahdanauMonotonicAttention(_BaseMonotonicAttentionMechanism): return alignments, next_state +class BahdanauMonotonicAttentionV2(_BaseMonotonicAttentionMechanismV2): + """Monotonic attention mechanism with Bahadanau-style energy function. + + This type of attention enforces a monotonic constraint on the attention + distributions; that is once the model attends to a given point in the memory + it can't attend to any prior points at subsequence output timesteps. It + achieves this by using the _monotonic_probability_fn instead of softmax to + construct its attention distributions. Since the attention scores are passed + through a sigmoid, a learnable scalar bias parameter is applied after the + score function and before the sigmoid. Otherwise, it is equivalent to + BahdanauAttention. This approach is proposed in + + Colin Raffel, Minh-Thang Luong, Peter J. Liu, Ron J. Weiss, Douglas Eck, + "Online and Linear-Time Attention by Enforcing Monotonic Alignments." + ICML 2017. https://arxiv.org/abs/1704.00784 + """ + + def __init__(self, + units, + normalize=False, + sigmoid_noise=0., + sigmoid_noise_seed=None, + score_bias_init=0., + mode="parallel", + dtype=None, + name="BahdanauMonotonicAttention", + **kwargs): + """Construct the Attention mechanism. + + Args: + units: The depth of the query mechanism. + normalize: Python boolean. Whether to normalize the energy term. + sigmoid_noise: Standard deviation of pre-sigmoid noise. See the docstring + for `_monotonic_probability_fn` for more information. + sigmoid_noise_seed: (optional) Random seed for pre-sigmoid noise. + score_bias_init: Initial value for score bias scalar. It's recommended to + initialize this to a negative value when the length of the memory is + large. + mode: How to compute the attention distribution. Must be one of + 'recursive', 'parallel', or 'hard'. See the docstring for + `tf.contrib.seq2seq.monotonic_attention` for more information. + dtype: The data type for the query and memory layers of the attention + mechanism. + name: Name to use when creating ops. + **kwargs: Dictionary that contains other common arguments for layer + creation. + """ + # Set up the monotonic probability fn with supplied parameters + if dtype is None: + dtype = dtypes.float32 + wrapped_probability_fn = functools.partial( + _monotonic_probability_fn, sigmoid_noise=sigmoid_noise, mode=mode, + seed=sigmoid_noise_seed) + super(BahdanauMonotonicAttentionV2, self).__init__( + query_layer=layers_core.Dense( + units, name="query_layer", use_bias=False, dtype=dtype), + memory_layer=layers_core.Dense( + units, name="memory_layer", use_bias=False, dtype=dtype), + probability_fn=wrapped_probability_fn, + name=name, + dtype=dtype, + **kwargs) + self.units = units + self.normalize = normalize + self.sigmoid_noise = sigmoid_noise + self.sigmoid_noise_seed = sigmoid_noise_seed + self.score_bias_init = score_bias_init + self.mode = mode + + def build(self, input_shape): + super(BahdanauMonotonicAttentionV2, self).build(input_shape) + self.attention_v = self.add_weight( + "attention_v", [self.units], dtype=self.dtype) + self.attention_score_bias = self.add_weight( + "attention_score_bias", shape=(), dtype=self.dtype, + initializer=init_ops.constant_initializer( + self.score_bias_init, dtype=self.dtype)) + if not self.normalize: + self.attention_g = None + self.attention_b = None + else: + self.attention_g = self.add_weight( + "attention_g", dtype=self.dtype, + initializer=init_ops.constant_initializer( + math.sqrt((1. / self.units))), + shape=()) + self.attention_b = self.add_weight( + "attention_b", [self.units], dtype=self.dtype, + initializer=init_ops.zeros_initializer()) + self.built = True + + def calculate_attention(self, query, state): + """Score the query based on the keys and values. + + Args: + query: Tensor of dtype matching `self.values` and shape + `[batch_size, query_depth]`. + state: Tensor of dtype matching `self.values` and shape + `[batch_size, alignments_size]` + (`alignments_size` is memory's `max_time`). + + Returns: + alignments: Tensor of dtype matching `self.values` and shape + `[batch_size, alignments_size]` (`alignments_size` is memory's + `max_time`). + """ + processed_query = self.query_layer(query) if self.query_layer else query + score = _bahdanau_score(processed_query, self.keys, self.attention_v, + attention_g=self.attention_g, + attention_b=self.attention_b) + score += self.attention_score_bias + alignments = self.probability_fn(score, state) + next_state = alignments + return alignments, next_state + + def get_config(self): + config = { + "units": self.units, + "normalize": self.normalize, + "sigmoid_noise": self.sigmoid_noise, + "sigmoid_noise_seed": self.sigmoid_noise_seed, + "score_bias_init": self.score_bias_init, + "mode": self.mode, + } + base_config = super(BahdanauMonotonicAttentionV2, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + @classmethod + def from_config(cls, config, custom_objects=None): + config = _BaseAttentionMechanismV2.deserialize_inner_layer_from_config( + config, custom_objects=custom_objects) + return cls(**config) + + class LuongMonotonicAttention(_BaseMonotonicAttentionMechanism): """Monotonic attention mechanism with Luong-style energy function. @@ -960,7 +1485,12 @@ class LuongMonotonicAttention(_BaseMonotonicAttentionMechanism): """ with variable_scope.variable_scope(None, "luong_monotonic_attention", [query]): - score = _luong_score(query, self._keys, self._scale) + attention_g = None + if self._scale: + attention_g = variable_scope.get_variable( + "attention_g", dtype=query.dtype, + initializer=init_ops.ones_initializer, shape=()) + score = _luong_score(query, self._keys, attention_g) score_bias = variable_scope.get_variable( "attention_score_bias", dtype=query.dtype, initializer=self._score_bias_init) @@ -970,6 +1500,125 @@ class LuongMonotonicAttention(_BaseMonotonicAttentionMechanism): return alignments, next_state +class LuongMonotonicAttentionV2(_BaseMonotonicAttentionMechanismV2): + """Monotonic attention mechanism with Luong-style energy function. + + This type of attention enforces a monotonic constraint on the attention + distributions; that is once the model attends to a given point in the memory + it can't attend to any prior points at subsequence output timesteps. It + achieves this by using the _monotonic_probability_fn instead of softmax to + construct its attention distributions. Otherwise, it is equivalent to + LuongAttention. This approach is proposed in + + [Colin Raffel, Minh-Thang Luong, Peter J. Liu, Ron J. Weiss, Douglas Eck, + "Online and Linear-Time Attention by Enforcing Monotonic Alignments." + ICML 2017.](https://arxiv.org/abs/1704.00784) + """ + + def __init__(self, + units, + scale=False, + sigmoid_noise=0., + sigmoid_noise_seed=None, + score_bias_init=0., + mode="parallel", + dtype=None, + name="LuongMonotonicAttention", + **kwargs): + """Construct the Attention mechanism. + + Args: + units: The depth of the query mechanism. + scale: Python boolean. Whether to scale the energy term. + sigmoid_noise: Standard deviation of pre-sigmoid noise. See the docstring + for `_monotonic_probability_fn` for more information. + sigmoid_noise_seed: (optional) Random seed for pre-sigmoid noise. + score_bias_init: Initial value for score bias scalar. It's recommended to + initialize this to a negative value when the length of the memory is + large. + mode: How to compute the attention distribution. Must be one of + 'recursive', 'parallel', or 'hard'. See the docstring for + `tf.contrib.seq2seq.monotonic_attention` for more information. + dtype: The data type for the query and memory layers of the attention + mechanism. + name: Name to use when creating ops. + **kwargs: Dictionary that contains other common arguments for layer + creation. + """ + # Set up the monotonic probability fn with supplied parameters + if dtype is None: + dtype = dtypes.float32 + wrapped_probability_fn = functools.partial( + _monotonic_probability_fn, sigmoid_noise=sigmoid_noise, mode=mode, + seed=sigmoid_noise_seed) + super(LuongMonotonicAttentionV2, self).__init__( + query_layer=None, + memory_layer=layers_core.Dense( + units, name="memory_layer", use_bias=False, dtype=dtype), + probability_fn=wrapped_probability_fn, + name=name, + dtype=dtype, + **kwargs) + self.units = units + self.scale = scale + self.sigmoid_noise = sigmoid_noise + self.sigmoid_noise_seed = sigmoid_noise_seed + self.score_bias_init = score_bias_init + self.mode = mode + + def build(self, input_shape): + super(LuongMonotonicAttentionV2, self).build(input_shape) + if self.scale: + self.attention_g = self.add_weight( + "attention_g", initializer=init_ops.ones_initializer, shape=()) + else: + self.attention_g = None + self.attention_score_bias = self.add_weight( + "attention_score_bias", shape=(), + initializer=init_ops.constant_initializer( + self.score_bias_init, dtype=self.dtype)) + self.built = True + + def calculate_attention(self, query, state): + """Score the query based on the keys and values. + + Args: + query: Tensor of dtype matching `self.values` and shape + `[batch_size, query_depth]`. + state: Tensor of dtype matching `self.values` and shape + `[batch_size, alignments_size]` + (`alignments_size` is memory's `max_time`). + + Returns: + alignments: Tensor of dtype matching `self.values` and shape + `[batch_size, alignments_size]` (`alignments_size` is memory's + `max_time`). + """ + score = _luong_score(query, self.keys, self.attention_g) + score += self.attention_score_bias + alignments = self.probability_fn(score, state) + next_state = alignments + return alignments, next_state + + def get_config(self): + config = { + "units": self.units, + "normalize": self.normalize, + "sigmoid_noise": self.sigmoid_noise, + "sigmoid_noise_seed": self.sigmoid_noise_seed, + "score_bias_init": self.score_bias_init, + "mode": self.mode, + } + base_config = super(LuongMonotonicAttentionV2, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + @classmethod + def from_config(cls, config, custom_objects=None): + config = _BaseAttentionMechanismV2.deserialize_inner_layer_from_config( + config, custom_objects=custom_objects) + return cls(**config) + + class AttentionWrapperState( collections.namedtuple("AttentionWrapperState", ("cell_state", "attention", "time", "alignments", @@ -1026,6 +1675,97 @@ class AttentionWrapperState( super(AttentionWrapperState, self)._replace(**kwargs)) +def _prepare_memory(memory, memory_sequence_length=None, memory_mask=None, + check_inner_dims_defined=True): + """Convert to tensor and possibly mask `memory`. + + Args: + memory: `Tensor`, shaped `[batch_size, max_time, ...]`. + memory_sequence_length: `int32` `Tensor`, shaped `[batch_size]`. + memory_mask: `boolean` tensor with shape [batch_size, max_time]. The memory + should be skipped when the corresponding mask is False. + check_inner_dims_defined: Python boolean. If `True`, the `memory` + argument's shape is checked to ensure all but the two outermost + dimensions are fully defined. + + Returns: + A (possibly masked), checked, new `memory`. + + Raises: + ValueError: If `check_inner_dims_defined` is `True` and not + `memory.shape[2:].is_fully_defined()`. + """ + memory = nest.map_structure( + lambda m: ops.convert_to_tensor(m, name="memory"), memory) + if memory_sequence_length is not None and memory_mask is not None: + raise ValueError("memory_sequence_length and memory_mask can't be provided " + "at same time.") + if memory_sequence_length is not None: + memory_sequence_length = ops.convert_to_tensor( + memory_sequence_length, name="memory_sequence_length") + if check_inner_dims_defined: + def _check_dims(m): + if not m.get_shape()[2:].is_fully_defined(): + raise ValueError("Expected memory %s to have fully defined inner dims, " + "but saw shape: %s" % (m.name, m.get_shape())) + nest.map_structure(_check_dims, memory) + if memory_sequence_length is None and memory_mask is None: + seq_len_mask = None + seq_len_batch_size = None + elif memory_sequence_length is not None: + seq_len_mask = array_ops.sequence_mask( + memory_sequence_length, + maxlen=array_ops.shape(nest.flatten(memory)[0])[1], + dtype=nest.flatten(memory)[0].dtype) + seq_len_batch_size = ( + tensor_shape.dimension_value(memory_sequence_length.shape[0]) + or array_ops.shape(memory_sequence_length)[0]) + else: + # For memory_mask is not None + seq_len_mask = memory_mask + seq_len_batch_size = ( + tensor_shape.dimension_value(memory_mask.shape[0]) + or array_ops.shape(memory_mask)[0]) + def _maybe_mask(m, seq_len_mask): + """Mask the memory based on the memory mask.""" + rank = m.get_shape().ndims + rank = rank if rank is not None else array_ops.rank(m) + extra_ones = array_ops.ones(rank - 2, dtype=dtypes.int32) + m_batch_size = tensor_shape.dimension_value( + m.shape[0]) or array_ops.shape(m)[0] + if seq_len_batch_size is not None: + message = ("memory_sequence_length and memory tensor batch sizes do not " + "match.") + with ops.control_dependencies([ + check_ops.assert_equal( + seq_len_batch_size, m_batch_size, message=message)]): + seq_len_mask = array_ops.reshape( + seq_len_mask, + array_ops.concat((array_ops.shape(seq_len_mask), extra_ones), 0)) + return m * seq_len_mask + else: + return m + return nest.map_structure(lambda m: _maybe_mask(m, seq_len_mask), memory) + + +def _maybe_mask_score(score, memory_sequence_length=None, memory_mask=None, + score_mask_value=None): + """Mask the attention score based on the masks.""" + if memory_sequence_length is None and memory_mask is None: + return score + if memory_sequence_length is not None and memory_mask is not None: + raise ValueError("memory_sequence_length and memory_mask can't be provided " + "at same time.") + if memory_sequence_length is not None: + message = "All values in memory_sequence_length must greater than zero." + with ops.control_dependencies( + [check_ops.assert_positive(memory_sequence_length, message=message)]): + memory_mask = array_ops.sequence_mask( + memory_sequence_length, maxlen=array_ops.shape(score)[1]) + score_mask_values = score_mask_value * array_ops.ones_like(score) + return array_ops.where(memory_mask, score, score_mask_values) + + def hardmax(logits, name=None): """Returns batched one-hot vectors. -- GitLab From 92122d1818bb8dbbab371506f81b2f7344bf8595 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 17 Jan 2019 14:54:37 -0800 Subject: [PATCH 0908/2345] - Add post_training_quantization to index under how it works - Add post_training_quantization to devguide as an optional step - Add GPU dev preview to index as an update - Remove testimonials PiperOrigin-RevId: 229823165 --- tensorflow/lite/g3doc/_index.yaml | 122 +++++++----------- tensorflow/lite/g3doc/devguide.md | 45 ++++++- .../landing-page/facial_contour_detection.png | Bin 0 -> 294705 bytes 3 files changed, 94 insertions(+), 73 deletions(-) create mode 100644 tensorflow/lite/g3doc/images/landing-page/facial_contour_detection.png diff --git a/tensorflow/lite/g3doc/_index.yaml b/tensorflow/lite/g3doc/_index.yaml index 1b3f1d616a..7153b7c6f6 100644 --- a/tensorflow/lite/g3doc/_index.yaml +++ b/tensorflow/lite/g3doc/_index.yaml @@ -4,7 +4,7 @@ description: landing_page: custom_css_path: /site-assets/css/style.css rows: - - heading: TensorFlow Lite is for mobile and embedded devices. + - heading: TensorFlow Lite is for mobile and embedded devices description: >

TensorFlow Lite is the official solution for running machine learning @@ -13,9 +13,6 @@ landing_page: iOS, and other operating systems.

08sZB24?7wg z4{qhDo4ML+YrMo$BlOC$;U)@{V`}5O>d*uHxg!V65@#RZN(*aZA z7sh8Ua9kDeo~HBi*4EeaY^zm-IPY$B_;s}O^QT``=Vw}^A7y*^@x%B0Zd;9pYb>ZKyRUTkE!Cxjn*ZyL4 zU;FWIw-0`@URHfSxUB4*lfiGFm|`2dlC2+R+&>+E=h8Z*&d$!eyUWj?KmY#fYVo@p z6*3zor4~vh9c*GX&%49H&Heh)($mfC{29Hj1@GUqCfS7=j8I&i;aJDiYxE_ntQzZ|DWBf_u1`? z)e$=Myhvo@ndiz33`usZ2f~6w7#TD?cF)ksTE3WzVZn_xt9PDRyrza%m$z-5V=|xO z^2?Xz1gu>Yf3jaxOiV9k$A1pPE@rg5qKUh3oBW|Mfjd`|R;!PdA%L_~qKVm!IpT7+Q3M z8!i~!d7@ySfBRO$lx?y5bJ!!#h0eDYtoylY&Z>*&-)@3j1hL?U^wnc)9!;`4>Ah0I zocX}c;^$}1oG~&so^Mn6=;6c0;5#c!&Rj5^Y;vaflGEZ!?y@JPavrCDzu&KLQa<<9 z7w+WaeZF2^LL4m1mMu$6Oca&A`u?HB_br|=yI)*6Tq0+Fw*1c_o~vTJCjHR7G6ntLRrNrsf!{DzE04e_7{t)f1VIGapOw#ZFGD zmsmW`E7-)*bwo#!l|f)~(;c04dvrc3S<64cyb0n@lYVq~r#<6|lNTq8T)up{ombkd z{$Gu~{r&|D6m)fUqYgP-;xR5co06IN^4YU*FE6X_V*VMf!oX3{^e9C2LE+A&)4opF zb0=oqf-5JP^uiAWtyr%e+FkrV$iu^-g=(H~Skd$Y8_@&q; z;a!saH_M!Nt17>GGxbheZtRNc!)wm;-Q7{~;*hN4fnUk|_Ll2xjDFqRBY3(jf3EfC z!;hMiXYINDaBp`_*~de3Ta2r}EeD_09{N$gUFBr%?QNhFD`R(;^~>45y0$i2l)YgU z_nK|1X0|vzTobw3Z@!(ZfIvc0kgj~doXYQCP8o7r>M5UNVL5T0gXc=uUCF81^CfEn zD}VhwyEx^&yxexR#07hg-CCRfXI;+L3rdS?p5^&o-8i{^&*z9UecrqALl=z z`RC`GW`~7@T>1F;_nZDv(0jE?I?Ww^buA8S^cx+!3yYL9*8l|vVsm>CpS$K|H)^q*sqn4SIl;lsiwCnk2) zegK`gGt)RdY;DxjSFfxR54C`1-Vz?aD3`IVx^m@8h?|?5rY7fQ<6pir7H02AJ3H&{ zuF}-BW$&w{jrLShpo-J=t!0`P1s$-v@ zpO=-9*_MC5?$wo*cXyYsUJ-KrxBiM(iEZnC-pKiOZm#w9HIbM1*Z;5m{q3Ucx%ZPF zuMoN^U&*jw(Nbql^EKAr92lC?)6Mcay=-=VUA4Md)cu2=r5}$g&)<1zp3(gFr=kK0}LR%@%itLstS=xs53Dn33q*lb(%#ly#E z&6RgqmW>V@vaYVmySvNM!s5vBjYP&88}}T106S7`+M8|{r_KFT>Mgd@?U2E zbHCV*a50G3^ZJ_GdF%fvo{sV1^=ZFr9AUA_+h2UIt?-*UX>a|mxArd1y}xDl zzxgkxCeFRhSMuhi_r#fo6Fp--uKzuOX}RCrSGTslzOgYGv`BKzni$i$Qqt1*_toA$ zdUR>|`+K0%Wp#Ac%&NN>-k|pf?aut=8hBL3`#Ibua$7qJ_{_qq1V61;W zANNU}IyFcC{?waZk58XUR9I-WN4m>p(a+D%=iArYsi+(|bEapSgpslFa{u{tudl83 zoo)8@-QC&KB(mn6Idf)b@$+NHj=j6F@$kKS^WGH%m>!GSRib&NgIECdmFvn zZ*JgXx0NeagnXNmbu?*1%FUdcLH9-NJzt%!XN=giNr#PLvD6u!y_KJjUA!pBaBjZ+ z{WX!syAB0}gp?E&85tPNu&b>K2?=TElU;SUE+fcQY2usv`|F>an0R+rDK{tQ#p~BW ziw*aNHh(t?dDzA;U-$XhS=;JwJ9h2*RB>tZR{vbk(qC!m*$)r5&$p=z3Jsn5uHeAA zbMHPrKJMn`_V)Jn`}=Bt$J?J?GtJw>19T$u#^mGcVs~#VdmF{d%DPM0>8jkhqU&+4 zuC9~S{r6RVPD@BosJpRVDrsZI!$Ymh{pa)X@on3(Wr_8iO+i6HYJPKO%$>Wp_IKGX zwR@`WFi_IAYD-&N+r){2cLlE& zWM*de_4O%DeDUaLw{Fyy1=e$lGBPrHq)fdcBP;*>_^2PhZ_B1llO|7IY^{-5{_2Wm zdiwJ=Ug>#OrK^&U_sy6W$a*O9-L1SUD+0~)?%dc{3z{h{^v!>|v*4lAa=*E3yiz5< zzGQ;-z#slOQ&8DWMOD>srcZG2Z2KrY1fai-6^Rvb&U> zOnK8XSgNY33JVK;)HWACKj%N+ZmIR0S#COF`cYdlZfyY_C$l;2>?*NjozT!wdHMMp zHW=jR=d-i7KR-YJvh2hX&AMBlgYT@%-prUV;lj@1bdagS=ZcgW7$hYn-QC?QD?w*D zU0)r({+4Kx=h~hg9(DEQfByXOp02kw>uT4#f`2{Rwtd@C_}H%Q&x?2O;vyn4GBZym zP2DV9oLl_*nr?b}y598aS64tsMJFyaUS(s^<`%zm*Dg0Vx4au09`67D@A2oKp!3qM zJUu<#y6nx3?fLhMpP$Rj$haVT?tX&N%(%EXemNTtAD=B7Hgxp#EU{Lydivyv&m4=w zGcycNPt&!o|5tN)neWR!j;jST7G5;x;o*7o__3?f#NJ+CFE6gU4@1ILJoTct0g zdAv_{wt0Ts))_bdapvZ}-LPSUZuB;vStcj1UNzO$?ta&iAs`@-ke9b^)v8%jrkG@0 zP}p_YaVt|=M#!{9i;@BYE=n#8IcA!P#|Nj0yKiB&C+qb>^rH-L&uNPkAS4BQID#vX0 zyMk5iCr+Fwe0^doh=gl-*MuVL-rP5oZZ~qzQ4PB`uzF#x3<1MJzf7LpZhIOl}VsuWaRDt zeRz1dTTFM=!S~PD*w_LC10NsjeSLrb|2sR2xw*Jr^sNYr*;8@x&YeGJXPfiO+s(15 zEaLsg?i$4X|JjA}8k(A{?CkyW_WPo@=Ord4mb5GHH8M8-{q1cv2!DT9`tnliy8dfuYlvKu?YpwG@Uh$RK3VJXcQsE>P2E-c`jV{Q)s?}^`}Xgz zKRr$N`Ptde-@N(r?QQffm?P<;IVkB^VrR()AutrW64u~hk&u9}+K z_jh-TOG~$I-O9_sk#T7W=UqWh)=rmAb$@@={{F_w!gAy0&5N>pywh|dohGFuB|Um} zcJ|t}YnS`ZzP3KzepmA^@0TxMa*OE%1O|5Y^{p#A`g(=ip{YqP&gb0TmizS7RM6RV zx3}xx-FQLA+WI#5v>DLK;4;6Kw$@hP`F6H?dVayd!VGbHt4wQueL3DQe_8f={G{pA z=bL6$ba*NR3MJsxx z%|VB(UcX*`byetAW83;)UosCgFwQp1J@lm~;ORl0-hXYYR=qkoS-qW4HY+)~_}v}L zU58g%Sz229`T0%Piv=A51YVWgE+^Cp+K%w;+c!NuJ&?t|;GNS)jvV2UG&*weV&TtE zPfhEhTW8FeadEM`wzhWs&Z4Dd_bT+ZB^+#;F=Ixzxc;>t^@S}jT9Y%r_}|)|U;qE# z-{<6Bs$tOzUTL$OdwVLszq@Oi|L*(ajMQIoH?8-rcAW^7)~JR47A}LfZLxvQkp7UcTH}@iA$V z%FDhLhmIUQ8W{~bYU9_jUg=&bQzizsWd3U>B zN-kX0n4A$Z(XQslgGY}h&7WWY;=)3yg5_nPGRG|U*7p4S-~AtYczJ2Xsivo;xq0V= z`Y28O^768Ke0=@oWxk-m%mth9>dMOEe}5{gtAF>&TF31yYJFGm@7{9%`E9(?Wq*Er zv@U-)<@W^8%BQ{6->obyuTDBUNn9sFL9F}w`gs3x1&*sq7Hw`*A06wJKHSDTdGcgs zj;41J|Bkiu%e(c#twC_V4M-8eOmM@893ockbKU+p86{qHLn!_vJJ-PM>+6q4!|>*M$T{{G(D+B$x3)z-8(-$i9) zYX1EAxMRnTTRTlToGvk>?-X1mA>wLjWo4Fgqu}|uxh2Oxt(UW_$w*Hxe|k#P+1Ytl zvg3+xha&S@wAdU47#SG_1qJKsK<9V*7Zn$?^GIx{{axmBp5r3-@e3TGOP)M=;y2f7 zu3had&_eZye{7zU_Evp0GBk|bo(E1~w;xJKE$q6oDb+hZK0ZDDdDgD1oOgG2`luD3 zon>0W-Xw5nY7$4N2|If`$l;cju5NB&>qC>>`(!w|xx2f&lMOy>ws!ggIuJ@r&Na}_ z&#(0LwYz((&F^mfvyPo#?#qjd%1TOyKu17D2sklVobC-sRp5B^;9xVTrLn)>{_e&< z)8@>Pad$sH*Sb8Z%FUzdfrQk;QxD~&PpMjzrMVD{P5w! zCr?0oXZn`bG&VLqeE6`PU;fyao(Wq(a{yDj7ipxYr}Ogm+E#zNAnP6Z{r&y-H#euJ zq%7ID&+hINP3w$yf-;%IfAyT3K6HKRD26XJ!t?p<3mvu%#e zY$eB;E-&--_VncB=Rbb?Jcp<5pZUTIwJxm=U++0t&GYc8jSUSA#m~<@d}YwGM5b?{ z;HsczcK)!iuw@@T)ym)BdwX~H_pRC2Z*9qR4(H)mc)xM}0>M>5|Ni~kxN&2NEw72T zcJ_kH6_;lC_F0T4Er}v(C;kwXXQ^V1lCarOTJ&_f!P#ulp;e z8x;~156bq_r}y{tur$29yj)vTQ#X3snn}N$I6`$~oRwYrOJ866`SWLLO3H@{o65?c zyUX8i%e%YlP_?0fLB*#hoqP8DInc=b`0?ZScXlpbv?%D|UWFDd=Dvl3i`oM;YX1Hz zb#!EO4OE)=;y@#_=snl0tXJLQ`V0$JtkBTWS>ribZPTVrqW8F+R!mB0b6c^h^XMbJ z*j;O4cZVqmh{&&8!Xqmn5MbmP77zax?F49;=jMYPp{!%V6dD2|I^dcudlBU&(Y^t=mjb}RxMk$EG#6%B=wX? zWo2cPLKnZ7sOZmMzrqsq`Q`1-oII)dGtAL(VdUnt?fLi13JX^rh!b#{3Mx%ZCQP0T zDzNYFF7NH_wUpswn4nVmvu{mI=R)Twfm#I?j00rcbA)YRk&VoU(0K z;Ea?&!CG^SD|9f{UP1M-m5%2Cf@#ABb zKi?iNul@aP=kDGAtIIhSo;m>9Ih)1W>2fH=VBL(ZORXoYzh+-QclK=WauJS&@}P1= z5wvNUr&quUX%91r+zDTl)-1a+|7pZM?zJK5YxB00Z%o@rjp^fLWsZeee8whDm%L7Uy0*JI$WC>llu!k z8O4x517~HG-iW`yPaB1XKG$8DeVKznWcN2!h6RP{tgHghQ{-g-@Oif~$HG}m z={p4{*|kiI>ThEeXXd`7s-PusW7mqU2H}PYb*I=F7I3kq`u+dM;k1J7fT4*~Q2Pd{ zwFfHxJ^XNU(d*~kd9Jm$iaS}BeOeIs;oDjXhlQ(+g&Pv@%whCV`?&FqAm|`dcaBh= zGZ#2KXNDc#a(uQx5${x=PzDALS5v-=iyNhSl!yVBEb zFD*8|YtP`cBI$vSxl_=yn-jM$?yvbfP3y#c8|(kCzh4jb&oY$OoRA;H+3-mE{C#;x zj;joyl$f_d=M%5P%alKV1zQ5Xwr{?JbwfLei9rCAkZNA3JB2nLOqd{a@<6-)|Gk=u zo|`0i*c|^PTl*VrF4xrjTW!O!(90=6#O{>%!c`xhot-^r&Yb%E^e-K_Cxw-wn z!RIK@*4DM^Hl8R-?ivX#|jvS$8 zXD)DrN~K+1S6AP4*pVYt4`hV;x^?SBTuWbGVtu_vVAaPBa#9PqrcIi(DgC_M!hi_L zUUB;*52c9}RaIebzZnv;vaGVMXf!Ekt*mXikbfw>OEDyoh3Vj{tE*DrW6+UvQt^GS= z#tb%IDNuiZwn^rs`Satq=iNQlBe}WcWe{YjXT`Kbzb|n7U47=vnTLnlckkZ4K6ba6 zhQ^Ah<>?Fx9EZ-H)ipEw_V)Jn+*?~Li=Xk_6;urIPHb~~@jeiASn0cMxwp+SE-2X9 z?F(NYSK=O$xMjX_T$U_=fCLVSU8K{*u-i7>#wh`*Z%%C*S>z={Q2|y`}^D4*zO93 znx>~e-(CLROGU`mb}#r4b;lKLhk1H0tnYMLv^D$stXZ>W&767k$dQQX=*zN#xgjAT z+w$&8$;j*}eC%dzy?e?O5s-h69pdS|pzq@DzCHW;yIWhc&GYWeFwF)Xae7zq;`uXY zbhNZqx%bP-T9rg>O6hb^SYWNxlBE!%*>-{d>9c3&W|_XewAB0cwYAa)2@H21dMxhg z>51H&W?T0MbYFpLbiQJX7U&38t@-=+*UvV~ee(3_4|gSORQTOZ%Q+v9K0`nbK19zT{g$w){@*pPi) zuhYc|6#gt0r+Y7iySln+hphpP95u0W-?@8NlIIV{@AOHNCfV599!>f<&$ha!xA!F< z`@&be_ZtL$y*bp%{p;5+O-;@6cXuT1>ui=i-@jn_^75CLmKr7>`|{>y@vkp0Sy@?2 z+*<-n+ul2JIJ&vDZQlI($;ruk_wMDFvx!(AXS+*z)iI7H3EQfYnwlCvzrK0%?sW>Q zgJSL#m$AvJs~(fSeEVi*WOV83)!yFTty{M)wO%CU@+ktH#aqn-CefU zLuC<2i*Sr)Tb8okbnEF1|vhqJM<<=x*G3)&Lgku{x>neEQr>hQR@xcK<{_wUbtS0FH{%;I!!7VE-* z9eH=H^7G$I8mIZpGU+^;6zZhj^kV|^#rHoJ+$no=Bkxpv|K~+a%>`^3_B#)La6QMS%3l14zpU^)$A07Rg4Y!h5h2C>qUUl${#mX0Vtt%>{nFJd zHaKoucesP`R*sVX&r^+`{eJ}Hw;WH%HB&s{Ybhy^`ob=(|8uJP*{0*g%kRzD^?m=Z zGyC6t16?fh;N{Dc4-0B)YQB8=qP3L2WtF{*^Ws_WZ*9$PYirxMapU&9yR)oHvz{(V zXGoaME4QZN?}2^3^QKSQe~q#GzKc)H?z`q*$0{>!e&J#8`Ff1EOG&Oa+6=s~=%UM9 zUlT*4H21jY2jlB{&Vd&;@aq1K+L-_L#>T~!>-XKtVPXi0iaPb?d+_TUuCskV7$^Px z{x;&Fw5ncdV%3|K-Ck$6Xo1do-?;JP*6iytvY-(!oBDr$7A;zI>J{h0S3Jfht4?m( zyxF}^#Q}|p?96dh&yjF#rx^d!!%vgpA*$EE~tGj-h+qXXOd%$`9 zVcOJb3m6$Bc#i$}254E0p!@Dqx)7WIyOpUHvyUW+_+gCT&s`Sz0 z$8X=hUA-il^WvuE*S{QG^r6z+=m>+u(>t7}3hwM*WIy%$&z>*V?d*K>q=hzJy>dX| zMV=Z%gU<7tq5n0Rb=T2~|v?%n%G>v`M%V-}uwZoHIUZ2V(E zhC$+_e{XevPp%MQ*t17w)vhJWt30nXU3#3}u;)lag|oGC@2P*Oj1GINzVgZ0>?nPG z?c`*2ekl_TW#z?OR<0{{9pLHBTHxL<7a1M>`}gnpmc?mTSB1*(EkCtOEG_t1EjzE0 z#OJ>IObzXd>^gU=Od0Avt`6~w@!x)x`|tM33Y~qs?kAs>=&z39Vo>@1Bu%DLpg1Xe z@`uo9{b%Ld^4J-cK7X`wPPE_Wy#9}~UYHg){&@9kvF5CE=VCe1=67cI9dBPJCil7e zXe@w@bJx>H%ZCKrKP33yu5q&?)9AX=c_5lRi+sW1ABu)LQIOE z`9w$Get5V&erM6q0|y>lzaFx|X}@-F zgc%mB+c?+q>z~A^c@ZZr&wh2M#aCn78;KJm!UKqfI4_IVO^2X+K4-?3j^U447T#YNiT>sne{BO@a2?5*CO z_1oP;D)iDM$(9!npFb~udkb_Xy05SAt|W~|&b#g2J$oJF{&k00%IQeohCBJwt7;=x z9t%-9wZwB;RVUVI({ZHo%81DFpt^0_g((~9il5!PfvS(Ztm^P>HU)@ z2WMqz=^84i*cR~gW{KF%&reNtjjx)<${;BwAoXmWo=nrt^o-}n7!<^q7&b6ie2seH z^=SQ(bS8$WpRFdn$XU6V$?4tcKaY-{*|~AyAI59GPg58eB8pfTyxABcazu4yy>=wt z-N?=)U}L?x@b%i5$U4Twma|zI6#lU&ERQ=b5ZCwhi|^x3?t3xc=hTXcSTHE4vF?n# z^XP#{bm{EmgK1m-onQ8Jdvb{w_X_``=6j6g-IkO|U;5Op{ASgK?fLNw13Z!$IKcag zLL5)LG5%e)ez$Pq&MWDcbo%=aPyeCG}Cxdv;PI5+k$1S zlhv2U)@^$FL(7(LV&;|U_GxcyvV^Q3di~8!bbgg0bE8g9rO@5!@f-_7?a8fc=Grau zzEf8Hb^eYsswt8u`0iEY3T;ikruS*)$5!jAGZOzrJnu{t&J<=s-L94Ew~O1fe8$w9 zdz!=DGeoDSul`yfS!TXG{pET6l)Ot1YpWlvm@-%TZX0XezsDpHC>NoA>E-XV2%?1_Dl@%2k;Pjv6_yvxO9hWmB z>}Ss1{r#Eb-^{AEpy-5y({tCHvi25u`+o7YMd$V(?-E#*G-ILQBGKT;!*fz^#q6H* z<Ihe_V#bQ#?}*laK5;~#;h0;_`%M^DQI~G z_ZsKzax*oA`;NWZmTdE7k^hyQg%|Ai|K7XO`SNW2E%8eX^SgKE%ys#&_ZZW*sh<-# ztcq*i$J||Xyiff}`2EfKxr^(pdUfux9ln~(WT(snUNgzzIk#y0fm8oy@J^Y)e_=(h zoxrP8J{mWfykj=?PF>{I|IB=D?=3lxfS0MOK2|PI|GVnOOG(9$hXp*n6Sn;Pp`a?h z?>3Wn{ev^-UeC3;{;}lp&rki~WBa=5XmrBg<%@$Rlzo-T&Uta;->Nl%QLkhe zG;T6=t1f0>ND7+pS?S#m+q0Z^!Z>}&I_TUWdhi+Jg;d2wRn-I{Lw*oXB7-|sxv-5_SY_3xhE zt8(*?pJvZrJniHi?^$~iZ_Ikx+kaupWn-ro_aEL1Tj91+tvdSevP;nkPutJVJy&q| zN__iK;mtoX#m(bZ?@w#`dA-;>H~H&Rqe;g_|7We{&1|WTRt(`eeu2YtZBTTA=pMV0 zXMWbr60sRvKP9L4-F;=2_|W|Mv|Gnsolf=XGdfqG9MbzRDz9Y~vupET^%|*V_bs11 zFSlHnaNy6>yR)?}$qGBIC<2X-zj9o>bn9)I>|Ki;I6`G*oRz;!KXGSD!uy}=)~%a2 zZ{F$JCg(p)lTXzfeS3eR|5NnC8`lb78Qph}e01q}vfr=jUAli0Ps8?y{|iiq?8=(& z9(~S|wg369w^i={en_H?o{_WCD3ntBr+uO0@`lBL!;U%qatv8gO`{vI2bbp*)o!grK`((9i z7S|Q|?tH%F_`F>!uJ~~0=PHMUf{$T5vE{C__{lG^n~R&)T1hvYy!hTy^@aV_-}+n( zr*3|15}Cko;PIzZ^7WtQ$35Tmrev$>+Z*-14|uw2el*706shjqX>+vAch^4+9?+($ zCHurSRo<6VtBld}5BR6bP{gZaljd*u|Ke$T8}|bB4=t|?jD0R}fDTIH_`9*`c{!72 z(W?!9OSNb6xak#TtP4py&TaJkV%T{{w`Feg=J`zvoA&JV;mRw!?=s((+)`@u&469u zc6q|?LU+4!F?Y7_(400$$1y`|BSVE|Hap)vDIuHsD;qo>{HZ87C(gK{=C2v*YS-`X z;+*^Xdb#i2n|1lxhP4;pSa)6wz9pq+|E=NAJDG;_)hRb6A2PcLuCfC4miMci{*`8P z^2rMAr$-`dw?A&(Tr7V7^;xxbk=yNb@8nFle_E37@wRpMZl;GdSG{7Gzu@8p)nw}_ zCSQL1Ir)uyx@jW!;dEZ%%6Tz&TxY+!{Uhi7g8Orh`t9G57Z-QGx;uAe$=w6@9A_HK zT&@3l!vCmS*y?k6cDK`SZ7o>#?2dEy+}_i+yx&hat#}1WclPSb7#yaBn#E4t{kXMl z8k_U=1%~YtE@x)?tzcqUUHIHpEa&}c|9|Q4^_Uk$d^c$LpPtS=O>Dyk<`4mqkR$Kj zy*prY-0#i11MlAXy*c#GAwVSBL4-wBY+Ks%x&sdD<+x`s&cEMYojRlTdt$dmn`|sGkH9BCjrS2S!hcD+{z7=DZ=5i#Z@W*@OOPU$^vR1d2tx^^=v|P=8 zH#Yjo^Ry?^o~_$g9rU<&wwy`kn_qh;>gV0MyltbjUm^1nbrX{V`oU2*1ut&w?%)6O z=K8Qb1_F}v>q@V0Syk1q_dQ)__Wln^-BssK&gOZT{`==E|Mgk9jz?aWxTu8xd!?!8 zoWxZB_vmka`NG19h4z(i&bIEaaQUFj?M z|NM0Et@nF=K6||FN3yY-oadRi6BUo2^qRX&+4IPCrp>e^o)bg@JUTmg-gi#%YU|iE zCF{;S`~6>YGx@7E1wUTAemCpy?X(C}-;5WV8<>O5>@?4LJbPokSnblDs zd7GleKBbe3*S1Z)X|Sjy)BTZr>=%BW>!1FgD=Q9MUjE*)IJW+7&V9SvY^AG2igRo- zejIgMeCwK&t4Y$|l||p~|KL7%lqElM=O3&886V%6I{zx$#D6z@?fqSv)xRR1EvV{A zae7@B_FFt`E06C+|JMok&*rkrAJfchC~2E&tPt$^eyXv>+NibHq?TySo}wMHBvxg5FC! zJ1RwloQi%fKmW2MtkPR_`_F*cIj4SwtgutLbaXEd=k>Ze-j^blZ%_U4Kfp3=Ta7)1(#VX6C<%C^u{i z7s%YY(Pdg?S9Qpm7m0_3GUxMu&xpwSzVumZec9V|=N~t(ns54+^W@9bGQD>f8+D`q z###LK%g*{ZSC>Jd$fAwSBb}>%n(pS<=$%iNY;oOgm-}kRotM|Q7OhqdbWsVN^5&a= zo%Yl7{iV-eTylN z=k?!SEV?v{H&|=ed7D*gj~1{0UEyzC{&PY0N4v&X{KXP`Y&L0rZ<@<}Yqi*UE4lB5 zcV0Ba`g;4_*jib)+Vl8Knfv#YiV6!0-4+MF5MelUxHRKL$g0rQ&tAX2y*hloX7I9< zlS+@43Q0!mReWlwJHCGIJ~x-%8u#a^ovVYlKbmn=MfH-+?AdNICrmrx66`}Tcuw^`fk6jY136wTRugj zy_olI-@l`?eY*>Cqb@ZmE^4W654XKhT64Vb?D2fzi$`k1Ef=+jo6Dd6Z5e6cqB2!j zaAo|RUs>H5pw%9Z&20Pj?W_Lw#&dt6>k*mQO5uyhz*7#`9?k&+m$jQLZlcZz$PW&CPpt zWo789kd@Kf->r?_ZewfP>c^tMm635`&Ye4V*2V0+v@Q4ct*zPpvessrnw~FQ;yZeF z9Ek|}bY+3RzpUk9X>ZYT1*N;mi(^)KR20;i25;IH{_e)^>E{JrZoT!w`Z&{)rEF_< znyy`S>Hj-v=N~h-c9zcG_5A!)?f7*vmNv7?S2+1!d%It8>dcRAch&P+gfH%%5pFYo zf<|ENR@Ha^^ZtlU+rPj5<)x*~O-*_C_t{o_NSJo?>@x=W1q&4~Px4Ut|L=ExLPA1C z#g6v&;};jZxB5-#;l8oG;GNdihp)aZw6m+7y~|9dD*MBovvcp{GJn&XnQ1a7YWLHn zOTBIXrOh_dyWH0%Vs!7to#eCCJM#kmKD7R})>bfXe|LLT(c45tb0#LG$rxw+w`s*%yA?Cb0HR)3#o zoPKU!?eC(er&LRn7zz*G3i}XlQTZw5{k^?&=gtLPVx;WuwqxPU%d2>UrZzp@k`=Tf zV$l=T%^_l9_BO|!Y{}x)wAb79@vZKmN0oJFXPHh`^L=$?<>enAABSp9oL9##ASNaC zPb_L}*{La--qUm>&2l2%-`iVg7OK(M*!cJN_w>lJpP!zdJ9p0MIHQ2RiOHW>Cnu+> z@9$(~W%m|5bSf_|U-?3JvqOFSooCLvN!P072v%aBL8M2R8raB0Sm-6DE1Kd!6{ zc5dUzyt(OV10%E1i7#q14<2l0zkB!Y{r&az@9*t>cX#*Vd5m=&yu9^`zrDT9&d%pMG(udj=hwyoL{qxVkpEW;7@@-9XJ!Pd66 zZ*OmZ|M&Oz=VxaRFT5w;)!8X&oOb5pt2QPd|Mv2-yVAk+{}@&>)K6sCckt%TpNCqx@7=q{D`g^K znA8%rR?KQ*#axTRMIkGL{O8;8^67$XFD7e00>h?9GXJw!1HMG8l=Pm>j6zxOHpn{<^=%dZmx| zN?+fUdiuhJfQ7=HO&2p_c9mq#G)kQ_XU@C3yTf;v<$~hQDI=pnes##oOLupd+uGWm zo~r%)_3LZ{i55SVCAYWdhp!5Gd7zQ``T64ktEj5tl2zY(^ypFF83u{(@9n+3-2eTZotrNdG8kFPI5Yog`t$R%ymeX5n;RQt ztxA4;zh9r6l+@xkrLOG#y|?e)t-JpE{r&y@Rcs#Y85s@n*EXe|e()f{*w~n#zyIuP zb8cC6(2{P@`Vix^GdDISA72%^IwvQm#gFAfxPvqEA3kpG?pAJbIjfS4o10R%W?!%S z@nPYG&Py$R%V!%VyDjyax+-jK)$?<6i=Lk1oXhb*zp0JwNA0_J@2;)%Rn={r>BWpaWE{=jcI3N zU=UC?F=1e6kXX2ok%8d|2QM!JgMwQ|1_J|wp_4N+qG@#D0s{j>QcD}vwIo%{6Srb! z_!BWzEA-vHy{m&(K6>=X$JcjhP-aL7XoAygiiTo|34@1l&_ZU01O69Ze>DUH7ZGXc z*?TVDyBBvMfMG%#sO^?4VU*Ibb7$o#-MqX!%i?DuTKo)4OifH2j)9h@_dT|&&B)5S zwK?6N@iT)%l(RE)3J+K7qQt{(Jcm{4PZY+So~GN{%fgV!#mhS(vE=o&vuDoe{5+p} zYRblq8wJhy8J1|8m^icrF7*=aba8S?OG~pj$~)oZ!i9`RJe@9=mibC|x+ED$NSS7- zl*ll6L}z3)^epk7uIILRqg$_()>N+-It&xG86mrQNw)uiW%na zW!c=_SELL)7t5L^*8J|988b8gM)N@hAR|L~k7hD`0{OI-_3hgje9bbo9BPNEA2TI9}%tLdM7!FOi z`hND-g}YmN+Ab8tt?ZLBy{k~7!XO|j*z3?ylaQo+keH=a*p{#-SC{0^wHBZQ9V=d z^Z%;d@^2X!lv+05VPG=2$752WnR%~>Yj(E#lTCXAh10!+Do=(b4z5MJNK`raYw=dqE#P#LGy;UmfQ&X>9+Tkmlr1yM-{C!71tCe%t zDrV&0c(Xcky8ra!t&$efsb;};dJmpdPn zN%!-b52oj(l6}rhE)9|XcWcvz!sNKMHvUT%EWe#3zuI$^%D;!VAMPa? zr%3#G&85I|u0Ap&eg2CE4!4QAr!Q!#weFe1<;Atb+2zZx+)t^QZl{DKJ3N%83kgP6 zX>{CplXvavT$AhTkI&ni9d%;Tb*(eAO(z{=KNf2@^UEj4FI!Y=3g5qZTKuB9Y|rjR z0kvD5R|Ia*mtd$9JXgfbaiHdi&&h*=8y)o@J>xU_GI##f!}s=Vkrebid589umm2M=XL-zl@`5 zu3x+g!yidda&Y)QZR@{s7H=1qUlP26%X#b4RE$<%4f9f2|7K#%Y6)f4q^Uwmu6EH% zlU^wC9=X*sGilQ8Icx8jhu<&qJZ~B_z0d5cm%+EEZ>RGF&on+2*Br9!j`jV(Dy7pO zZGUdRwZ2T?!)msM_@?9x2F8MXVTsF0mg0I>j&4-ryS8hAbD_EaEB%nOQTLRu|61Et z-svLaKc(VT*fgd%zSrAd9agGHI{W&0pOwgr#q+$kd1_a5_0PMb9T$GTBt-i3w8J{l zL6?4}8v1)ZeU*B?*mITH<+CAWMwg!_2LD;AS-wL?@ym2R2K(-Zl{W316T+IBo2A3n z6s&N$xZZx@rs*q`dspq*^k>?!>*YIF@YnN7?%pF*b!70F9 zs?+0djQo$IX?CwH-H%j=CUvGxk^lDOO8ow5+?_r<8>@dl*nj_RyEQ`t$3rWVh7%%r zGqz?5#)iy^%+CMwm*vOohZ0f_DNcNQyO+opPnyt zTK+@dJ$}D#Fw^G$OdWjBp0as3vo$9^`|@(*#`nR6Xa2JDRaH$pnUz1kD|uVa-D7v} z+KRHXG}yPdv7JabHBHyL{N0^Rsi(PQSr{&ft)ok|LW9c7vz3p*{%C@MP2Hav&np); z*Mz^jvolm{>IF>(1|xox61D|bvsPbQAzksl@z4>U7drQkNtR60I=WT+`P(;dTFx;s zOz>Q=@aVY&1Cxw9HaZt?6`!zmQ@XcW=)Ax0%7C56rgr}e3k!2fXJk0RbL6+aoxsG4 z895hs)qc=kS8!Q0X5%U zfgvR!Bg1f(QR%N$z14H9-%ZrgZ9Vttw91+*E06AyRy35gs_g#x(SU(rqO)`Nxu(|c zXF3rtFJ2Q_vu?(Umx?E^YTvS{zFQ$ zFGT+1&^Fh-m3RE!;^}RC(uyTw3=KS-yt+!DQ+w@GQa1Kg`{WdH&s7!mm@0NowtW4~ zP5FK zEB)j5@9lZlcV^tqeReap<=muGBF`dYt=`TzSh}ut=g#fr;b0F-WMn*y-CMOaaPqW1 z-Rn`$z6n@)&kBfoUi5BD`GpN^X=$s%)<$v4vNI?oABfCrWMyHwvg-DRgvU$Gerj{f zRpnjzhD+@Aj@8xg|JBvifn6za;eu1uj}M7grAj|to0z9|8!Ri<;TxhEN_owoxmT6Il-1MtUCtirW)S0?&lHb~E=6Tm%eR_Htq)x!vWQ#%E z-m1`jNv6@~87=SJ=9sG*8~I#2e8tPITVJK_?yHS{p~JwisI5)x!0qk%r^DBzy}NXB zp^y2(%^U}|6>RjZeUyH+K6iUPn-mo@* z@TF69^_3k}FJu1w`PtgX3~DXDC|Me|dUeS9#it*IZulj!a5INab!XTdyNxwVv#zg? zIWg0C`GrCT1{e19otOK=RaH&bh84GG65yOTOyGZ;T2FnF30xF6_rI zygUNRC0A^8E*{$``E36mo$}VY^QD}kTGw|JUA%wll-CQ3MP+|}d`v$-PcX2i=Fh=q z_V@Spo;L957Smm`c5Q1P8$)K(L9Tr-4`i5_W#9aEY_Y}iu-Pg)b=tpfO;%Pu`zSxg zqWTi39j)^yH8FAGj2Rl!Puu5udU@@t__%2L^yv*N85G_=&?%qwxN=|c^mo_a?JvyQ zDfjh{FY7Cx-e9q{fx8>s77JQ+SM*94I;l-oU7C=Z`t;>X%@REZ5ABQ>Cg1b#Gpg&~ z)4r6p+GUl1Rd8U)$<$CZ>Nste)4}+PZu9 z?hTa;3cd>$UOv^-+MTA4$e=vVN>jx%_x?Pyb;7*8zd7c*_C#&Xnp)H3s5ia*ZP(Fl zj7F9wC9@)Sm)*V5X&h_yHasFl*vgyj@G0N!ZMQeRzwuPjxwhq;lGV0t+vMcrT#jtY zIo>A=N-3G#+r^gen`>SEP2Olz#q?%1vz_&Suln4!;u1G*D>-)c_tyU4obT(_Yh)Y> z5HdgR|NYzP>mfzqCI!>>?VV|2d*Vmt|9iP1dhfUXpYx89bshUWAmv3Y52j zH${Jaym$XVzU89X7tWgSpElE9G+*w|!ostkPisAyB6M8$_V-d-;fp&3oSFG&emY&K zq{JI3)Vsu-ZNK{YJ7@A+6_tt#r=`^bk@iq;0gMCSui^*=Ob$v;Dia_NGy=^mD)8&vjQO=jE9dIQ+lQr@!OCfddyV zTnLf5aOKLB=auE<-w(I*FMqTlXyulmrCwaDa^?&If+i(dpgY#@t}DK{?e>|?yZ;0hsTb0ZMG+IkFBU$ z;I4VUOXs&w^>i}{y(K&|+Vj!;%;R?+x?c}W2{!7Kx)*XXU{2zfFy6`WW;>eBEJ)7L zeeo-^cjFQ#N1wN9%a*B6zP86CrpqMkq*`{yI%@%c(^IE*q_W1d9zwZLQY z9KRMnUpD31&NRO?0elBNEpHzDP zBJNVV@ztKaTiiKcp1k|&VzrR?T{Dp{0o(lM&9J<8p|bMO$AZPNj;+y49+qvHxKw{$ zppLwMnrE|HDj>Zr_bm3zha${r7UaQ)>8zh~u%g z|0EwgfBt=W^3uIOe*Cz&*j-#V>dKKLPtMJ?Zf55{r}5RtCH3?)+saQ*W*8>3@k)j4 zEP9%KeI00QCcnMSj}UD#j_53r%5cIBeb$bj{Rk4#%s;B(`GPz^Z9c9?a3(x3A}8dv~;)o8Qs~j zp1WiD@%I&venfv1`Py_@XkySKhb3o}a-S*jRgMK0iaN^~MT;!};?c`JF_m&{rD9&ARZi8mR| zyB4_q{q=Qy{Qi53T)UU~&Nj=x_vhkbcPDkGAJq#N{=f41`FZWIl@k|#+Og&wZ*XBm zu-t^6Oys6PLZo&21v z;O59@S}(6;)jm#Js~5X#N>uVPv3bw8=iGes}gz`s%chsEG(d}~0r~2*SW&Q9gYC%eRS zLAz9c{0RB0&&6PWs88NL?gfwJRZt6S+UlIBEzawT{x7+;!02Sdu~UnlP1?g6x%qmV zZ~jazt;dHH))l6v{;gT#qNF)>Zr+`1_GSU5<*7Sua)i@g&i(8;J1%hFouF{{nY){} zI2wL?vvJ9Vm`!1RMoYaOr7W4GS5&P!+1R@HWs_ZcV&ud>n|7_+wB+}!qk)qiP8D4i zq-O2C)LXM?hLPpw54W3lPO40Kr}t?UtBQNMx@_-~PpT=ZT_@i@-dQYoGJNY#7vpP@ z*%4oV6g%&>I9;{t^wgkZN8>*IYTcN|%lg@&TtjDS`a6@elj8H{)*syA$18nh^XARo zcMsNIeS3Smb=jL8m7kwYRCYf%!%*3Op3R|Wp2})!YFAf>=il4&^WNTSc3!C^0UBP@ zbWU!1(;v^tP|x$)`Tw#THzLj#P3^sz{ba3O)B+A4$Ly;AegD5yY&q3(a@~>+5q{s= z$<3C|ZT$WzwX=Iprn-5~cbjI&cj^C~|Nn2yG+y#>@ujqht2%DqR{o zPi*#|-rU?gQQ7_1@87?F{3v;O$yMubv|I-V508v>*_+45`+7=uRwwRjyt#F&JiIpErdnn4$+|t|uPT3kdwZl)*gXHxx6NF^KA3{Yipyo zm%Y99_SV+we}8sfSjb?cXtL!-<5ql-*2T*f zly(`PG*VJBE6C%pOlySuC7_tzcoldb;pqVVOVrBbFr z+4FXsIdkvcz3bP%pP6aAJ@2ko$O_N_DHeKHpRH#6k^NVfzbl2QidQg@FPl@)a!Sa$ z)mol$A0K@b<$bR5e~a9)1COi3!z=6kGk;{Cn(XT&m0Xv8b3*^>j2TZS^sQfe!Cg9c z(?-MZrtG{y>8gR8PcxSKO^$sxre$aVhbyt#0@#S-UIWarV zecyD>K}32{Y+a!6yx3_)wmr{oF8yo!G`(D<@Amv1`gc5xs-(VTit;YYk4+77xYY62 zlRZvqv-qU8m}_=VUVJ>dgDJ=->>GPn-UHO;8q;wVHIJviF(p z#4nFq?rnVaME$YkcbheecLdyAT&B;L_E6JwwGph>o}v8cs*Iq!`FyDpPTSVaUU}H~ z?zZwL?{p?-1tqGk{<8dPR{Gglp(_FwzIgE>`}(?GdHZ{Z4mrIDnCWGycmE(;jyE0ItPzn{r6>c;SCQ@?LKdOM&$;oa;fyTS z^OGeyBBsT3own(nbADC}1FxutPVtnF;X(PTt4j)ATu=-Tzb>jBwkCc*=$NU#zP^=) ztG*rUd;0nL`T6_5mD~}(ef7DyE@)46)7kJX|G!J=J-hhrr?UE69*e2HZ~t8PX8sYb{kyV{_53_NO*eX5TKWgknm<%$`u)g|J`>h%7x1X=) z3|l@U&Oq_R@oG<(tUv0Z8X~p7zwvT#Y}mL_Gj7k0{(k?QH}bWX4E6jbTW;*!accIH zX#$44v$X7OQdB4NWUdMk2wuN((~=&(xmVj>&I}Uu6)QC{<-K@j_afVWlTJq;N{Kb! zto2RE$fz`1>vh`n2~*D;pBh=@wK{Z@@2nMU;gjCoU(cyrC4K(+Cxe+m!58m3yB)c* zMsNR<^*cYz%D-@_#3}gludqLxYHx{Meae-Tbv@up@$~1Zn$veHEuW_udN{?!H#KqQ z$9>Z#zS_B1@`>KN?Ca}7SA}Grn4tLhSg&>Er<9pSsX_TUt?s9%rKMkAJLmHI3Xem} z*S1YxUhN;hOxa9&>*TmAW&??R{6XJ=+EzWVR~dToXetY=RDoxNsLO6lub z$HaM|FBIf!w#v1gYT3B#y#N|- zVwsIPE4Q1jT`;p|S8<@WlG0i$KW{NH`Sy!NYnnojube8jsz07bTzq-h3LOz0Nu~AW z>-Q~r-#S%QcgYliD@Ql?mV7uiHTF(NhfrSm%8JT!YwPW5e*ZWg8?&5e*PdgpQ)^gV zb}wHYUa>y8*1P_-(FytLA0Hm>D16Ms#H8jo=f*;3_D+`LoDBEd_Dj!niTER4yTD%%0n|rv5JT_3~_Nwk*28qc!-~^zzsH6-|r^;`TpV z_io+aU#8lRLzi7S(ww!Z<$|~HuimIv*2ng`Ocy@C==|EbwRbD_o<49>JU;WpnVrw~ zAJzN4&dYA<-!Dhdyq z{eFMu7d?iV3{q16xYoz-|MumJj7`Oc&Q8x+`zGpj>0ez?S?p$%bX`?x*Y4z4$@jM` zJtqH&wEI=1`+nu(>v}J)X1`e(x_4RWyHf2wb*0C;Pamu;U-xF_^zRR*R33k%Jx$v& z-Y@KcdwiDJqoU`Id)av)73K0xqI(&+1(qqq1%hk25+tM-&>y(_3g#w z?fvHU*)OJSey1BXX~~s!qIOS{eUEKQ6 zMAuvAYTmtkX_2LItP=mBq?Gg>$`agP}iFx0yE+{F{ zZ+npOuCg^R@1OcUK{oKr^zBui_J6h}pWPR?&^v2twAU{E^OnMb6OS!OT{2br>gwq= zGp#<|T`;LYD& z^Dh5bjcL+44FA$B+Rk;%=qsIad6t@fMXJ%-m7eCdp?aK;x)_6$@P7FCI;F;>85*l&q`NPu9X>ax{R(T7RAz?h5hx^ZN<+z5I9c>f*MBK722i$n5aT(K+4aPn)vUT;A6U9+>~m^xJPx zRiTl-J!nPU2d(4+lbNSxTzL_?%u8^knbBmO%gy#@ z{1bZjaO2`|=0DTcSEcOPSLz>e+jsJ(__^`Bd!lc$weLFrwd};>OY`<$J$CxB$BX6C zKi6b$%Macfx^@b8OtIlX>E}AT&e=!VRQne6{95$>**4vRE@=mm4_mJ03;oK}PICzl zz2}kZZ*MEOH6tZTUpn{xj0%;B9VgDs+Gz5j-`;M)oYQfKj!Pe&)M1!7y)@+A?G<+C zjl!ht-aL*yl=jRgPLs8EPjPgr(<_xzsb`Pud2H`19Cl$=r|*NslhXOOPFt6=(qcnk zt;3E?yZW!0t1mD2_n%?#@We#r%vrCNXFh(kg^Rmeq~E>xm4)d_JA2cwzH`FX`hDDT z_4~I8by72fSMz#JeI(8mzPe9&>CyFj9(|X!tEsrVtF*4J?&Zfc21$W+&gm&9gY8bL zoQqU(xpUe@X=aK`aB5fSIv(d+Gtw&08(J=%rQIn!Z^FbGX}Te6r|<~Yzw>xBYxb-N!_SSupTo>)@Y9{I|Bj~;4`L!J1s%f!^A;*_Xj`sJwJIU>vb|7+x>*Ld9 zYD!6=_J;Bns{?BP?)rbPIrMM&(|YBZT$d*?cd(u-N{^7y+;n=`cB9yY9e75yZxwF}J;dw{jNjA)NrZRiQLYWK^?suClfXoFf*iB zWW3<%R?S`V@L6=6mx$P!+rK8MCwW=aY&>^$F302SzRX)!b8-^v{+@lJocHgYa$m>w z7bo?sPpcO)Z&TT)wPeQqAV-&~MJ|0F`FlkDIoI=)Kkk_7Ez>b2a>?14C5il{;Y>Ui zK|!!0at9-Wk!5;GlzH?CKNuDuv%;+py?x_RQGS+lI<=_voxe<)$ctwb@$pe@s(v4 z=Pj|^&KqQHHhWT_ioB|@WJky1JAIdOj>PZG)Oe$1syJz;ihc57jm{30cQ;keJM+Bn zSb6u*l0}wJPw7X;#0P4{AB)ZiI&uZnq}`u$bJI`bY43j=I~D%_?PZ~e|9fxG{;chu z$M@@fErUbLg$t5`m$&s@3Uo_Ro*fpPHtUG5#lowTmSjc;&vySc<#miM=hK{G?PXU3 zw`rYeV|yumJw(7T^z`Y|yH3fZE;*rB+Gln|`0|3ak@D)-9DLrkoth&wFXGzr-ji?E zEnB8vx|xY*_qrE6M%%KZH+!0~No0`Z?^|fRaQ(HNT~*)bXZydjf3wE#(bbdTDLcw;M~|Ub>d&`|IoL?GIjj?e|W|4AMy! z-C6Fm{i?eD+MLH5-~FrhTAymbprHFeLdwo~^Yp42!B?t}?e42NCwMn{;pabFCrv+a zt3Y$Z64uK{&Mu339_Om}Zlmq?y4;%H=)ckJ;u5j>KGOvEEq$`>d*;^tQ$*r)qb{ZG zkJYZ>Qe`#0nQZhm%yzk#8T+9fPf`tbeS6hud5FPC$;6~)nn0$+-mlW9%^a+L?^oS0 z?LKd}?pM98in(nX;TMyRywHhwWUtql`eRGa)W~VGSoO9PluFu^SKpo$sqyXGG3ler z#k^NfoyQ28Cp0z|SC3q7^l7{KIfh5`{}*=&AUq{yl!2*=jQ4%f7*dbr+hk3 z9LQWeKjwbv>2>MrSIXr@YJKOkh)wdpJ?|c9IiBOfg~kj0wG%&h9*gLU=<_%x5_0Kr z#lO|ElkERI43eA>wnJn8)P0lJWxo71V_kUh&Dpc|eY7r#eYEk|{=|Cq+O1_*g@1qH zv@X=IibJB-TG?SZm-j5x^1R+^}Xf2(~oyNJ>8_XE#`p#gu7ht zj8~uC(ztVb($e$>9`5YuziJDwZ~L{*OLU6tCj8wdpI*|6Uex z?A7Db7Nv6@x_jqxaArp{{xP@7oqEzhwA)KK{zck*|0>0aB0l$)nes%qTc>1 z56h>WVpyo5W4E?(>W8G}`&S#X!{6=asQ>uj)3L47S6$QjbadaESC_RW%f}z<+BuK$ zLHmUZGkN5HOxmoGwP{c5qa%!Gla*GsI5qQTEpm~a*z)4~^iv0n!xtqy+mm&E)0~Il z=4&F0l@u0!JaN+W(@f5}oD87FJ?Un@{j$Eub*UX)y4Pyfsp~3&6HT7Zb=to#q-`@N z>)+!o>J#ENiReAI-81KcCWFG?2NG736OU~PvfH!fPn?9zK`>H2?L5nE)nU>O2_thI8=lj>MeA=xcp`@p$Hf>)_;^Y4{8=tTVP7IS>uFB1Q z^?Tfc-tNnrw75m*En^elIaieK^h)L5$NDw-AG^I%5BM9Ljf@i8v!VJwE4%WRDVwhy zShsFc@@Y4#-Mh@*-nzQCXZr5w$$!5u)-A%jDJuSArzA}2^ z#0gV8KIgo9bb9Wuukr?oQ_CKITCTZ1-TQN&<(@Ti$}=x-kMzvH@37VB;HvH4bJxCq zZoVzM{hu@Y#~P8y-6lB!t@ZOAQZ6oVbyhMBd^E4>|BWqgE-ufP&;NJrg68q97pKqa z-EpTf;Ju@>o21{_J0+Hfc75Bk^Ki9!p}vR9zS`G&EH_oOU0Yo~|K8#D>RfqawGZLz zS28^cP=Axgh+KCrT+v^x94CjV>-;%VWd+pYGUu zF8zj5Qc+#0=qa7)*Lx0}p1u70lXdIzqD#Xv?p$7cy<}eNW^?3b6M}L;O6JT zwzsdt%XtOlX?H_g>9`BMCg6fOP`yT)K`*&&3A7Pdcu@-G&*2h|8FWxB$ z)4m+3>!W6FI`gX5&-16c)K-4u+k1P4?n>p~59{RKbuGPnCal!-tLEQ@JZIkAJUKUH z^{$m^SHneo)>>p9K7IQ1?c2L!b{5&$*%=xduC(4^uE9)%x4Jr^YT-Yr`}r3r`F=c_Z!m_Zb_^f1cWsD(0QUC3$I~sD&4(rwbyNa z9@|w}UG;K?@y@WdAGRs(+L*TX%H}7X7n%K9T#9dXy#4ET|MHS|<=q`G9vp0zlau@Y z?(S@x%A!~Od=nxUEOb3T=j80xb#d2z?0q2nCZcL_bYa}-$W5$ggJ1Lh=1L~fomDd@dp{>+)m_1T{$)SbSj^E!IZyMMo&t3aa;X*`#A?>@gt z@~?H=s(t(GS#Kw8-N|mex2xQG_1R;wGc9kwPd#^O6YJbne|MX{xOY$0xVJ4hd;6X( zB`c5L&j~)3dh6l@r(@D{ck4#%Pqvp_pC4PVbMn-&f~*|-`7g7l`RZJ+tUK=c_|Ds+ z(pW2x-H9F3H-B!a4etmETErB3TkMMNiWhJ8yqmd3%&rAsx}#?dwi~p)H>Fp`QhV3ZKWcE z^h1yK|Cax|x^$gZsKLviDQUX?n+m3XEdLt*<;V<=>t!J_cNX!TS^pv-QKERB@}h0N zKG|sx)z^Df%@Mobl>hVURO6tkFE1{32r93O*=dw?q@z#PdQ*;;O~8@DBb#DkeAYPr z`nXa3Y_s0dbe>%sOzaC+_4At+Ms0Om7$lH+HLBHX;+}i0USFRk2h9n~ov=zoXL;yk zrK6Yq=7gQr5}9MA7PdOzW=)mg^3ZILrIYO6zqVR<{Hdv)*0akT@jWZAdFk5OPdJ)+ z_0|8HTV8AXlvlp$cKmmynVtVwpRBZmghl12CuMJMd8%00F#KSQVG-JWV)?J1E-q7Q z6ejn%1ue=o=`y3_HzcPfvdd_|zKddKQ;-$?!4 zEt9>^SmxcFwj`WYL|FSIU!LcUzaMXEiKoxpzWk7im-&xPg(b4CW~!4{JoKH)b5wF! z%#$eb)Xo+0+D~uU&eE<@O4{>UQ^RY&^*jv+>u1R)ENlaHrssYA)6ONTwI%!dy6o%g zcxA0hE-rEvR`XdAzyF_n-^oobN1~Se=xyxp-Fa^R^~p}DAAkC3e@lL>WhbJu`qzK< zKKpk^v_j%@{p0u!A9MS9^_`ZI^N()hKfm?vOwXTOv|L|)`oI0W@xd!~{=L4u|IyXe z;gi+`TKio0}^cc8|F8~CmXN&T6e2LN$;SCOVy(1;U?btuX$Y(89~Do z@~b4p{VVmVBfFd4?7Zyxdi_g}lV+b2l9PD&50{<~iizamF}l-dw&{qvixgi=fy=SQ z)mILmdv&5o_n+VX?=vS_9A5DjucgA;CZ>hU{lke?a zJ()eLd_SB2X5F}PWAd>c&ERD|-`?Dmx2-a%_>geL_`vzYh9zxFpZD1rZ!4HG^^5tm zv*%>&lX{F2Egt2w7Q1Zu*;kijnK`vdNARh(()nYT<{sa*p&;2w#r|yn?;-`esQ(Y$ z9w4n%n$$Dtxv}CVr7CZis>e%wInykcY?jjS7M++|^Yx)g)p9qke;dm_1{>-r|8*{X zeQj;*?r+C>r9n%p!8iJ_sH-mzk(hq=Ue=ErArjBkBSk*>o!|bOGiq(w%}uG0Et@<% zJZB~g)Uc|J__%IApb$jiq{Lo9CAm7w;~5dg|up^zgM&tyOG4Y%^Z) zJf7Rwabm(Fh8g|lGMwg75uS2)epQ4^7*11jahclX`O>!h)!K<2vZ3yI-adP?)aK5K z=(V+8wr|ms>7lbufQEDhH9IWgoL!D=aOs@YfBb@)&rznFuXj#f4_2LLsoMD6QfXDp z%_XjRRsu&tPn|DYm49h-2HU^FAPo`cHlE5aFDCBYTe~u7>9JmEacODok`+DAZ){9X zJv}XUciG!%y3ya>-;ZD7A-GVOF(tv`^jt&Gq`tPg@}j1tQ~&yI{hVK?@#^pOFJGm- z`1k(WFni*PBAl|IijpZr#@+Joo{vb zXk?iA?v|8I6Q7z5?-^o*_vX1pg*8ct`YhAWx_3GCT4mMYRd!sr1 zbnCgPjCb}_7JqqhapJ^@mzVqJUs*8`v}KdoCim;?OK&q*|9=*Ga|!3mwqM0AM`mm| zZKc)!-eZrO$gHHNJyX16FZR#d_N%_^8ISz={i>Uc(>Kk!@Y*AA&5Doe{?UJzRDXZ> z^T!VsHnu)F+g+uvuT@yRsGet2X;k;;hj#e79(ntH+1J;t{PabNLEv0bdd%EeGuFLY z^6KFUnJ@nfY|ch_i?|p)a^4?v*`j{>(tQt=d9{}^t0om70mE%eiVLxcQ^1n!%7a`xq-G^&mUR- zyTf_5z4}PXq=%v@f$uB7&r*{uN?zF<61>o<_4IUo{}~1cj~`#2cX!wJyt_hH6E##+ zRD5O{9X)*b@R>6@Dk>>YPE1r&TWQ9x%b;>N^4Yf& z|J-Et{tKN9M!GW2|CgPWzHW2ty0zJtWfvUly22xmZV^+vJWES^R?Xf!t&w~$Tb`*+ zj-K&r%2|o+yjk~JS=Z+8skpf4%bIkhq{waQ=jZjy*=hwYa(Q}cDm$Nygrwxmd5kQ_ zKmS=5yZg@FyP(GLn;RQ@rOg-3W4t6H^~*eTWl-wtWm~_SZSU*n5VAVmfAjX{o0fS; z|zKEU6s_t5vjFSq0xwl6O(#!g^ZwrtsA_x`#c9~O3Y zcJAK&dx~bTkdP3I=cIFg{{GbtTeBnKVAE9Xa5>wmD_5_EzHnif8PE2^J#x#dtE;zX z-E3OWd-iJJqWKeNDKj4z4t%)i(vRPsll5Ou|EVh5Z!+uI6fqU0N6wFX*@S+7<*QBd zd9dqPZFQN1O~!%^o|{rm8s*>HGuu4huKwSht5-{pc8Rh%v(5hU<@w#c)!PFXyZ!z3 zRobrR$Jf`_SBI=rDp6|K&noq+e@oWYQ{~T|Ea3H>loZD`_v!K$mW;{aix=rn-!1T; zGq2gqF2i|qpYCg43H@m=bw58lySwu9GiCQa8M~StDJLgYe|sZn6{uDH^V8Dp+shvx z>ye~*~e>GP)?PRB~{?s~BHtyYyt#KxSB z7b5qqj@p`Ko_9yWrsBi?|Nm|;@tnM10Z&2sA#+t#)w8oqSG#lyxh*!du&6lH!uhhs zU5=rqsm)I|*{%Q0rFDfDel273opT|wPKQ~y{GjyC_T9UtX=LoId>l1#ep0H$veMVr z_~mR?gs+b~+{U}R{Jor%lvc@#_ydJ^Zf(u(7T2$Pe{ZjL_&U)2I`{6yy$E2aH~ko2 zf5sr~>92__!wgQ<3;=zT&B$Hdl6^DsIhEXYcspG3)uy_UbC} zGmGwTE)I8_xyDOv^4Hhb*LzRbGfqGE<n{O32>-M{YF1a7%o)*0fWebszje$@P{<+s;wKd(POQu(Qsz@4V2 zp4&TKT{q7?^J@3o>-FUviZ)gT7v|O1JiBs{$148nZ1eoIb8~(^I@mGhcm+Ot&x)m=8duc)iq_2lc#&CB~!`>NCy)&2cd z`sRkcm=u77*#r%&6Cub6_3_(kM3ehwRj%$8vhqGAP*`2v^1nq}{JfY( zfWr%&mB()1zJ26K%Ed*lPV9^gJZ?JMjZ(eB!or@PlhKde_Q-o@Bo0mWUZv zeSdSQIajt%?%w;SPgP5%UAeM7KYnY}-n6q)3!51n+FCZ>d8rt`ujcu=IUiO9v`(F! zR+^_#B2(DOyZ38C_3^#Cx2JBZ+E?)K(7nCY#_w3}GALM>lvouO6O!|>U2QoxX_kpp@AmEAA06#pyJihI&PCTbFPAqr-@ds0 zoBh4p*3VwDF5KMF^y}^9YUx?J@o#Q#NKCU{Z;*Fqhmfk5N!Aq)P*^lRv?{5xE`H{- zc-h*nZMXAgo{oK?<8)~cH*Z96`L!vAcXyWyTX}2kE`03P%*Olf-rnr=boCN3h7$)0 zc;?<)6|!E&C$XGb4*|Fbt-W9!F!rt9WX)=3G^R;TZc1C6ilEPlRd9^(W4g$r2& z*2nD)JuS|bo3pO3N~J`M@r7`5(>2pq*H>R&(8%0!Zql!1esgD;=if8Sy~QER!eC@G z=c(A4S*F?Syz=3DUMC9AzT=ecY0s8h^WavS?5xoF#?@^XB7@=+5)!s%U43_F=VWDf zzJ;3^9NOBecd}mInmzqTOUuV#>78ZPs|?O-mCX5a;N|2d^Q}2Cr;UTJ86Pi<-CZ`< zELZB4BtsDExgzeB_51fBm4x22jHOEWw{2Y=vg*O7r8A4$UhOP?9u*l`SzXNyavkTK zr(*XmJXF4xZ~yAj(*+@!iY3#wBt7BbFDofJz52%XYU|URoQ`|eDk>^IJvDWA-QTKv zdn!R$WG0JLlt|V8+xhoDzCLR8F7@d$r*zL+oAdT}?rvTXdgtQd->F73FBAsJpZ`2n zD>UokBG>!-YQY-W&lPnan&kR(peYZ(pADx-ERXr0@CiWg90xUl+eKVr$mb zz{PHyvg*Di@9))qcyMq-#ySIJ2yS4JV-u&`{0*`y36Lbn6AG^D+Hd=2w_iWv*E-o%RcJ2h7dQ$Q6 z5vVZn;Yi=<>b^8+u4Sdmw{MR=2M4#D`!riAFTa0}#p^>igXh1R*)hvJKW<0CL(O2L zSK?p)SABnXcXPV`6=Q}af-=tCAt50lFE@!!?aM6=f1_VAEnc|t{QIL54~n0ks~5E7 zMELxwudk-go_+h}<>hnd&SgD0t!rEUeY?y{OQN^urOpawVo)&4$oN?G^;M|m`A=c* zYGrJU&oBk#JNwz*yM6ipm5oBGUNyz-Q?Z?~NL6nxKi_m7P7{>AOz>etWfTv7e~-LhrN zK0iP2e?DR5y7>KdZ*OgNwq?~eh zv^C1?=xtBYX-?@s&6Jl{7bjnx#=Ba|B;$ke@AI=vUmxidKHe|y|2ko%o|)OTn>Q;z zKRYX`9VTMM&5%-*@uKJ9x3{;Wx8)g29sMo8I;}TrYu43YC+AxBX$LR!nQvEXRsL?y zw1q*SOB9|I{hXxg-NMJjFjHPiszp#hAYgOa+1=&u)6ULHymrj4@>9z8yt_=y%xBM@ z1?LTUDJd4?tSc)fOqej&x|~b)$?_Ffv&6-xFZG`O@6Spmh6ek_HnxKqTcg6mufM#! z+&u42#0wq!YwP3p@7Mu47MoL+h2fuAMuxzpR&MdA?RmBD@7a2LPrS!6{SD~m%;;@A zTwI&BZ4MfUn;8zov+(jV zb@%s|mzBkB&x@U^720y{Q?8wzo&S8hyBm|+FE}zhc;D2<_E6TYW=H-1e~;!cKY4a` z_SaWecUOO3w|DQ}3y~lNk@k)irLV8)#_sxZV`FkF-zQT@v3hfJ`r>(v3V$C;NVzg43)oYH@Su8Ry`ANTe1XJIQ2hCjU*E+~l2HqW1DntiQT+I&gS$_tSl z9iS6?mU>N%-k!HK=BX5e!rcct<{hrP%ib0}I|I6{cww`EV4=+Qt=ZR4PF7z$kI_NH zqK!?Elb5%&yxhM2-=4*b7jN)THn}w2k7so7$jA^_tQov4W>?8e z(3Na|_UXs(^Vuix?#YuW=kj?OK8VVwHc0SFo9Wot)SRBC`|{5|+v;yJ76l5gBpHq{ z9KWCtWS)Nyv?@3uVZqw9xxe;2{`K|s^YioT-`+BvXfuJAq2AKOq~rR9h0gJNtF~5t zepXVk{`&g(-Me|Y4hWdAb7$qPy%*2TwU)Q3*iicV+Lmf=hKBf7 z>!S`=R)wq#+MMPqFF*eY=su2;@9*x0NP=z%yTAUwY4$Y@k**L)Nd|>b zP@LWU{r&yixpS|tiQHWJ`daDhYg4sCU)|c8U2pO1^mP5tPfjMMr*E(P{ES!H?8eQT zXXjd*hhAc0SRyv(Dci=)n>M|=vNHJbvEE)cWx>1q>*qiJX+KB!%KiKIAy;S=K0any z{Ors3@BVYwFfgoSPv7ZyWznKVQf4_8`S^{lAP+d)J zU(L@;H*Z$n-&b32p~m3w%E`GIbdlq`2M3!wJ3HrZljuEG{QTVChZ~mr&;R!&eQnrp zP)XxE3v_P)e>*FKhigWLfN6aE{cX9oXU&>bcWcs=DJ4HXJhYcIwz09%(|dQhU8wWO znKL=>@9nMp{Os@KL`H@naVaSm--Q7=_x9Ype*OB9BTp_L>lWAlcXGzRfB)vE@fsT% zo}6!A@8IC@_*n1%6d#6$koLAV9?-$};(9R~wr`()`l-v3%P;5IRDQa>xONvuZdTT< zC7zQH9B|Mv;bc&1X>03Qm~wK`!Gj0q&6~Gu*)q4qAOHOPyx%tDlWRtB@MTtRv7CE* zB3FhiivyY6{CwfVxIaX&wut*ex9ZuKTkh?pR#-3pJN;oCr%XA4%@PI zYwXsnsS?*V1g#9=Z=ieRqCphZjXga>}-JXv8hPDBWwOnJ53dlI@(t zQoj>fX=)QY9z+l)i?R`RFXR_)?RcFYHjN1=jnDInaIdYofIf3aKXXp8r=lEUO_j|uV$mGUt^LE z{q;#PQa87*Te_t}^j^8qclCHqh6C#l7@GWP%&DAubH-yfq0%~c$<8zOZ_nJADJ&Rmq5nD4JI_6G;hOpF!B3}JS}*<6nbL7WZ~YOI z#>-M27J6?K#XlVoC^Eg4AOGfRbla5Un|o$nIU1Uk?)Sgf|G^fvc_nQd+N=KS3hsVq zl(O#ApB;y$#>Qnn+%v!Zk4S(0!v3q14}J*!x1M?RnGTOhw*wb>eboK+X*L7HhtCBO z4ExW&*sn9IW76s=9g3`ilHdGlv%~JyeJiqjxuk4c+k(9h-EY46e*#+s z7jEBm{=L##d$rxqH(xM(f2PxJj%Ru1V}lKH@`4AgECOy!{g7$CS@&PK+xx}hd4?bV zxu<$@)=gftZs)UEH!c*^MShaZwQEyXGVb5wvV@;oU1@Wp@!HR`KX65Bepg94k+JQu z_{WZto=r0j&+-+T_Vn?)*6(xAY?(b{ZD?fcKO635ptOMfyCZ|j%(RrE%$=);+7 zH^01X4R>LC+xvN9ww_SoEo*)alPTUc|H`i3e6lcSna+u=Y1v}#k%wOT8ct7MS>YeO zea*(#(_G{@(|ov7GK0T&SGc(7_`ko;_x?l8Tis)aJZD<3PW|a5vg!5N9d({_Iz9LO zDF2=J*+uF<>v=2x;Nu(9cxTP5{poA)|KiRPhKBVI;(0xCd-krHpt?SEhIzJ1FMrSO z8Y9gsEBZYYoKu2UOx*qLf``mU{j2{cNDLX!>*0s7Xnt#oZq8f)fft4ZqVUqBxD`0@IDuZTq}8n(aY7o2!T)m~6j+iZ)*iKF*- z%GK*zq}nhG3VOGh&(fH;|Nj*anU8^i|G)ItMClygb9I@HEKl}p zpJ1}x_2T;doG<^}XJjz4n)9?RBwZ=-q(rIZt?w3sf{#}eoI55c`1`2e$Ir_@=I*{V zO@CVLwG=L^eqDThXV1*1KQgbGop<`(dty_~zvtSoz1lj=SMj<;Dcuae z&*FUHwf)a+Ob70>9ltPv%VKV0N5$W2C8d9@RzAw5FEfJIvn0pgt2=2h)zW#H*CQSq zLBpc96F=4kTzhG2s(svU{;Ts7)~bABop@)@M0LTyg@1&+g6?w}NOf%eTOco)ub^af zY{{B8D)TzNyw`lSM{T0WyEq}GMSr}qFSXrKU$jGby976T<(JkIFWW7z#Yjnu$Y=dA z5fOYD##b-CY^tT{$y+Pe{JR?6ra2+mrJ6H2-z=kX@AAp9T@^vnZRQ12{zyAdn%7a` zYuUYL*{z1)IkBEQn@(Jr)Y0)|jjFRt+Eeb}Cv$g9|Nq^Wf#HYfn;#z^x1M8=JTU9& zX>ZRTbJR}#|1a|RqM`fkO>2CV&OX=MS~FinVp;XM*;S(9JEv`46*+77reAN3r@zVd z+FZnaEhzQYhFOPvH1!ppuIY{rFATO2P5H@gcv@WTRCi?f`t@2*GeZsI53Rd-tn05% zanvS0S5==W7xylX&1CO%S*G!J$=-^-%ff;)`Ddlfyc;v?{M72YPj`>9gkFuh7HSjv zb(Qx#+3tv{o{kqsttYdk3Wxb$-&1*HTK<8b>pg3}_}wd7vN!!xz|k$@=U4Ub54xh0 z9krup<&(OaB(YFO@zuvleMApFT+BDiB;%jvz1<1yyV>-FR&9&&u))|sjb!rI}RT=J)B=jML>te;qH9T;RG{`Kxp2Z`N#)|g5< z&8z=&BQEO1F^NwPzU$ra;<7e;FIS|Q=9T#7%+Dv~;WH;%J#1I5nfm;7Mg03*wUbMY z_SHD|*k(%~HT3;_CQHINwL(dAr-=T~hvp0az5V_B+&t~5Eid}_J2Ct?{lLm(iRinh z&HwK;_E+U~c>OF~qNws|`~7ngpT6GyIKS$n`m3M+Ki9~~Htqb6XvvwJ?~W zK_c-f&kxPQ!or0M7dkEfq51FMKkKqLpbOtX6CGYlPt7)8e?gPMp~vW0AsffJLx&bc zY)k?vJU`FY*xdZQMkXV}KKAPab*Lre^s$K{FBlkBIxoK*ySuFS`A^V=yho1Av9F)k zu_^t0+=c|lg^L*({_wRuuUu%%u%s#X_O`8=mycb#Wc1U0R^p5sHzM5KkF)d3ameyA zJScx4A!XEgYiqW)NY{-UH~gc0e0-|DyiiO~0d;?0Uzp$-5g9pEJ6!Ln*y4*fmibCA z{KUZUN72M2MGkcCkGc8(FAKZ7y}!M=$$EyF;e+~x3ll0$GA=l*zpnm2a%s@lH#dWC z*f22EGx74yZ13#!oNHA&>3@!a#4PiCy&O4^9c^tsORK-XJE^|w^i=Ki)Kt&_dc*y8 zkg!{?l+Af<5w1H&c^M>S=WSzp=<4DEx@mH&n|9=;Cp(Lue|mmiUP5Aq7PHsVAa!;1 zw#1-R6HpgQ(%Zqq(9rPlvEJKzDvP!5m8`v(@#yj6w{PAAWglp1VX3bEUHSQ0?N<|V z2GE#?3CH0C1D&`%6{n_X`pz=p z2{QZ=zq)qL>+DFJ5w;J{P zaABChxp1Lk?$4h;Ev>EPt;^or*qB`W?99fE8wITbS7qMY^YiWP?cJi<&mKN}`1rA~ zyu5yi62pm*3l}DYfkp#1r=4Bs+E@UQ3I9e0UhXK8}~4zr3U* zW^2~eiy0zTA`B@)JNS54zJGgr`(pS0d%Mfm_xJa2-(K!!zx?myy!-oXb8c)na^y(m z=Vzg-LOfqsFid#(P(mtba@_j)w$)`5m0Xtuy?lFnd-blKi{UZ=-HW>_0JE9`p!1nTl#w1?Ag&T zFD-57la&$|R|n}>apA%QE+%GX(5j-MCnt6mKYw>?>*|1oPEP7VlbjYxSe3lkntfeK zS(#g0Z%@t7qL?{s4fh*fU-;m@Xwf3KUa771|Nl)?b}xH-YierbLgCKSbFItU_~qrq z#X;v8XoargkY#5u5|VK~oPX(MiE;WlouDNhK`ZwZKlcMq4F5lW_Uzjm8K4Lr=ey!(^i-`nfjC1P3r?#|20%a{Al&&$lzEKv%~zOv%t+1cjaQ?;JH zd}&$n;lYg?5etPG9oQP$+TuaG>Q7D8?w2%9JJ`f(UG_#JV#9(9oknb}PKFs57J$Y@ zs=uGRcoB58fXs1DSuO?_-s=k=rthu%oc83zL{P|>W`#UIH@DF5_x8JctIL0UcxY&7 zxNqOStEm0zA+ ztzLEL=;m29|K1w>dG&AW?!W8j?bSQ^e$wSXrzQP%ls_-eond@UKmV^+*{6PQ+pPKg z`hkYqs@msVyuLjrYI{a~$=6q|Pxi}pZwW8cxmokC?84FRhdz4h?A%Iv4=((A>wYe_ z+B5H{%#xUx0~5bmH-)Co$+6$(^42^tqF~-9zia#dZ|m#pyLa#2`uP3tuB`>#Ens77 zJJsvy`+7l!2`?W=NbP%lZEduucGw(~%uAD0y|?Awo@QVF&tAr0dG%p2)<@y%zRcb6 zxvcAKp!DwE*qucaQ@@HWE&dbly75}&rq}-VRUePKtz49{z5Y|Q=GjjoCr{ts{W0SE z?#?ac`gbl*ISSYD_l%85?`rdVS2_6>fWtxN>jYoBH}0Z@k2egdcx) z-I#E7cQW_w)pg%%?I)am%FM>Iqw;fFP0gOgiytp??Jg*kVz6^`K5V|H?(Z(|>3UyZ zUCq9_>gv&MaqX}*GoqBAYlJj(G>Na@FZ%r;RHmM`Nd38Gc9^WV_;SC0 zoA=h1DNPOWx@GaT{a4cF?XT{iz8ZH*E2k*!{LG{ZJv}|Y-zy(m^mSNljhegZ>y}sk z%KL5hZiwCQouw2c`fBfz_Kyemd_FHU$^PrJXE!$;t`-e6TvCv6>GgfKrB5~U_w((q z{870#tTmKpDVw$RZqT{eHWdZu=h+_Z7PqhaQ?ZJNn?X`mN^0LX(1GKvTsLpr0F5Sl zPuCOj%=$MWW9ur%71U||3D-A=C8Ao zCtG^OEtD2j?021ReO>dL;UqWzDUT|R-d^>s-evaDqrYLnP} zJNddQ$NyjRxR>!u*wy~lnZ7rd?d?@Te;4KLF&tbLh8G@RYT&H<`RdBr6CN+Rxv#yl z{rYcX=FP?a_wDZeS-UW}Z?{QCOqay+U(C%-=l&m_zyH5gWB{8&x_*WW02&QlG>+O+CO{$lIzksr_5!~OP9&-`Saz_HRiRur-Vz|#d=(0 zlf56FvSD{uM@Q-EYZG_ZsfPHgtc*Eyang-9Uw4;0U3b6A`PxnH+Dewym6gX%?5&o2 zP;a^9#+=A~du>IyBt30TS=<&rnaj0NW$BLe?;S5be&sfo^GrW8BiefY%(&?eF1PxV zEv{w6cGd6s{G@j^J73lJbwBo6Ke~6v=Fgt2)rY#SSRVcR|I(+~{0x#O?{6|v=wR_S ziaT$(Q@dy7sySCyFT7IxUBKrp>4h@T>t0QiRa^7>pH$n zocVeG|7mY+HlEhZ-2HfO?#tU-#LeGk-o7mDKbz4Z>Tv$+3G0&L?Zb=NltSP3)plk$ zynm&ow9?1V?f-@hrKHc|nF%|}er3w|yZE1bGs$%I{Mi?UYk#DEdic_^EAHvr&XWG# zqLob@DMiLglUC()U%ssOH>`anFQ@+bZ_;Ha7EBVF7~pH)*93#xL5S;=f-nw4X5t(oSb^f=62z^Uzy0C!xNnPNMOMk5w>nh!B021!&_=Cp z`8RwF6RZkk!M6sX9E~5-&c>Lz{!K)=Xe5H{^=%L z)+*nuzz25=`rDS4#b?Fccw>L>#g`vl>|JZ;UqAHn_wPT;TNLL1d;V+v4|VH#&1?-{ znvydFCLG$F8oc@O>F{Hgv8{QhNo!;1Qb47GwX-{>zLZ?^-q?r z?(mqkbxQe4nf>;inLoDvJ$~l%ZP1*O?81eP5zErTCQY*39JM0H=GFHZA)%FT0}5mW zj~BY!>hBJ!-I*jh(M-=z+w`x&$LGgO*V)zdU0!un{@dR@C88UTzWSOG{_gTcOL1}G z$we-Hrv)ckz5A>bF~>?z?cL>?1-Dku{#KGZ`;>Po1H&J46O#^yPbIM_7gRs&(JSfg z-@bZvws1PX|9Zji^J9HL7Jd7(sWEr?x=7>78&3r0&Y3agb&aB>kzCich z^PmSm{k_BQ^4l2hTN(X*Yk5aUKj)l#Tb^E$wRWqV@?zVckeHGOd+N@{-i`hJH9PFs zYM+&z>{ng%{q?gP+g9xPmGkf3<+L+A)-NvBoV|Q7G@n1>>5c~(lTWP`N}JcC z?Bx~5Yjt+oDF%igYBJ8v7lbN~T~UkP_NTO#FJ{-~?RkmP#l~&Ap+@^_`yL$?dH!>X zc9`h?-TBck)s}|3$cL>_N)na7wzI3Y#64)o5?ODTYs}2dR^nT|HkM31bnDEd4h_XO z5gq$nM^1QsR2T8J~nq`0P%$91t_9J4w zYLK8{q2a_=Q>tqsB%M#qF!VXHn?=c3X=P}h=1U$nrN@``pY7AVR`cZDoTD{Wy=!-V zJNWSOR@k&pKLzugg|BX;bXyf9?vLkkJ)uq``gv7P`#Y5^?jzf!55bnD0*__ z>l8mdV^>=9$tqm6t?ZVK=7PI3-mx#6n0{ZiJNoIxnwVW#N4@%!-PdR3@3|RS9oc;{ zI=&?DX5OLApFGVU9b5PPq_koA3!!%lZ(m&PdP6%~9()YL^}xL)4|pndZ$B{7Ro@q7 zwe#%*K8vHg3@`b!qYqv`vSHg*@qhV8wbn-YdQEMzNcz9tYHO6KXQ=%3dB5&m3ca<% z;&8p*l&_hbI%}=gUf=Xf>e@D@h7^l7wu`^_i>|iQ^Z2^iTK{ic-n^z_tI3x6HFnxOaW;yc;s{=Efp z25Vn#KcC0UkRoHz#%6dbXp5KS=IU-S`Og^_cRF7#x_JBF%BQAE5r3O^OGVC>xoP@6 z&*s`ve-DNRlh!u2!lbij&vcy3OwLT4e0u5c7o0ZbKVD6~!9V}Xs>hG6XqH_&cH~H* zi;LFpH(@t}BR8bh^}M~XB~x;}k(~0kBLc4X zZ(Q2)^vL1g({_r#FV;1Bbz{@SbL-am?k+3UfBf{_wUqyVGae~zJ1=2a`|^@s>K(PW z+(JLN*%*FsTwmz8Mnc#~F5aZF%_!mZ!sAbmueA$MZl3~a%<-x}f7+u}vEu3v-|(z$ zUN?~LI4f3pK3O7jvt;z~=hekm*52#-aItiL{tWHxy}N6>ZG!esDvvvDWwtUt|1{KSWjk5azA&P05gLis7@1|e`G^x^c8(c z$wQjA%rm3Z>;;e7?sj+SUmw5YqlZ=Uu{*bZo++}pe&Owl_&qnba9m%esw<(ibxFWP z87{Z1`RhfJU6*8RcJS{qt|-67`y<^YYoV zZKz+N_nri`nOJbUxc6c>8zn zzU4pt7Hkn061ub{^Rn-3GhRs}mkk083Q`#vKW2F?y|pRz^t#yHpgXU27Cl`ZzJ6JK zn>izUjrly4#s3#s-d48w^u{)|BJ`-t`z-Evw{&H%WE^k#_U_!f+&s(vtmkV2Ua?)3 z-v86B_cuIEUHi>-+V?g0a$a2e;AFBpc&qS3 zDdV=6ckia(?D;3FAA0}({-)&1ivOoo8E(i_4gW6nKALasMCS(P7v=Bo@k$ykaqE>b z&AI}*_o|gk^pZU@LkDlzxrXwyvp*I#o9X$j z-n8MzxfZoKVYvY-L|me_hAn&~U!rN7e4%ye&(7dlxyCE6EGDfAdAZYtO;Avhr){cs z__-N|iz7FuO;+>Wv~{cRa|Q;5D2ukX_em1VYJY#@m$xe^EBp58(=3C;rl__5KAyfL zw>n_yigcx*h??#5CYSr~%UK(s*#X;y^z|smlAy>X^|tc%b$d!)UUKi3vn_sh=F(E{ z!1D|fg!&dfT;9^&&M$BG=gdswTLjd%Ot!7~ zaNzjy=SRE6i=UlY8NYvDfFXm!nF|*_aIcHqeQkez{aiou+9u3L|Upqx3RRf**|^wu<+v}S9$sQ@9ys2o_+n@&(F`D)P?57 zt&iW3(71c|?x2-5A09aV|Mz!k(3uU~4Ex!5c~>qDT^$CB$nx^_0Sgb!Fid{*_;IVB z$_(drzMZ>w&$ljr_vhzlZZVw=pdn5{h7^yC3=dPUrA1#}Ty%7Fym;|qUS3}I^>wCF zy-w|3;f{`uUteFBH_yBC=x8^;yxp58U+TFl85sVUn3yct^ycQ~_0ikkU0CRRdt0t{ z__{kgi=VgnO_5u)Xwk24Z=?6u{axle8?G+rpYRCKPNdHy|{^78WH;?EN%Ot7o{^$d`wzf8C81PwRTiceU0UB;S5{2*Y z?cK4%!rXlO)z#tYechKE8yibsUz=-^dFj$p@4tWkfDfZrVmOg=;lc#DO=)L$RegQ6 z*u5V#1e$Ym)4zZJoYa5SPMkQg_Scup8ygZ&Pt#>)V#>O*LJ>4{I_JWL33Yew+&MST z);jS}i(T!nEjc$oot&)hr2fk^YHiuMIhJyAa$#X%ii(Q=|9s{@z3h9_RE7`V9h{x3 zvTtq4EGQ^w;M0Ic({E!=!O zcV|aON5>VZfVE-Aj~#nZ8<+f z6PX!O6hO*jVq)y(zA!U2eS2wX_l8OahJP$lQbwZr`T4C*jxJtHFP%Q^{lbEQVLv}F z?@X_DK3R#jiC#-j)(cqiF&udB?CiX3l79TYnxCH}dD_m+wdS77%J887ft{2^ zE1trZ&5EMOO#(GKGM-*iU1^~vcy&eS?!?E(R$8ku9H>7qCrM$++1cjbzrMR=z1~@? zFY`&I>G7G%U&Y?pe($L3ty}l^l0B z7sHR;53I<&nl91LP(U;C@4iUsUEU`$IC!5=eYGO+@_x%o^XSd_-_+h21-m}_nVKIq z*Z2Jt28K+w;};aXzVU9}{^8P3{ra25O|v$Krv%qk|Ek!WWqE6Ar}XWoXQ!sy%#13@ zZj{epIAQXEU#er`k`>C{DLu8$f4XmECSU3NrdGxyD448PS~&IA%=#Pp>3-8JHW~TH zSQ$Bn-Cnlmcl*xGp=mBIWtB^JZWa0^0?;K6fCnuNo9zIZ2{nb{+c0 z6Zq}s<16nsM~G~?xVLX&+UCgu(a2JiagLkny)`lhdOj_FN7&+xgAy z#RcJO`wpL38+rC=a@F#vv}K9;;yzv+UBbVl85kOpGX$3ZSiJt)JX3+0({g8~x2~_e zCXu`-`C23I&)M3C-Fa&*%fu}!(y~wPliYmYW0%DI`g=Z-(zX6)rRCaqXA127xM^a9 z-Ie#Q&;NZaT;o}n>~j6v!r$|ZEI)g9o>WkGU-6eJlE#q^{XV<+@q*7iK zzUh4YSBb7FHin4~&dnZ$|4utu|8`+_G3d&PZ1XeJe6l9R^Sez?$BU@^%xFZzi>gp?U`~~l-1@V`#WY^zc}0L(Ux=eZf=Fr%C)wF zf~rzNol8FbuYdSl`cw*+Qc{P3BwmMeYMfUnR{udsSRqeono%gmP4lPC+dA3C zLZr|k%X}ZBbJ1%~;mo^9XPX0-H6)+AS$oC5a+*iv#>HOiw&^WM*>TG(*Eixi6VJ)D z|1!n*J=VLvW5e4gXMe3IcPW~!D6Csq?D?bcZMX8Q9erXuS6TF2X!niQ{$dWgK62jw zZ_UdXUM75gcGmh>&zw+$^VjDG-1NRO(JeGCvd;VQ>)kO|p3e{ROjLh%_wbZxyLzqi zxT>FbejF|jy}5arXH1Aq^7QG8=KVht-;n+Op8nGt-P`m2AKAyiu#y>+H(z-u3$C31 zntA`f9>XUGzsIH2{QRTN{``uIOU;$@Z}}M%SYpV&%<_yT8&ma%cYm*zPXm=d((fOc zaDndh0rwA@?Ue2+3RGSqQsr#qxQ zeh#`E!N}g;eql2M!yiHN9fYNRq34C1kcw|&$P0$dN4K_S|Ni#=dGDSJj-ZW#tH2YB ztPBht;c7WA?(Qxxe|hcpHW&A>ETxiZ`Hy?sj%r(-n_;u9{CyngPAZ0pr*e4rCOlkZ z_;|VK>kY@1(hC=E{xkiJYIOgs8*f}QZhToe8MMHkA!*9Wb|)InHhWI?(fPy?z`K6cd30^(q#7+7WS^|HWj~o zRQ}D+)e}B5x?9`>j@tWs9U0)v$x?+r>!-*~L z#p>|&(>|`scxoWaYdArvJ^Df-H#Avn6 z((ZCwlawb*J_@UGg4DH4`Ix37XQt0gNYWc96Vggp)vjT347QL;j`8eyDzVqR5 zz4$YmB!B#>vg+>ceq0&1HthA+UpH<a?VPCWh8={Xa_4_Th`-Q}NNtuK(desuTq zjhnZ3Z@aH&zFFML@0L-+}AJLtG~Z{c6RpZr$zIgSI(R6 zp<*@n+{u%g78VgNG8h>4D<7Wo)X&FfP3ZQAFJ~{*i~VaPBA~_3a6tYP z1MlB2FE3A$OWnF{Dz|y0PNMO>*3b4+)<0tj{g@}0dg|`ZV)qw1N!K^0`%iuKS--%t z_*qFwiHHF|!-4q%)z0_#RBoP_|7UCMqF(4U6@Ga0Fre6N=_*tIc{Z9QVu9Hg z7ad)m>M_MDb$j03mb0LxHZFHB9Pg7|9kti*omH{ysS;7E?gN{w%u8~1F3jvMlQz#4 zvg)q5dE>?u&ERL3+uz>WDr~^Ra6n!t-+6uX{(Y{q{rqyxZi}zgE)fgdaPIljOHUo{ zJ1$=4H(Rkp?BlDOo0sq0X(?!FX=!O;0ZN_^zIT4fT-`Z&*$=&_$Sr^MjDH_;N*5H* zeY1K&@7XAmHQOp4+H|_?*`L7ax;E_gty@A9*RRiCAGa5@H08r@m+KeY7H_<6U;ACo zGJefpy;fJVg^xQXItokg**e#K;u`Dixj`BtPR9l3zPh@4zE!D~mDR0d$J}1%Ffi=* zkKtW=F=N)Y4B-z?_6EQ94Qkzaq43D*j0>%AUd{}aEB$kV5!CN_{Ac1^>+(K1TPr0c zrLAl}brQF(Trt_oHpOeHtW}AGechg|TT8QgIT=1UD`mZyF=K|*R_Q%YWx32Fl@e2( zjtg4Pdp=)I_UL}A;%{Fz@>CuC`t0m%&`lKG-LuVdqdJ9DTlzl7e*E~+IQ^VW!Gi-c z43n$Aztf%iueARUV}rtxOBeLx_pJ%pDmHty+rOyE5^aK3erqod=)$B&kEe}2p`Otva}Gec2t#r@so`9D591f3}T_3PTu)zdB%GAMi&KDxh$ zk&*G;(!D-irFsF?Mz4NOf1-8YJo@R!r`n;`)!)`!h^*^!TRhVwbJFI`o9E8GJJ-70 zs{Gv?#aA;sxixc+-D+D*_T>G4^7q`a+rA|}QnGLf=XL*mIX||{ z-I(ScK6$p0&l~QyU$XCnuV3|KUB&~Q+G^v>stF%2YgcU#+_~razv4-+Lu22~3NA{i ze$@Em{+~r1CsuD+TO&LeZ-|M%kMGhbg{&o5!1U}#wQ?99xkr=|*9 z`Du0Z^?~+o+}m62-Y0W&xxYMUVNZzq6T>e(GdKT#`~LBr%Kz`L8VkDaGx4!sy#4ec zF&(}2GalXB^z7;s)=eACtPcObZY(&L+i=39z`BjS{T(KIs`6F#8vd}IxnY0(vb#qk zqwdbpi@j|7M1tM=+t=K;J5`hxx%2bP;ay$1%6|HhEo)cighnN%`^`GPK6#bM#4P3B z`_kXq&urekqtx1}^4q3=%w3qafJultap;BsdD9-~e%^iDdr?NxBFRUhyPja&c!Z1TsDDO0AHBpzyc zbhP{ViHVQDy}d1KRdS-nTv9|cDCPgZzroA>WKA*x)<$i8cYi;4^_dNeh3WFES<)u&l?ZHO*hrZ64y2in<^5kjH?HyW`Cx2dV`+jqYn!MKRJzm-OPvgmS2gl6MT2hXyvT}t&jfuC5?n%C(Zp9fA{WMudi0_ zf4*^*E}Z@xH2BaXVYsN|+10J`;v}!otmm`o`0|A(h^Vb zAe;z8O2x}s9$9JW=#^P(Z=e5q)aspYYn7Uk5^MS$@fGURa$jgEPP(FhLT2xp|2NJZ zx#3+sWx~8WYvuN;7hcSDcH~ce{UlTU5%1jT>N5Q1w?spBxJu=@OGo~C^rYZv7ngfy z;p2x(K0kDOUGpi*vyE9%X5z;ea$h%Ww%U|%dWP}bY>i1yC7=B>fG)Be>pN zK=3ZlmF&*KQ<@XKUKCE4*RgHhrL0cPg!3--f6r?MA8Is}II()m9jlWCkN4>*%#+*y zxbt!T(cmlGe%>x#oy-%By0^^iHhHt~omYe7X(7Rrc5({nx3bNRK0nF7FrM$<;@~^+ zJQ@>4R@u)kGB479`t`%Z!&jufwI4ck=+?H}+iN0?^YhzShX zX#KzSdw=s~Jj#2_@GnMBkyBcPMN%tRP{gJ1N^7*E%!6;G*Fy`h1b$x;6&}sr7nL4c z+Q2NXSJ3=`!F9sJ1nv{({NBC$%N%kitkL_y&p)5d9=iZp~e-^(gofEnG?42D@^nd(~rBeCG&FC*H^X`9};eCNWA)f(&y{NB_%e6kB;2hn*IFD%*#u?r{A~{ zu}#~FBXfzC*9&R$ye*l}GjG*REZx0Q)Y?cnsl?aAyCJ(=M3o_6g;g9w4;MqjA_j&_ zMH-?4YiCblaF|iha^Q~t!bX-!EDQ!46m}2xSkZhu6#9 zmaLm#oWEq-?gj=shNCAK7&wF+{&eXvEIQ7RSZ%vu)5F}4&zeOT8jh>Zc|GlE@GIe~ z!Kw_du|4(A*-RSG-Ck((_T-%(U6x1x&fE3QIj3e@+54-{udEE7YGA{_aMt9;P_URKIV&U4P(IhlloxJN}9} zg>w%z?cvJ#K4sP{tMqeoqPOSi#_lTl@Zg~7&pF@1y}Z0Yd;OD=9=+fH|K9fe_-&^h zIWif(h5ucn6#qR$1<8i;GrDaZdi$GBPj0-PT z-`Q2V+O=Ek_O@K>k{1`=-QBILqjO`ONAN_IpO5?P-Q3-|Tb+%Ull$PnV`aWAZ5As`8%Pi^+zuW?s9wL;;&nsn|7D6^7K)79|6p3-^N<7=u@f_{{g-|l`2FtJYb%Ng7T&>WRsZyw_jBLXzv_?6KKazK`1bmz*^(!oK3n_e*On~|3zE*g z+dV7BXU6t91{*}@YxQwj*7#nn%bMS|zFcsAjf?!5@8;X4l!A2Pp|s8_H~Jn+mg+8Dtp&-Rs2g+nwatL&(E{7Oto_*f6o2L z!yxv~jycD>>`lb~*BjZN&O3M4KJR0n>}=j$JQeoy7#mDhrLW`h^{RaCski^;j+16> zOn0k#Um5qmSF8Ul6Y}rFvt-K;(Uo7PidPmdU27t&s4KZ7nF7G|0{H`n7!}q_KUkp6yJ5l zEnTw6fM;{{_dIs?_9;`QoH%i!^7Av!R{1qdpM785y3OS9XIlL`@#%B-Xm(kaH!N(p zyfO9g_Uy>hM~@yonmWIIWB#N)xpJW)F*v`Q(cyUArE4F*Oy)J8*X>n5rS^T)>v%P@ONtHo zH}fC)@3r01b*gt_;rlz>>mB*5M2oxUEMK1f^82-_FE0$s-rRWe<_(vmnBtO`t8#;* zm+~+ySSQHNpfj8ETiK&2WqUmOCIs?sz4zttjKaW|mqN0mwDXR6uSh?}D4F~C%~nErLB7!!|f+8#6DGE@}HJ$?vC6o_u+6 z(OE%2#yN#R-1d6e z+hmEVZ&80mjoD{z|MpG!U);9;%f3I7Q<*a7ri@+I^E1~~PflTYDmHQYeanKHGegt6o2E{x=QxO%~1@ z?P3-(X?RV^->-Vw;Q7?|?GMw>&AsOJ`c~k{Nx_pQOPYLm5cy`do>WV{exr$-)%z(X z7E{f(KUZrzEv(_OPc^pCyYk^qE`})#xwkl@t|d*ApK#7fVW0Z5FYncNlz3~hpYWg3 ze@oAk{m`|R%LhA`t1qqFuVnpY&ZEM3{Ud*C_o`{1-&_6NLgw7l)6=zW<}orzw9ec7 zd`6*Fc$i$ZBjaSLs#WvmFTZW-cO6X-|y3H$arC^Z1(L~ z$}_h+-hz|MZf*A~af}7=`Y&Qt^lUx7cU)Hvm0VTAz`#+zkV$Sis{zBAzZF~!g24aHQ-RekODTrTceYhLqNVoIJ!jtDpL_6B(BX_#4_wb& z{cz-1{hDa|m6r3*Gy8p>c5WF%)Uj_W1q*-tOlI)t+f`}axjXIbd|Rm(TB1q}6ZXp; z-uU`~n9rw;r_0~7%$KuX`FDBL?v(r1-xpn0V6RK9<(9DyS8DMJJ-zu33&ZCR^NfDn z<#;lYA^&&clzYp4zkGPWEKph*@UJfa-R=L1QZocU^e{3AEAxGxxYSi*WzWM|lcp{3 zVfQ$@l+mKAs9jrjm5R4$L9672xC{FxZxi&e{FbtA*StsTa#TJ@sJcdMkG|r$p=J^P z_N#}Y!(>947+x@lGwisPmXZ{)sA9%uzJ|tDO9w4}1_vH320MuvvzXm8KO|PH3URnm z`Th0nHD7KO)P?PvqggBUBjDG~Qz1o7w>S1r4}W>j^1N~JDX%Ku>pz#zkLv$b`bG5J zhVIE(^^ZhPy6_s8{@P2wdpzwDla4U1$~m(m+~mWFX>JS*xl+Xo zXYA6cif-d-P&v4ibG@kV*}J-36C2kupPzW#B_w9O=h>hypIT=GF~0aB@sOY4vU=%7 z#s{CaH0+x-kDXzO@8Wx_PFzSo%EMs8%l+hMRkW~I!zTMOA*cTj{ByFo7I-Y%x}%2S zLFP*)hVbQO!WCv!uhx}p^qNwY`)bZ+Em=K=f}IQpo-%5LBqT)j2d;@O)`{Z#)MC6p zf5%*Z4F-lMpB~M5wCVSj*xEKmhv%$&&)#1db%2>cSC64VBk%uRHQPC_L;gj#3aom$ z>bA_Sr7Oeiv-udlywlgP(CZFh`e3p77xxc`im&2J`?N0k?G?-XHGiYs9sPuIcCF!F(ls zZo7PK<(Fj3i!q+9D_+OmnX2_W?!%#(|6K~>uH4+b!vAyJV=M1nyRU~mz4O2Jusy?v z>$7^(yqE1LnZH%H_T7cD{byz@sW#j9{N~oTrKukezrDsYb8p%r*K4s!_l{cXCi$(2 z_Ff(J&hpBEf#JZ`jIDcuwfFMz z-WT(I+Quf!-yP)ka-L1(?(*PgcZ&P#{6tH{&7C-E-Abc3KGEKLo!{BrL*k(N^~>9? zC|~lP;cr`G+_aNnlKu8WW{K%6k*qx~9ygdK|B=7`?w{ZP%%hV&KR>@de*eEGCnryh zx$-=qXk!H5uV4B53v|TAPdBb#|9R!=!};ZpU%jqRTm0Q^TKCZfpMN?wP6}3;&$8f& z$cNBPNdhlEYkySvKGQgzPsTz(Mkc1R-GL*s<&|0cKd<+$Ya)K_6q9U!;|Wa94jC!kgfv+#=xgibGgRu|*)r-Smr9iRSdSJsWekv@ra%G+BD|jqSP@7kp3Oog`f3 zBNU_je(SoTPaTV^-#;;~nQ*$hAwTKvnzilihr_>aJ=48=*W>k%w+T<@-=M4Uw)pAu z4>QezKV4Y*wpsfB=0u0X{`sFH7v0!-d7c07>P?n){8EY`p;_-_W(iE1Z2ckr=n1tq z(`*(UxFnI{aQu0rS^tl&u!t2-AEJ5RtgkNo-Iw(IV(`D~cT8%1{=c(-*ZFIfZT8Ju z9}lnVY^=Qe{8e)F)NSjS81m+dB}ZSm`um1iysGl2E3CHC!9D3M0ivsRa^@!aaWO6t zVr#H0i4)U)Y;w8ngT+!A2dBN)r*W=F zm~39LczyDnJJo+a`8A!`{P8G*h%4iXc=M%I+ml5b_Uwo_cU3R?$LG@dt2fOLEZVpu z=l1rss~d9OnAY9k&rf?EQdn2_Q&7{AmEqB&?Yg&7}|2QUH3u7#3WII&ea87}d@j#5d{>|V+kSNkbD8$Lt5Iq`Rx1x90u(IVoYr-N(+48jmmArk6YQW3=j) zQw|&p&#%~7oDmR|t$)&+;gvP#wjRN6*OQB;GBC(Ah+o_kcw1G|;l}L3H99-CdRBca zd^s^>*FIhIfAfsyG6ngay{l&>vHX(Rrza)9Z5Ssqbqo_37VicbVxh={cO7Sg7o+mis65(UFD^ z%7@Mxo87xEZ?Hhw@!PB|S|9Q*HzV+I(Ha?3OBxVZvoH=`Ov^1lj93#a&j`b z8Ot5*DKgTzzN;&LNj3w6g4g~DD@sl~l$}!wrYPUM zH?jD!=7HDSLKqwZdupB6vdUH{svqHx-P#`2%+tW_Fo}Vor9pwQL51OkZ1|J)3=9YE z_61AN2w$^DrT6K*6I(qem%rPT*2VDPZ(pd-zs<~%Arlv`xzmm3i6YJacfYKXZIyxL44Zf>ix^dyP%|5>NA2dHSt?^=I5l(8Xi z?I+pQ%g%CceSKA_?(jnQpF9qQVM0@9OkrIAJ4SNGwd5($_4X3;^u>?MMDBT-*SIR? zwbnwhk0#q$Sa_`}v)asIv)CsA)zH~$=MX67BJ~zVxEfxU|uW$M`Irqw5bVlEn zyn1l?_jP+-n4DmYPE}}G6?1m3^YbYN3J-Lp*8L4)`s!n`^StPBZVThzpBX#+a<_7A zJ~)v*rg&YU5O3O@R^gH&ZpMl^ob&dCs7hFJUYlv)rgo(H^`*-nePpXQt=)RZkFKa~Co@j^*T#A>gr56b`+I%B$)DBdclCVE`V)6tOMKJm zC)|JAx0Dxdc&=hpa=K%~eO3%04mIE#=5O6{}bG7qs_1 z$~C^0QMz9Ce$j*VGjE6S%>8L^bF^1rRh8+M?V=0}{VUd{#C_yQS6;NCXwfpn&T^}n<=E`Wt z%X%_8Nb~>uKdYe9L|A_H59Odx#j7F7m7U?tVeh?J?=G8b^F!(ILgBskRl6d;{JACn z`nxRmUgOW}xfkEA3%$N}n7a)JBB?r=_@>zSzI>v1CNPpz|`tz z`h@jqZT=8^dXhlh+MQb&ecoiL`XjPMB4@ zwfedCG@ZEJ-;!ErV)Xxp_EScN87~CYp3X|ybDeo{O?6L@`uY=L zEz?UQE(kI)L`VuQ{jOY^q5dK7&}!y{>;r$c##iZ0zE>I;SS7Q*ZO#PeOSOilRM}Hn z8QkXY_g{Hz@>7*|!!7IP@iq8;cI=*hOKr(1@BA0l+h4Y7m<#ea_(fLtWi98Zx~t9% z8nb`$D5)?r<#c!V(Wl3kA36H-y<~}`Fx$F)H1H;m@9NzJ9S!*7J+P~eq z*5asS*UEH}+G?)fQNrir9h+~-Or7-b69>byi@G=EH%_^}+t$D+_viky2XpOIxT@~z z-8wqce4A+ejMXzF{q*0QG#3^Sl3c8-upnaVCo$6-oh%J2n^~&+PAg4eaOnB^$a$U7 z3YHQ-&!ef&k4j1|&=`VSsc>$9{mp9(u6j`MAG467A?_!z${2Q}f+;e~C zy0$;_T{La#*OL+nbFH$UzWv{u{WaxyqsF1UcSZNzfBVhx^~rd$|LcdAZz?D9b3Wgn z@aRh4&HekYoqAmy)uub!;CsZK9}RoWuB+*LNS9XJ{o!`(>f8U~9E!dncb{FD|LMGB zB`1UGB1Q+!4@yqkbYoUBdGt^0y`|?VAgs4`%Nn1`_g@d3I3KQ>qA_o}_Z9bztJo_| zID0hQMdx@hUQj%Kw0NDS!~C3}1vVe&uz78L(4phRdEl{*)Vj;t#2D4~{J+|I{UgsZ z-pVuw*5i*p-JO4Q?fw^c;^SDJ^Vi=`OTKO|^!ewL-@f|)_W1qHZ>;C`>yFf(-t*!0 z|7HGe{O{-K&PeII{J?f&d3g2G|8mD-3v4tVop_^Oe^iXI;myza(TCjq=9<;s^!xem z?fD~Z_q@0Ld2(QP-QQ2XKU*fmPBxq;=#;f+nTmjZZ3W?Y+FLM$HY&k9orR=#pYrkrBg#7hh~&ifr&x5vr|JZVBKG$Q9n@z#8x^ zdKXXdBCGk1uceFT?U^eg%;v<Rns9eMVtaWP#k#K!zi0Cf;&;d5Ysf$p*#gyyDybuTr&$ zeDLVeq^VP%K7T%a%9JBvyj#yseLQ`x#$V%u3#Ks&eEW7yPd>BiV5<|uItGRZx$}8A z7kUds#pr7Ers(Q1a8xib#4svW6(x&bT;neNdQtnsLL zp5A9*aG0lPufOuu_NUsBQBii4pHdzl>z!*|E@xBm;m^;{yu7@tcd*Gmj#{PNz5JZ- zpN0D#oj>dDzQ*D~p3aAg?Jtiqg+Dv;zFy^GVEx~^gh&0%Ihv!>J#AR9dlqj>jSRuT8@$-_xuSsFNnJ*vx-Ou>#%iX}_Yd?h?BW=sBTwVMA z$|Q|Kk4@%g4QU z?%ZkR7N2KRS@iDC&d0}kmF;ta11mLl&Ux}#v#@UBob|~u`;Hz>pA}cL%Kunm^ZPCjR5SecxY^C#}fr?2)mCwKif{m8?hvVY4@(UWC1`)hs{Jv!1^ zS68R()>HA}0pl@arxkl3UhU8PufMY=E%+1@ z6QdWuZ%@_NS2K;%zrDY2Z)iC2ZPzO1nLgXf-bV5B^A{I?K62#9$H&KuEli*O@8MXu zKE!stbN!35C4b6Nb{_Ytx+++@mv7CqnwpxbZ*O*HUS75?b~m4dLBhc%RyVg-SKdZ1 zW?^H~i`#SK?p<3gtyPZAY&p}L1%iTqq+9=rvXRSwc4p?sj~^KxJbSh+_qN$ouHW^4 z%)h_C|Nhq2*T25L{{Hs1xQIy0#~y)SJ+n%e-w4mTJnR1LNz5X}|MtAuCug~R`GFpG zR{hZ1=TF36dwMSKurjC9lywoZ>kNN>$t~}yJ@)u`fBKmj6KBoZm34L1ySux?uWYy) z5f*ms(b4Y1hYn5CkGHG(l3}yFSK!wotzV`GAAj`Pl(hO;#QpPErkE^WQF%P|dgzm` zI;Y4*kKTFTI@0vL*ngfz@K%+8X$PO0m|UH4f5W3YDpS1zH@5EM?d|U;Cns~s?3p#uYw7B+wVytG*syJzpO24>b(zjFW2YC{As6$zn16p$k&&GG z`$^JOnRnHJ3l>Q*6cjv~cEmHhMf<;%^4=NF+0g}W85m}45sG3BEqwRzx9Bn#yE|{U z79aPs+yCfxW@TmN(PPKHy}G)(`ujWMv@<78pI-gJIb+vi_kOeNYi~|YR{#F)ZuHiy zsq5D1-4<1BF>w=B-LifA_odwF^XAW+R9IerRc51;%CQsTcQt;Og>4jC(BZ|#aCYZ@ zJ@t1nfA%}~$=$L}JM+TJilzVfY`fYjCnu)<g_Riwx z4Gj&=?EG@pWozpG{sPVQUNQ(R-?ldO^tAH4c>Nupk9S|V8d`RN>#*|f=z>M>GS9T8 zGkEmhnOv0T_G0O0+o~@s-Xu#(OXuI)^Yho&*9#Xe)Q#F=QT{F_RX%WTYFb*KtaaIm z35t)8_wTR%o_Bj&?#*=^ic2Q0`cgHcx%u$l6$M7dDle~?w1qXxvBu}7$lFcUx&O&0 zIm*p$|DHWR;v7PboPBn7cKA(4Y11qbA)!r$kB_-_i~aoZXr-Ut6o`$y}GheShDV}*96J>3W=pt!aiQy-y3eeuzF+B zD^XR386WoTusogNHMQcAdfmILzXKnx(RqJobNYGrez~7Netda#6|`$?eO%_3U0Lzn zi8Jc||Gj?p?Aw=@m-EZnMC>eD+Sg}QuJ6o|$sqPl^Xk>Bdutw^;qT2p{_f@4DJy>O zb(OW|Vvlex(7AfBP3EQUoCiy;l|KyPD*UtGIp@nKrMy^EL&LX+F_I&%ICnp{rZeP20Ehy+hWH(GUw~>4QVPWn6&u?Y+ zF5Yco-#S%lU)##wYZ~rC9z_-TZ!gBS&J!!pYiql-?(i#j#dEbgZr|QK^J(kngRcx4 zjg5?M?I?VFcD8x`ogEL~y{r57CUTqhs*0B{UoLj*m9ne(F-0@@-JPA6m-$MA*1?3z z=iHmRC-1G6wsyCxceMqt^_u*PQ;vGssXm{XbfxTQ`}yL?{Pr)5A6KXMfB)?#V_kCf z)YF{npZMI}k8e&tAGfEX@K}%J?QOZyd#ko?-fVo^HI!jv#GmK$>;L`ze&7D@m&^C| zR%;iSO0)=Q9sOi^d*#32Z}0B>so0+%cS*ENTho70!Va}Pp?_K5w!Z)Gv0blx=KKx2 zCEY(we30fFcgL_VNQ6XZP^!9W`5^e)~;t=|7=OxQrDoMOXuzX%gD>` zFM4|F-rnlXvFE})y}hG1BpjS;T^_bR&bH)5z#l15rxlx4dOI;Yq@|@DKWD2k*=(C_ zxbTjGkKrDi(cyo79Js12E+ZpzW8Kqe(8k4MJ(63qu10N6>+SC5zWuajQiKjDctHob zy?<|?eoh8dR<4rDnsR|5ASUL{;nn%OBJN)-XK#=G_}U{}n#Wgt{@kPbd&J9r?wL7L z^7hjj>#jwhD1!fZxx)v44O6@Z*)b*3IjS zKRr2lb91`8yZh?H*LMgydHAk)e5OI6!&my>4D-jwU(CI0x?**S=!9p>kG*+ex5d9* z`Tx81W*_HFo@`wC=}GPHZ;3X_EdiCUEZXZHe|UJ<-fz~ef&+QUx4-3FKeX>rPkSLd zyKeNK(_b=!Z?0Ra^!4TC<%N%r?f?5t`gW^A%Y>@H*M7&F+4;?kjbC4`Pxw18`|PYQ z>APjjo~pL1vi;jpeLT$G-hNy5N{{MqZ#;c{d3ktJb_=u!1X*X~5}l>z{M|P~Vyti&u(^&#{xdu`bpcqz!Vx2?sClTDjK^N-Y6LFEzBVHEh|kg>UYz zVjb(-CGiXA-9NU{X61GBXZAM)%PfzaJlXj+N=wN1@?5LZPv5@HGtCyey;Y%Qf>qFQ z>!Wrlsi~#A(48ekptmJ$#UTCsXaV(<@`Pc+HbAxfc^r@bHc%Gc)tH(<>AD zzPaLV!x7M5bri{16aykJJ} z_p0FZvl;O}Tz{U=v$M3y`aO-;xt%X{_cSj>vF_N|SkQ8w?fLQBvK=`TOP3$yf0qhM1(z74 z%4C1p=H=xD3YxoKygffY=2V&Pebu+$w)JRpm*w4;5)ha$bLPo1%c)8#Do^h0ES_za zn*`R$Gt0JJZ|d~v*LThiSG;~~-}yAYmDgU+sOw0akuENFDRcVabuCv`hrhobU(d_W zZ(sk<=5}f5hChG)Ec2cH?)LWcmoHC#8ztc6VVv?7CsI7pFe;8{gt=L z<5+)vzh5tJTeT(Ys@67cM-D~bAYUOV29HU1Hotz~x%ijs`xkoWLcdo`7T;g9V|Dua zvfguZxP&-Zp4LBk@nT2D#YKY3ZUG`k!zx8Jl$DczeR=u&`}_L$sY)#ZzxKQ;ko{$A zUHd~s{Or>AG`tt~rC zUr%dpW(H@DX9rutvYFZWwrtx5Dwc2RJ+WV`@v2?EP6ATj-dM+~*kU52wk7KH>C@S_ zw!B>E+|3{FZ)`ts$=jdf46@9wYP zpMHMc_xJa=Z`hEqTdYOkQpk)8QVEAzIJKr`U0)ZQBmOBg#c1a4-M^byxmRWX=Wh{M zB{k!Ml!K0#dG4(*=k5Q0`S>w$_cZ<4mc?mRRlg=GyF)@pT5U_(j@sYb=FgA!@#*R8 z!i0i9KMJ>T|6KCu(IYkAS%1D> zkGHM*0&!Etj0;lE3j-=DD$dL>On!1=V$StXg^S($pMCyW|M#mnsIUzvU68m}D%(g` zckSQb-^<_Kxq12WA8 zRPA{Sb@k<$mzOy@I_BQmk_ZZhRcfpJ^H)nHU0V~WH8tyS8*h&Ir#|`mKZRdjT$DJ^ zp%~Iwz_+$?o_+njO{u3>hp)G?wA>iW&2V6zZM9kMEfGn{laD`w!+jOMPe$*S;^*gb zZ*OyTaoHf(1uwKq%gWNW3$+NWN;6n!8v6d;-q_gK>H6{Kl6Xyg%U@kNdHQtrw>LL+ zrZoyUT@_N>vh38kb7iHaww0e!PEXUlx$0rR{XY&qK0a>l#|0nS*jfZuCCs=W1v z^Ru(nA09Mr+g5f}{_n@f$3aW(dU|p$EO5LkFRR$H%GM|2tXa|#j@sJ4XJ?y-e=MBp zwY0tc_!Q0HV|}vGN88;v7D_P*M{O1r6*bMglybaJ_U^9I)u$foMQl(oGy8UBWpHR^ zK#RbtK!b&*aTysePE1smx2wrWNeMA^=i%pne}8|yk5A9am6=y%1QlDd6ntL1O1QNp zbLY;T?R>JUnvy$@CWVKC?%?_M`uchK2#BsIYhw98%4jLlw?(eU^wIy@r%$X*>`jMNK1TXg!HQnK%Vq|9a z@B96FMg>S|$jHbjYHH!cvC#jZWy#l#IX5?5xNu?8q)9C;ES)ZsX3vh^UH109{I@SJ zFaQ1ZRhWb2?d|RTot>5i4;q#)fBv1nTfphX=K{Xi+8MKExw*I;Ida59Mab56@9F9K z;U_jM_nW&&!%IgjerM6r6)QXz285j0z@gY;$N&7oRmZ}@!hShhIlCGSvF`gemIV(E zD7*L7{D1K5*)#q4eKns0qN8u$zWw{cLT44vy8jPA`dEc`S#|XHmlqW&c}`li!rWg< zQu68Z=k?D|=*R8JxUyoRk6QI-p84m~CERqxig(_5FC(Maav}XeMu(0sH<)>C|@l)$}u3ftpbY}6QMM^#CEdr}@W?Z=1vUTg%ySvN({{7qP zl=#wE#I?1hrRMntM@L7YP8J4_Nmnjkwk~*ZU|sBP7N&#Wr-6dO`-4t-Y4O`zTmSw2 zJ$=d)jj5;1a&O()Rr-2%`yvgm`1p9Gi5c11)>c;YtV&OvI<<<)+m%D{i=;*Sx;(3r z7ZYa80JT@_=GXuJmb)|N-&to@S65FJp@jh!xwp2gSfK%SnwLfUx&(1&C$n|YtNEs9 z&fn)zIcLTX=WemBJv{q!TYfBAvgEmbUBgMHnjedt@KKu&n@!Sp3%pQL;hy`ZBvtiJ&c*G$7sA}T3(6j7ADy84@6)xZIle_+ z6%`h0CQd7!9cWosA?w?-dv@4?q_`D(CMbRT@8!U;a8=9og@R5Tilr@_y#h`gE0ujR zS_GUtWD`+WPt3T$q1ZCP>QIXthhobWK{e14izR&qP_5Zrp`3@V6ui@!8^y%XHF33s zf@O^!gT}l$=k8XnT*P-M(|DU-efose4X2+n7&r*2Tv+7f77^`jZG5<%^=q#4LJH`J0+{-cDPOW)2_m}^-Hwrj$)QX&2xVmlPfqPy*UpRfcpMK!ZB!9cJKYuT9 zS+h8HzaOhy^S-^>`kP!5GEb_^t=^eheQw>_R!*aNd5uh8EN}gP>u>n$+qya3kxbs+ z6?H$K-gh)w5ng>JY4gOj_WQ+!rPn_B_dHp#Mc|i)&x=)xlke}J@cVpZXpqrTrl={a zef9)zcsgZ`2O~p5(EQf{|IaRK>6Bxb!1uM&{CtG$FqKh zg{I2SWw-uVYZm`!o31#`c+ZSAI+rd9T~apUe?Ft zn}6Lf{gRR+_og=;@88Yd_-fwGY4a-rW{4MfSd?w*GwV2~bG|nx>+9t7W%<)0zWMwPQ|J76eDx&z)B7!J zBDtU5P-a$aDd0Q1NliFw_chI1yG@_h*euM=elu-Sz5VRQNlo|NmdI7RNLXe^U%#Fo z8ZKIGRsJ$c^7<5Si)%9<9=>yX;>LusSC1SvdM?#;o#(b9~r%8n%6} zS)IH4Rj%Z6*P#5}_4OOp-_&>;Zf?vN@@cnuQQ3W_1y3&eE9mx4+EcOhvlN2?!!5gR zU8ZkQ{Kdg)?tAW3F?Cd_Ew&E$?zi`j=o%K?+WiZyWTOg>`WIc-Ki6C9$-&T&vf#s2 z{r}Y)pW7|GACM4xe8E~<6}B|}nOaL*4za1Ss>>TNY+5~gUVoITo?2=3?@7LD)9Qt@ zcry>*I^HPY^om7zmamPtG=spF?-#c(_FA;qOD{JIvGES-tql1y894W(B)lD!G?#Ou6{_ zY0;;*?0+WK@;+U@?qb00iU!@t<17tVUSI2beSPw06Q7?|>NoGs_R^c*IO))SGtHw6 zYG;2kEZMm_aC+OO6)P5Hm#ci26F#=~)qB1NuJ85z7BPARq@FI4RC_1MD`K#7=j7#2 zZL(v^59{R$WvE`z+{vSHWV#%~X9If^!5v{$Ywy(yulf<}z@hlHC4J{=so;0}Ep7A+ zGS^>QzmSo^VRg2(5W|V`ZTdHNbV*t*E4zK{+5Y@faV1k3A6zqIt>97!^kmo(wLoKk z_aw%KOPlYMJUP9ykcVOa7xkbYC-zyocVA+c*g8Lgo#jzWOz!u@G_6$`Kc8;Dx{vF0 zq;K^9SHaa^wT|A{$+b|7A#_i9XrUA@!-GqG;T0y27cAKpFTVI;_s$Iq?uh0yFesGp zPFrVWR5#D8G;Zx%>HBe!;sOjjx?zkS5?wX#eIFbs{43qdYrm7`#G% zIn4aorI7Rbob)+GpVIn!DYq`oxhBT2G>M_%f)c|Err%bp)lZb^)P1}i%9K_7aG4KN z&37{y1_tRy!4txdB}7^Co9^FhV@a6Oy0l+nBL~ANvlDwiKHSF;Q1jAmVgAag<;Lk> zExWEf7CSm^%ChwVtKZ1GpV}X({+Kh*_VfMag_kacI7sP7X>PfuZ2c?x=l4rl_F>`w z{Ga_$RN~}_oqJDAIh2V(`M2Z#ecNoFaiw1J>E>UM%fP@8wSyq8D3@HAe5cOC-dj1w zw&YC=&+l(no*hsBr@hYjNb{9T>bxaeYOOw(vPNx-Iklp5;&Sc%3uRB*oU|`;oU-J~ z%PV(JY@XT|E`IJaxBlW+S6;q6^6(Y6REFmf-m}Nbo_(^o!}e~1ysp1EV?)?}$*s|5 z4>DdBx1KNRx7XgEe{CB#1A`{(p?hmxcZZ$cH2K7~y(?GmR`q0k@F3Z2w|xKchqtyq zxp4mI!A7n2bGyF2{c0^C5_A4iS;>{xSEBEq-H`D0ywT!5HRraBo7=@^2)a(+#A`d( zGNv|2`p4cX&fcwCWn6@ow&pFq#+IAQ$u~d${-odui=O7_=UffFv2K0b+BI*UuAaJa z<I3G;|mo7CpbKCtaN0e!);xw`b-N_0-9+ zho)rb`>5LetT1iSJMPu)cb4tYlr?j`1^>QY`}t)2&u$i$ke%UQj+!UB1)b`+n$6BD z^+i-)P_bnd^B%slc52^SwZ8Aqbx^na`Rs7*j{Ha)=9*9&<_3#)uQQt4>-$#yIsDyS z{Nl<*m(H$;X%JJBQQ%Z-_o{lH@=g3cON+p&%nw#3tDJV_`m&n_K6&=qHzN7MtvPSA zK%)!~Wu!tCFP@zfX?|0WNwLL*`5d%KKuT+(PJY*;*c0b2KYSz|eehk{^PYs=pk}_8 zgomD$T)D4bES`1<$uR^v$Z$%o~$k@EXljQP4{-FLQBBagXbhoK|95M ze|sAo6a?;>U2xAh5W=t6BCx6v)=VMAO=v9~YHRICkZ5}=6)@Gy*Z1v(h0T3^e6E3& zm6gkuEfaBl`SPW!o7<*XDJPDF+YJ_G&w2DHiJ7_i`#cj<)3fK!r7e|f5pa@kP2ZU< zQ(OCYUF_~@^==v>=VqJtudM<%nbb;rO%e~W*xK$j=g*uaXOzOx{Z*kw;FrL>r|UXX zjAm}!xG_XaxAw)uhlS72$(DILaVUO~o8x@lg{kqt>C>yf>@O`TQS+H`AkP-mWN+b& zb?%imU-!jjyVufBU%#gAmT3`idVjcO-NIcZFN1!`zrD5f_lIkFEdow!K!P5VKv#7# z^vK)K0}TRj)Uw>uTxQ6{#TB+D;^T{pi@T&YY}jC+rL`(}xu1$>*og)f$BN2I-+4AO zPd^Pi(ZHb?GC9#Hn_;Tg*3{F};_Lrzjoh5(=jSJCu_;FH`RAXarc$nf=K1$Tx{uB_ z%YAiobGj>Uw}6wWu-X=Izkf^S<&u(;KY#w{=;++gdwx18DXFQcNk&$dnTbitH0wy2 zC5PgYl?Ds1hR4L*xw0}?SV*X*wsy8z?x`}%|BYTtxwyHl>;L_EaIiUiO+?`8FkR3% zzv9_u&b57?K7A6?i>dhW;UVZE@du@kwA6$;{pQ=%et&njm0SGR@86#*Y{1HWn>g3T zP4P-~a5&HW`TF(mpFgjT-mZ81X~>dxet9-l)|)qPva+z4t^g3PYokt&CUhePi>Uws*J*c-?Q7EO@V#g{x%hYRW(cv~;$SmK7Z{Mz6 zyqLIq(W=Q)r;18QL<9tED17WDFE0<;jS1>Job3xHE`Qf4tiET@9;>o9Cmtr;o@6A{`Q*#X z%jNIx%(O0l*CT1XWy=<@mK*Jyv7#beM}K{N9lk#9?y6Aj{QUgf+}v&2LGJqT`*^sx zPMtjq+Tp&x?(YpfcEy&6HqO|Wese4gYk!pp3k%!Z@3$&_wIQ}|SM{GCg$W4@rcS;3 z?CfmQ?6tuy0;?isT$rk%rj~YUiss9gFZb=+x5MZ7^~IULetmtt+<*SN%gfKtGR^k! z^#x5xwL~;<#%dN771jNGI{m?e1bzMWe6m&#RG;T7O!PQ6&sJJa&My7j9MBefrrjw@ zEfd@hw5&@g+WF_a{r{NVWx21ft=+q4&xc8$%TJv<$H&EW>fE`w{dIfSuh+l*6kP7L ztn+YmbTm#ovm$tT-}?3XaYgGKK7RZNI*TTH`@3gnXWzYZ2kgaFOll=ZD?U6pI7Ktq z$k>>jN8-YYz{T%nd?SB6`qis`fH|@GS+30Kweh-V_msX(Y>R=+5lwdT%#t<*J3@jlt<`tko>U0vSh)Z6EYoZ$)2uJAuC9K1dV2U9 zB^8-{(-;_DM3+zIIyEmS?LOaR=7xsHhA`tnv&MytB5yzq{M~v(wiyfy`rT)32RYc7rL+hHna6D7zK}3;=1gvJy_k(ju1jlVJzTGTm&}@7_9e%|C;NEcFNXDU z?Zs7Jzgm=UTA;(YwBNZYPxP8m`;q9Iynl{=Uw4$jpzyW$KPCnTi{4qbC%mTBJiKwR zDNXqJ*7e`ZQg&%P%JERkSblF$WPU^a@~E53{aEeJEy_J>>A(4fpYrpva-&mSe0#sR ztO=F*7jPqc+Vqz<>+=i_{HczzIIw!^!o9QETjcgneQ|9)C;Jk9J+=}mS46n&b)Qu!iG!x)_R}Z zam@F2$#UcK?#sOxyz=BHmKCgDU-9>G(~j=n`B$VZcci~R_|JLmA~X38E#7x__PUXq zv+De3AE`Yq|3OD)_KpaTdw-|I+wQ4|dU|1LwD|gae1eU~yIxyaW)yp*@4t9C{O9+g z9cB8FnqGGbELPU8*W0`2SJ1}g`OmMtG(6Cne6c*_eOQ|eL!akt#r}cWG&=S# zxV)=T?_=lm1wMNuZywz1sQOoV^Tv$_cWVeZU6oMV@+;}dlzCgT-J67k=1rI`HTUk; zwKMfTS}mNK$^Sm;OV$B)h6h)&@ATDuGl@US@G#_ljI;$?o~+e{^Kl{?mAjZ8?p~~Q zS?<^s!BfGHgpcf!JM8{t?b+uH4ZY2({0$-ull8UFon>fvlg@d5lZA5|PkxZQL$DD; zMe(Owcb0DDom~6;>1T4ET65iAWh+m|nH@eq`KQ$|S$OH08ZP96#;CsY4yLjW4zMI2mGu7%Y}BFqC}FHeO|%{%(ne<4n=oZ#A1b7BDbO z@ygt|<&9Hu?RPVV2bZ?be7HOG@+P50eAAf&s=9XjzW((?pGUD}AMehRmqFXIFN7RD zdi3`8{P(xEX1DhHbQ`UoV&)HKK`tsVr!1?YB42lLOXS}v%UB5Twzgd&3 zE?Ax&=Kb;dNpZVcE2)rMnNk}gWJEq47_W22apZwC>de3><#rrH{0tPEMDv){x_cei(Xvd z$-uBcQ}S@Y&+Gi}Icg{5@J%>c`1Suy+fRRL=dJscw&Y2@eSe9W=#(XH?{0m&vbO!+ z&SHMg^yosjH#4utn!I?l#kl=^w&ks5b9RT+FWLX}a`E{YU*7&c&y$t^*W`&usPdU(HsolX7fjFJ(J@g&Cs)UZYcZrbH0BQ&rHzcb6TXKr?$LsHP?I7 zP5~!V;d%X6RLsP_u35Ww?b)oovD_|P3=Af$EiG!r%vyv|`ocVm`(I$7Vl`niRuiAdQz=0t^VG=U~!^+s%hXX`-4?Niu%mA6%;IT<5^jz4n1ylCE>vHsD+)@!R>65|KEiWzqZ9mvsS?DjEXS+EsD>dM1 zK93VeXz&fo$G1b{p7-!yIRCCCX5U{WW6NH%HQB+}ConANj*qb^wX^j;w>mfM{Fy6f z4r;tSw30h9{LrDq3H!ItTsU)W-NXIt{jrrbPwRXH6IZSF_nvp8W%hqy^?pr0A|Woyq^l%P<*HKI?RamR06^g4am>T+qw*y!fx4#h;>H<@?oN*!+LAvPm=S z*<{aI5&!CFqkz+k{RduSovZm^wM9)Z^~k-NDd!%4oZ_X*aQ?)id5fESAG+%+Ouzp+ zwdTgr6;^Zq+URW2-lMWqeJ@Q>J;eawFs;VuxP)gvP;a5?@&+obh%qwvhHtd zt@ZpdFVwp~<;4Ci;cKn#?5K!Ty|P%RrporsqmQfoU+Cw9rquQyY`LZ*v(SMdVU6;Y zYim>A-n=gU;>GftlQzj~Jq>z!d&jb;K@&qb84g@FG zlU-a>uCp}cm!IjKhy_r_0^#tYP!n{c!W%*sSR``}b;F zENxvK_u=QBkDq^cFFq%`r{eydYi6%s1}mIl`_*4;zFhfSTS`;etFyDevi2aPk+beJYD|!nt69lKMZr3Bs1lt`MHT}cmKI?aQFPkBrnCA zD_Un;I&5ACT1)qW|@wKdj!)DsIrNu8f6kD{!)V3T8WIDce zY4qPmI&HVNRTl5IVo*4V*38>qXTQDr|DSEjD%%Rbi#ah&ZOMF-a+L9c&=j-V4|euO z7+uk^e)8j=aHJL^r_I836E>d2zA3s?f>l+t)et9NIAN^aaDaq2}LYcTeVH@bdDU;CD7AHg>jo zd1v(&i`xvo3k^&Dtc<3)2AH{ zSCT|}Hg?YKUcGwt>hA8zGh>#plz#g3^fWU&-<9?8^Fb?|R%}Xes<*ba-7Bsi7ZDYu z7qvyh#AM2%Rgb!K+G}b{+|L)@m3b%6zhhp^lC|-(1Ls;6tGjZZTJvJ_^T}~{EyeO4 z+(`R%X`{paNg9?h3Lzrw_qH5f^!eSK?zazK9S9dovt8`CkcA;(p7&H9h7`MLr^`?%e;qXw|e=p^G+!v5Wuh*>GXUZ`K%g?MI0bCq7?cvbDZm z)ih=D@^i;hy*Z73U%UGH*}8W-AC~K@?o}#U+`sI``7hadS$U32+U8o6fAxBCwrcP3 zEnBYq-5?h=rSkWqryW<)TZQJ$oH})1*wcRp-2+1+B5vHT|9^K?DCj7((zmy!s+`n+ z-zMNRi?cd9bcS*IIiI;!S1(++aQ^)HJ9qZH;$ASdXeU>T(?56lx}4&!9=WtyJs%(>>~DW@ZKk7mgiM zQ&2c?^XAS|ecb|1IS2EjLnZR_-@m)No1KMaO~l4UuH9ny?%mt9OMYf~*u3vi(X|&| zzB+u?d-)z4JN^X`UwBkR3Vt3c|7HC--h9oXB|r7Kr#$_)bHxs~UuB}9ztU4vQ(4*A z#Kgq%Zf-hy`}S_HzD@zBH-{}tj-Eb!I(kRJ!->l7Ro~vceED)EV|vodMID_CFT0< zkH4?xJ8MI1U)w?rEfW(H6_u0=3mmn>)~pC!Ep}T}u_Yk%aLc-w=jZ0mwklmUZ{EE1 z>({rovfh50(KchojK|0OZ{NPn&cb2=T4%%O#IccCc-N(sA*;^LHrJ2ewNtH+OjfyeH>^9f>X|+mUT)$fBx(i)3qvj5%BV8rk?owi`DHO zDqMVg`|AJyTjbik%x7lPdwwU5M5pkT>i+Y7{QhlU`Dsc0|9|V&t;?CdSS&;<^rhGEY}X6B_qFPHhwuKM!gAb2#k z%wXZokbr;<85b8dv-6j|zqhye`8m@kkFpC33kwPgN=m*=QuS8zpBLjCXX?c9H+;s0 zH{Ko|GptIpa&p$JU%!6#Z0XxiLzJd^mHzznlv`YnN8YaH=clJ`Zf>9@`%bU8g`;9) z_EcQFe}De@=ll`|3!>uYfCA&=n7cX!vuSUx9?Og5j4t-%oyJ5o+gs{i+MdG+^q zvAaqxzBs+*=+UFg{pMEv`0()3Qg3zNSxXqbrzp2fxOJc7R71sjn_A^%mEUlQGS@a%rhIFE1~6{Lq9)ZA;YdnxC8Y@3;4y#LULCVcWK} z-E-ELzQ4Ek@$vrMyLRQ>*>UmSy?Nk(S59liC4m7;& zvQsxT{d!|#vWRQ$y*)dNpPvIK7bza$C}R;(QP6pN=jYjmg@xr@|Kus+`t;c|z4(29 zj?32{@jl0)_+_raLQ~JItXG$p``=vmXfj7r#s<&&SLd1*~s zJJEe@o=`;vx9GY3|0)mL{|NrvvC!~my&{BDJ}dne8B`u_8(|JLr$f9rn6b$!q?ef@8x+xzdqziZ#le^s#brwd^j&qEnQLsO&k~pQSo?JDp{3@no)_+Hwx2!! z;+L4&(RF)yw_fN7YYBX5r(v@C8e83#G6R$7Ow%pbmSn!p-JmaO2w ztLvQ8_4j_ewlw>F?H9{W8UYc~MkYOW$7W1!_sg^W`SPS_@wz>yR;|{J-1SQ;Tk6Bc ztg}b1?Bbnm{eF|=f8FTA@2cNaUSjlGKCkS_i8rQpcYfa7`bF+` zeyq2B>AUx_-tX5{9)DFlx308zd#HWh#5sPmzW4M!o%Lcz6+1)8w|PhBX_fdt-;-XL z(zYmd*R)yY>FVz#WY?%aExVllexKF%>-$>6{;%Hm)V@@&;`KlIx!m`5l|Qe0dpm#E zw&#mAo@P$)UwGYQ8u!M+i)ZHUzwqdLxW(~R=XNidekJT=?$fHW$mUGjw`q^3mCasS z+0>J|E941B_0pj3dA`%;ELaxE_w~KOElux7wzsxz-FvhCcIc^_T$N8f?VSu;PA)3q z%?ZD`Ip}_C(%Nb1(Rcn|xmj6c<@M2TXX>rYOr5z)&s;8FoF_EtjNtWMyk4t{Cf}Mh z<@Yi-UCFA|JIyq^RnJag{iJc?u}#evTl2lC+&peK+H>caR6P+cvM`l>?8Y0Dpt-K^ z;`W|n`<}0Tk#Tj)^1FG#;rB}AZe2X;Tg^HwKSEcqTJbb%q;`Pb`Ive)%E&g7~7j*ML?BnQU<6@0RR(rWzbCNGjY5ID4wwM0D%*}gLzRzmASbWU* z>*fy(#n<13Up)KdMAl6c?IN2T1H(0cG7==FJ};~leRcKGrgJ`w!9IzNW!LWQp3xl_ z8MtECLAl)u&t|tWeYbgemp8%i^PRwV>(u?fx}UFUPY`|u|RkpEXDwzu(SZHZoUGHt5Ps>?6B{@pfAzuZ}Vy7XDA$Klq6 zd2*!-92%#6J*IZ%?Y1b9{0sq(uQ3w)_HLafn0k(_ZA-6C?G~PvV<&urXMEvdVO*N@ z;nu!QYqQPVmTbO!_D{Uq)n|)huNNKuR=_^*a(3*VP8X90VmWg*vg({!C~Y=xZshsb z>yG&*7Os_KTAw>_mjFYsglVUtKtL^=FFbHTEOb;g<7j8ix6*jM59cC(Y%9sZ*&oc&aP+nL~ZHn90c}#IC zDG-U_yn1x+2Z5#CmpgPe&kdf(e5-vsSA*D*^6p!15-}HB{w_!<36$zTa!f;f5oaZ% zu<41G$mCCqfoWz2zp8J(VB#%gUv=PHq}i(ecc++Uh(C?UkY-Ci$8oRURsM=tOnLIt zdBv+7*Ksn~p5geN=fj>;ad}oj124lt?8S#sJ+7D$2*4YJVu2f?n`eu zyx4xkmm`V$kHJiKzxjT8S1)Yt-B?hkKJU)GgsVH9WP{l|w3|-r9@R@;bSGak=vJa| zvq@Fx8$Z^bDY5GkN*?fh*Z%6LoWLU=f1reW{=<}OH?E)cnP6c4BG-5}PsHvICvVyv zH_MAZb6TwNe(L+s%>S!i$ljT>?%_$B_=Q}lee8mq9*2^i1-LCbxFmr21+(J57YX50 z_yu@mqW2wtn0Geyu2@vd_R?zBokl{}e}1~=!79$GcVpge2Zue$I&RyUS;bxlZn56_ z^`WovVY^M5ua3RtuJ*XS-+Fe;DX;h2eS-IJhMait{?)WO`<^d2y;4@pbN7esyDwL{ zMa(Mi7d-G}?K)%Q0~S0|Wph@g9(j_R{8cY}ul-t3K}FZ)6s$JbYICsneN4 zd7G{C9=vl2jQeqPJI~a_uZ#Nia*n>5p)ue2$6*6a4#RoRg{$S)7e_|C`Y94|X2$z8 zCxwE5tq&4S(&!*xFI zhNF*fJ(-vpm@ig2BWsVZz>?cHUoxh?IXCI>?5xxZR;y!0j*YE}@8XzB@&lUQgw75K zTzxJ?m7Pt4^kx`pWZ57&eOd5#9RKH!&-uQO{731Yr=%E#^{ht1;0yB;{aak6{kelqv4*}5|? z8Jrt60$IXJ)jg*e-E{fx%^rB>_Egab>(b?xoGUg~waC|d?OdR&$Z@AK>GLeLtjilb z-u66`lq`8yy+GjTk*1vT7Pe+J^(+~ane2NLyjwQ4@y^RLI1XB4_wxtsaE@xp8uXqxj2{)$9j6`Zt(VM7X{E;?$sf zBFcG3a9UhL%OYits)Guas%77Ix^TPOaC{V*Z5U-?CH5+DW*5T@=Es_an-uk%rzNtV zA-Yd3FNDyjqJ)zmdUd6Q7N-@Mxw z)VY8)tEF|`9P#PVb~|J?^L?4_8xm5*Ft_E_DdpGM6%K)aRIlv~mOXd)>M}#ERcCBc zycRY1c1Bu==H@vpteMgz*$_KpM^~{bm*5=3ma~6-3ZEQI&C?3hQSe?PDEp*v$svEY zOrdKEdV9EpdyHA~KQ#QFA-VA_LrR55+b83|1r<%sVQM1HZ=H%{A2L>bcG?l}wktZe zX|I~1$PfSROl}<=Mwjn!$h>r}E}ZW4@9yLN4QH&F6!y&X)>OM!6c@37a_xmsk>w)( zITHMLdMD(3inQq$OS#kWFsbeKTi)--8On^ZyuKaSolyAEXx*(kl^F-ur?Dk4$n7!x zSDx%-vg@JZC$T*W4(i{%TW?e`R-REiueN`|tcr<#(Tn7pa&)FAnttDPiod_NnaSR+ z@wIhuTl`Al{|`(Rt_wYq{PBd1wB?$F z>FI!346jzbc6n8s7}H}fH(haUhiq$+^pnc!D>EB5dL7s#^#1!-R}qpIqCX z!*KZ`yOV%t`~zDy5&iQ&E~-!ZR$#7lZbo2}|2)+mMIRUY5BUOIJ#U`}W-?FgITb4* z==f>5sGhIQ+TSgQV|w;q7i%n8)p}_Kk6qrb7p_q(|3lXLF}F7!3r<$w_+y^#&+g+5 zZ5tYxk}dwf<`7wSC`M&T<8qxP$D6PG^t|{X1p&$A8J zd)n@6-dp}j_3#lM6Vd3VU-9OdUOjDMJoOwxTD>h>k~npw>%-Pb^Xgcd7#%y(al#<2 zL@;PU|FNs>Gpv&iwtsWpu~T^BL0;oh)$NRBIPS2b1 zuvy)*Zr1iHKOdEeJiESLo?WHf%BSz`+%E9X{>_Ku*XLf$XggwaT}f?*uurnI?wVo` z>+NE9B+j(Py-1Y4cv5U{Q|##?_tOE|X4`*XoWR8P$Hv}GCnLuD+4nuidUl<^)jzYv z+VGpOa9dz^-mJ)r&s^LVG*S@u_@gFW!d64+B^>gw6uyU+*{pLYu^;G<#zwZFt+L=70Ys*D-6Re z4J{QE9-ThJw@%SBS>Ld3@{YcJsXKT-Y>^h+STk3Q;a<{}zWM8;RFoO{D!BcW_}q2a z(gegzwW7s7N~WC7kh~zuBsOhH<<2QR``8Z8`+MiT+4fk!4L_Y;2y6_!^176x@=u4` zhj7LF_8fn&Yll<_tZeFBSec~7cm ztlx7XclEQv)4UgsJzQKG@Wyh3=X&>LON39%f7iTQ{@Ioz3ldfquKoQp^i<*0?~DJv zO?TW^v28c+1OHteKjuzlU^uy#-Nopk*`}YGKU^bbXP2wY-Mq)LTYc%%Xq5~B&rMErM4rX(G5+U%I9eEqrLNuitkn%o^<@0S>y70?PhcV4nk$i`?@-30rM zPwi)hJLx8Q>ppYnvTki;*cc)kewF{G$cYKRInOtrjeGRrnW4f7{?6~*+O!><@aWh}6&t^k9h;@@o$ELC|9Hprf^m;P4bw{H>vDfK zEZX^@A0t$q5q@M#f{cXa|Gf-+v#&&4z4PU!7H`ApS zHO>XA?^nOCe*XK}J^S=?b1a|V**V#oVa4%+Bad=Im>XmQ_oS5pZA- zS-|aM5wp@lK}}7_L2}ps;Cpu)t_7^Sb-MQNo&WK!laEg?VsuDSSpIRz9hnpCj0f)S zNbw1Q!Xcuz^0vZjHkR&d~TxZu9fpBIdVcUW5B{9i%C2TSDq9a za_^A5&>zK`!Y=5q(R7FB=5*}`9~A1_Y_!zX#W(FTO?kcL{hyRShtqFu@aI!co-uW& zhKs6)hG=^6vj6j+{Uvr1UGFWlG@KRS-=W9(hz9)ij zoZjiJ!0j!u<^O@Hn-b14Ff&X!^_a!g!K@)i>r`Kt%$48ExSelINPK&-BGJw|&Qk2D zXHhcy-^HypnFV(QidDLHJ~2z(w5FxOalSN5&9@2Xmnk{cOfvG@vesdS1ZyPkC)>=h zm?I0$epX`C&N|_y)IIe$&wNX^OR9<+y`<0BxLZvTf2Jz1X)&9nQM`csGMR4QheaYA zUpk$8;;rt&cl6|Ac2@1rhq`_)YB~60qgP>F!kYzu!wSpJEU>+H<~-lj*S?C|y|^E> z9sBm|@X=*w#MvMHnNg9Nv!hH+-NsFlIgeY3k(-54ZNkB6Y(7oZG__f zCKeyEEjYh$$G)4wyExvqxVOA+;XCwp<)NgL{1c52T3ldE=8-(3_xAUj0`C`BMfs1^ zE~tMYo&Mn00lD%~@x=Np@AJ*Et5WjxtO^b*~l*z7Tz zr*ZArg`ACD6`K~wKlJ}x$o70nxbLfg=NI07sNBH+w|WWT;}u{ z7gq0xJN$uVPWSVNn?G{Z=^l1R!b8~7tro_-C5mvyz}jm<45d-I+wU_QvNw%k*7?M z-O4Fj+O~9;^pvz-@%*)5(W<01kCq56nzTqMNHc6@(9M-aAy2zb1y8Np#CEgkjccFU z=M$GtdY_0s>HFmL6Tc#R*U3q)9~T}8Hq!j8v0AfQW4Y#cjr@?%P~TA7(BrGQLZ7c% z7j}1fmEYHeuiRIOU7dDnk>8?IE4o+SUU_`w_Lcb=CbLw}&Y$LVdb#ehtC##P$z8Bp zaq+6#>Wx<`vWl;)y87zsx)pmv)K^EZU|-=Ms2{XG$bLaxrY=#d-?9_-gUn{`&WvunZ9(p&w9uA`|Q@`ueOh? zpY@OJ@8w^%%3Iy5`k(aaNbjtFBFlW`z%_+y32g_(n!VX-+uYg~wj~`lZBys1?vZf& z=Qh(#yQfd8RQhsnZEx!_?Vf%~`}9`^%rkDDxq2q}Oq?+DWCP)uC(|Z}O|A>pzRYqt z>GIxX-_L5K$)&wa(>@b*rs{0(+2b?Mo2)n9pX|8p!v=wfHJdE5Ki<1^%IIWKV`}qL zW9?Y&ZJ`rG=U%nVp0#y;>Ace3ucp$AdfR=covoT}7JcQ~)v$!<-0GWrTZ=af-&(sp zTi#XTsil|6sfef90?^vd^N z>ddZX>D0fuwz0p~r`E!5cCA&-%zZ2O&91Tj6bopsbou7$OgZ7Sr%hB?QTEMWiRU3+tD2%{ZZqRS&-086FymSyS$$}ssqIW7p-W^5IEZx zCs%8KOiNAwS^vi)9}OR`K3;y*>e#s+TWR{F(G)H|I6ZO)b-0z8>-H z+}inma?>QO$yRDTGYzs29ZmV0l30|?rKxI|8trxe$b(L8^)kPrxgl0BkEN_W**f{W z|Hb)}Y@eP8`MPq>O73fdav`SL95J#I6-BjQ}* z+w_X!wxnIWZWMR3wW$5ms!!)d?{snRxB9p1*Y(DuFT10s@6f%uVakq>Enkiq?N7DV zGS%+ZlGm5tzNe10*!5=DcJ1ZsY~r5=Z7mc{othJR{MCu8z1e53>%@MpT$}Lr>#GB= z_P*x1rW^IW&Nlz=RNjBm-*QauMC|)`S?%Gn=-JNZ*RMxh3&~Z?)4XwMt7+luoA+(% zR^MH=e{Id}>2DXkm9k8;U(;qtsWwzGs>le*4{myS>$KzUSOKeIt4w z|C`BgtN&HMdT;*C{Y}2*gTjRKj@`|#`7ZKLy{i26^6P487GBxjlf7@-5A$y~sWg}U zmHb8i_H5r-tNUNbL>5gc+VbSZfsKojYmXm2UhS7Mr{a#$)~D}Ia;mSND^< z^>6ka`@4#rjX&#O>gV-)W%Fd+<=AZZe9?G!@$T_E$MxrV%&&Q;^KwyXsI6&T)bBGd zBa6#D%5Q(}JZ~M}5&z};lz&TqUf(tSnm+%(xBtH{y~nwC?s}E=Y;oaxE>>^;yY}?$ zY5QjEV}D@}ZYY@qPW!=lTd zk;lx$R<_~#ZN>$vhx6Ob7&L^ZrW>~^ocg?^=|Fwb)1SVIc?>z<*%cD?7$$y@PG!y! zo3QeO-hY<<@9Zl!e=d*~$=tPh@+EQ6c@Z4)Y;5-=m>ja09(1Q9EU5o`;K1~?1~+aq z$ea(J$N5yrzBx}ydHJ?;(;vA1G|#wKsMWfELLCDG|Iy5lh>{3jAFJg2T)o7U{G?R9 zirfMQ5U{bYC`e4sPAySLN=?tqvsHS(d%u!GW{Ry+xT&v!Z-H}aMy5wqQEG6NUr2IQ zcCuxPlD!?5O@&oOZb5EpNuokUZcbjYRfVk**j%f;Vk?lazLEl1NlCV?QiN}Sf^&XR zs)C80iJpP3Yei<6k&+#kf=y9MnpKdC8`OxRlr&qVjFOT9D}DX)@^Za$W4-*MbbUih zOG|wNBYh(y-J+B<-Qvo;lEez#ykcdL5fC$6Qj3#|G7CyF^YauyW+o=(mzLNnDRC(% zC_oL*EGS8Kttf$80OEs9mOTCWeEGQ>L?DWEJ)Q4N-fSWElN&xElbTSQAW13 zAg8n#+0N49RFDwZ-8m^~`W3klo00Xnd-?{z^?-sgJu|letOKMPS!GHxTwOtFQ4Uy5 zO0s@xPHJvyUP-aOp`Ia%hd|2);5tzJ1Cjv;0kZKGxdqr&!@>)!7#yxvF8Rr&AWJ=6 zY?VOnwMxlP&P=faGm{LA&5aX{Omq#*%nfx-EX~bylZ;HvbS;d{jf@hFlT3}0(vXbu z%quQQ%u7xM8C8*6pqH7MVr7tQYMf$XXr^moU}&Oil4_EwYhh?%uA5|(WRhrZ0K&#d zM)((Hrf23Q<{-NYWK>FKidC9rvSp%)X_9WDNus%~Nt%I)u0@K0xvsHknx&ahvWZc$ zfdSYkP&`;U26);k8R;R31mq-^q~#ao+A8@bCM);{Bh-du=B5UhB!WWC(A3z%!r0W* z)WXQbz`)oPp(rf1s5mn}4`isJfu0#yA|=_%Ex#x?vBXv>GdD3kRlguF9V`Kg7OQ~F ziqxD4m(1MMJg80!3y6CV(ve8gh89o{ROA*|ITxiSmgE6yhPMU|ceJcXvt29l^fGK)*{iz<=q4^AzF z@F3pG$pilFSp$ElrY>EiKJaO)t(*D=AMbN_9+6%`350a?i{y z0LQa}1|*GYqAD-XNChQ8149d4Ljzp{vk(JwD?>{wLknF4b1MS_B}kMj*yw|bVwjh0 zK!ugBuN88j*nkQ%D^F0G4K6Jx$jMACf&>pZ@dT$9LQJyJ$Ds~MKRE5>6V#5Z45HgP zzo4=xGd-ikzdR4G^;i@_Oz_D}PR%REYd5kINU$K)Saw{{pbBzvv*WVS2UnY*DijhH zpel@(7@7oWX@!Eqs3jzY?`Uw11{X;oK$7Co)HNDhB!vJ;ibqoy)q;x)(Kk=cOR-fd zSF*R8dZk5!fq{W7$=lt9;Xep2*t>i(0|NtRfk$L91A~|<2s3&HseE8yU|=ut^mS!_ z#Kp!ZEcnw&+Kz!ifx*+oF{I+wo4e&Ppv!*W zk6U-Y_}urJ_218apY#5?MvvQKNyQd{pVwYoTzoNWYt6r(&o4GLyP@)cW7JdcTu-C6LksiftEaAIJZsHzT^VoSw2E>W!(r-ceu zR#8^Y9uXo!VyyyBHfw^GdKvB3P|@My>~`W%+!VGk$(6I)>CmDH8#G!3l(LOBBjldG zdgWD7v7@AA!Um9_V#^6Dol|E(Dpf!>oCrGFCHnLC`~9BY-jmM>2#K|}xGJ^?eBZov z>(YXUhhp|tg(6w}=GE2JU*6uH9$)`A)XMpZSNXd;3oAZ8Qc+j;PWOlqapL%>bYgF} z()P(WLGIyDj8o8DsH~#X#lCVWcZ+~i%YDfP6FxOjPdK!}EjNzRP`D~~4`*REYFd3VS1^!(CycOsh>f&G8}kL!XEqvB^i zTcY09uD7EPX3r~ zZEfv0-_G~vr>9GcUHZ~Yq%;E-FjVced%Ts2l}kWaSoyoQU4Og=I6N3w*x8fQ)2Dw- z-+WWYG4Kgj*4A0y-rimupurHZGUUsfo53*W1TS{$T@kq0?bv+n&{ZmPm5O@n{{BkL z&*yLG?d4@=G$=9`V;OF`07W#0i=0@k_m?G99>i)6%`~?j5UpDW+Ql zoNSc8YfUx0v+HO!tIX#!hDVn#d61Ahclr6-AEPgtWL<2U`r7=2LLHOZWX`QozNqQ1FK#h;*|D4>#jCYRa{ctF=PHMY} z6&GoAf1dGu!`8qAw^paM8}_WZl+d@oM{%h^nW*dz<8QV?>umqtv*4TG<7}vXsYgrX zh=6giQCr4o;ncO(Jc|p8%Y({loWC5~?7x@y^}NX%7w=45A;_rWcKUKwG4pT#Wr?K; zLgjo%pDsB6^ts5IM?SLzMY4Do9549DQmB1B;fj6Yge==BPIaamw(UOnH8DQ%RP*zI zG_h8JN35H){!3{cj(4fw-Jz0fXtGq~&d)Nv@_##eOLubVKbKGZ9Qy9e4W6Z53opi~ zX88#Oc8dvY{b#W1x>nM)Mz7TGXJ%hK8`E%boye?7nRn*dXew(kEoqs@YQ1~8b=-CH z!i*1649i|$_vqZ?eD8ozCaetxQ zcK-Vgudh!GDo$T;(JnH2nR2_wLBXZ5_vY452Blv82``^TCOv1LT%_0&vRAODP(p8x zdg0NMcXRe7R!%-7w>3L;%FO*swtSl=KUK3`>+|a_rHGxO&kj~B(Fx|W2^0ox|SRx@q|CTge{Gw9IF-vDfue z8p8R>H3_F&Uq(rmX14nrPs@MFoGW-GwQJe_uui4J_uZm5H7vFNe(w3P8LTrHT-P#+ zUNMO);*EtR(~vN16`VDyZ8`tR?>i*(zkMn7$D>bbPl=+z;< z{+!Dx225%`6CW`QWEYOSLc>I#r<$lH7MZT*R z2Q{8#(GpdWpLJd~SbIxZLf$VsSDA0gb!+8rC1oY;`ysM=yVOFL*3;^f1K$1+{=LmN zaH7kfw*P{gJ$*N_F5a+jBQ?UKKc)$hId9&x|9eH_K=a}2)S4&*)A6r$RcxL+c!pDoWkIoiR{uyPo zZT^jDJ9%{z5eaia_T?^T%8mxi6!J7aCS;r|{7wGO{j?|7SO5R3aV#or<=U>OIz_{K zg-28iE;`R|Z1EP|{3k~x#%Fi^o#&25n>AVl)_*v?rC>kXjK2Iw^Id0q9+f(N&bA|d z?|c)5uixMAyi?Alc_Zm_l;<0%ipAIFt5+SH?lS)$dn@k^9^L~RU-+)$mp=EkVd0V2 zT!Akhn;m&;(I;sj7k{poH@d_ABY&E(#_8=#zU*O#gaB{^0FY^N2BTTfyqxVg0i|b@sE2 zckg(P`*2-6%)DcBo}jsToI|7O%Jg%~-ye+4tN**VXLGpe9_eoftlmi!R@W=M>tftm z&3&mpR59-lQxLuX`Qj~`l|N2bz3}v*nXH#KqcGQ+!kX{FT`YPFGIYg* z^xqzNV5@QZd_%0+{N6tTOF&JSBdddUh)Fqw-jRL2Y^tYS)s!8z?jE}*Gc8$gzf_{* za0*k9;>#bBB6?djw_8Z8wx7OmcL+yrsQ8)beo=X=%+)6pJY%~t#j*IeURqGc{Vmdm zkBO?p8ZQoLeEFzq-CpB{?B?CV#sAE_W}mtLu61+nd9y<8T^Yu=mdQE6py~#1v zIa($ruju>1qugCe78Rn3Z$EW@k^XnNpV@tj=v)J76N{3pcB_9PyPS7>?wZEp5OpN- z62s<$JlD)LHo7uutkKzQmpN1KyXJe}NxLHSPDz`uQBjBBXqL9t3_zm7U(R0{%+ZiHOG$~t@mJ_ z^es>2=?%3<<+6&u?(%p1+AXD;rgzAXHFM>Hf&zy|(`$KJ7PWIt4t((sGJE{?-g-|h zpO4!;tiBnW@B203#QJTC)1RgMHwY+?Fx z@Bht3ml~Y@emEq#l`FD5j&+L;kGSBn&zw`g>ne^?>0++>M zQ{Lpe{J$(AnY`ZX<~xfWcOHE^^mR$5tifAr=Y#v^Zm3+W`%2y~fknu?eL~o3W7p-p z2Ws-THE*O@o4u1h%2hq*nD4=xPmQiKxdj$DPI&s9UA$;}Ge4`{%-*y1UYC^kh3l1i zog%kI@&qKRbsj0TpHPr*IPH+#?DGxEnVl)WBTv6B3FVlBHj_7k>Qi5V%Cn z^;uolwR;Ji%xaJK*G|<`(0J3epk!^QWoyr4FYk>k9<8~8YxZa{PuNt#tf`iLW!uFg zZ+B}pzZ2b9zgsiY{n~_r0{hP0+mbDIOuNml9FUbiX+ z$6KXx01O8QhkO z3UxbIy5!1eiyd8+6FEt5*~3d~t*?o`P6+lnc(>J2h%aNBXJIkt$+z?M3=e+1EQ!&wS{Pup_-mPJ zohye_yu8fo@g;&t)g_A` zVNR)UJ5K!(KX&Nejq4wdF3gh5(VBYfR9JHS1k&0nOyQp=au=ag#C-3@D>bvm!D5_xe!vi()x zow%w;3(X$q+zMJ!ra8q++r)0F;ho$52P(eYI6QwP=VgO8wzHnTUL|%|^GUSa(yhu_ zyQ5X+b)6~ObGJm(=lS+~r%Ip4B$~~B+Qz5YQo(jF@2bSY(8mv-d@140=w6`pvgzlc zpYGx>!bBWnFS1^4{rvrGk_Ojw=chc!xWnh2pMJPF$3JqJ%#j#H-s%hgqnEjdE;CYz zJuV{qzhmx3_t27*RUU^Wma; zA~ZQl6unQq_yxYqe*IcG|r$GnRaaq0;7 zm2Z9Nbmj5h>oTo|-QQ;|UZ2oYSGW6%&28`Q9p9H$)!VuUANhIw&P)~`LC%|B?uvT* zy0=f8!u6{4(u$@Pb6w(nCKpbXo*~cI)o*HchI7eXY2jxYKHIwkE-dEI3J#rPwcm5q zMZVop`wD(4tn|0KC-+gZ;@*5y@UX&>(-R729?9N#+c7q6ImIcZWpQj zubn+tK;BPJF`I2T^WY3MfB&<-K1KWHMa{aY^Z2jl@o2reU7J8%y9&3Zr>8A_et!Rg z5RFec5^MoaHa_+?o-KKEQ!md0^(8?o1GJ`w@N-Tr{MGU}_sZ{yz0QA z=(0^KWW|AZcXw;X@7q(-c4A{2pKRCe-PX?Sd{fU=oO?14G!pXPbM@6l*IzGHcIgun z6?L7cp5I)Qu&0RMgdj7dSA!? zl6PTYVMKIva&ofrT)1Q2sdq=C`?yHU+S*!3Sa_*2EDT)4;zhhvrFz|%7EjnP!yhu> zlk)V+%HWvYWxRXi`WG)&K6vn;h6tCCkdTCBkxE%v*(Fb>LrHcE3}&BAd&#EQBEVnk zs3CIW)~!Wht3AEF52qLw>QqesL04|@x)J0PkRT5Shk(f@dA%ab+NFpEV!8QuG)8P*yXj+ z<}YO?CZD(r@mGcGojZ3l;`jZjU4Qe&jSCx--NCYFAVWdxFC3Sz?+IId_Qz(grHjJW zMqM&=Is}nox>xmD*Jyon&fQ(6T&+%4ZcjMWCUeg9b3fR`y3`mPW)-42xw$Xz?X}jL z%Eivk4$|;aWMZ<5yZhreZ+e0>3&DA(BV0{EfuYsOv8c$%cb3USK0(X2n5H_*B^uiW zoSqmN8XC%27PU-|RJ)#Gl9Ze4>)~-i+qP_~muoW{?}n{gr#336K>fL(_V>4oi{1HS zVq@jCvOg>R{Pl}TKt|?|SO5QacXz9(sB9>E8>JP#PKQ}Z-pj+|!KJ0%BAP)hv(59p zAgPS$K#rMl<)@UU=H{Q@Zs%v-+LF2NZTI{A|K;rM?bFW8a9kg^H>Kc-absg+&DX2p zH*VcZGLYD?Y11N?PN5Z7vySyh9)57JdFf=3?_K^C{r;9)_4So&O^ppZpUeW6PNAQ> z?0%BH=>x-+a#eaW$t88H*AuTN}sqwRA{(SkqzP=YFRt!9B%;x#`Tvmn%iHobt zeqlPnB(B(U;-HhW^T!V#4xByPJHP&)<$d;})pzdPu_$=JP*_;_{wuX!G{%&wsz) zpZ?%L`Xe^^3aD?Cfkdez}@>#>$U>PVBdF;+S~&$gyLWc9-WT zB_-Y1UvIzEYw9GwpZv^hJUiZR`f_Hbar~=C@*h8K&Ai;UIsJT+(M*N_t*JGiPO4ur z)bBW>!=>oLSM=jUqK%DB#ow>jZ|*E!&T{m4$mTR(Cuir*uC7IXbFKb-y&iu_vohlI zv$Mu&f>XUz*G6w&R`Kx>$b>_GjJQiV62|fh3LYQh zou(V@Hq$6|N$|XjpyAl}_x67L{{8vv{Cx*+-HNhO?f>}g+p`-Rldr6co$cJt=h?o; zp-3`{LosgQV)y<>4<9-DMMURxFy6(t?*5-oXqOH^K7-YoBq#@tVH zT3cHKmwJhca2@sjpa15@MusgpH$Sy*xxc$SUr0#kiHb6KxMXQ}clYA@|Nmn4)y$Ny z|1+`Z=_$|UdlHWK$v%F&{r)oNc0QlkX1vrMIh?XInfbY2~{wxh4_-1O(Mpz`ksx0t;A`2&s2 zoc#RhH#enL{^K??wTho*E|}r`1guH#>TImIN+#`}6H~eoI^1v)v0g7KE*qw6C+dU-w&gZ}sML&oF@$!8!Z>ev6)EmV4^O#l@U_eAC=7JT+kD7E8Iit8`1w%}G&Pvp$~I-|w;9 zCgJn5vlW$<7ngVno8{m0*_d?nQ)#_P)|HNCcK)J2KMMc+`Lme z3ubtO3I}F(zJf0=0;SCJ&YYcX{y8^(j(xqHyu5tgy*(!vI=8E6q&Y4O5D*YJ(8eqM zybEkXJ_%oty`!1opj|A*E@5pS32{?2E}>v z=DoSM*E+s0(ca#kjaTZ(rqt5~MMaYuKg!$K*jQA2P_UZY7qvC3@Yk1@HkET$6@Ho{ zqu5fR*4f#~BV#dPYxea`iHF&wOtVzxPV&|i;d*s-b$fd|``=$*mG$)c*m$KD1ZaHv zQuw;W>esior;qhYM{duPJv-Zc`oRw~Or&_<-r9Qj+_}E(_v@tD+1dB(-TV29;VDpN zuxT!Se(vGhx4kD*9&Nv0x47u(sY{xb4u5}rwJdnR@caAw<(Ze4-Pu*DeO~VM-sh9E zw?=(?f8T$qR%i-?O>5lVs*Tm(^Tc!_9G3gd-BJC09k?)lKTR*z>+i3xl4dy)pwt3t z4ro?-TwLUOb5kmJ>8mRT4XJ@tc_V&*Cksh!y z$@Sa2yWSg9Pp8E9kG}w=`Oeg!DTr9%H`t|ko z?^|7&MNV;yRafq`P=*Z=ilAk zoq2JQ>$> z$DX{SU7~wF9+T#gG-^58Ex!0_R{X7Plk?{!)FlXfZf=UGQ+V{y{K(wL#}_CxH8DjN zKdIXG)mrXZ@qqe@{d)F*nb! zuebXwp098z;ilW2*1IgMtb!6-wEmh3o1c59RB$bwdr89%EV;931@f%VqzU*VoH;i|IZ(t-t@w_pd7P$7bBzS8H8YS65M0 zwJGonBPP^?l^OR`fdAlz*5=>V`zRER}bFv3mey%E1RTQ89{IVv`9Ge0* z=73Avc}q>%XWCYqiAHT(IOSMT<++nr#aFj>7p}WLA!wFJUvA8)?eX=ePEF=qA(x?D zaPfkqKiQhM;{C@3pe)~TYrc4n5`z}0YR|#i)e0)+;5(h8uQny~I^*Qk&QPFQi zxy~GWnqx8LW#qAw;*mQ&_#3YM`**94Z7<(kzyI&KZwXCVz2Z>RTDE4ny$|DFg#@0h zH?Y0GfyH;ak{=hk-3KUs9!O^Zf(*AKVVU zlAW2xF05C0C5vP3-YzDotgY{lpO`p5$?BuS?ZWiPvNddL?{@FoRD5wwq%oh2#er*U zqcg9r5;Zh5v@GNK|K+m(j~_oS>?}^dwl3Ctp;POV=D%E5RtC3&%GRf+L_u@1N4v#A zy$qMU?Nwj1UR_z~yua>m#I6!eK0dznW$ip}Hg>;fo&S^jBk!61o!!}bN#C=k9Cp7P zxzJ_4VWObF$AyA>b9cD}mm(5wcVU!ObDyL@M9jqzctsz;%hd;i7Ota$y}^+TL@1MmHB zx{S%qGru_6-m#B3wKaQT&EJDTXEZe~@OQ!10yx$f<%*j7HtdylzPI|=LP>68z0&`C zvt?P2ZBr=rF-^P3yS*>}c~`x8jOqHagAScSsz07ik6#hBHOsB=*&G!^!-ui!z}O4 zi3N_$MJMJhowPvHbG6)I4{Q6$x$5tkF0PSSyg1(4-^f1aP1Ok`bd_jq{+ z7tj0)JfTw!&-~pYef08r&Ffje`Ifv*y}6I$0pILncNdgjzAiU)2It>G+rQRVl+^F! zE)QEh)1pwxIPJ^=NV7@TJ!YC+FFzjm-CtD-FfifU~@%9#gjF%J_bP>(zMw$LRi+`@prjVrnmR9 zbhtQc=S-%nFXu|H{IpSgnyQ7?o!NVOU%p6G;+ar%{fxV=Eu-kxT|CEb*K}_FuktwO z{ghM-?%O}PMdt|pEPh{?6k}yrdoANsmTutL-EpBN4Y%aZTo>-xSHsre?ap8DX3?wz z2VQQ`$@kCla;nOn*%Ws2mQQZ(>1h{lTS$M?Uii;;<;vP4If=hiUo5-I^=!rEPtRuO zZ>s-acWrI7`)sq^j^5td)Za$c-*RS|WFG1eRQ~a9_xlaow@>FU<9qV#*|C3re{bBh z>CoZB&BE$_;Iey8-s5AvKYsnXwAh`0ntpuW;>F2*w~wa;H6AMY6`vopy=PJA`d*%Q zalAY;^>3`&sJ-mbp?TbQc4uE$nNrBG=F+S|OlTt+IRHo-1$f?<{Wr z_V)JXijPT7PEIfG?cH6oqS(UP+B)t0ytC8e>pV|S)17HoTh$k1pqd!SG4cJdF8 zRz_idNOx?qUCQ-V37@vvYq%s1&rDgWS`ih!Eju-L=H4RBAhzOD*ICy66lP{SwEbWI zO!nl!CF$PHj_y<5-EYW@Uuii1XGTC+-E_8f{O22HK1vP@yRal)W_wK2yNh?{&WZYP z^wg;?&ERDpzFv>Nye&66?aYjiLEEYy9AM14w#IX&Q7Q)~=R&7euJiB3r#uhbcCw`X z@6Bn+RgtN)d1T^jyAS;QJX2oe$g-YVQ^9SyoQ{r;XQFB?YQOz{IxY9rm6gK3zr9Vq zx+?U?@88dNSj_{q{(jy&(8z3A{ETPe!i5!;l}}I9njD>%d~e2o!J<5=`)lmD%tR}z zE5!G`dDPsMVOC>2+xD4>Q`ou0vmaki$uVrIl#jT0_`R{%`S}N?tKUz#o&J^4blrQi zq_Fi2&-D2o-Sy|&kny`<&G~m}FZJCGbY>r$Wq)o%>e)08>r#dDv}ad$KCtYvMLw|F*V^X{33>59uf$m&GwogZ1TXhHdh6D&oq3OU zPRR|j4Bl3eW%hJNT-Z%D-mQP{t7M*wdG_x5qv;#hYh_(4e!SaNJoDN6ZnnePnzuu? zuN!HIjGZhyeGZY(p;oN$f-kog<~^YkCbWGudlD4?@Tt9{PKeL!HX4V zKb5Xs{B2|U%P&>3E4F5su3CO;o#T4;wPI&4%kx&v6qCCM7XNu>xA5&VnI^Ko zg^hRbNVU3e#iJH_Ou~aN-hHiJ^^F3P8Afd`S8n#~ZTV1o<=v*KrtME6$pz@l=(}JQnA2;{ONvhe!4aHy1GGy`Aqnx%`=N=IO*+ zQ@5WLareqkOyQ5)TUrcK zP6+(^^73#Cr*OdPtLsZ-z&+b1iCVqi7A~0a^Q-#KIjgT)*@Xq1$=f|EovVvsRg9=e z{1V;hZ4X|)^n84*_vWV5)7`vXA7W0KNitsgcIn`1tGLqIm64BYzr}f1yiA?lbuH63 zy>-&Z(|ieD_wTRW%zyFw+28Vae`=jJGtIj*1JtH~_t#!&%jSb}f>TG#p#%eioErvh zywb<~?f)K8pI_4y^wVhHsrh?zg1vqPH`~P*He62JEfM!xLB8*Pmv89)>CxLfWBc9- z-Q0z?8~`#sG7X9AsY;zki;;{og4U7rXDDv~l^g{`mq< z91}mgy0{1k3x7T}Ph|4WoXd;)-7jWkyNb2%<8Ax!JIU4U=CXNt89T$bX)5i_nS8IP zHk{edwX4}^Dw7mzU}Txy_4}&} z-?!BLS@rJZxpKL^2~R_(<$n6|CFSHK)jj|J{q~t<@^Wc@e41FRfYN-AeIIu(WjXcd zC(GS%t0$SMT9uz}e94jhuQ*#z{RC$vmzkN_`n1F67v=;@y1v?L!|70Q!aSqx+G2w} zlQ#Lj%dKX*kWwPOY1d=p~#(We>BSYKB_$v zothgoea+b;$F^qPUGT~I_?+3N&GvExS-EpeeE;<6B};x!{l=j5t94Z&rKdjW@=vq* zX>R)B;>(jorKUD9i&%J$9Fr<2Dm?R}YoDA^=e6B?EXBBzTzGA;VCVijXO%r}s#%o25@BZNdvaxEuzv0BqVtOcoSqnS-aIFG zL|EvH>goJxN2fCD?0<1|m8$Z}M=KQ6a&(*3WR^Di8+G?Bxp;D`K%TFSh3R|mb=!Z= ztJD?JRrNZPSz~l|VrT7XXt~~pB?l5Ik!LQUNGbSN|UHf zr^2MxJ{E2_TYK%}%x$YL+P(JK#qWP)mKI}PR4c756|B5-(8q}T%v4R?varH zYF9jbAFk`{xw>R>w%C+&VoKr@!3*F%#H;!%@l3mP)ct%6>z0r&8;>pCVXS5HeX7mL zm_K%Ihf_Fr2R-V__RUbY%OXSjB$SS#s#w$03+sIs-lpmvVO3wS_EE@za?k?rEQKL@V%a=7NdGl20`(pJu!n`Zp;%a*wGjMpr-ukz_? z&!elAwLgNo6D=oH&lH;9NcLm?x_{!?CHoKUF<<^TVrT9&RsU0&A~D>*CvS__TUdQs z@5~%)qsKG%&h(xqVLVlXlmD*Yj9at$#pfq@B};$%BX??3vgxO{S(pC!g*|$n2Z>AJ zWjBgh{JGc?-kwO5Dbwwe?aa9=)wJuIgp+mo0o6Pe+dp>6KdisySbtZ$z3eKxk4w(& zn{ofKL|JZu=mXO`}@whueE>N{V~*{ zV6W-hE$TmSEN2egTEuC!Un}Bdvm2M{w{17CbDzmm-RHRc@PdnvW^9y-ay9*b@J{ut zVo=A;-D2Z=m#trp8b_`C|+2DS31uYG!$J#N0$*Zcp!OB*!XS4VpF zf7$=NRknPRZvFPe*B=W-%+;Et{B-A^nZN2*PxL4D$439k%KPLqX9q5N5)u(zV#Pf7 zmh~c|7eCj|?2TzU_k3QBY8_X*xPQXat%oe)e>Mj5)lIn)wy=Kt^_vzCg(ExlL9;$b zv^l5pPm%NNusIp{*vg52+riySWXsq6IoQ@c_2+{9hn?%c{}WP`l6h~s(joa5`WauVKYEwtT(tSrCZYeTkks}^{dd;fNI9EnC+=U{ zRPj`7-J@gT!By!$J4_Vk#eCEL`2X5b$6JAmlFn+xa6Rs2TFw+TVd@Y4si0BumI@YK zQ|Z!$X^ZtPi9V9FIp*~TRAg=!vS+$2)H~z)>3Iu#j~niZ=khz8_9*U%P^{cmq57jo z7V>0np5qz)E%8^Ot!*361n&#}p5T(OeP>~relRoJh5hvmjEu^WdkRfIr{}tUm)V}! zWqW*217pdX8~vH5zuRBg=2E(hyZXEw9 z|K8u?>1p^r?Q zNmHY7nPJw&GP{qu%)e&m{M#k_*l)l8`-bORXL_iay>L5P*l}_$RIXvlW&b+^}uL4Ww7q#=q+}_c&c6Qpvz^{_;?H~VM z|9@YnEMJ_@q|7CAj5p0MQ{DGdX#f9x?Uu66Mz^x8vN+=_j%*AGd*mG<;?(g+?cJFy zwsSX*PGH^r@@4dyPm8u$$R5jm^osqqAw$$AZT+M}2|)`FHt0#pY`tk;YyR#_T^IZN zCFy&U0@jP&KQ5nsVAcJLr7LqHTaqpOXH4&}hC=`;MXteGTbL7yceRoqAN#WE->J3l2`HbGwDbjq{7-vr1+i zvYb0J`nb9An#JmIrgvZ5Kia-)UmVN(UX^DzqVC+eQ}91QbE8Ad4X-z}{w} zGwtH8=Dkd-e}xvXBpteT>0+@>_H_1(xsUurROQmoPxfS;W3)G^$Li>nf6Ng(vLv0e z!_+Iz$M@$vJy?C=-#x*e*|VkRF3(lec~ZY4^!&%s`;1(i)CQYrh)Sb($LFe)fu* zy8Y+*{l%~29;QZqoyDH(JLfOOFN>tn;|c=ltS<)^|D3P<}_v$vz>|IQ86* z^Y56=(EJl{V`q3=%E`Y^UGLfZv@B$0xc7C{BE~D<686n&U|ex4OJUi8x|Vw{yk^Q) zXWV(#{X5E5=8xg31H2~Mxz2}XK91SMw_wxa)Z1CFm)lHvlKo~!$YlS!I>o(Hcj-Pj zpXYx)WNY5y9|@twY1+#S9tk=M@=SZ~e|pvRJ@%Gx02 zOW%qfuChCQ|LxnZl{!ydFU4=1BF?zFI^XkTNn%)HFIRxbnuF$Rb0tl)wNC4WEe~9F zKy=?jZL{g}=bwa$>eR<(@@7taVH754|J1moH_gNP*wYpD)0L%5HcHG}wzlPV%#IAD z($gH*=J)=7|9x3}Q=abXxK{A+M`hs5Hrtm=0}PjC8;Fht%rp(dn^q zE{nIZxMiO{s-&I$`%4Aqf``5L<|p3SHLYo_befH;YSM9)T{`;57FhLxX1fpD*PWfF zop$C}=^Lvu*#_&4<_5EcjarYtxp8NE?Hld@=Y>x`|F1p0^9D=Oq55ZcHLEx8J8mEx zT%&V}YnIRSJv=#oUr9^l+&}#3VZOn_<1tFUH~0U)c2d9_6t%x5xopn5rIj>)Dd%6; zIv%d1e}8_S;pb8^FzApsZ!54g`+4%V(Ot!quS`r(e)vhrFmd+HPt{!NBKE~g zqGZ~)y*Vf%cUnJv_QiEsj&D|*CM>u$Uu>(6ao$Nm`^&E$Pi5GWX(PKrgQtVo> zv}A#`_F5;;U%r3L>d(RNzXhz4Ep6@Y=}lh{Kk3N_XP+1=!O-e@tKGNkW0h}pZOM$9 zWWK9cIpJd2z4aLhuh+#IA8&vB&E%5{+}E2Ygk%>KUa|XQ%*;RKTxCeLT1fv+r7I~HCin18 zdu6SqCvShKGTc4VByX7E@)KsnHZ*J?&7hTP2s5mFbsr>I?SkS_h zQ2kT8ZGPWrbzILZ?Ot^0(Dhbs@uFvEB*kMxq~Id}sOhDPLHV|6e+OEa}u0 zHHnD(`}MB3ze||mzcBpTk@n|I;O~~V zE9Yh0j_T#Q^!!CoiD1l*iizFd|CTp$#J*zAU2VkbdV2oW@2_m7gns-!)5?Bp$B9;M z@lHQ8yXkhj^&2|GPstRwJZ&?~iTlB*x!S!z=hZ#SJtz0{$$`r2S(6PNOuDVA3^GnC zUWiXg`+x9Ev{C=|JJtdM0teQ_)-$RJok9#yp({-_U}5NNFB@T&5SD3@RB zpF`(12cD5Op1r@;J|Rq2R#(bD?d8GX3M;J_58gRV-sikh=ohm%+c(93bIhAx zCPd85^Qq6}K788duc4|-+onHW(SE#dom)Oz-g<9w_Vzy4ow`qFfG0j%1pE(Fv@SI+ z6aIJGe@)!pR^?|V=g!7V-nhagc9~uLj|`4mrEO1Mvs*m9*A(RNXKseU@(Das)yC4(UmKHq+99y&SK`g`|5DpPvH#Hj|`cD7hk-enw<51-um~g(4PG9vey^FlJ3nv zyI~ve$90!`UY=G@G|NqYyRYL*c}Awy4*7KE=fWZ~UgGuU4690I9RGg!FDPKqe_-Zr z$)B;OduAJ_}NB=gkuFX>_Fx~tvSKTrF$o2zR3^68cK+WEf&R(Ghr zRdtz{vVNER{IA*W{|h(2n7QxJJsI({a>Y{o*BY*iRnP6pJbt5A=daz-8LQs}UOB!! zsZ`2*Q`*O4_LsBEUs>$eo4L%?D=|4W(>3?=3-4q6)8e{pHV92!&A)!PM#nBGPCreT ze?M1GNuC562vM@0wMbO*N1wXh|HAcQ2RE%>d~-9uZ6u4=T~WU{?;q!LBfKWhtusg>r6WNBTo11TxDyk(%cQxHR6K^+-)p?eXMZFy(Smic-wF)-x1o8({~xR>&)(#vor&zw ztYPO4d#`?trFGTTBPDfGrP(|w^A}1iJKR|F@265#N#XhLPiHJC=@U@8f4XyNaU5r} z++AnYN}m}4sWp*OC00CAxyv>wRn@(E;eP8%Kvjr`^Yk^x4EKhdYin?G`Tn!@VBEAK zWwpJ17L(b;k5_+GOrN4sIq_58%Q@#!x=*aIt zX5qQ#8jfB3xbTw=+g!fI#{}0td-&GYcct27$Ns>@uFMi&*bYocK4rM~)FsPJQc)a= zpNua1rtR3CvT>EIb{XHpH{rQ6B_^G@AfLu{_8IZ+M-i17hF7<0%EZ|;PQA|cxaKLlpXHChpy<~AV|#lSH8jPC`z2qW zwN>V*&wR<-FA_IT)_0l|=+@`{`=a`fr8&^OC4t^0=pYaiEB&VP8z)>(QyR!o+Ate;&_a%NXoav0;KNlLm)XT6KMUtegg%DN_g zwdc>ANBv%l&;B^-vH0qNy^ozOA9plMdPv;!4 z>wMH}zaV`o$18KIBbV+PYfApLy1lD6`RB!FU+NDti@kN_ossNkRPc<~Y|fk`uMP79 z_Bz_Lt(os($t%Msa&*?_4__ku^rf8UhqhOgKfK*jIM@BwmCP>%zR8)rts%3%a?47U zDJR~_D{+`KMdhUZo4XM^>g=S=|D3vXv|8g`RR6d6^{$r3#YLQ#giHplI}qq!zoML5 zYL?Z;r%!(GS-X?*S@#<2BH8Oo`-SfB&|G!ensG3_{jXmdrZm{h)1R;L;ujmUUA40nXd$Olhs^P1O11aao?0M!+CycY{{9z- z!#_70if&l?e9d-GyHtmTc^=mF|9iUg0;lIsn3Q?3YgdZ$v#*=dGaeW6yr155dBe;d z;(^Y;s;-D6m!*2^1a63Wem*jMlaz9Mo#$uO=Qrgp9V+6q+OcBJ{2GOt$J_6GdFg*x z$Y0u;M<(vCt$Whh41l?Gz& z+NYl;ZtZ3A37oUxPsw z{vzwIH}fv!HjtkF`Ff$I=cN1@$~Qk8m3fx)^RcAV#em(}?r+NV9$3$n{(JAe?^>ma zU*6wna`f)AE6jeHwJUvpS3GzM?vF?8vFQ(|*37)QPDQxklY|8(89DTKh zy3qJ(Z-37|xmJszAm?JiuX8tCZ*R0W+SzTL9@hJ8?Ml^~l{pOI^(U&H+A>`GzNmZR z6c+CvAxGyVy;@MHG0R-J=v>UCd-EMHOi;_{R9AW7J?*|uw>Eo3-No)uZ?~YMf^8oA&zp`r>D2Bv*y5o~Hfnri9?3eO_;0#NHA<+g|$O>(8vVtpdNcMZneq zNVyg+xYlr9(!jVSV{NnQ1dSZYt$WP>r@!C#c-5-^7Jd8Qr75#%yt6G@($v);{C&Y4 zX7hJbXU%M#HfvVYUzyatD|f1&%Iy>?n7!=Ty)G`TO*`*={mpC=zO=QV@%-ocapM4g80%PHoA&hb?WEP$7k`d!(zBZ&|Ll*`ZmqWk zOk6*3yu3zNP=+S*}6S7w@0n)8FS^^{+RFD|q>~X+Nd?mK{4O zac;fuqP(9?BAR!39#6E5>Gj;X?c=F?rWsLYODE_(-5L=6oJ&`%yY>Iy_w}GPkn8vV zn>EuoJuM=@XXPFNr<7u@)}{|13N&4$0xU%jn7-6Gt~Y(c%ySME6&75sx9-b%tiEbg z`YPnr)z$6^2?~3wz9#+n@K9swwTtW}|1UU2`Q7R)Pq$ZEwf#UW>x@MfJO1l1C`Z@# z9bd;8QWze*&{JV)-p!BiCg0XrdT!7Bi%I7%Utj(EWsFr8--aI^M$`NH5_g>z|MLIA z$($D$>B|Hv@OF5Y9qa&4_P!_VJ1tU~^`DAvl}C|V~Nt#)B5q3H_gi`l6tc8 za(_(W%U&h0ZC7koe}LJV#V3! zcfY*5JA1(b1-E{=UawSH`<*EgQ5+NBva+xUh>0EBQ~CMDrKQ}3g@rfQv3>8)Pf6JM ziN$}GOXT@gKXk1;@2oXA#>18NY)W87*qdYPvI_-j`#N=_KK)&Fl%+cK$@0VB87nHX zcy+aU6VG1C-14_bz02n2c3aT0>^5HMqF-M!L96bzONlUkdUEpcySuwrhOd{~w{Kt0 z?QOb~Lw&xU;h%VA=6Ch}q7UD>@9Zp`xAye3#W@eY_B#qXJpWP@&~jzwk1!b#UN&ZB z_r4|h_xI^UZ|l+D|L0OxladE${>x_Slqp9RI5ywdU9RsvO=n@u&Z3*?&wVGJ$&Q^b zDY`GIdj6t2-Q`c7{O0ZamneJpzNz8zG;Oi4zfHEza)FyCw_R4>RZyHNZ@1&Zti{g7 z7fXUHtp9!^dvj+?4)tdVJl&^7r??yuUyH!Gj0ejadGcJ2^YQys(h@ z>@3sdJ39(9w%$4ue&omPoVcBRkHrrkTxwA|+x+NumuswsiALVjq$Jx}k3SO2=_4rqut;;>0 zV`iLp$3jd$?o5}c_KL{OX>B>}>(}e6`_EI+(CE0eHG6ZxL#HlL?P)u^X7;AtTOYS~ zR^s6{K^d7d4-Pi3EwctqbRFTgDtps0eY*JCsI5vSCQ~vmFH6jy|MmBYdC?aSWw5v! z&NBPo;N`C-XS3^cud#IIr9}1aZ@#I?>oR8PGf$1;ezy9^j02vmDVzTtss8@%;@W6) z6Di*Pf4@n;d-qOqm#vIAH9En9hS0N_Ww>is?KSyn`P9 zu*o|v-MM?`e8n^O}qXmYVrO0|FZG%@eF=* zEE2yug11Qh;f;=tuKV>;-K|H$aj{!(#HJL_X?L%0j()cBhlf%3fAiMZ`aA44FE%{! z?vk13c6gQOifcLCw*SRlI*ob_=Fac&|0(z8`txHY8Ag}?YaDF~eE&H&fcccEzJI@D zdVoc&(vv5Db)xk*-#qi<!*EraWSm;_N?@&y3x}fAMbzs;)Tc7tgAoX zZomKJ$T^42X=fKjZcYOgL#^E6j~+ajpt&ttZ3lZQvP{zf@`OLL4J$x<0 zw_MHk4GW#yjS3zdD4Aa~d9H2MmyWu>zXavv&UJ}u8{I8katV~s z*Dv&$X(VG^#?v5WnpN=dQ0tqGkF{jKoo6{ZrTxwPfBWCf{5@qw$+sJ?Ln5s-+FDv3 zynfx?-_O6h{QWr-N5KWYv&}%ehUVA*n<=InB_JuOsa|Hq!OMH}`0@5fN4twZJUDp9 z{GkVEihqw?b#*l;)lX1#UJ<|FPCIPPgheUBV%@DLC#zrHS)2}vs$;#<>7m~bR>!t- zi9R~e$Q-gNgi~H#{@dHz>g?Zj4QKlF$lK5Jn`>oQ{SCa--$crH*&T7oHZiA;`iEa$ zUfz;*l`B3ze&5e$vw32-hwh2xm$O;m-Y=)3rnV{RDA%WFXJ@N^%l`iEZpphlGY=ea zaO;!ljM6pt@YDt!GZei&??s8#G@ZyvoZ(k1=Y7%PQarLhU|o!5pS-v(58QG5Rw{Z_7D& z{(S$jUg^!**Vp;Ti-0q4{euS&4t#og`sMZY^8fz*TNAn2t!t8NgpSz1@B9B>Ti$+){`fyBZhiWT3kz%hd_10WV?*P4 zyWeND(og)CHsaB35y*V-dOnky;WZyeSCa;P0Y?o ze|~;W@BV97_s1gl_O_*IXJ`4$v$6br=7&GCxL(Wx@9BC|bRsA1EPnpz&6_z(zxh{z zmVWSttO#fnR`>hx<74uxD=QCQT^+tuW9Eg4%I+V&etmkk{QlGH@%3lT90ftk|L)iQ z?p?BENluaSuPqxhE-JmczW#gzBXh*ws?urPT@0%(E_Uyq=B(Qy@PDq~^2NTh%_Mr= zc9p(%dw8f-Gj!Dy5w*!P>}sv#YrhC?PCE-4+l@@)_~?7P;Pf=zjoY^`_nxk2VPyr1 zfJ~Y70>Z-1@9*set?G~7p0_mda9dues>-95pERCb@n0dE>4rI~YxN+mc+}qn! zR8#~+L|iT|a@CC7v_#~X*PjoE`D=bWY_IwM_dAcIk&5@ZvVZ3l9hWW&T@JhbdY?5wiw2B1#l^+V^6q%N zzP5H{;9|Du?RirdEm>??{OrR)c6o!sM=tyR|Er!^@+IG?Iq2kyz{L@}%XFRF`BHy; zc&MVTo_?}+Z{1(3udl8?esr{Zj!osHTU)c!Z$H*M3>so!QbMzdyG%`+COBO{vpvZU6D3;@R2R z?ga$~&FuW=z8#MOm1Zdu+WF-#tqfLQv}jSpmW;q@QRO$H=GfKF>XWqw)tOr|FQ=r= z-JWo9k?S1GVzt=11WPNcplvxfxBrXtS~_W4?(LwBNv@U$gh2T!IZ{VV&3D$3D=UL% znr4fAeSLlUq9qf5etvG8c1B{E|9rpS-`*}&n0e zx-P&V=?KT4KYudr@3Vb-YwP3B=k2H8-tmY1w!bUn%BqZYg^ zCiJ+J$JJJWLtH<8{w#cSgtPAd-*PF_tRr`KmoL@$f9U4s^pBrEA3k`n@o+o8cfc8^ zGijS!z}t`~?%la_=f>LKWm0B29rNeQ?=F8oZPA`a*KV}qkH*tX=A6Gvie*40%{`dFyQjPNw`)Vvf^J6zQBz9<-R1ea?v(qd z*hscb0}XXQ%K!2Eck!Peg}1inpa1=S|M?k)$(x=QmG;ZqKU>^yx2WdlCmH)Xnd<85 zOc~f|cMNiJa&PYKoo$$W?85f^c%v*|*2yPbLPA8=ty}l<-#x9VU5(7_msW@CX9@_3 zxmryWjcE~Z+B5O@_xHj3YAR=zC||lddFz$iTQZf8dZY_oY!z_YBiQTKd@*B+Q%8xa za13aSwB>}#m8`8Tt`py-NPtsmhmVLDFI%&S6Ks3;9-p>_t&pfxJQ6=CF=Jw2VqjPS z_+U3B;Q$R0jj3KfYLi8rmt;pwnFo$?MI+9au_7;h)0{!8JUAwHW|&CLe4=z|>KDj% z;EA0c=RrsPfpT!mkJ}qGY`c~A9$w@FHFWZX+s`|`s9lFCK4GxMBj1Pzyf@rMjl0{3 zM;E%USTW8M&2g|z=O4Ex2DUA9t!j6j$qUt}c=&?S-jg{Q;5ic`!IzODFMZn#o~!$4(nKQ;#g-rH@pV5{t;^mxEWTLq@DOWE zOw5Lj8y_BQX4i?|CnGN}uQ|!%vzfT0WT)5Cq??;kSy)&eJbTs_wDQU0e*0rLZ$@^h zfIYr-Md)g=-{0TYKfT{4W9jtt)YPROO1>*Yw9ce`*8X4i^;IaBxSo!y7yG68+ARW) zwz9CZKYsDz$EC;X)~yo~IU4=(dMQd(uKEg-n_WnUtY5hJm4Jmy>(&8rLEcFCQ@fF9|!FQzp-(#i2EK< zRqtskr6-7lxo72y` zt-qeWv$ngllTpFFPX@FDHZt#j(UTL70RaMBt%tO>`{$j~Z4n5|`ugfB%eHLy z=+BsOR)cY?sNVi>?j<^$?{^fwI@FqTH4fC1X=dYn5x2x)+MWF$j=0$5GopwuET^W*e={sKFrrm*f6yplCV%fS7<^1JK3N8Z^_ zU-o{fhG0YX^tKyTt-^K%GWolLwjN4X*jbxj{QdRUq6bD@yY1h}@9lSEo$+Nx?}95Y zgZ`yeomiZIY?8#!Z{Y$Lw@LP`Ud}GISg_T~v}r|%hQY*=uWYlLR4?3WElz)6;q%|@ zhKX!b^;)KgFK!}M9r3^I7xCZooxJT&`rbxIcKx-~zvrbbn_C2wJ{z{5 zRZe;yoAc>ubN2p%{Bu#0G&-`spFeqbY1FKJ)1O{a?8#D}QkM14cTPc81Z(U~s~fkp zpBzY?a;J5}$+Ze;Z)+Bvo4N7ibpF#{_uh&BsJ%nfZMt^tBZp6~gE`R%kKR#al~Ga@qdRe=FR{ z7yD^_|LW*dA48^Ue2IB0%IdX9bKdK-x2DF*sO=RrNZeJ+c=_mO34J-Ws?Tgmb^m*x zh~J)hh_Q6%_ki7&`)>BHe4(PFGD)K9-Ok+VyW4v9I=`|LbDDaKO*GE2q9#CgYp-&| zqmIA#3zVL{V?VIY-@x*(OuBaTAD(b>hsegGi;7)ycOBZhGrjQTEe2`z*sOeC9l^go(uE4kNkUFVg2p4=GwM)vx`nBs34P3Qbt z4G*^5@@rLnC9<8(-{t!E=sWj()gDf?F57kZj)dX!t+%f>KY700`EH4b+`a2NOh3u- z@y_dvetK(izK5_*`TIqurt{~?KROqF{G{z-dnvikA9knSVEcOb9^WIgde0{lzn`!- zV|d&c>2i-bps|-}!-Cw;KeM+!-F?_C$@mL)AWU3~uwh3>wk5jgSl zshj!|LtUTn8S{*7qMDy(9!rC3*k1EK; zcrxh*?Bic2S|qsl0lU(+xz35s_N-tLORD^Fm*3{j#;HpxxHVQk2s(9WapddD zEuYIpUM+f(5u{Rjxc>RU3ol>zF3^42+55`s;a)qncWX{-Z@;}SE%nOB=bdvtSx!uU zw{XM6VtvSFEA?I#m>n^3f- zPr4NCAn$i*Bl8ix+YOQd`~TZTh4K~E{9@gi_00d}$(48CoL}hkS@4~nRnw)TqNiv2 zs(ikFdv3uWovu0eL}vY$oi(|;bEnGQu1wG1btkm@UdQs3KRzJg_(<1ynX|9%ElWDUjZFkq*Qoevh#fk0*9q+ulJ@vJ{?7=UOa;7n*ZVYt%^!2vp z|N74io>c+QPc;5}{&z;9YWnL%D~fh9YRS9~cfY$z@RWA7;hqmcue?(4FVMZ;x*|nk z=8xF+h8I(EjT?ey&q^-VIv~^-`2YDAiPosE=_|VK`dztMVKP;EVd$RrJ1nF}4R*AGIenyG_Vh&FXVR=SSBJ^}}1Au1bzJQ(YA~Xhj>Q~LvV?_v&bVCms(h~gGp-Ldbk)O4xZ6Ro@-#g#UG=26*x(dEnZ z{d@kn<=>b#^?dIA`#JwtpF4Wx|FN}q&RQok7yREPf9H;GP+GoxQ5T1zi(2aHzn`7| z94mU4XVLByE6|^B=TiAv%q`@JjH%phroI(fyej_>9Gh|O9>W%keeE112yp4>Jv_5_?nHz&q_kyM~DC$t--6$Y5Hs3m>Kgg>X3bW3uE{l`?s9+8}Htd za=&-*OeVAiLcXimS#T; zxj9{iaqWh)3t5`Ej_$2hS;3tgzedK=E~91cJDG>CCl%&@(ev6WY}UHco|o`ijgAKa$QVVvxVb%kTb6$Kx|oLtmi4mi z*!AaWu}Iw9lb07tY4r+~Ci-7F*Y9alD}DNEw1!ql<-Z+z{p#VrwN`BLTQTSDM(z*p z&l8%rt&p1RT^W-V?hw98LhE#;+}VG9E!Q)`)6SpU5qh#cXr*KLQjuR5;tiMfNoUXI zKe#n3aY~f>m2KgjG3BDat#}JozU{4@yh~&K+XwH?TzOEc%ym2MYR7EziA#P@5BO<% zcuT$==k!Gvwn_H6FE(0X;+3)k+0afih_z3(`e&+(pmU}Ih6m;HGvKPR?6InG+J zE=i&2+P3;VzUMFPp2)(+`ta4Otf{%)i5nwyK26kV5xACqbycX&k0;5?#Pol>k3Vux zHuT<{2)%jFcdq^;@@_@<^CN#AJ=U-D+#%|8@`n4;7T8(vuQp^3l)rzn@k=Dl`e6bVlai-;!-v`qpJ{5^ByXJe#KV>AmwoX_@QccHd2J zU!5H=O`}!m71J{DJgIdB#YcoBXWxpGQLuX_zx})Y_SD}_E8fT@RejobJ}>>fDl+=%cGxXf{{Mn((^mw&g z{m0CS*W-6iYE%7Wb?ijRy?58|qhT+4q} z%l6N>nzL`FkXdoh{Pto$QLfz)?@DjZSH0B=o+NYPnCLc1@9u$=o~IxGaL%ktIxP&I zQ*h##7?xOgFd|R?MAUB;ThOY;76I^rBo0OJk#?X{(ZS;t0#2X<<-muggS(puX(P#< zpc%x7jU7i^@>JEl6z?5adqjoj*qfW;pB!#^FB6%XaQCebj_Gt8Dwq$|67AxM^+u z_}#m_t+$tNh|m%H=^^IC@lkDC{{3@TRtDePSG!xpc~7aj-<$>M=jX}T*Tr;AWPAde z>=JN_Fx`B!$7$gN$SyX;mWtAd5G~bB;FTv}Pk;vnK%NHo&q3Z4f*ckP8|(lD1Y8k# zZ9hm6G?ZYsE`St40}6Wa0{E19khj6-%5y@(7J6bI=;Zk+pz$eWMO!yS=)mRo%sQDe z2|5-c@aQUZl!#cPpyNj1ZY{)Hu-hU)M=wJC-r}kV@s^O7>#+cD&Xyn9Cr_UI^2&Mw zGy9=UPygM%ecSN=amk9xO3!4NgDb*(e0X|inLSVIk^L!k&Shap%6_Rkw%&7h^+xQh z5$u&Ro#Zz0>|CqTF3^nx>FMq{IXV+3P6W-fw6w5*1`2Ue4CAK^YX*V=vQsjC5Ed7c}W#i!q1@WZ9ADOBTrt@dHN-;>U}_fKV;)LU6y z{rKIxf1mof+}-VdJ`vWO03K%QSfj6^(vnrTwP3ZJPoq@xQiYXo?sf$(yzZB#8{xlw zLEH=L+O{C5i_s{fElQPTEI4TH^rHNbD#_pbW>}*uPnx8qJLUPnuD;uvV zia*_zpZI*?YLl!_jGUaDK~r*8WM5x*zSXtW$?@~Ev+-P@gg9k`hRZag++XD-?@c?5 z=3e{g_t7e7!u|!D@4wz@%ss~xG{rn%$31b2kT>1oq49omEEHK! zx-l^`2Wp6{2+^`Cd7%*hNGbNvBCn;BK-o#@?~!B2f|kc6^UbUGxw%gI;*FQ>#YQ77ew@oq2b6c{(16PB)tQ#NCr^(r=xvoagWE z?!L0_c1OzeQ&aM^w;%ht{?2I=$JYMfA1TIwuQ#i4kc}jSi+;Udq=@TrmcUZAH-}kX3gfG z+_Yrjj*jj>8<&VS|Nj1<`@FlCKEiAvjy*>T#aC_$Ub+K)C1a9Y;2}?_NZ{A$o zcICmTsoHPu@1GyKIxKK&R%oV3WI#xWh`4^7%gPX=`hPW`p}RXfjX^=t%4PfappuFX z*Tl3#hYn>3U1+K)b(?&AbF;`3-3!O>J%95kjOEJrhsSRkFHwJ>Yf=7AW^L5gMM+1y zeCAq-T3heViSE{o-gaSseLd(bunP;FmlmcN-rZH2e6WetzkY&Er?#P?AqzV@Xfc9j zkkv&0qKUeoxx_YKy9FnH8>?#UbG14iGll=wsc#p!O){@F9XaBn=_M7lr^3)Q>q^3d z1C3{98mmXMyEdr#&e~A_ziyg-{5f{{ngfrI_b;88qL_PY%fX8mC%$-*v1sw)&dyFn zH8nPNcJ^6jxmv7V?w2-eY})fa{m+5)`TdK1+C?p%u2x|6WKWyi=q=4Q;c`)Pd|A%4 z*Qfq1Ua~~x*Z24S9v&Q^wHQ7#je@46gAR(3kvX$1_cmzuE-o%EXo}8(43kO9?tG6P zKb~K0Q~vJGoxRoJTvPg%qz9&C%GZC=oZmnDP_^T2RgGoO*}J`*JHw}h1{rmIN!otEM@ZT}e^ufKo)sY(#7(r_})YRO%CY>yOeeLbe zvh_hLRdmvhJm1OV+`UFkPp?mnQFrIB0N0b(>=$0T;T33fVour(U4CztrB3G3aCvXphyYR*jYbc>uOek_x$OX-cIuh*dny^6kEWiyGc=7_);!D zIvO-#f8or4?d<7MF);~gdbEGR<(D(8O10u^KC*s&eLelznVCUTe7u$hU0miX zJ;h74tgP(I%ggRvN{N9T_3s}Y?GE|0s!cOLxr^P$b>Wny3)ggdFU*?qdRwQXv%68= zpYWgw;0d{V_wJRvywrNMTm12h7aFcV`J=YwoP2k8_e{%TwYRsnR^PH#HJYv$yDK3% zx%p(uqT1izf+p}N9KN9BZBg<&f9?C~l9!iu?AmpTaoPrrO_O@1&4q4Qb_nUFJ&f8kPeeu7K<#Kj zo~yf-*!h#ULQiklxbft+tvkxzO0A9Ft`@(q1~m73ILzfFU)Z{slOG-)o@tQCWLy1h zO4A>9&`Ed6$NMTODi&PKxcBS#-F>yv*4DeX*GcP5K6&Kp>+3Vk^W{M2w=(M{2BxrX zPCJ`c@j>EV?tY%j*Y$SK^>iP<~}`5cX9Uh zbw7Um*ii8B(3`)Jx`!_)8F%*gFVA%F)^l}OX~rFp`F4p$uu< zR*^;W=DD|A`sHjTj8Zt7*?1>4=dprX636>wS4M5svj6kJd0Tw`Z&y!GPmojI-rl}B z;b2qF&MA*>ZccxBXQy#oTpTE6MBkH%uM%@zCMYQA7&?DejQ^RlyJe?&2b@VNn)Gz(5={gH8ZpJ zZ4D6RT9C1rQ$ysFwYv7EQq2bMk5xJ|?ns|uTlM>A+OOJOwZF}JrOnmU)Y^EZ%>vd$ z7+PKkb6b2-KtQ0uZ~5Zl=jSRaDnN&qrM{l^=f~rI(22MY54RgveaR@Xirtp44ANZw z?oP?8E1XlkTDNciUi>;NJ=KUucg?+t%I+CAp45K2!lRb|^jy${90z&Xb2E?XX39P| z-}8Q1+O2BCyxZG)^X~34OgO;M#v?f?cVC3jX5;j8po5jOuBkUv1-$XWD+sy^|K~irVu@ z>*hA2#@_Qfatk+v8t&b!TVCgzu-4V}c#%}m>oYYwRXT5HR(DG7L#ZW@W{$IVGyzq!uo*r%4Ovy7j+ z-ztA~aCf8jWm63%8Jl(K&y_!%Sh;B8{BKJyFLQO}m9q?0iS?^14PY^tvGb2aPN@EO zwa6Z^L-MnB$mKqJ$1D6@XMy)+J|5P!t5y~sJ@iO`S;}1WXVHOgR~B$=c-A7=KgWx& zP37~2u%jE&%&zDzQ+v+6dv!rz;5AOYyT;338wT9A2wryCs&0Sk!m71ZHg$h2g16iT zx8~QK?G#q$;N_jVUK zqiuR*Uu(DWYLBFGn`QB{3oC=w?S8*8?r~e(;;MKl_`?eQA5T7aPT{}RRovLKbH%SK zbEgz6DZ6~(-OS&v(<}U)PdNpM`Nf#ne$T%nBPvq-(^zI+w^t=^Gv}QcksJ2HD*K}* zpIBDV;cZfE{O{>^{b!2~ylrh2ykpYf=4g2QIrEA`-+q2y8+o^F*;CUeOgbCYrv1rd zncCHM^uNth+tXPW?t7*s>qH3o)@;_Bc$%%E(DiLrs_BZyy>HGnq?S(2(p8N<|9s&( zn|Yx%J4;_r3*CM@c6ZssmoGi%T9roZuiL95IqhWW`+IYHrOl19u4w%F^0GN@{rRHb z761R$g4W_ZJ3E_`lXGF_OxiT5Er40Drc|8d<)aB(`y|Y%M@#6A%?>C-~cRB2S z)l_1#=nkuc7i~Lb$}S0n1U9eb`l5DW<%4(foi$YlT!ibTXH{qzW=^%3<#Xo$Rgc|Q za;2|aYny*fweZD^#^X<~PWgN(q2ANqhHKW;kfMsmpAVnSdhqkd>I2(%bnQ-`)Omiv z;{^d48rL$I%$@fg-`vH&yF71c%+8{)t9fVBj6qG3&(F`NA8cX`Ssxec_e|%}-tYI6 zO-!cL{r&au?c1}nOtUqjwq#7Zv0c`>?84UUaM0qP4ngIBl_5n3{ZCmv|;zyCE(&1@Gif!UNyPuiaL_YogX(7w5bN&z7938ht zMb&W|erT9JNpiu0Y+Kn*@yRo{S$9@ztcZy{{q^z5J0{GYZcWdRZG1RSvR_H+&K=V& zddnVNx9!xinZ#qJHS4L_=6|y!ecYPD)e28ANcgbghWiXQS^Y=XFSD=y{%-56vg-HuZ2$cI%gM{D3To6Pi9LDo;>4q) z-7haK<-WQqH2LeRt23+WdDxf_xA87^@0Zhw*)ie4g9Og5%nyspE-Y{aomwTR>~>*w zxW16Eu&NH%#A!;(${)Y2Jvw#9oGE$xWu{(}x|SWfAS`l5{UHX8m9LjBt4=kY&aYnV z|MSmh8}3yG_YCdk&MEq^OVGs2D_gXo^}hbhg9&MucTPFpovijN^yld?PrC!pWKDHX zuiROyIMaD~kLE$43+^*27$hb!_BLJm8Ep4}d-}CU)7=lAJbhATU$WceUpwbNo;m$q z)rAH**0_05R+X!ER-gLtrSyMSdL-{F73JN+YZlLaQny}JHR`PT0@vuUH4zgZ-rk&k zUQR1?Rm=MI`qJikNABJQrJFV3>*M?nA824?_VDvNSMs~y=_%2gni|lGBzAteBj?Zi z^W{jbirA=>n3#Cv*s(`XpB|lQoUWq7H8D&+Zt9*@3(MEKc0Mx>SheQys}2>N1#F)~ znZ+j^SiFa~?pxZln5_xFQ-pmP#Dpg6thgKc#Jl!KTFR@fUH)-8rhD%87%_{z`|tWA zXhra$rw{*n`rKXn{CDliGy77Pt$F)-{p3TFq`1!h*%5vI%G|1D@BE(43Tu3Oxwhij z&5dr~+-GdmUa)kV^`DwQ^Q*W;ujs8)`ZUMzaNB}-)%1%}Q(pfv-pgKdX35F>DJRTk zoJ~8{Bk6p+PZqR}Ykuvw$Yp!?_E&s15D*eN^!E1lk6*t&y|p!aX4>uPda+&Gx0}Dc zy*>Tvs?aBAW(M=Uh<@dJK&K|a)74JM& z-`ZBAr&Y3Q%7<;=e7xT5t~+%gSK;Cep=Fw8<}d8+u<&N{awa`Oz+Tq%3`eV@OiSZ;l)zRY*_u`i+R7no-xS!_+^yIfya zSUR=BtY7lX`Rk7^eU-4Vw%(j`)9BW=T<@!^LT_$JY!0nIckKA_%UiR{D@i7nyPGSf8Qp zn~Yh953S*?%DZd$>A;c&uG!Ydwr*X%?%I*-uP0^3-fvsvQ0Fguckfn{bIVz#cFE1- zx+ddwY(?P3z0J(=*Di0Uu2pLLZX$BI;heMY^G#B(me|=lq^mud$2pxTYTwIT|Lk(( z$>!2O9#7*8$!aUzruyFaw`ld{6Ejt~)BX1f7@2teGTF4SgRSJ9R9k5H<9NsBTM0?k zYf87t&z7mwl;1M9)VlA|p3vPTGgtlsEhyZWwdRQJB~m9^W*V%i|e_*Q*G)C?bW7ov)b~!+4-ew=Ikulsa0PVd+FZ{X~pfasQ&)0c9&K_Mn=Z6JN!E}x1SIwets_b#fJp1?a@ws zO`)64)ZM(T^A>^z!}9vL<}{w(xV~~%rpS}>h9erM?c>6Kguj3NY{dbg-&OzZ zo|-DyeXYtd=d}sY*630kC4D{kUL*Ja^S4vJ zd^CLFt2JlNo8b6l-MF>iH~o82E^RnnvbC;!c?{_^s1=f%%oYV+UyY*RZt=VtvPb<4)z?E8-If3I^a z-|EQkR#}D}`=3j@-OD}E-~hI;_2c`pJly=wcXV|v z@}F<#;^G1t``{AMNDzH~Y)j^43G+Oe`!%0^r_G)_TQc|dw%&h#e_!5TU*FW+Y$ETT551>t-%z{#k;T?TzO$#NC4YS1KY!ml;lk~QB#bp?%nWvW7`ah;_qwBn z|9`l#ZClauygK#ey(p|(Hs;!lOF=Z=16@S^*@#Q%lzqINMy)TrDwKh`hpep63+L%FuzQGraSg#iI;PP$AgVZE^>GkDhCyo?MDQSGoLIX5@u z+}|h5&(Ht*MxwV-UuyQ{z3sNUkFS5Wi&Mw^`-kF#VOR8?eYq3-{>{vD?cCZ1wT}|) zX6~4@{`A`Ae=mGhnf&STqs^&`b1b)ZE5;@A$Zei^s8=f3d11}b*ow!I*O$m!-u{ww z-~b~_q~C{+KjuF9u%F3@xNy^&F6{k*da-NkguzkQ57=dh>f-(jm;7wb3th^TzE z?|a|XEr%VO*)|qE^#ZLoKQ3QCM^U>9G!D!qsui#$Be3lKJ>Q3iTAwl>>Z+)y*iia9 zY*qMrzolMNZ){5KZgExo_h`24+U7MmikIe{GZatzTpvH}LefTq%u6buA+awnFH2gN zt(kha{anMr7cKGK7bAMIZt88`?5>?@)v{($Q+>LRJAdJ6 z294~%jZ2JI*ks&VYrSl-`i_2XxufwL*L#}Z%r+A&{VVdDFK)@1?eWG-*NgAEQn&Y1 z-TOt%SNIZk3cEkqIz{gMX?JIfGS9P1JpV6FOl0RdS-3L#+ww_YZw1~gnt64<%;OKo zd|AG(J!r3MR{sA-*t+YP-kfs}t-6&kRV$Q(m-p$JnZ{SvL{7fFJ%9P>-?Mzw+>!84igl+S z9GaJZ@$1LKvL5?#+*bQ_R!HTZS|Yr4ulCcOUfUf%?~u(`dH1Z2jrV?2;Br6Cc~M`B z&!2x8$Kd_xx=&QCkZqM-&cC*miTwdf$`-u%YUpz0kEDS%3k%Z&`8z$+*?;HGtLA?c z)V4r++SYpkO9C_`tV%Szr|T_EKR>VL*URNId@}2$SMf@lJ$U*QbP3gs9firW*p4pw zAbe()NpIxkWgAVNJkyqXWI4@e_OUIQlOz6qx#q6FyRt@Gn4LH2tNca%)_=B(R~uD) ztu%1m_ApU(9Il@r*jVI{EbV$G&`c@6YTX-Xo~~BQ5LH^s{R`FI)UQ;c<`c z@Qj}e+xV{9C;nTrvF6|J|2M7AZ_rq0#&du3=F{&sHFQmk*l;8+An-_A*WScV)*o*I zJ9WPa8D4Z*eC(e0n`3g-{l}VOLoVFV-niYvMU(sX_um_}ec$kwzI%O2bmm;k^j|j5 zZJ*cr*>2-o(=>Uv`r6O`eY+WxXP7P%znsLraq(vJ<=4)qtT}q*UF&I1-oM>PXFD0h z|LXQtPuUV57eBk-+)~N^%)5SLczD_21T24?9S8=lKt@B4dl>n2}~Rf3oP+J-K4V&#*yI5peV zeWDe^@a>JWcb1^WbMNxm zb`oD(8!ap@K7HN0VGfh?8{>@@mfTM!9=@6KG-S=1Wx;ve_jRu>UmY27 zPgHPrmEne1R;$uiU!rbjn@FvT-Q6~M@@4h%yfan0w^GE+I_Ep&@>EUHe495fULb0% z+|F+9Q`gsoufN*l*8FMd_q#nSL^ei#X}GjRnN3vhRDZhNkPA8DLp;C_j$)l#a}*@Js`Kb_1xxdmlsd6y|r%lA&2*AIa{Mv{QNBM z^XrKnTC^Ak&7G`ZYXul3=n z3T*CvcJkNxjhSAluT!c7j%_zhzx4P)jMwfHitlvZp5E?oFQ2pccJ(dozq7xEI?XIt zB<=rf^TpVx#&T|EcfT#lAJ_H%o2@o=`KRm~OkH~p7jEL@eY*?xz(R-y=<^~>#Vx3 zp=VydInpr6NGmMh(ZZ%VOG9|pDIDLvsq18K=#{mL(&p`VPWoxb>hdnutX9k^@X^l> zud~5Uf(|PSD=QafXmgzP<~o{wb#)Ev!VK-lodE)YT}m3J@>2t9W}lgBJ^j>F?aNEO z#l>`^zT~wkHQaMq+4xxE#qy+gPu{xR62E_ED_7)v`AoeN7t~Il-BDa(>C4;{xi!lu zPEJn8I&bqPAtSCH$-Bb*>x-s*);hUkZF*%zow)Pts!wkZP0rjR@NQ$#83nih-=yE3 zsx+!OeDnD&6WiR^UnczJJ}#Wbxwv-zN10u#nbrBN>ce|3czzbzy=(ou81G~AUr(rc zba+eAjsLd))UW@&SL-x2YHixBEt#MhjH<7%3=+fAXcX;l&!!Y2S#%vX9gb+#Z&==<*MF2IN{-`PIuIoU!M%#XW2V{@`4XeU#?3=EEBw!&3}2%gVTNAuWU-a(LdvMpU0eM{*P|P z?5~@9cX#>X5UuuEYu z)WHj-?r{>jK3~54Vc-lc+BqljaGPQ3DUlwx$Lu@*DY%NMs@&6bQj^TOy<1;UJ$>T7 zuOS(W&nytooPKiKo+LxdBQtKMPMPS=ocNnJ$9`4v0jt`iFM)bZnvs!fCdM;` z#&BKz_*TnS;qY7TXz?!-xr4;F9J#z=)AXbJ*GXSq?(ZJ^vOZr;gz<@&7kjC4zFTy- zyy11r+SjZ%=UkjVbHCZFraY;aO-s^Evhw~{GU`q^z1^^`i6?coUByn0&Q$@)+f6Q| zob_k)w%C0r_p!)Y{`?K@E1Y)T(Th;lk?>ixcVp4Sy-D1wL`&V?Dtz!yS@U*Z$ccQ{@~u5J%0o?|C7_M=6g2BZ`v~7%bRoN7Hqj2t+sjI3U*I73%}M?#v9fM zMLx{?vFzdw!{cs>r`^UE(x^%oe_9)M}(vn;VeSyf4~ee=}F)n6Js#9AAwMMdv@+w|zY#EoQulk6|{PBE!l z@pylP_R~`vps(Q z^z52c`{2c)zK?d7KmT^F*eA+(l9H6=#k;Enuk2Evc5j}{?W*ZN>-i2p{G*qaZ{KD2 zCqC@DRAbF?wGAyBlP<=WWZs+q{u|4sC+i_nLnO ziQLw|vgqlhhi~$~Dwm%2brX2K`Og7~JEr9sxxC99w|9tqPx9seU_Mel~H-|r6PRk-zSycFM2kOwZoIof51X z`5e@)*t&Wqqcm?yk=TXPsXRH44jf|Toa4Q9?e5J7-bTC*e#(*6SYwmB`}0cW$me^Q zc6D#jf9$_Dlsz-jsl2ma><)Ha{pWeaI^KlR^IQ${ zN@cZ*mY|KQ3KO^BM6BZXmvx)!VJ^fGv*O$+mq=mXGu4QyvzTpT@UUX3CShI$9Q?}S1!4ne#n^&B1*~a8=w?eI_;ouT> zlZwADjvsqx$~kLO_pH6M9yo1kVO)BJRafz&A-FWVG&`op)VOl-lo?7N*Llud`HSW5 zU2U6!rqk7Wi*uhgvYnWqEL|X6`Yr3vwOgHfSKBWw-`mJztM>S%8vB>bY5W0ee)6w5 z8`$$e&pxpFq3br;_op{5eVuW0r*hC9Hzy}>1A$`l|{k3^(MEZot3z`pw0f{bfZZ9cK*?2eh%x!Nk|%*oEOo3X;I z^A!&@HOHJ3-mKy5Wb+U$!UwNHvJ2bv#GKA_##nuTFdkY)6d%^KKSf-x^bzu zrC_@2%6_*K&rX{&KCS#avAoKuEi5s3qpQz1{XdtY|5hjH1?Po5xE>~VNp<3pMJ-2G zUtG6Q`Rb-q9Y3OvpFi%yan5L$Nx>24WVi3rUY&Tla7nPHq@p{|6YX~nAA_{2Y`fA= zO;uq(w!>Xa?lj93t=Gk`OHZpVPiIwqC;9MG_PhQCVlTtbH!?-K?D%XX=+iiD>(nWw z(662+lG3EttyMX)(X49(WvsWgOZqwsonB>`q4-wzyV13zMRyK8og($YzGzD5h0U?N zeWmj~c;DuKk$mhhS6?qcFG1wh=G!~MwsE>H)p>EAg?DPS$)le1*k?cb6}McU&EYyb z=jWx}%J(Ga3Lky^vRP1MRmdC5r?0@hC@b?RdhIG=sw#24OmbeUQl}QaFJV&dyXhLd zruC!tBgwmdo@@`yCLE1aXtvRemSkW}U{OzR?6K0`K7aAlKU}A8?6YaKPihPnQ!5Xf ztobwAS8+l--~Y_Up7Zygvzzy2^tUYb z-KFQ|PT);>vArSqt@q-~uO`fXZ?y8gUC}w|12@%}g|_Po)y!CI>yu&PwZM$=o6Zde zao!iJH{W+xEp@C9GoQmfbMMoNr^WT#78qxDc9|_M^fi6IBhtHPTFN(HrK5kW0?+)r zes)6UmRIu|Yt`p9DVcFpu)cWyrA@cq%tIV@iCLC0VMI z3ZK6ys_NlgTDDflXa2rZ&pKaxRps|z`|x)Bp%1#Nlm7*BpXI7+bngwC^Yg9xJCi?i z@4q@?yzY9^nlNtWW4jFg%w9jk>V^L)bKL_E92c(PnCn*9o8Z0T(`2DlDjPKvKc3=v za$bjd^UUnbp5ME_9&kE(D(h70HlvC%rtOV#x^psaxg2fy>Q=+-vPkds%gQ^aI?nYy zIjxlZ!cA7y%KA~p23Lh%ha-x$MhiLeT18Wip1HDFj3wQ2?hb9-k|Bac*^K#+AOBxz27o?|dltaOzt3Y0J69 ztxQVamuRxu3r{hbyG^it>gyYOYYbDg9L}oE%?t|Yy5_OR?pk}|m6t!XyZPrY+|unJ z>>r+0bGcQieOke`vmCGf33@GGBo;F}EY0hiB+t6gSu%pBXLiU`e(U=3bE(+x>D9)c ze>?p9n{8lS6rQ{#p4Ur)*T(F&lx6$0b&H*+p9?bi%{yUDv5MXGaFL0IVgji?!J=@ot9D@>|?{^O#zB^4By>6&63!Ai7qvy5roTZ{BG!pH6GG^_;r-;QZ#3N2WPF zVUM@`ZLVIxbYf<-OW({3?%U?~uD>}sT46m$8u#>`ZM7WpWRG9iyx#obpSE19JcY;H zXU^XBS?jf18<@Sc^_y5}C|trCnq$SpIv`?1|rW&a1p-U&3?#y>*@f&uSLmTm9WXwaBY!^hYEX8rZMN*3HZx*FUZ z`TL*mm)m0_a-vkN%sZIN%fn-jSieQ3>ZxyXzBLo=HkK4EI@e^^!k2w+3TyC;;-}fa zu5It#WVSSL632wUE8*X1;?bZriAFH-u}ToH8=LVc!&Gyy*JcX-|gANpfp$7;=w|( z59^mNOP(m+Yq0TF+z!pACZ@g8t2=JYJaFx|7}xI=D<e}zv?w@|q*80*yXaA~iJZT5pLeI?F)jDH)#PPX{vtL}?qIuSeC*|?Q+5Ug?`gR`r z6}kUH;e}n|*H-uMI&?~td&h#>=E#(@hI4LNwXc-c-}i>?j_v-*hM9Aw*Ylrw+Mb%e zRQhFea<#nc@`z`O_nKs5V zl*$e+FBABoe(9Q*>CNc(Rkb|R`Tm~N+-bg7d`?10sc5zT{YMurm2Sy-s9EZ`f2y{I z;z!9OUjyTWN~UJ%6&xG|i#DEXl4EqgcxapM$2GMI3Rzc7cnX%ds5~jyW|Tcu?*FW; zDI8bUE+gU3<1~A53UU;niyZde5y!q#) z1D}emjP*MEUiFP{^5WK2sxEyp{=2iUpY&bgZz}4=vdT1I@&@b1UO~2P&qP1|KGpay zSN4Lt)X&1(#g{gV8tuALqOhK6N*>p*@NJ)dGYW5&UZJ^iqtWYCl5fSu{%kt+@_s!5;wqaA`tEM{l8J0%-?HwnV8D;#N&!aJ&d&~D5ChH8H z&zCLtJ-rnQ7hm@c0)2vlc%0;z!S3oY)_|O-N1qaMRx27oj&idc#aR-`|^i+xom==2?%f z44pN%clG;DFW>aivpDSH#Hro35nKOi{Q6KI8Cx#4)jQ;dOB0Vh{vmySKUm_O)|L2~|6UTvol-0oAh~u< zZA$Gg^H8nVbHpdOGEO`;eZ94CSl(^n#H>oq6RNj_QYP81O4zpH$Bvgb`8P)?wC|N! z?xW<(-JRgK^E{VeUyJlkJ@(u~tKVlA6{QL7_^MI6*G|doYf{2)F3`~}U*9qJwq;*m z_wdP+FOL1nzwU6=Fu7l<{H9m+L*;zcuH#SQ>MxafxyLZ>HDlWw>650-G~=v7*Nm-E zC99sF@0K=7)sbvw3y4x~_-f|teMSBJslPSBSKd8Lxh9l6sW|(vLex6V(+{uycm8}w zEP~~&ci)oy7ffC1@4p$pzWB;vY2ZGWmg8r3@umJ;d`u~9?~=w%?=EreiI#X%9BFUa zS$}!Sr;s|{Xora<6IgGrjW*W`UF8xGa3MSQ{gWq0?En91-n`j(zFJjM(ie@`H`gpL z)?dC?&TKC)>tz;fbK>EdP4PQp>}E`J4W7rmH;h}#cTuUIPmf%0H@8yS7QmdE>^78dK$;MJ&n7;N*0eouK-t@u}v*89#1ocP$DRjK9^QvXJA!0}bWnvpwF4 z{6Bf(-H$&-YfhdjED;OJ&Ht@GeYVH56sM1&-%TxF`fAj9#n^8WPYGT5YNnOaq~84c zJSVm!j+vL*^A3xDxV`?Z#>E^F@uW2^`m%lQCnvG+b1zHTSu{oa>fB_0pO*9Qo7>yj z?bq0G9@sh6K4{MsAFi^$_tZofSyUxGghKiK*DlO_nYbp>to6V{Yk^Js&wT&)^4OWP z&HFE#B#68Y?^~F{dHdfUhhK>;BFP2o|C#dg^YhQ@zk6oogpU;>`G<~O{;&3LURdX( z*i*;bgv6(-^dH@EdpW1^+NT#DKEAV~xOwt#vklL@5?6`VM(Jn^wU>Al6_fjb=K-TvE5n0a({(Z4#G`z3!G`zAYgT)APlrO5H@g4EoCau2nO zdT)~&_ZTa>8av&IY>MJdzVPt!lu|*BE$^KJodMK<6qv9-n(ta zZuy65t`1>sMRIu#HtSejN{SkEOr+%-;yQB$lxSaFYI!$!vp65kL zYE^AAH-onP^ZmA<=#wDZ&YD?4SGP>qn5>qR6E$0>bH9tx{?9Ho+twdC?#uUL;|bXV zMYAP}FSY+Jy)g4jgwmo5>UxI4Umutj?|l64@h4H^V`u+HmfQW>x!S8tQ%DHZ`uMS; zph#Hq%AJXxU*~;0B6vmVvX!-9j7yb>>LkwHtJY@dcJT1cI5u_0l7}q6*wZC$70Pt| z;Wd!uewfvhd*=nDuiCKQWLx!_XOZgej1u#5-i5T3-R;_x`qK5)uV-8Jds1u9SN*xX zfme0s@wL&w9uI)&9^ilG}(}dbN8bQW8o*v7ZbMi;( zLYJ3Y=S2M!Ha;Z3cY(mvvtp+dE00f~r>m^?;P-R6XeA0ZI z6sdo6=^o)PE$e5UodR0tmdzEs%qQ{Gn#eV`sv4pV<&RI8e&w2FFf1l9c1~Q<96C{cZS(I`=`=854Oi~MTN9NCE0;MdoL(>RmA zZ4%IH&wsxDv|E}_c751(S2L@BhwRU_^3F~rIZ}7#z1sRZaD5zud)M!W zfk*o9x0|Fn9k&w64{BNYCf;0XL_nP z8b$b?&bUx+`2Ea>4}bL3Km&7^;?KmDHmsO&8Z|?3*~* zEVk*x`p3<7Ia*(}s*I}7U*+GH&VOG1@h|Q@wx#9$8w=i*w4Jw~FhgMK>H@|aoB4Im z-!S`S^!d75@C9EfP0h&X9(&3!EiOH`dzmA%`@Uly7sP~r&Q(-0n`+CiQl@DInoyjq zWNf`QwJAy=Udwy_mTSM~{0Lc79P~ncuh`nKe$RDBuDX8yl3x`R_1tE^$(J<2r9VEE zz7ALz$kY*IT{p#gnTm6TwA+OA<#WRCJ#Q)bk}+{{VAqV~b;5PO%rt_gCWQoUaq={p zai+uT;=c6@MB+{DX4t(Avg3U%qpNKlk{G@>^~_iOIXk{wnY3?<)aK@iv->an&{jW_ zX1plFY4y~_7jLXf^$9V_FnTcQ%`@ZIXFI$u9D5P4Gb6~Q>GGCVFXqe52K&}pPL^YT z9(3y|Yo>&Mq;=-c#hIx(i@#qs+TI{0IxFROxq6BUZ&{U~-Kux&{Xz8^uVpk3x;j0w zO)zb?ncZ~1Y03&$hwP+RL9#!yRiyaI$xpPGjKb5>b>@x+6vRi8q` zE~;%ma5SRv9-mrMnDmF$3lHhf=GolLlO7RX(g|A&uz6u(AaPd;7#WeLwD>40Y^oOV8ETRNrlsl?+&JBof9SuD{yI$@&C zJ=Fr=I|svWUw?9ZMd3uN1@|@ErrbTJ`qw%z_l=&}+Z!kEW=GH5_>_ZHphMvG#>{l- zurkT6fZbx-)6b|Z{H>y9R-Sxt$H_Cdi$oSS@Vpk~PTHB5Gxw%wd8&+Ja9z^Py>~>O zD4bqbv9T!8|6jD-jCIDnYxrk3rQY_L#XIjT|7xcbyDDuGrxkB`Z#-}2;p)ee|5~2( z&y29{D^X}yO<9&=5^3KTfeE7`pMP{neD^*ZG`d;9HTdIXI=axQDk5|2aCE&wh zM&-EplLvX4qTDLYCB?U8&XmyHv|;U}qH}CrVpE0d?HWI_>+o=fOuFyeSYJ2s?d->$ z1)&%29&b!beR5<;9Dm1zbGwd2F9|TM=h~Ynx$4c*%)1M>lo;8ohFGqAvNqvMspMPk z69Pwm{n@nH{95#P%PUJ|H^!P9ZklGj<=ayCR)=Lz?)ofFDteZ``uwV;$r=lIm;3kj z@!SrXyKv_9WtrSt-?wKO6x&)&?s*hnp7h{Rt3<(qMfD6iw-3#-JSbA(8@l|%pSED` zXF~f@81A$1AKZS(>QvXao?nxK+-yCPL32I*ej*1;?w?p!v7_?h5mjZ2Rf>8Cown54 z%rrgd^Zx0_JGu)}yc%?4QjVOOIXf+;Yt_uR>rAh1<<$k|fa?0B@pEX_X z$f6>Ki@QyBnlKe@zhsiROzoCH{`S^SUYXpYWi78iZks0*-Ew-0g~*|l^UI80#^t~0 zeTX&>`1i$muFV1?!Fez~PYo0o&E327ncCmP3A4^X1VY4@vHvZbec`?*) z`nJ9bO_l?P?%b_Xdw;>Wa(iPoA5(kK+HhCJxtX2Y4$!@|gZCdh3ss6AoFpmajE<;Zcp z`P?!4bE+Lrx*E(>3|F*_+*tHvM`x4j(-n-ZQO71Yh)#DZt=a9xe_}%B+J+PTUuAwU zG47Yjx+Oa8vg*~U$dF5!(<`hs_UkwVT)(u#&iK?Lr?_%e#YFQejKzn@xXN>t*h{&>sH%;?|4bEm(RMh3;IVAv0&vlR2-nSA`cR9Bi9hw*IVy z-ny%Sn_H|G9L(xHd~KtU=ZPoWCF>{j?{Sq{mdPD@;ViFCqD$u48SmuMpQb9S%$T5^ z^LfFnGrz>EE0%34W7^;rVK5_SQb754rRt+%A!kdQRUfJ@>G@Tzc3xFQ<_Vv_T+<$W@@8p|uQpik>P;WJ}5%8xZz zt?%*LDI;>U+Z>}*Rs7+&+R_)dSQR3qkdMw#T#~u{4BLEt==qI{Hk<{ zRCU>E55E3O6`O^p%AC0+QKBVovPd_2>;M0Ci;o|?^kidw(U!o#1t%v=sr;34Z0h|b z{l!-Mv^nk@w5uNHd2y&t^+%ne+?-o(I|8OFZZ`#{6AP#8+}JT=zSzahE^bGJ{{DPh zW}vIhzr;UWeOug$*JaTw9&=^6kluYs+W;e{QhmxLU#5oXIZT znw+4CG3PD5_Rg>8s6RQQlF+-k==P5uQ4ywhMcUhi9Ttlz9PARiy?^u4O(!dFrp_0h zzo_)wZKYp#q-vPjgLb^tIA&V@Y`UARMc_K-dY`wx-#YA9T=|x7*8c6;i!GTkD$459 zb(VhV5ar4Iq567xf69qJ8DTAY3w9hkFp+ithDTvyMzQfnB%hypazSLFe?h2P7}t}$ ztwu3imTX4(Y5DW}>O9%2^eo#W)|nc&mp?doM}9}>lWd)O9>vEUwl%>f%KzJXmgHG& z)Tj)vk-BD?e~rOVGxm*{PeEpCI#by14e5~zUYCWt=cYW;^NrRpj#|sv>&BVpu_y9O z)D|^S{&Q{nr)|AFd6$X*o9Zu7NdZeY{QP;r+A(0sw|aVn2`Je`7OPuQ}~?4Xy6 z)2Yz-vY9O`4sA)il6Mn$oQ+eSuHJKxg@5JI|4yGLhbt$PmL2;4d)o=8G$Xh8iEsbb z?40#i%h9>*L+RSmkdKHuf=PH?B|h-G0_)W})jQjh|ZrckP%E^K1rB z!pYuTLr{=d7*3Kwi{3Qo&w{x-cANt0YpK5zQ@Q1V;+S?|{ee*f9`ieoPGsn2GQQu7br@&r$d2?@VuS~bTHJew`?`E|e@+1EzbuAXVL(0*5WD){#MlT&Io-s!!xv}4nW z;*iJNulbd%w3}l0mN(^vp`^dL<%>6qF0lCT`y(k6RG+lzV&cj9PhyRF>uQ;QZayBf zOpIe=7Z1=7<7L-?ZkFGgg`$kzSItys9{RU%bHt53uE+m< zzaaXjlvi|XakquGqS>u3jm-u%EBkI=Qk_)s?Cfj5!v{?{WbEb^TAvGeJF(TU@o~lu z8wpkL;(;Fz?!D15>VLJ9ca5Zh)b$63A)EP`Ca-2Ouz&F8=_a3;s}qy_{I;BamnHvP z)LUY~q}z77Tny}$T{&0V|F>*><|1ZQ{&T9)yqPO+eHBq$dhOz-V)vEYzl5J$xz?J~ ze_^uYN_T%frd#HUv-c`~{O~94*|{k9eR_vK>d$)kRr_LmdcsV%iuJZWpPT$HdDgpw z6Z876%?|U<6qtk@pC%o4XLHAg^^KP7UuGD0JgMh@eEu$%_D`$*2mHAo{rtUfY6Q!a z^_f$ZQ-m_-g?GKG(GyvFYlhQP_F&cB4XY;T*L8(m(M&Y<)w{CA{(j~C1^&VDUR75g z6&_$%wR+vgjFV=d8U9>}h^?T>Gdthc!Mn>uy{alMR2Oo*PCv`&E%AE$+(+kD?vE%s zb714!+dICtvaemMo)Nf_=k|NlSTay_Z;<+-1s>+bCH zJvX_cYG$j5Fd98S-CuOxZp$7&qj~c#v!B2DrUJG`nelRsKiaw z@nd@4|01IJa?|X^N3VJPJAY{EZm~2u!$a3NU{^cw_V%6*~x2)Z>)lKu#t23Fi-}*gP+o5rGi(Lq3 z?-ka+or@MatUPt%U!iK{rp%pH(^%}GP9}JH_VIaBogeX(O(=&>)!sljvM05?n-GLpm z{ZH!e<1*9lPtG+uf5a%xYoBiNYw?cANd^pQVBJjFZ=k zgM(|QNlxee)3A17LFQ|jG?@;kzwf>N6}>$COx}BA*%5I}01a||oEEvo)n6x}Z?mg-?}4w%R(0H_Nlbb3FMT}wra~{1 zwQG90!t&Wn|Nkicb{2{#Im4heF)h_)(ITIiUC9q->81XBc>L>{y=jb_JLgYR+wdUk zlg2WK0%cJ?!78>j-V1JjV$q2d`LbGT`LcGVcM_8O3>&@E8k-_hWcaQYpX3p27gAZM z@li-4(<)b4*=yE~y)}l~X7i7%txDhb{reQZv({?a*Vgn%nQ~2j`MB0kUT&t}_t>Qo zHoMGY>*d_d9gj4KzCL`|L|WvOdF}2?+yD2^DC>XPuO0c}`}!`0Ys+7+Fl|}?SM=~J z);lL68twL9andoGG4noq(AD$(T1`CA6(J=G6<;E5U0Lt@-=3}UD_`W@++O+S3mx*e z>tdWPW^Fa9`%^Jz&YS=ZyE`)LK;uLew`zk+R%Bf|F;H=~!wF69iTye=HWoc< zlH9Q2%@RQu$*Z4kD{04z7A^=XYn&{|)^~5C!1n`ZR_L<%ElGT^b_08|(XuE#i_<5R z58bWLWx6Dl{cx4woVhG&x|cRRS$E)tm=ss%g6-d}PH*(J)KL5wggWmQ93Ykd!w$58 zNWL9ansQm8V-`_KDY*5@?_V&@2mzPhZ$op*qPlCyQNMFHdt8|}b)dd@aP4>JC zmtT%#)OO=;Xx+K{tIxfK>7VkLugkE+rA>>8eHFHXQ^$erxq9+}HAhq#7CD?gmUT;f zx?!!W&GVj15+a#Tj!=I}q?`~FKecrvk;a0Nc zCUEcUY16e?eW!iqb2K^J-&cEcTduToJKs{!y*VmN-fPY-DJ*B4uy|p_T%(v7d*&Di zhAFgFho6brSJcjvw{xS^F~hX4o?l+<+>&ZDncw~4f^O$&{+anVW>xU42w_rdUYoPm zu8rMLj7LyR=85OZYi%tRUkn3ZG@f}n`(y0C$!jbl=6G3#zWgU8-RoBO;~{(1mlp?5 zP1TmPD$!W<Lf_R5npHfT2f)?!^8y+ZPCRPVE8jTaNu&e?C9w68CPwH9scX9W&poQyDx*Ge|{SG11&$>1(6=dWMFEb8ISw zroMXHA*!#h|L@=Ld>%IDsoLSs?(8gn`DurL?u=s>Jf9oH$DF(n8C|f>z9TPgcShh+ z3*XsS68Bv_v)}z&!Q1Xtf39seT-~#|dSO_@1E|L@UniZ+uD22A&hj4k#&H9r>R^CoV`9PYpgi`O)*e4F**ZrA3ri8Fg&)upbqTvWDe z{-O7;uCBhaBCt98`ntkTPdrzJtejLQr4_dN>*c&I&n4FINwI%R`Zp(qn)(_TB&^qc zqrSp5ZENVpdq+Lr!xL%5sZko1rykr8Ifqp#d2K_(ZmXBu%~z`1 z3T|8}&U`y`LGW_F^;>H_JUG@yZ(o*typKo5;zM(vV3k;!!TJVs$tMR_d*cqLjU2MK=F_TimW}i)Sx!4k>z5V?2Y&A8n z_G1s#_G)Sg@e~k>%CwtqU$KI5J1c^!}Rbi)vp<*)Qkm zRqM@UI-9Xo&7@Prb-J15}##Iqy!^s$St@uJGAhuMUdo81+4+g`cr%$Fme{r+|}7MGX%s~Z@6 zn0w{ow%ll~kQELI2@2KU-X#9|@-pSr6wO%sbg#3*hBG#qyM<&G1n$d{Tq&5_D}1@s zjs45xqPF@8Q|F)FQ=%oZHA=Vk_cv7?ou0e9%YzrWbe8O#56=ImV|FGV3sx>lTdt?M z(N!@yA(eGv|JsVHVT(DgZhW##<-#T9o10RTzrVZt=gVb($k|O!H8nMMKk8~hJFBnb*vw9Nn!pS=q=wnz?XwdkSXHPcW-o8{8Kp1xY;ajx@i$J&*53mcqAl`{fmDU zknl*0i-kvZ$LZuV=a#QD*I4`5XZ4vcjxjMZ_iMk)hOdiJG%=a-=jZ4Bmw$1IXdIZR z>@HzZpdhLpmU6I(HDpD=!_RdSp3c;~y+EdI$F~wrulZ$`%l+npE(!@*7c)~>-S5GR z7c=Vomw(BeP;oy(SImdgaDI;Ymxsw!eDdB~FJvTMIaTrU(f(wfkAWPAX1fcx1g1PW zF)?IiP^($)tqWVT!+(8$|3BXMyfM z^P6k+@$dKh%TF&9dC#7{s{F^iS1(K6r=6Q~5_HI!RjHP4)E1Bbx1xXT@z|4J_2tdc zL#n-?^5yDwc6KtBMNgPzS91BfHJ)(a_VV(j zP0}+<9@t2$y)^=iSdgx@04#!%ka; z>0x(+{Wzw0t@c^_uQ^6?ZJ0Oc7!%WMF?N2rKM77BId5)GPyhbz?v+)c-174B+j4KK z)mB_$WapDvkaV<5L^o=Rb30$HiIbp%j8(~tfR{pEd+HfO8~5xiHpox0mf1Y*z(v#Z z>eXxF;=J}4UQV7nPgf(+#nnZMynMg>i1l)r(mgYEr|ULMEuQXk=W@ptEse)F3WWpbf2d6{%hl=GN`x~X5`H>fO)ktz{*40G`2b;F!-WF>pdU|T_^}LtG(a#Ml12wAG+o;rk6cW*j z=ywa~ZvuO1ML1py~)=E+$f;#j6TZ>ltE8V|Z$RlGoH`aRk z#@7p5duHi~=tOtvc-_ptx^;tls@IPYXY>8sll7x^?*3aYw^ib3xvHG;|W$L zb2YDh%V@iVRR#XkRUh@Zaw`IRr7 zI<}<~qCei$h~LK{X|#KJU^#Es(#SN{ny#l6>%Twv`T04hA^-E!)8xOuzJhi~$J$6Y zDFiI_5(VvAUhX${N9AX=ef##kJhb_J@!=dZytMSRd%3L2Yne{| zQ~PRvXIwcl|4ymf#Iu+GPqsO>UfSZ4gNe`*|0nAu8~>cVZ?K?QxK-{2=dDR0b?Xj!)qR|52wLoEf3f=bg5D>0 zCNaDU`Iz$X&&?B0-!J$6bZu=k=mw})uTIUeEViq7_VRL>a@v=o#3+BF*`>~ zf_q1eqIdPq*ONP@HOS1CzxTZ5+x~hB%jIT{YyU)S%J^3;+{FOes{a4qUlF|+33++> zLx&F8z1S%*byf(I*A{M9hEy)b=5)QW#@fK2tk<-I5>gKRwlUdF7)YIel-; zba77C-}`R7^*b`9bx~zwKuy`*^BSLJh2`b_pP!pM(b}H=KVt=gZN}LLUz}EzB{xz39s6&0SB}mFy1xR{eFY@SJ}B$sK`* zU)zz@)56ciN|eP$Z5s;jHNT(mE>#7g$y!GoZS=FZMG58jq@v;W_W zB_|fS>^gAw?%c=6`6eDM>GNKFJK6B1{d!>q|Gx%YJJP2)I=u>1`t~%6yWjrsZFWXO^u!d$=r$&&~Mu>9fC^W>oIx zu$o8BV(Z__9etcU*+Q&JA$HppSs9rbMyXtnkM}Rnyu9qpEK~2Q8TNwfVsO0u z>g7F?BBpqhb2J_JvU0M-4egG}#o5Bu|L>@m*7)TqInP=ZpcL@-|C_s|4p+X*AOHBg zkeyv-!GfgcZ`j_Cbp#OI!E$CewjZv>wv8V z*zxNxqg8S7Ifi$Cf22P?{j>Lb?fyHj?e4$-{&#J@v-0yJFQe=FpSMj_EtZNj-no@` z*XyXXcFFtJvfkWP*S4zltFD-_UE8`uB55FYl2fs{iWQyep<`;rQK@^=#RPYnE*-5qi0A zeD3k6ZOgwqWs%*Atx>wn?0iS=-=7bzDK0o_F_2f*<3dLx(c1uao`x`FZ-! zPfu^$zCHVBnY+9Dtd)lBA9P>x%12jdzvj9i1{?ZP|~vphEuaY;*Pa zeKlw1+s8*uRY~5rZy)Gf*Nw@?A3c4VYIghYi;K#yuC8`p8MJhUeZ3u5Ytu~QbfeT$ zB5CL5curRHO?mFIDgC@$)wegEpfhM~tG3*<5p+Ma$ip!2?k?A>ueNSH5jEGQRBdLw zzMJufKi@p{*5sbO(wY3?>gx1WH`o36Q2{z%Wnb;@58u8mi`ba-DNS9OlarH2-foU< z^|uX$kKJNoW6!4D&cCz6ac$JrjJvx`^X>kA2lZLAu6AjKt}@8IWs;MVbEZuG;>(gA zS?g(MXPZBM`_^}=R_K|m-%r`ZCnlwwpEvih;z5hd^97l~;XS`tG`+Mw{kSA|Zhc8XGi7!zctCZVQ@7s{r0w8PA;ybi;G-A z>2JmiiOSFW7rni`J$P-Dsa4sVj{biB%u7oQXXo-Vw#gKW*k}VUvHLo z+xy}-yA_vHCN5eM@R0Fxht>YJzt!av?d$6{-*~oTheg%*cfJJ$2KVmW6VZ!_C~ML8 zSbo{C=!wUlKYwm)NMv5>J>6{e+xr`n*+J)%+`c_~mT7j~hyA;Yo^pY@k)S&L-=CjL zd}o_oy{4h2*4D_(Ui9XM;kEVg`DW!u_V@SbbWPd$I4WX8!9N+c%F-uSE=9T6@6d4d zO!RSM4&V0m_@iy|>V2|ivlno5`d^6HS7WK{*0Uh*?yidJ>eGB5Tkq_toIJ}kyXf^b zUB7uYnPrZJS678rR8?(CKHgU^D#G~b>FMJWmEBLwG*%bY3VCp1qVmd3Zt3UdEKEAu z6%iFx^yf$6X8tY(4{@8@-dFQ4c~HLp!HnIDL{wM}UOd{dWutNBoG))~JWDov z>v`|#p;m5_{ChIy=H_YV=FC)@Z$2mJ&%fXAD=I4uOI`%9@k*t{#h>$0JJu)Ly?wj6 zb@{s`4vkFzGwbT=>Pp_;n!3ofJ1H}BX4OA4NuD`YrCm2Sr-QDCw#-n`>AI5g{@z}b z+>5jBd|ftqV??s1htaa`+C!}G->(SUrm#9(*FRL=L|)s>Y}!2AY9T49Q?pF7|9xiP zSureN*2^sw9V^78(mmTc*3vFL3%iq363^HcWm%iBGJ-={RGI2Q_i&{HvuG63F zqO*%UOueRPIKH@;kaT|SlUe^Yo>T{X5~_S|vV8j0#6SFhRW$awUtbq{b9=u0Ey zPoM7o@uNcXRQ-nR>v~$@>wG4w`QF%=+*yzw4t@byS>NyP?pjuV)46x= z-jmbQ{kuNtOYpEA>z6+9>t?Rm1I+OzI&Jp+oZ;N^a2W*WD@-}l?EjO*k46DJmV zl*-7;a$0?0cisGA=YzX4wKYpbo@(!^PLjR8?aPt-`Q0o>`BT!EfBSj!V_gr({}a)P2;ll8=r`Za_w%!}D?tZ&|Npajj@KXQ z>hJHCR)2p7YU;?>{W$nWj&s%Z*DtTHmv3xr%(=gB?i|bFQ+|qeoRX0uE6bm6+mLW_ z(a$gMZ%zyL&KCOnVUoyGZbQl5n7f7&d3jHdY&*~z=Z}7UOWDZr_{KyBqx5q+u0Q+VynTCfLn8CMdGkWn$H~UW z$7^~``u+X=eWyH@Mn+TyBc7uEjl z?Z=xB&Mqx|TUn8}TjYuH#iMQo|E9N1(Vx*)TmNPX|62bur%!{9dpp)6dH6shv!{oL z!E5kwXI@ja0{2#xmc75%yLt2Gn^zxPSm+$GCPGkOUmtV~ebCA&%&s*>MQ0ZoXoE(w z+;aJi!XzBF72RE`Fh3yXi0`@ed2a33dS@8cuDUsw?a!on_Vs?x&&}oJ=U-m?`&(M% z>($}wH&uR4+p~A?%DBC|BHyuRUt5#-;6NkjAfTwNStlD6Rdl#Mu54*<4_;f9yQb*6 za`@Rop)9+`6T8!QZg^&WtY6OSe_icTh4>SjQcqjd{rM3dS?1!xvOVwaudMTDw{G2< zdSZfN%Bhq(^L2YGO771x&Azd(*1DBjd|I=kM})|u;Ob9LI89%hC^$`=T+{nJap4sA zbG0S9>)$-ywB>mTLXip^cTE;Qnl{aw+G+f&Q{gn`Z&|J;zL8$ z)m00FR^HfMzMchi_fE(8v&W9H?a!UPGI%3H`n2oL(qA3AKIz}FvFL7Z6t)zRa9HX! zb%}ygjoZS73qQTEQ%nr(Sns$pWK+#cr(Y%6Ctof#?X0Va?E1v-i%+?T|cx^QUW_v=3190 zJ6e#Fq&u+D+cE!(z{#Jsg~z4-Ygli)e*-bSZ&O_lI$N2&mm(}X!zVrOH z>TrO@nS2GiSN|&4<-R_>>FDQ-o12!tk#MR}o30$;|~{nC z_Sct}mNFs^v&-gMKDqm0^_u3m_0G$CkGp*?owqS3JNtCs+Sx0DmUg`naH>(;|K}68 zb30$^r6ryfRaIV&pzDK!grudVYih66ZhiXx-rm_SFE8I*{XMVJxWyw?Nm&_mRZsf) zd7!?pl53Yopvxu|^BtgDx8iSHTi zM}cDMsVNKd?(TYXcXxQxB9Fk7lt2!}mLEQc4j+DbYpeFgjT>cbDke0sa(e~3bSW!? zuCY3zo@O)?G@!ZEd-}4xySrG}*}VfqG^at_GylTZ*VjQ>KYskk!pa)Bz=2V-%i|){ zToxZS;m^;{FVDHTsiLC7BS2(j8${dxiy&>6mU44)a*SUc#y!^sdRPHL|4TYfk*Kk z9v%;t&#zmQb90kP&W#Dp?EGEnMtx7vH!?9CH^~8L8`{(E9PhX&MrVrwe_W~j! zN50>$_kVU~re(W%d@x<(@2y9v;0P&(@Mux`Ts;`HxU7NPI`g_opj6lsU0Z&NS+^qWb zMRRY}*F}kk+rGTLJ)N1I&m%wtay?mby3x#tokgmx+~RI4LyA5=a=o=ZU%yrGk?a$b zEdnP$IKO#yb@dD%wY>ZLe0_X)rt8PE9k_ULB9Ekz$HXJy58uD{zq_k+5q?(HIuB*&9OZE{eFFa-1_6^&+~_ehgTN2PS~K~a_Zsh*WR1c&TiPe z`EZI+qS4HP;$meht66g_i-qLm^jwAFT@N;~2CfWwQL^j$GVQ5eUQ@NEzPWC%rPXDa zd~8G6+o-xfAKh(iZ38t#J{{xw$Pad@&(^G~0jsZq4wn)ZcFxJsxp?tnL}cW}h0g4n z6MkeBJw4^x$i#YYR$gvy@ABp8n_}~u4<2}Qv|BS^0mHKnQG$&7< zEd2EJ^!n^wK`WY9AGh4_zI$)p$Pl zw^zy(G)M^AQFDJ^ZBgzb0U@zg4_hhItS`4p6R%ynR`UK{ESK^=tMv5rn3$Nje`|tP zPP*FheXe!+iz_RIOJ7}C7`ZtObnPV@uha=?b#U<^9vvN>b#2YhPwA6Sy1c%&cBz3< zMHkCuXm5ZsbuAq)yo%ki)cDc(}`T7p%j^MZH=VWTt02>)%tbG-n+}*TGae7aBkyK z)Y5wO^sM&J&(EJ9>y@5kQ#r|VvfAEK$##K9k57i{DNb5>YioA0iB#a~t6$#S4CYdr z#9Lim4VqwdaA3&2z3p$(>9^O{%irFfe>}k;!9?oiy}j1y=jJE|FZ22F^XJ~Osu+%s zFExDBCVQSzaQSp)OJ;CQZEa^q$AOC%1G$v!ZhXtV{o?j&^;zccV*Z89b9F6#|1p{C z>AA0w&+-kVPR`bszg_;8E#cFXlRk4SCf>MlWBU4yn>Hog+?4w1iKwETv1Fu(=DhxX zeo)taZnN{M+h%j)`loui?u*$Oba&fJ-l|Ve&YkVt{5(bd9}gcZ`{!P>J~K(X%P%8# zZ<%2EHa+@(MYG`gyVVR4JByx1JAQ0!Xkg&%2Hk@8=>1&#`gzxG8eM!odrnY~h>BPK zf`1n$2X&m+=sz~=kJN0Do$m_Sl&^(_fsV-4*Vlja=+V6@UneIf?eKM=X}p@xXU#vg zaf532@5Ng-smz@^RYZw(n6;~^$;^rXoiY02&s#=VR@6J6SlFEo>Vc)l}x|DRVUUZyHeV$D3I+akbZ zWWMl2n`cVt*5~i`Oq}!Xi){F|7!}`-cOq_|*t!%H2u>V{*EaFIY|t&dBlGy=V&&-! z9e3@TH}9Xv-mCU9G&5lL6xX>gzfQ9h^R$l^JEbqL<+Q}9NGgg$@yy(2_P(2;Gv3KO zy6E~hd%w|}h;vhKi#@nCRmoW@VOKf->NhtZTvODmvVSsrAMb{%8W)dBY`CIf=sM?# z&-&&kv6uOKPdOa9wz6gI)+GmKDc-nq=k&_N%}ee%Eiw3{8o;4=CYDWIhw<;{-TD8| zm9)+X&icfc+4JZ}k{-{SqbnauD^#<73adZ!_5!hzYt>MVUQ?W|8%C9k!ePI>eaRgdkY_xPbHDifUi8{C%l$5bzYFghd%2G$p={? zQcq0zu~}29wuduhTIc$@>#V-ZwD0il>URCN>q++X+3x#hFFEJ5BmmS+ygTP^q1+V? zZ`FhgNmrU%qvrmwJ}j)X%wOT%#pm*GH)L5(`I0x&@a0#HI1`~Tt-u5adC6|AVl|<% zzy0pfdNp5+WZ3?+)T(*+RH<^!`;cn zT4;ZwNdKR8C;v^2nsKh6`gr9fGi|4NlFb(OAL?IBa$~&wWsdJUCqb8E4aqN0tq@~h zuwA3!n5W~Ji^U1iOkXz6Flam^#C_m{#mD={*4=fRQ|+I#W?kV=kCrQu9U@+CVy>^I zX^LN)DdO^L&4ZV&%GYjgO61~yaBx@i1V!^#J@L;3JZ|i0m{H+=<>s8uo10qLPm6yp zI4Sk$WW%SArW>~YJx zN@ZPqj>)ax+b`a$pYvn)pVE!%XSsjd9HLNnw?OZQ%+VQpwN%Z11?BvdimAKp*Z1f9 z&I|cWiI?|uUY@e-$D>w}C@YbtN8iR+hj!F#EncwUjPo|Rc~7Lmq*=pKBrj=aF8}iQ zf=HuN6rFkV%{^~GF%&z$9(A~<)*w>=EJ5`X$+AOm-{?yu7i z*FV=eFhx;ZY~w?TEma4Y4!`x5$dhEO*r#m$YQ^dsQ&;``)s>=P4Iz zNxR?K)GM*Lxxzw6?bdMVe&*J?#+vMWAKIbgKb^qBSW3w9p2 znDy5@cY6JG1=n8e>oa4u|dnZ>dW(*$`W&Z$-CVPLz)gUc&Q$dk2|vW==YNb#VphK z^%k877y7$RzH-CWhRahH-ni+Oq`*9F)^eM9clcvxK1^8?%JX>f@rX2!{+GMt`-S7; z`9uF+OwlW!8@+gb62HFtpEcIL3dvJ*RPJ;ix0hv$R~luO9ER8%JK2F)w>K<2c)7 zD_gC@rtkEv`-9E-uWa-BvXL!ho0pHje{uC;<4eCq$=?2%(iOloY3o)7U_29*Bwoc+@cuPIQ!zOgCf=QZpTJ_wutH8<&vsXv?J+t zhDy;nlXp5c4F|7Dy`EQFc%q`;x|XeAdA|Q5rOT7+KJdTT_OJ0AgI1_T`LlJ8JwFs4 zXke0-ao1P=eJK9Ky+sCHKg{P_7eRfS~-kkn}OZFdmd+>4Pt|W0Q z2H7L}zvVZwb$pdP+AJBobNf>xt~D3t{x{BKo%iv_^Y|2XVJA?ES`xsOZu0X|!lhLk z^RID)UWruV|L=Hbtp!7mxqbqRPn2*(WOCtaF{@@?NwGDm!8bXS>UQ@o+aSDbb-K{( z))|XB?drwX6elEq7El)0UeC?BFuLDy+^`HFVQEfaZ!L~{J)I`w!6Ci zJ+->MfI(zh=i9I^(KQ|JoyoVwG`y}J(|V+`(eRR%D~Do`l_jt4nHjudUIunnXC}|w zJ(($m@rtXg@c;OquO2Qup2G9!pGCo&i*oIkd{5Y2>^u=6-1X*XAQPKI`D4kDO~+j> zeeHUGYQ?qA{f2MnpPc)*LFsw)dc&jpe@x#s{qu#~Tiy2#PcC^}&$dqRw~2Y?@1El; z_8*vAtS@v&`(^NQ_EzC(Qm#p}Coj1l8L{d>N%wK%<@@%?U0Rj)^oa0{Q{Tg1|J~?6 z`%9Ct4d)K;Y5A`wZtb2oqjYhKxVNW_*Mm1#n@d(H=>6OA&3^B_b>C}>((R7jeA@pb zEr9*flE6jPMg11cE@$5QA1Q6V65S!<;wIL5(?CUqZ|%&I^OFvpvzF6xTqdLF1 z$hoNnaRws2bIlATBqznqH<^;Ab)m5**XTn^3jaG68<&Zu;=6A9AGG4%_EU&o)pzM1 zfx=lgj`thBa-3=RI6hH3Gv&O*$Fw(H-ZAsuN&d<?GNtg ze?C2F!~qq8w{xVMd(l$Jqd6}w(VE``tX#aJ7f61n7^N#9KnBS zmS5qvSnhSQH9oiA?A&m6V%RHPP+wq@yz5H7l*}L&hTFfCJ+3daVYs-u=3>^=C7G5} zV(hCE3;%>K+H8K%jaO`^_T*)r=9ivaZO}6JTWT61mgPB3P}%eOw6-4$=DM7hKL5Eu z_@$e!(&Z(r7O6W9?wv1|V4uLVEo;N73!KZ3Jq_6Yx3PrPRAa-z%s(DiX6?Urw(dYd z&eVk9dX8_$dhdMR-BJ2EMOnkvJr)X8cicgbLFBRxCKNl?26uSM&_T<(_<}#aC9(d3aJ1f|RyZ^|h1?-mf zGkt%_nD5vh>Um7cQfT|;GFKgs z4u;vcYENF#@i;owpHX@O2h|cPUEVNe@@*u z|J!-#^^b2SGL@2(Q`P_Rxba9In_Rm8l4A5R{yWbd>5q3eDK%(X8EV|Ru6Ua(zes0Z@7c-9 z|0P6Rxe|CDR;`yj^5WpOfXp``4`cGa*Cqwj`<$?4yLjH+{I1uu7Y7$nR* z+R9XHK6$zOPkyd0a53L;MeUc>#_dW5cD85#p1c%p_?6{YbIiY|9AbVi4d;DSeKmjH z|H&m>zb?*O+-dJFr?SR9S#J%uQ@_x1skoZU>zb~Ff+`D+&B?o5j@;OHN6xIZMMBd( zYtgx9JPoBwzggV)|JS`OcI_vD+P3A_c_Yjuy!ZXZ=F9(SXLMoa53WZZ{`U%N@Bja`B>2KccfKT+l7)#k zLf<&JO z#`|BwrDvzlIH7mrS;6ubEV9p+&96QFI@Dq%@0}a928Hts1yvGUuQ_HH?)fe8`p(Tj zx46frUcC*g`YNDROulB+&iC`bg{^-K}EaF{6$tCo2BCF8=!b#bUd6Uw3t%kFWQg z!dcPx?Ma~K1_8|(F`L07ix4XXz!Alv*dp*qcv7~Q>#6_8k`oW!y8XS=VCla1c5age z_nvH-+G=z)H@YXzJtSt%n*&au8|XXEuTDQdFXhC9#w$N&eC8I{+mL=u*KgZ(Ww%pqi=6QG0qutKUvD6G))Us!$ zQ%$PTCXE(>l@s>vwdIvI`;+r|`=(8gPE1suUn=R;@x(E&K+p-~DIu{|0T81FG@1x@ zI7k%I4^(V{^r<-%K}9iWuoavd|Q$lujchuTxOq>uCci!4ns@*GXe(cmKE{4e`U6%RG^ziUd zD1UdSrM1;_BE*@8Vy0?`^KFgl4O?CNHz9Oo#;q+YMLakZkBF;Y*WsFYCgseGiIY^l z_nF>3b*js6u9asaRNtMiFD^1$TkpQUEcL{M#zn5(UWG6j)9h&o_|@)DTHIKkx5q)@zxUmz_MZOu%V|Ve82hBTvbeird%LMt}VBCFQ~b$BL?| zS*g6BQRFKtg;l+#B%GY2nqeY!)9_3LXfs6AR;AoqTaFw%_UX+{<4q|iokBuHKvS0w z548rVCNGcWWlUoDIVS=?yI&N1x1cRg5eA{LEJ?eBGI)-qS5gUQCE&KPsM+ zlM}Hafw7sL|Jm*Q{i#R0L^my-p&1??zA5eOuWKdM)zxhLayqO+pv1Cr_Wb$spb`1R z!)-Y?HZZ!oyKA1(zO^Ou@VeODGwf=uWUb3o%*?{NlG?1w-}OwLdNulT_LUWiM+L#U zC&#T%e{o^qmseN2K}(4K{rz1(b#`P>Qxhw9(T4|)JZzxl3(p=M?G6$#zP!+xeTvu9 zczGo;u`M|_KW)tfr|KnA;8TX*X)u!IbHvLcKP$$w{Bfp>MgF>1IkPuwSWHpEi5ixT=w=>&b>V|OI}{`4E)#1 zCu{ZL_xt_EnU_>{mA&sqZ^11dw6O~*!cz9IQ)Y^*2p{>S@d zmnt|dDY`TzdPcAPzaP!t-rl~vHrl+6SGujWwI=-670?p+-DPhJ%FENw&9Pi@HB0kU zx{$Q==_e;ATb94O6CU|*srU396(5y)rOp3oo!-82W97~N!QgW8|B(z6qr5v7)AZxd zZAv}8B5La@k*CI8U0sJxpY8@tp=MrI0}VpE{yefR|GwY*dwW3_Me|CT{K$GOC@b4L zckbQr$ZvOdm;e0ve17J|MXZz6d=DKw=vc-BDsMi%2hB=eTN|BhA_barlQd47vZzMU zd%9lgqa&SX=G)JorW@_$XxA9MJ@4Y~@_fCxJtyAW+cd@6Y>;J!2+{w$&V5T#Kr{z6zSM zUdMNq$(qQ`ZDzT*0=DHu*3{N6)sTPi^78VM_xI*5^PO!}@W5eP?rpbuHkC^Z-Y*PV zDPfSnaPZ(k(0V>0Rj+~v2N)qK`iFNyf`Xo&-i{qRKuLJAWpSFOm(eBAeaJO65*7st ze}8>-UVhm#@E+*WR#1X!bvpR*@$t-yi&{mVGN+!OH^?MSrjJ_Pub1jkTQUy*`T1GWx@?Wja zqfbvyUm3U8$~f&z!sBDTXXaQ=Hp#r?k!a`C%*Ja`_Xo7pN<=fLMb&$n!uMh>#l*lF z3)TJS1+0&=EqimrF(5#|H2d0=MJmOamzSm9-BoImduxhO>ZuL2zsokS_pGV0S?u1w z%&C>@%G&7Zkfn-R)~D;2T+EnZQ)zU2TW<2tPftTug}hw-bt!npA^zY(=k|ia!i50~ zoqqiOJz3RTOw)_&(sFDbPzui~=-|p}4@9HWlE&u<%ufMV`HoB}$|G~q< z?L88PO^4h0jq~r>yt=+#-&OFDJZN=G;5@rpufuJ;A3uInG%yHgO454pn~=c+}l$rq8--r=H}+&H#ZD9yW%Ho-fVn&x_CRWf=!DGGB z#jmgFf>ViWR>7ASfiW>LC+1q4?=E|5G+R)kctL;$C{cm#2e1DAjz`{3rupE6SKB^6 zIXQWXc6eXu>uV3+zd!%urgpW`1n}wWTTEJ8YU)Fq)f9OJbH9!Rp@Gu>3Vm?qrc64d^=*t z#rcoj&o4e1Kc~lRef|1f#Uwcm=r95B@RB%-`}@2j^nCFTk3cp3iBJdJ(lt8mrk zsgKs*&pa&oEU5Ll>8;6MPa0mU`hRH8TK^S+iv{L?bIgnC%ZWHJ>%inW+c#F75EOL0x+*mIO0x9&`2Bvjx8;8P`Ze|Aqobg?w&>_+%MFt^D!F!D z*qH3j#KiRC_ICZ5MyZP`K0XSPNc@(P5~$)`{QTU~xV=@N*{heAm)Czxf0$O9?a`gT zC}LNMny}ftn#bGwBktz%cuzm4?At3gS#vR$huq zp1xUNr_XcFy}vZ)r>=hLbD?s}3eD$%(|7#jju2R}u;}I*{jffvsa=~&eu`#QO>tej zxA*;}_!*`-dg+Vr@5p=oGj-Z;8}P6L+{_ooI{pZX3AI&5WERss)-e-4f zWpAg7kG%q*zyM zzPMz$IPa;5kAHeicNYICKV-X4dB5Ao(0lCZ>FL|@?s_ftnp#j=ntFdoeY>LP=l+IXcVB(gpx{h_mo)!}*Xure1}EPzdu}28yoVtvuCi3*cN+hL z-}2!bJ}mDO$v2zXJyGoo!?C|6h1zY=t(&H3+?)M(`y$J|shqxDV#!};rt3fd?et}> zbI*J0Nv+22kDpzdX?uNfWkHQ}?W|po!WL=7Mon4h6lwnK-G1H!s{2*H$XZYL{HqnX z>HhjVyF|~N|66x-iotBf%h!s}Gf1AkzI^AQrjM(eCoyG-Ev}wo7zG#D~ND_8UOA9B<7&zN7H5fmH9wwEMN6o^Z~!E??$5+pI^< zR!Ux8zUbK*P4-f*a^@0WI}*MWU6wwJtImiT^2Z;SMSl4AMjVzcMB_wcRX`{mp|%P-NPlPrUO8Rwj; zs?&Ss{kn0V$^;9gpNDTLoSS>^a6?f03;R0uov*Jg?mEPDZ`T#WwzG2gA79yIz4!N* zWvgGZIIF!?TN`bvwKQ~L(fk94&MuF%U!Zil*7<^+rORf)_Ln9b#F^RqUG|qtz0RvU z+`wrXC?<8ZY2p2ek(?`vE><}?3z_LX^Jw9`JooRJ(kySy<@FM}HFb?w7am<1s1&>D zhX1P2)ouX+0*s7|S5}2~xAV)_-HknxlJxV_Q&1(}>*k!1q0uX4I*Hk8>d8r}+@Q5@ zudb}DsH(cOt2A5Arb3}rP)MxxhtQ(Mi!-mT66F@x121^ZzNRC&b}rxCdyQVc#}hC8 z-tf0T!f}7@;?U<_8DG}N#jkjJ=bf&I)DyQyp|xIKyUGs~yy4hh(%@HqJUGxvHe^pm=-9lO#X%ORD0ne|1R zUGpp+Rep^SSQsASGuNuwYUkSvsxInlZ^m<7$#KoR9BTGY@YKcJ+b?ge=K3#kQeNi& z+z+qrPmEvdVfNl@na|7*pFc15nQ63R$Bu#r2O8IJk`vd9S>QEQODAeehi>#X&^q)t zZ{I#$;CJ-Zm6f1CiQJsFG~?nTornzzk-Y_=N%19C>F4Ggyn0o%*X{7XzrRn;HqYM_ zWv@DIMhTmUrIzuoU8ddDW=qbCmcL5d?y#Quf=VyXstsqCJSjTxbh`TF%PD7h93EZG zGMrFy-b3p7nS_ZmP2PKFR@rKN3fk|PY-3}yB6PLb{b$=$9=X2qu6X)PxK-3@uUP9U zsa_kK4;gnP`1_+$uDC6#-pRXugLc>S@XCKPE=Zm~x-;|Lw14ti3si4Z8|Ow%SJ`R1 z^2VVbpy8>g?x8G|OEG zSne#(@ay}kMz(;+sK!G3?ekUKHrcdFE#SZD9&xGH;5w68;>|U7mLi@TbzhczURwL# z`R-<+a}I|>mBUsYxss8YI{k-{Z&jDD+w*;=cgikS|MiUV7vERs^MHig+}|JI||*6TLEI1+rJ zv@6YL+wY0HcUwC;I{x_mJ9xRDtekz_pUCOof4|=!yrUrT(4j*doSX+QU;f;^{qxS^ z8tLHl^J(ASJe|{9@w-#Gye}{H+Z)$2^L9;`u>0ftd7Te68>PiAZ@+1wcWjT~rmYWo zw8Q53PG4MEXE{T3v-*Y7BhRj{yYSa!i_A@f_LbdY8#BJ;Y?IsjIq9jz+wGHXZcaB& zKRofs#X@J5<$tFcq`#h@e@3}Y_VMq{U#CiM-#5jnm1|@5_dKiecRp`#ZGCxvzx~CF z7r`Aa{{`;-a-d4gw))$LyXE&)PnXsd)JQL8Tbty!y)ei2UxUfgR~OIT?Y@=w@}j-X z!7q`k86r$d&C->>q^z}_(fU^>Gq_ShTg*4%%*rE){zclAt>;aR-^Y&_AFZ%I8ukP+P{=-XSbF32`rQge+EG{uCJvD7f4YR#XdWmag zmH2IEA&!stPhRA9zq!qp;qcPfH~r;_4-dHINZjY!z9?^tUEq|7x!sf2R+#dLs|Rg; zQ!{Umk=}|^QGN2Or!TI%+LYhY?*8WXhMtCGt3KAeMDGKY>)QXE`)cH?u_jRE^q(eB5O9vxchJ>9O>VRv!+GT+&kw&%w`J2&^V{{BBs$9m$`UA&{0hwr~w zyLX%4gd6-@oZI(d7fzm%u1?(JmkW&6s$emCRNsFU4dwpq0{ z6C)lk2wOJeSNWS86K9!bU)o#!-T!l)6wjHN#_gVy)h;e}=V#-Wo1>GruWSDo>7}!L znYy$7NSTO-w;yr%|L2RA*0#TrI;%E*{BY;UHjnl1n%SP7u2uOriGOJn%Y*71JqNbT;axCW#@4c?1mTT{{_)e$tGyZSB=~wnz>EuR#dHTn^T=THi z_t2aQOWt>HX1n~g&1>;nH(5t-DhD4QXe-QCQ zjvZ4g&6^$dIV^xBAX~=HMnml!$60o^qu+JI|LQj-J+hgi?_PGrOLc{*rj`cN{4Z>i zlaKcWt_soImV5iyy}i|vmPIL_c6iHYzMc}&nsV{l%TQ76uqP)bDo@dko))y!%dqlO zN{Q9nKNrP8gPTgGmH+-&!WIv{yv%NIZx7m@wkIs;mX+%6ZGQR@QBg(}9}<|DnJo(* zFsOP@Q_;}y;L`z*bp|oZ%gb|#YPI++e+=1Mq&0Kh$}Oc{eYtbvrW&XFndRR*b7G=0 zs0N#BU9P63<;7<-QEBB87AKC0D`Iz-fd+Y2g|1e6{k3NE+ROd6(u=gryB@p|d!c{O z`nJ_p-L5wA+_`!C?p^czyJ+p?cg?q~cJA9DX7uRZ{Vk9;?0 zE!pNbSFI#%{VLtv)wK!H%jIG{--&yfaOPyCBoS@ZJpPS)8jT^sx<|MeHG>gwt) z5zVH!_0yYumUw^-NSeEpNB33C;;h@AUt+gTcgy?#vH9)PX=)5EZN<+jtGd#+|DGK( z@kZ2>JXRvRHWIvu`DtrT7v_039}kMFswe~WnT+&A~HUOSmyoD{9TTQ|q^euL1m zDHpGKc3g3adfuUb?%cV)?Ca}h9$$R%(f=j>FQ-k3m-m=`DD&RWN#@bB_RNu!?EEHM zuA;upr1Q}B_uFk=z3~6OM17+~l2T<}z~d($u2&v;axLmf@$0+0&7Yo{`uoK-OR3(r z*Vor4r>9SUm8G0Jci+1A`~T1LnyPhiZM6AM0Rc_W(%O$RUH5oJXq6GN*p$Sy|L&=VVMDwwc61U$}IrXMv+#`2;dZ>@&qI-9lZVh>(ic~dgGRQ$g2 z>o@w(RvY`IS?RyX@6<7(`Rn-f0Z{DV{XPsMqXg@1Lj|N!DjSE-$%N?&ANu z+2erX%B?5-Pp;Y8A>h<8#Wk-$@X_judhf{_PCQSrId+&sm}@!D6gS6NTcoDE3Xh)k z;vyqwmF|Lrxl7oitFJG({lR zOlFb$QWLGMtHR`ucUg9OUQQQNog4aouD!#=XZ8;^zhIxIBkR0&)8w7G9$C7Vb}gCH zBnF=F=y-DK%O5>PHRTomx^miP6o_4$+vt3-?R`$>B=4^iil?78NOlf?wMF94|Ee9I zA91Y@kDkS#a^&cu-aRKz1-?EJvyyk;!EVEs*FStY`BUU-luJ$IrE8uXAM;O`c01Lm zwRLo#nQJ26;CuLYfl9qwPG{=zzS>USDYMNFzWjMx*ViCcRYj%Yr|8;i~oCH1~2>b_5ak}=bnV@K5|Vdc2ZRLotxWRO`5|Bc5T-FX!vmjPx(yC4-7Y_ z^FEB3tf>D#cj3c!i(BsfZj0HnE*h*eDP6qc(I~f}^YTQkq2fF{Cp7?QY?%clremjnrKbfv?0-hN9mtC!|5g#s~=g^U*PY)#ameQJATncjr+FpTepY_{`a|M#R(dvo%m=)rd7Fi*lY96 z(#!zU2dVhEhpjP99fsTL#cNxcXitN&c3wfw81vHy+5zL=bae-A=adEowC{g z$3ONOEmQ9$UCf^w)f<{G9B;mGl|ch)LG1%b?#8J;QTkuI=}wpUwK*CUG}&w@VsTu z#Msv#e}DaP-9AjVXoB58n}y-R|H^iLEO(CBSt4hz$M5ogqxKS{aSZzuy(p*ce@;!B z-~Q?L%opa@{JlS%a5;@j&#mC zV$EU$Df&Q{y2{5z|&lRdll*ZbymR?@yzRf$}YM|bhB znldY>*E)F11XH`UTk}N>U*6i>`sv{OMf=n|qt(IFiT@&hO#GV=*qfz#pT}zEN159! z*-AIftKUSIw;aov?Xg_*^~(E;?5xe+F?46G`H?IpcDwyT<}*2|zOT=gi>X@fuTxpO zEG6~%#f2%Gy=HB7@|F7cYeG#?mGC@Srxo}0ZH?33xIK+~Ha|S(_qU7(XBWi3us5Ew zOis*a=GO_L^=B6Ke7-Mx@8gRGll4BFw@s(FHH!srIGo}(eV5Dax`RGZn>*Z&3cvom z&hP}g{;l~Zwx(-#Z7CH3CGSV_t{>A2G{k;ZPkbq>EF-0^{_f4}FYm0K{$HPQDcCGv z!#{>)J+jA|lCJJ3u%G1@TfgA-np&GnH`L`~xTM|IWd08iV0onf_aIj&|2I)T?u*Y~ zTr0Ct=9{~)r@3j<#QahcRIZ#cYmP%_OJJshBYny>-marDyU3ojM=PlW}0qf*Yl$lKSlbtE-PnS z_IV^$ofKQW>|F{QP^-D7CYv@A_kz^9N_oo?U%^r)T`n zJJt8iA9h^``W`1JwW)ivMRRcGsj4*QOA}T$7=BC-50mQ6Idp2DcE=N=>}%=I_By?M zuXCwv*1~<=Gq$W_j%<8kTx~J)SpW9B!iQEB&su(aQg-6+Lam5NYup;bKiHQvTx&J5 zySj<@BePcF{9M#u8*@{?6(w5Q-62lxz6AF)<48feYg;rzuo@g&h(Y-x2!ss zcu7T03DNeS+MkhV@F{h2VT@Ah3E#h;&(O^rGraqigc!W+{*c)ws}Sm)CA zxWGj?wf>I3O?pAhUfCeyl+)eHc}&XB->F>6dU&8|y@Y(#Q$y~;qpS*N@9r00yGF{S z>(pvrm1ljw=df6Ce(<_Hf3>fHAC8m{@ViT7&Om1Sjb)!t|Y zvHSO1op}>JKk#i<(2?aDZ;#6#dmwi#|F^!~pYusi4Ac65%@c5!c){ zSbuO1@X^*Y zPxNA>+H>_vtN)ycFYB<_)p+so@}&Q@vXjO0l#O1o#J4^P{5|=(TkhN|*F05T8d%8i zwVzVZpEP^6bp4-?>@P1b_qVE_wN^|=OxrA7c;=n6|7*^i+Pk(`?sNOSEb}BIyPw&| zA9UShNqBPJcK6N%<-{lNw^s?}7gKy}nH9yhO?Nnl4Ux-K3r7yw(z^O>_$S>n>l_Hcl-NST-xT>`TN?XZK*$2 z{?ZYvd-BueYuC?OySTGS!uoEzLyy${*ke5{-+9^Ni7e}`y}z`STU0x2N$u}%zmyeD z_PL3Q=`3za=OQhS=?@sZBrI~y#8njft#t({eL}N+d1j zcHZB%C}XjrOm~rhRl35wqg{d*>>H9&PX}#tF>lhp$uajC==6(PvA66GPBiTk(KKvq zn!DeFqwB+gZGODo-_k$cahSyy)35kkc+C&zTUMqyMlB0i4{L;(B-Y9a|CPIO?d0Xl zlaCz^FG_gqVblF%!=DFp1uM3e3RUhaY3%k8`WzM-fA3fS>4LM7lf-hpSIsy%>6X=9 z|J=DQaeBQvT^%Z#S+Bn+WW82dv_V5jsq>-!!nK#v{N`-5dGK-fEh|+&&y3ec_U6v5 z2#8)T$s_+wwvad5#xZ*N=knN@|Ko32xfVTFn^>l#Z~JuXo$a@**8Z8Z)P22eQP|c} zp}+_cPqTCHBF_fc+l$#g=#O@Jq0=QIe<<_y=iSGA);dp8-&*RFJJ)5Bz}(FmpKSiW z*dNPa_Vw7Mm-8DhOxeru)_T9!vgc(?bxE%$^nLx}refZiJGZF!MW_GIM^jdYP0wi& zOj~)vT-k29hnlspKHG&qcjJDPmpy)-y!B^gg({zpY~k*;!P6IATw^VG{N911SAH+F zwe0E4oqKNSZ}(_*vuzKrX?DK<@~^W@PVUT`?X$1F&H5C}s4kQEQR4KIr`!LEf7Jc@ zpv?2ziR3f0jeeFzFF(I~`yu6e+h=?>2i*VKr5zQ}xL(Wjc(&cLwU;L?<;)ZWqoQMt>$!>IdYaqLRo3bJx$+_QQG4Ff$~b1RnEd#SbADVGv_5uQSz+1We+Qe+qt9-FQt=-^nX;Zn1w|Q1tTp=#45juX(O5R;9N{ z`TcD*OT8?3<^KF)Io^){E9E8Ga~FM$HjgwY(m8(e#J`)x8kQM$f2E%MeO}XQw5rsD zE74?Tq4`Ytk2m&5-P)i3y5#!*<8E`G9%0q~9`|0TTQfZDlwFpi+U2)T?_|Apne}^` z(Cmpdajqvz{G*pIUnP-NF54gmN-k+JUE7}>_@dcz?w#lBhPsftbj_(+QyMa77qTrn zpP&BjCBMa$m7?kKmTt|Z?U|7l*7kb7r?*$X{!sSRXN^Vv^Eb0T@@$@#{x*H8<qSYr}@f6?DVUxZwT=vU~P$7e$TicoA`XKV?Cfra9l5_|wvoc^m3Kn<*KY zKH9yp_T+5S)3u*d!(Iyr3Rb3>Y|{9o#PoP=oSH~79{x6Z}+{=513#~SzGuyT3#!x`*5KTlokHaA+FGe+I!($jrEml?dfSNb1m$Cs^o|2p4E@9JEgC#v?2O`Qse#UGCTMDzxPv3Heqp8 z`-$#^{fCaVPX3tvh4;9Qf7RM0Q+w}Ed3xFPc&NQ#tE(VmI#=1sQ#Zuwd}hgcOrN*k zFn6o{O{IRr>~B&X{E7c232YKs*ZTKI!71_F^J1@8-zeuzf1+PsT;U*mOphn&;ilvg z>FC*$cLZ^PNOS*XOWrfX?E$p|K3fO&{=kWdh zvVco}9nh|M{qMtJh%l-22me4`04Kxzb&KW9cyur$^2i zkL@R2jjnq(yY1-Dieq&QG85Y+Zf)Qc7S~&n|59kn{6E@(g6$HIug^$Y+V$}H^^Q$X zPd_qEP@R_Y>zsuQpI*$4hJy==J6sb3T~s1}|9ZV1bhgK2b$>NGJG;oWzgtTWzo=a+ z*SOc}a`pp`B=Pl6{-0wF-h6j^jb*aVT&von>w~Qgmz1jOiL7us+H=k#T>ik3rR)(8 z;u|v^5AK+nccv*$v>?V~$FGaF-eIfCXE>!^UdXPOo`2;o`?5<9Ll;XWC?0!uB%0Ui z!s-O6Bv0qc`)UuK9DO9uRIz`P`PO^Yv$HLjuSZ_kCwkqQ{nF}S^7C!_J>Rw? z;z8JK-rH}j_udf77t8G~WKfGMJUzic@t4hOzaxQ4`NvxRm!I|z)feZB-R1WzXJ(<+ z15dfljMw+Del$w)3HyDiSK9o?r_=f?V|SbBMsG`bes1oQQ&YX=jRGS?CW>o6dXd)l z>*vN7FHA)rOMd+DA>sA4wIy$FiOS2%+x`Djye@wKyd&3d9qY|tS4>}bZ)QsWqfJNW z3Eaz@uQubtu9w>)ZqKck7Lu}(G!jn@xx3|yob{BOiLNVt?Rxz-Qf}9r+p7huum88O zPs+_dn{?o8!h`kN7q`!!Y}jvnhQG5jN#K;Zhy1fEmd%KhUg>bjx9Gfw!Tk1*lDpN z^YBg8SSODy(VcrlTyL4M{o7F^8xy}g>Z*xP{5E-QhCrEkq2}_7cLaocFH0}Gy?9DE zWBBzYvODx*b~tQKJNx4Da(>W4r+0UDrktMUEAJB+A@XzkscE{PP1`%FzOFjN+iGYFb7syoN=>@8 zB@=WqGw7`CHeTtXUteCT?EIT=`oeMjs`Qt)_o$_wzJ6${cf6K}&haHXaNUCpdB;w+qu#e(N+upPEy=_ zoq77>hri6V#n*adFq&q|PFyc`bia?3RhYKl<&DuNV=r{(+UF}6g$qVme7m%!CQ~do z_IQ7GdGcJ_Ij-#M9~o!Zt*v{1*8Q)#k=ldefB&+?)_(qb^n0VQ(6!9(jA6d>ydsij zuUzs=IrYtrje9Y^%Se+}@U}6SwEb z%(kPuj~+d0m~}|-!r}0^HZ?-!GETWzNnwZf%$LB-1e0=8UD3TywDkOXHGI_ z>^I@mno|=5I4@1moGRBAx2r_+>gw?2pkuA(+y8Ghy>xE2x&G>~wMicy9Ndz7ds?5Y z^{4Om>t8d^?E+}J%@?cXnVuE9txX=CzEk@H(`%=mpd zebvg@SF?`Zzds+eT(0!>wY^&cUxG5?n;V9kQcgBapDu2ke$FNR>yjLG|9MOD?(XW5 zHt&0Sdb)ApBNwBT6AKP?99neZbDGgi3Hv%5=Qf^&b$@^P%rsIJ&DpB`tcOi6-gCzc zRp)JcERx&}axT`biTGK2)x#~{`qU=F{g<}oRi3%F-$F2{dA6U(y2x3{$y?tR ztqPHUCGvwg^}~aMA*;iBca^>tl9KY8X_Oiw5f~v-d2yCm?x}Ngt!El0v#pKV>czJA z?Yw&v=Px(3wwQMFe($sCi^U7i#`kvGzS_K@p4+Q(+M=6r{`+zwTW9Io%(!!LwwtYb z(L#ovoY1+ORsXyU%3?mNCAx5-zwa{3H!*j%b|wdI6WLp@6TffHs?gP!mibDH>Bsf# z-d#N@a{HsB-J6S_`$cWZa0Jc%Utce8o_FU$yA|t=9fitqad9%%Wj%j?f4{ugoxjJe zRAIsu*KlX=Ny5Uyht=oT96Hu3J<~Xy&$Ua$)AV-U_NWaJ2k-v9=E_yJMlAZ#-bHI8 z)-1an-hFT1D^cqu5A5R#3k$!zyxeY@eQiU*Lnp84dUKat|NiJ`H)x4+@^L=5UMbhN zx3+#==+~wfx5vZ5f#Kl6gEjyEe!sFZ*xhwvh_`b0q$fLe?J}zTl=AC4_&k=GcC}Tj zZk8=RSrZ<>VxY$6%5d2>*VX6Qk#8EB78>(xtHtv3^YvnO9Jsf)dU8zw%ZYim)}No9 zO@4J{B?}A7g*}zUhYlb9deBep(c{OUqkdCQO?mkBdi?Vf6P3BT7HwD|a}iDbF<;>vmt9D zK$Y|g4b3@c)07ny9+W*UtFQl`vRhYea_7Ilzd;dI_w%VZ=(OpKj1~8usHj<4&5GMw zb#Z|svsKxf6WelcYsBo>z@WUWdrTwSJl z=GNBi%I|l}GwgV$L zbzT7h0=CuPd@d?mOB$!8Tw3DE!pbTrA>na%SE*&?rzK@A;!m#G?$Zui<8ic06f|!? zRVy?o(M8U&eWB`2&>>RCkGF5hynJa@sCHcK*Q+5{j|a?O zeMBmmq#~!Jtj@i?E$7yjPC;e23roGllai7`rldT%#w!2y)m6|+_mp#UEWiGZGM9RJ zaWVVv@9&?#xVU(VW^kMDY_rVg6~~m6lwRD~X}mUi`?C7~|3EA8ugBNNwh9JDOu2I4 z^5w}FE(AP!^k_xQPNT%cL@h6&^zFAnYj|RJm!)1@<-)^=V^+>TfxF_Ut(^!%+EnpX}no$H%m~Iu0#bVwRVecV@2j^mli6 zKYsi6?4eff&?zdf*6;tf$ZxLIo7>y_YkzuZuS(0&ak=7|;&^ha% zv&5dAnOXDq>-CbCmt23(I#IvGf5H-tqg|rO@9*se#oemV)kUwaXo@~n?e6bS|M1`- zC{2T^$@TH}e?QhSfjVrrxApeh|C{ma>uX^F0f&;3EnJ^DUSC@aI#F_S`gzc9pIxP| zm(}`$#?~FX9j)j!RZHvC{saSw zjQjg+L5nTk0)- zZC&i?@c3HSemUEY-rmr_xCLvYw+C%Za@7i5b)=0~`ouij-CduiY)UzKs7KORBVvQX z-s*NwMjYCba@Zf;7|jM-t(&Myx-==D~j%REEVFOx*prktGg=I-w4 z+j4J%YOS>M^L+Q!{0y1#T*J<8-owM~#;K=7Kqne*-@d);6Avh8Z@ew*le0Zle!tee zl}q%;m&^V?A2jm^rP_kp-Jqqw6BL3!Rzh5TvvDzs^7RCt+2{~O`6Uq4F^LhJ^-@k)aPp{mhmU(&E z()9E5w&dIlvhDy?-9kw>=;k$R|+IXc)>K++i+nyg^_V(7%U8S$L#CM(W zUOLg$^D3wo*;o4;bhxEy)|Cf`TDi4OC0oew9qW;F-kf%JhDoN7ot+)%T-n)Ymp!sk z_MWDbcyCXoPQ(TW$3T}jt6rTh6}AO}%5I?5dxnLNTx9)fJG8^sU0CSMuB5CCI$>@7 z-fvPLKYpzFaFBh^$79kgY;3DM{$HG^?7kvutJdplYZtq83Tbt8>?x6o6j6NWCmCP! zk=1*e&cXBN``7RL#kKqHxwr0;X z&7Q{fRsL{AGaIjjaavE)q7Ie0`zK08itN08zy7~(x0vpZy1!QT|9&h!ca#})Gm%@b z)X^!L!E(1)e%;uZyj@>HD|XkGvL*GJc6Rf=->;v4sFm9|>xzb`i_0XQUY)Kx>fY0I zKqaG9>8pT^Nv>u&Hzs6d7k+&eI?FWs*rTJ}H}_V5SAH+{=;6bI{Puqw7Q6NCD0w-l zY)Spf{QLWE-p_xt}Z3tb)7BVpKN zl6lGIV)4FTue7)2-8}`0y24|^QCl(u_f~yXQc-C+Gt+prg3_W58k6R@xVwM89$&xo zRIdMQGu@rV&;Ny=FSL-kvOa$Pp32XleQa+X`av79K-+deYYI0e9Sso>1dSU^(bv-I zGD!lSl;jZt*0HfW2=FVEQ-Bd}5NnT$5Sik)G1sX~xzgI^qcPo7|YH4i^+?eD#O+VgmXVKH3 zJKt`;Ez7#LrgQrA>F0t)Kv6ewIcQI&RoNSlf&v4}q9-1Kx34XB=kJrZKesA$b;-O% z9V*$s*n}ree+Ie?D0sP_N!FE)8#iuow8UhwqP)HAcq{Xv0e+pE)c!ux4~#fPt7 zRg0gU@tJ31c{Bg~zS`d_A~&mnPNpw-c&Mt`RpsVo|2VMGak>tZM0W!2xDem?E%tE;;fxVi+X^y+kZOuD?pQ~1u^yOUinzq76W z7O=Z4*DU{@&qAlxFV8wXx)#qrpfu?T=&;BK4<4x0TOaVAu6J=mqI2DkhwQAbf}R^q zK#sq(GI+U0{63p*as6dUN4pq64Yk+T*H5;s{wBW9?&HUgM~)qP^y(FtO3Z~vZ{PYZ z^_nVSkkD|?0H#0a-=Ci|O|!*(XPbde?f&!excrk76PwNN*L+^O=KPP3k3DCb>CQIG zWdbc%|MK$k<^A>bdn1)5Ey_#@beXgz@o<}A?XQwUhYxRz&{^UOIvVjCsASxK;p>Ml zU!I(ps0?voP~oA0RzdR_!OJ}M_i2Xqjim6cTcVaJ#l8RPVGH49%C z{Q8pl@5f_)hP-=wJOcti2Wcw%&N8WLc5z7zba|4x;bso#0<4A3?MYjs^8M>26AQn+ zxp}8n;MuWW>71LJSpEI|&&{=7Zte}L(_G@VyuQAEbLwd^xsqi)N0So2zq_lS6T8fB zZk2xmYe#42!z(L;b8c>O&7TH}=Z-xY>-T)(GS9!ar26~2CBC!8nhzHI-ZnN=lFBWxl+$RJs1&AJ777G2N&aXNACNeLCo<+Qh?c8`ICrb&2cu-OOP- zkhlHwWq>L1>(|3A0r>8Y2#LK8*&!=1Z5Ha#e? zm|<0_Wn1;-K-Shrj~+RJ_F-mSsaUsurfGJVl9H0a?6aWbY31wxOsx6&=_N0?fSf3< ztEAMlV#SK%=Ps|0w+A(?ve)nZ7IR+m&ZboE+*?}?-n~25v6(G!d){65`h`+FPtMN% zKF|4?5~ww%biUzk-tn6^XI_sx`NQS&{f=JMb9w5{x3-+mott;;XyM;9p2$nPAAVtB z{BURLYM+@#t>50>e$6X7QRHx>s=&l03U+pKizf?9dx?8)yKV62iKLRU^4By_HsFqN z0fEFo5Gf+&3ZjVTs8||KdGFUZ&#r&QLtUe9f2Lipo6&JQ+pmAV9;o6`npCvox>oba z%~F|X`FBp={J31___`#MrH4PLTy_OVeo#kF?}-W5C7B~nT-b5PUfz7i!#6*zwmH`e z{0AvvNuTQA64X_)_Wt2*#flg6%?|DjlAV+O_DI?5gL~vB{C=(W;kqS{<)?>7d}aD) z=_x5SxQV%{9Icr>_2Ja*vMzpHN=1JidCuie*y#7KlX=l~TA1`VU{9IirXS3HW_iCt%%0xcrZjVpje%GAaI;;M@iMz@5{lA-D zfA_g_=w<3ngMHKZe&4(-@!;FfPX5_?5W8ks=q1|E{&8M?rk;`#sNPfpiz_LCBb8Ln zq^_4`^-7aW>ULULCieF)e|Yfm`M0J?Z_DP)ol{^WG4J%J{+K;yTE4slr^uuN3z=P_ zt2$ITLB}mCDLvY$yFYYw*q0Xr$&IT`lfcYDo3CZ;SF9{jGcIQ-qiBncNHnV}w zohW&ENl;MG@zH71zRKW4FGV<=O($4Pg-RV)gN?yLWu&}vH z86pvTYfEP3|G(eg&rI8X`{#$l{Gl@7GD+~qkH`J*Ek$BCrFd>iJ-zHu2e^oB*uMSy z+xp!*cfJ(Z0_ya1oRB?t?%a=Gzmg0jI=Z_T2QGHg>Vit--rjcfVFBp;h^=P?1%H~` z?+9fQtPDJK=n!a+`NoYK_5W9 zuJkD?oooWN({}#)dOiN-ot?(5+~R51*Tp_LGt>C$wME(2*ZIu1o11lY)x}+<+53Jz zlfJand-+@!mq{jbH)|-JKYFxVJoDZj%eS|;gU;4CGuPTYl--q~`Jlt@vbP+3e9!Le zEEdrU=?GmNmYEI?Do-qWl;fdh7FlZYyJKu+M*6$6{&&hx`Wj8P~$KA4QI_Wr7E0lweZ&|^^L!g5e0vEd_ zZoeHW0cminT3Jce*VkuVS>d=kY;DB;I$Jq8Iju9o1~Yv?6_W34Gf;cYYpT}KcXxNg zG%0FmaPafq&+SIg*9k%acgCpQObz}GYpd>HY6}AyZ0TreS7wX4F;lapyFL6^Y*@4YgMl) z37?;x&AGd4>O9-(M~@yYI%J~`GUN8P+$$>ro6GN2wy)p+PipnmRfjrGa4)sq(QzW# z)y?hF=5+sS>*LRFNIaZzVL@Zpr>55S_RE{o{XrK<9_bYR`0ZO*pv#li3nvzJ*jxe) zK!mIi$h^Ev)x;#k@uTX=na1kH&(Eda*pOKB=i_mc{Cjh{#r0PifU|`DanMCgF*}VO zAMa0pd1)!=hMq4kFRwPxk6atIm4la8HT~S2imEE3#6v8P9zPD15Uk8Ak%)462RaJ| zG%`3vGx){r?fP$TZ$Ez0bHDcFtOegce?FYH8MKY3OI&~5qK*^B%l9)V72UqIH5)Yi zk(ikH=HA}fe6m&=8fuAha&l|p_s_ewHX1Z8_2=*3&c41dNTQjy_QQvQn%dfz*Valm zHa3E`yoJZthPHaL@vwnMO-)RubaZgo*w{quDp?7M&`mg%^Y8Bi9Ry*Ver^RM5d76mOhD&F2#AOr(ck~)(DeAa z&ef||b4@-Ix+0)4c6Zssr%zp11}&AaD$yt`EY#}hurZB&pfu@}i>vF!-R1ey^kPpf zaBR-Fwq|D6B&WG{wX@`Gs|x=8sZ{fyx5oNy$BE#JCl+~VZAv)EWMgB~(b=g8iW$S! z)6@0ky{GFf^`CDCIRwtFKLZ`H5m)(CR8(zh z#)Sop)!*JI=HA*Oq8Bqm(l~8}hSH}aB@$68tlho6ponH+WBYV7eg4I*+2M2M&W-;N z6~P&6G5Kx%(S~ZlTU%DP{kH9ozG0TmSFN06kfP~*y#G{U)q$Wl6BBjj*)|JX&c1y9 z`saDam7LN8m)2Tnm$N6_-8f^xg4{h9GZpr=8rfXd-MOLKu-GhK!I@2X$w|Mv{ZbQd zr#!gT$$KWIxJ4rF>0&?M6S+nSy*$Y`-{gwq-e=?A{P3L9B7WfyC&TQ1F66fC(XX3- zV}HGUU0vObvuSF+vs&8Q+wC-zK6yw*ig+#rbucb2a^>XXOFP~tTl49py1t?Y(+rKf zOE~kQ(~MH5$iMc#bDRA_S()X5uyWH3b1I25G09&&`&${L*07wCoLQ#{Hct zGkw@TiE8&{UKZ1_*?s?@pnJ1rZsCO0Iku-}mwovA{r+dSwN+2dR9g=4u%3QoRBE+8 zWQ|kaMva>DiSXCw#L=wAtib!&HsabKUR6X`fe@-D}9a%*XO%;DdcCX(c=e?M$(Y<9+mpg0~u*ox2~!>J(3^2#>4%8k%-?*3lV;$uF+0mF|@`SF^E+kyis% zJD!<*vQ{pwT%t1!li8qCt253%Q`y?Lak^~9)oh>SX5qc!?-?FEI5cxgZg}^cBX>CW z3MyX7`K}v(PQrV+U;M|{SNwP~?{@ADTI}+mRw0Hv z^%L$2ZfthjTYhPmdwNyPJ@MP?5C7EE(cZT&)Ij3?^3M~Fw&?wwcEk0uW$MNaTl~b0 zOkX-|e|I*$2ctEf-e#GzC*qGYgJ_Z3mxVfi9n(ARpLwI3-l(nfUv19+%IEbt zyn62<&%U(s)yyzbS^53g?c1{-JV@XY(Qp7|D;}OBdD~w%tAgsllXuU~HvjqO^Lfw# zJZEQ{ryp+Pt*EL}ySR04V{saP&Hjq^87sMOO4m(UARl*pd4^lg#&SNv=zs53EGi#P zs+eO|U6{W|M%}XFpVYPpZn^nCCY?_?u=c_3KkI#@{Ni?YYVN(w9B=sQyfVsk_xjxv zEu+)sa8*n?o^!x7-*FF*q~bH3*^l1FSk^Y2we}CFxWeI9uGec=IOCF7@pBoE=P^$u zqRe%*fB!pkk$L~7z31FTjP3OpqHfxyYo3)BsbAT?K>pa(t5fU#{@R#$n60U)Nh5d} zPxZGq7u$VY3c@&DCankEWK#J#&B@8>1Zcx9|8~QGM}NOn@Me3TmYSVu^(%c7(?tFq z5>L-UGGibni=fz zjGfOmWsm3|rT>p=el+V%F$`Sl%g`4ZfBA{>kF=NTjEz zHQ~$;dbk@lckYUwJy zKXcCZ8y$ALS@-MLwUuzTf6gt_o;ZC`UjD?}(N|xD9&5RLJ9b6P2S(-D=gvLYQT6-J z$!>P@qFqtJT(fvjCf#2ZzFuzQ#*M!wbasGRqn4ZF)2>eGpPi?YxAh2;F> z{kmzt7vtnc<=6EOPA6J#*gQ9}`17ULi_2%2e9vB0|Kx0M-d&6Trv=^g<$Bt09yprx zwIlOx+r7SZGx%~8j^9w2mByL=Y=^0U{GmUpiY7+ylP+{d%sKAA=*soCGj1%loBMlv zLSntg_x_u5U#1`_wv?`=zb zViv!kRk)+)sJP{${K;=0 zyji~HpWni3J>$|E6XX6k)(4N~U7fS;@{}gE<}O{Om0jl|MJA@47Zh|lTXz4`<38sp zCFT!;ZG^8zmmf<09keND?$Mst?>By*%)j%)vFTS{PM>*i@m`O~Pc?jQ_ZX|4`fb*@ zb2X^&WzCa2mYx~4?{0MacJ1_}qm|YPslVqe-SYNIV0nVz#1MDoZly_YBB3Saj3=AK zzaKkYIH~#h`oh1bQVWgH)HipSzG2{KAnV~s?o~59qaq^Dnw{};xnuT}&xfwr7^RS4C+41msh}1of{fS~+ zF&wH1r?$^%e9^9_&z_sUUhV&z!kTM~HCONUPt0lTowM%Rl%_PYqNv(^j`D}~M|38)X+)O7kcz(}0-ILQaAO5?r zPOsCu)bzodNqh{m?|L1PSJb&Tdurd@eM?I|o(Xwb)6lCr?b4oPYq&Kh>t==qyBQnY z;8OgXvHQ2_rlsD;kA~U{KMI-ka<=`0$=e^V_&3qqd{^n?Kl<(xdn*j&(wN2dWsVD) z&u_cB>~7qh1CpCNV)n8xmvHd_VIMR-PMd^HP;KDSvOwp zxEag7z*XhzrK=Z|CKc_hG%9_mQWSgRZ2#rupHDYtNPUq!QoVlnN;4%Te-BXdS(K_5 z(O9-uYlVQ4lD{WtBIC>r<--vIH`=%5-hTA_`SAu%?g)!ozT-OQKV5n8I%l?>zG;(Ryi_H11h=n}c6|8B|kIjJwcxR&pkw;_ao{{2($EWI;t z@7eOp6123UV~Ng1^>X9miO(mey)UY7mOTIe>*9CPFL!RJmgAkxvZ3IigjZ#iQTch- zH`tbeiye>m$%$Y(uVklVCBYbz7Ij#t#^yiQ{QA)M-?A-sFFTlU!mBF%fY7lEZN3tl z3m9vo%#wwlsY_hwQRF*OaDL0{HQaxu$lX7m>h!&6ip+*7CywhLt9mby_+_W;7pWB0fFoQJNl_c-3$wKT!-Zcu>BB(c~BN=oN_q@LIJbxNL{=^-!Z z^b=dbR#Us$AlJLjieK{BhtJxYcPp%x+pRu*Jb7R0pWn78e`cvY(Ax7&F@AgH^Lc^W z4`)vce0Tca6XT@Usca>EiH|vbx zYCUoL&x)7#@3=bucJ@=rKcCD0{Q0*$CoWSZ`#^uq)961F&+pukZo2%n$dEqr8pvi7>s;{G$s|0c@2{oKcUae2h9i>I<@3PnAVFnV71 z=KxazSU&)x3EJ+MU}-r9`M-Ado=IDxyn6}MjIG6KbJYa+;QXaeeb?mdS~8S zu!GOB8=MVn)=6$=+K~7Aq&xeH+sBQ1c1Q{_&EV~@|GHpx$JvVVjKdcbCv(Vbm%VQ_ zuYA+XR^1=HeaVln@E5Yb*|?E2@#@$8aylCem2Wwon7O)K?dIYgB416CrOn{$NcOFMm>6`T3Z zwKVD6+{fpcwIytR^LI(jk=U9eET*9L$KOdNeEtI_(eN0COBVO}RvgU^d-mc&osNA4 zqxY4{Wu-qRZJ2Rf^nj#Hc<1iyOGkSbM^4--e|vh(>2-zjn_s;6+J3J7;{!Lp@N6%~ z&3umC9zuWm!Z}@@>^BZt_gN=xpVhQ$YgZX>NC=e@HQ7-AwN%Pq@`!uf&J`G~JfDLz z4)<`(Y54p7(VDw&8w;=V9DQd@?9yxI5azCO%$ z(tJI4+FIZJ3*Pka)eL^7ThOwx_OqgvN#zN~%-^YIdXcA=O|Wo6Y&(nQt-6q1UHi*y zy=VM_?`z^$PI&t;ekx=G+f3*N)X(qMo+!(cy!toIs3Ozc=t+weoBFT%7w*TNK3*~H z;hoyg^(Qu7EYAFQGfTEfY@*7^2L%=~TFoAxOc1TV$>+Rou8q(UTVs}fX6BvuyRB|{ zg-rRn?zf(Oc9@QsblPUiho>Gx_loL$KO-vs+-qTZ*TjQq$1{wW%GQ(_NWMQ}y80;7 zx-=iRAI0bM^lm)Zx3r7^7SrfZkKX#Hi3^DeuvW5Mmt$!^^HNK}w*>W}%ctC6m?mNZGVvcnC`8_ad*xWhk?d}Qoi(d!tKlN{`@Xvp*70%t3|Fh$wEq`QEpFow-w|U#lK(jZX zIg3w+_Bef|D>0w|8wE^pI+Et$nI02{TNTk;WwHsj7T{)Ve7(oUi*YR2Y%E}9DDQpY&Z*5JWxlZN|MpGmljgnM^YL1c)mdr(&o8X@guivItAE4yY0a#Y zdmeq2sCu3oa^h3rpODXw6w`dRUVOKClZf8E{sZl`KkK}`-)RZ+sheH;y3g@T?526M zm3dd)u3!8&{6Ht~^Z0q(zO&7GpU2#v%xj#-yfg7;LP!fI=6)JyV2*9);}|XwZhy*SiPU$F?^wOJpHtIK*n2@ zUpCfkJ%?^II2H1K@O|ysS{;`de{@<%=b3rcKac1aSH8b~eujCjRPgh)tPhUrPZwtJ zdlPeZ+XT5Orz5XVGZxxj{e0GEz4N}HwaB1#O&x;DGYpfDO*J*10BYi#*e@(7c<{tT z@DaMMJ~Dcs)>MbJ7>IY;jZSY0zTG zUMbV00}YHRX=!m+=f8ZgPgSDQ!nFU(d+vSDs*OFia=C7DKDSLl(TrWaF80iCH&Lmp zI&QCiEv?~CsoX0Q`CZp@Th^CZ8`{EOSx0X9$MDbgzOmtAPF-QIpe--His*)P90}tO z=)XV1q*&Cf+G5%N;(gIu*&CAGzbDO7Ke1uQx!pFp1*P-NyTx=LJ)51M_V`%un%Ldb zmd~$~S{-`60#p@yy!+!6tz+};Msm)L4UM1$C|k2a_x*e}d-aFOEFQl;`QEEI-&_B4 zxy|L}{Qg_kczjO3_Th_k@XHw!<}cB|_w4F|`K-eBN#XM<-kF47URPzazGidlrGue1 zKR&SZwg0GC_45CWZ%*+1H&@sQaJ*I#(2O67n4V{eLDca_xTQ zt)f()Fk|N{Hg!SE%YS};2HhxGSXj8{+pTO7tts8|_jxM{f9LLy31f;gw}`P!R;cpy z{`{z`=Z|}`65F0U8;e5A2IIJ&>8IC=Ez1N=!Laem`5f<)t$f%jUa&Lf*Cx zUVk-8IU#Vb?sx9I+HaA&%HOZcJC=Q=jaT}`&CTjztEYZ^d|X&ueEA}f@{W|-n^L*& z?k)!{r{DAQ*=!XRl@%J-Ikw+E``}>n&nJ`pC5%!yVt1DvHQ*6-bJ6Q~7b&vS4YY`u zm0Rq>?(+OwTQZ&3$L%fp*>I1^Ap4q*kdP4Qh;GnT!u+;hCNwPqC;a)l|Nr~#>F0MW zY2$2`ee>%u6|KT9JynwLqV#tigpS{o}cxh*59PAKO z&bYV7a{u3Nv$-a7|M&|!Dcx~x)K(84pOnA9zFt`y9o`DAs(wBf5*Ci!oW=`UxE!~) zYR{ifr+?Jgi9XZXnsd`A>)M)={r3M(fR4w%zFuBYQBkX>RgfF0M!obYakl+M*F(_fz$7JOA^G z?($2EpP!SlsR(G@dB}IJ)znp?s~;8Z^zicw3ji-A`ie^^Y$bm+z?^VCIW#f@J@VMW;@93iYf0aJnzbNYF@-GCkgjYCrR|)7gR?s5t z?YGw+icz~?`(5_xs?f*Z@7JdvZsYy(;-WKT`RtQVMm){!a#bC^v&{5}E;8 z4qD9l_V)IEcDafM_4zfEw%@Dj&b+(~bonr37W2ueiT3q%YX0-iOwkMmoeiIRYYS+V zlGd4e4GA8jvNsW{!qb?EHNv=U5g?nCHoW zV)OH}vs!23Km4rWkv8kGDt#5OzpnP$+GzK+QCmYGli7E)6%`w&YKMb%G)S3b9O%~H z*Kz!DVB)!tm;LQ0&X{pxmT7jx-YV1ozwiIwa5D#D*9p^t#ElUzE-q%5Hp_Vc3XG_& zS!d?k$G0+qrhX6e+xtw{iv`U}g0?hOe0-$U(-E`ri^-ObKhHtS2;^$N1cHisP(|S5 zbH+5B`LYQ(gPUXse7|3BufOky(!+-jtt`Ql^emg6o5Eztj+ILG)tLgt&mVs68HG7)78-dE@C<$ z>nF(MfVLWwsrAqzkPA*|wq?#*c1l5MXXxIlub_nkdeghNY%w`>=+K6nIWkry9je~b z0(KUqih8;{(Z3PGBzW@drcIkHs=jEr_sg*=n3{gQaz80KIaEP#;{Jts8G*8`mep>jdkj_J`+^ZeHD`JXtv+wLMY;<_ACN(iJvE=nN-=l({;F*{`_wd67m7kwU zv=wGWDhMp_oo%Mo2M!yKlDD^}e)#Yq_lufF6VY!h}7AVeVwoSXw&`o&)4mK7Zm6MT2SEP_xj38;e!bi*m$KL6zvQV5S+-p z#BrzVlTDt!zDpD5TBTIlPEV=NuD|Qk>BQRE)$`~4_o**Ge)_XOe8+xgci9Y(BL#n& zPnta0b4A2Ney!>IRepbFZElIs^KCzsUd<;Ew%tzHZq6!ZU8Qi-vk#P%riVVXnHX|H zc;;)n@K2oP*5@SFxw|~cT3U@c`V7igE)ZwBfKwKBj>`4RJbFq+uA9wkn_kaSPf+Zr z|1RgVe75s>{-f(Jb#%lCWkrfi)DF)n=GwNdGyh^mS*di3wLtgc24UX}eemjqrlmEJ zYa9%|GR&~{tX*pVt|!7Nx#(Gp*`bX`!;gp+Hx?U6KfG~j%Hc;=>Gr9o61AlY6W_m- zU!$!wNqU8Jl*_t^gIA?Ll&^AfnXMSn?f3Y>4t+*)1 zU!aLU+NbW1^nQh>U!TU9*J&tbYW`rpXu;i*xJ-59>i7On?91<7U-nX=)8pO$hwFqo zPKav%mHy`UhtXajY%%ZA`;v}FO5HirM4s<@emilUg!`!j|K8M=8$M{bZ?9N7TRO+j zz`VHPUfKaJH^U^WtBM<{)PCRp-=*W!k#kk<`r+@wX}8ZEyqI73$DIG$j%O|+yhr!f z8fxyH|M11O#|%sAOvIj7Ucagu0!~VLs}C$Hd{ei9-+j`zL<_D7?`JBuOk3A^nQ^A) z>rLUe>VI6R?P)v9W!b+!!l}s0|5M0O#t7~(rB~mID#F&Od~N^QGWA^Z%Tj^D^RYKB znNQeOVmEVl-P04=`N?4~oDN2FP25^2b$ps-!QA}2cO7LF1W$gw!mFzkzNTZ}`lOjx zr(fx{_N;!a`NzNK(XLe+UGB)tlVv-6xFJO0^?GxWBzC4HzE!PTCNt{q(`3zWf3R?= zjHIEydgipFg5eDR_4$%^-{<=`SEk}Ex525TjsH&WlK%eMq0?jCvT3e@JClC$MR!)M z(Z4q5|2eByj7$}iw+pizdY{qH%ds+g>g4_%5@rWp9bvLqcYc2D!q2it`lr75=ggri z*0}YZut@D9v6Si7g~*bQ{3V&M$Wz z3yvo7o>nQ)_{)ELvsmA;JD;@E@8xFbhVdDF|5Nz)^NtIp_IEe;%Z9G{{BH_>=>pd$ ztH1$Ww39!r-Re8vCbK^u&p%rz%fhfYEpttT;hg31;sx{Ts~dIM|8KC^@cKG;o%jEg zSiy;R9KP#xow&Nzr+8gr1I-=OaJyO{4g_uZ_Y@oK8+>{T5xe@)D{bi|m|?Vlgs&Zlbn z^@r-s`)AgtO`H)i_jOXT{>gv0?%eKcX<>nuk2|FXZyg63q$IVP^;nl2%=u*gr{8~b zZg1ngY*KvnxmMU33BBpw>=EFS`k(Il>)wCad~bX>z23sw&Ra49ux;Doa*rJT(bX{q<`o47bNfQ-*7`_ z=fBU>vdo(H-iw%ZuarMBGSZ3>?3*X&|Ez!i*=PP==BM&A`b?kPstCHVAwuJFlFn>? zi7i=|=Q$`W5OxGtROd{eJYBfQJe93bHZ7s}+0JeIB*LVhM)@aQownHK!bb0P(rO-H zGylcA?YC@uxcI*6zQ;aJF+bOz)o<5rk$yk-)OYP|A5JXxf3ZXWv@ zUt0*fr!;2o61z6R;M9HdUj2QMhm6mEyLwU))Pg9=oo!cZWm|23+*dYDKq}UUl`-d> z$0Mtnb1nV*o*X^s93lW}P)y0bd}n9z%TFI3AF(x<`{9O>)(Ocsi9b8)KWMw{vz;iY z?7mV#NvY`U1s*-71B+a{cYNQ+u6h5TuoBm?)e6racI@c;(Pu1avV?P@pkT?%OOdYN zFhBVhbZb|v?yZ1rXI(yttX|dhhc!JU@R3#B!Qk^|$~JdgZ*R*D6%iCXdG!i!ZLhbN z*P}kQvzn#fBNlnzTh$c95bmRy_v(Wrf9d;^Z*Fc5g*f$2=I3W;ujF5tA=DeMUcZw~ z>x8{P-~+W&+yA$G<1a~jc&JqioN&F~ZAw1QmvuecVc{<3oqGTDM4!Z;2;Rc=^p@YV zch$ExmAniR1-Bej%R$@tw_n-U`=@iS#*_>bU4hjz5xiybD z=eqgQh_`WCCtO9OulG2Fv2A^An(%Xyyk_L4C0*d;Ui7tK_uVVI_Zsf2G1V-st`~h` zAF!b)nkiE9PSA6+FumX4_MnE6Qqk35^Dm|cUVpu`p|H5`rg!kFliFG*>Q|gNJNHEJ zMsAP?oZI=p5p^~Abduil@4qw4!nK2p7HL-ZFI%}=r0edjX4!qhccQj4p00~?xwoe> zbjsiX>VVxFx@jHMMY9lvZbYE&Q=>^%`68fgCc7YJ_DQa;=?8gxvYYf0!?CY-?GmFnwa; z#(-r$Iv>ubFmHe9x!qol0t>ERKO2+H>Bbr;{4 zIs6))nL{UNzwA!qet4{U`KP~Kx+Smw?cgfv+?sp)Tgd7>4-byH*5zT~_V;>SVRgTx z1KsO|?iZ@{onX>BA$mee$nEFvWgnkz_kQ`;M_%-a(5dJTD`E5c>)qQsJM7;*z4((+Zl`TaSF3K#-%PgnrSW%e zUDExz{LR4Jz? zFE{v{deVZP4r~JWD`Q@?gWi^RHRP!_ zdS1}B=F^A!g|8Q~t($R4Jae_0eC^xhfa?Zb(QCKtGVES>a{1vn7feHngqA@ z{<6=>Y2WT||MyBg;CN$wsG5D6)Rz1g51uKReR}l#j$>X{$R=Syj!h4hY|rHIcdM;? z?BllY?u?F(f_ZPQex7ApZ=Q4Gz`MJ<^-u0Qbm)+Q@8p&imS^YYR%hyMFq1BQb>-m0 z3mq(=KE(8csKsUcleM##I|c7-5%{ED?bmwf_v)LQ|Ep$9DoShEBz?s7;J;?Ic1B~{ z6B8LCm=jv0M2@Mbs;{ZtdEoGLOa0q*Ivf$_Z&mwDag^JCa+`6KRP4eB=UNs8vbp!q z+;Ki)f5;TaSDcfbBL(749DBS~re^vVsb zUs7Yo9!Xi~m;cuK!;LTAdC#y!S9y+t}OV&`RpyH zQ1B_orOkEMDSymO%Z^TYw2W{5#}AA5&aka6TW6bo`SRtL*VoHGJv|+?z`gYK zwU1ve`#=A+fFoeB8}GC8^Zoz-`&;>HBTW-6P!m^GY68Cg;I`_gYMeblvAS&F53px_o|>uid0eiMI`fnbfi`J-l#^ zA&y~rt5SpU{NCzExl3}sZPYp0Z~lDm&GQ*rKKGxhpX8mmH0Ms}MZdc3^CMr@|Nr@V z>b9bc?*EI*9Or8csAvGCjU- z=86?7vfu5#y(Kd^Y+a1!>uYO4hpDWM+M4wG+S*+!K&xJ2Y^=-PEGXN{CbG(U!s|z~ z;wRO%StdT?l8@tgmm$-&^X74>19kICH=ld4()RPs^x0qEJgxip=O6oW(VCTv$8$O;6rbPTbn^M?nue)jon_nB zp8Iv1p<)wvRpF<4``2Fc7KLhDFEc8?e|eg!_q3R=&sSy6s`+x!9dwZ*=!~Vv&1oOc zS-%h1l5sKnmvzq8sFzn(3VV8bhO7u^lrqh_u(!ILPsU;a_nZz5om2nM-`iWgbLqF- zC;o3-7q*m5&)FZzXu$s~vhof~)0yarcWW}vbLPn3p-G2al(*CYAfH;G8Pft%;#&D@tvsbIF@JJp8d_vj3L| ziA{gUEOhGT#>ziB9{WwU_CXtRkC&BiE_-`x^|zS0_Vsq0oSdK$c+duUN#isDaq;E) zA0?ffok4fBy}q{gaNhRdeKnPAJQ5C7RlB;BmBR1w-xZImc*r{S;i6h@5+Mg4mouOg&q3-`{#!Zj<(?w%+1+$>|Qr{+4bm+5jr8-9ILOT zZ9L1rUDaY`=pC7O3P?btV?w=4Cnipa)`Z`8EyYi6H(x2Wvaw9Su8A~nQY zv_2{xvEtFdOMIxX@ts!DL5Wxjjzj2SalKeLJ5 zU8W0K*8l3tN}KO@ia`U?kB)Y~=Kf>8@Z-1J`O8CBht+&MDsJ=jO0b0t->Q(6O;@u* z8(p4sCCr$$;#=3WS+l&7*6w2AHd}Dy?ca4@E_Vh67W|n}dF@8`x`5^;zZ>P}3e4-5 zdbBY9yX^e-Pw5U*4Jp2v>F=)R1so}BT>oF#P>`)+Q~TqcSH7+NJEgXpZDY;J{wX?< zpStW`-QJ%6eCP9dpc9us`?vh&S|x7IT+mvde7KF5N5Y^X_x3i!loJBCw&ivwN`Obe zPt9K)yI!%H$voiIxkRmFrfR82UHWw851xA)z47+j@H@Y)YknAjMzMpJ`z@{h{_e}0 zo5IsiFJ&$l{PW>3|CjgoTgdTw#$Q#bz2j? z-48PNWO-cl3hT@7za`D{WI$!k-(O!r*Ty|O+#VnLJv1)1{oaFl)$bC&zPf6Xd8x&% zSE}%S?e~qz$N5CvK?Uupkk4uC>$mbfT{>;yo=a9e;Zg0ER=4#A{a@5>Vspyt{j`NW z`|o>d^6c8q{rv4Q@qG(_u&__w)mm76!}afduvC40b#$V#dx#9^%p&d6 zlSJdL{o(;t>|1OmpP&49%Gw7@;-3cy{ZCA=DbzVp*xh)TQ_km`?3c~oD?Y4W95ZEV znCj-}TYd7}o8AXB$ch)Hi7KD+ic!hAD}UwSInZp5kgzc5#F_Q`erXjyJF~Fp=_#!< zsRD9x=RTjeKmY3L>XT>AEXldK33P~KPY(|?L?+HMo?86MaB`^hW{HhD+#7;cw>-V# zao13gP5pgbcc+ntXyG}xH*8Ct9)6JQcdOrd;IOy%dAUEkJX##jtQ5%lQ1UrI%Pn>O zrrq`D7rp1v|9vjPUhU9=kSmsVw^^>|?>EpW3R_^>(QW(JjxlS_oLm0RcR!xi-+y6C zX0TPsivx%G?axe7_0G7kppmPXwWGHebY2JOs54N4xU|&!^EvDH0vs$MGJ-$-Gitbh zhHolzTcL^ zfISVYHjZA$ZoZuRLgCAV{ygK_7V%9Vos`)l-J4v*Hx!jjZjY>!F7#WlX>y&P{JbW` zBTt(n<#+zHxx1ixb%2(o@1>0s?q?c3{>*bIyY_Eyv9dr3#xjlJO zhtAD)eB>4BpSu{+V*Gb`<==HBnwpwl-rt|Uv-tU^FPHs4KWvv55)xV@{Ew^TFt52m z>M0S>@jUzM{#HEhH3!WnmEW)3-leQ0J~c}0sr)Iq%#~@j?6uLY9}^Ql zUy}FH=wCX2HP717IjWOv&84uxie2z$2xLN+qN-v<@B?RH@+3C>HnQ`bo=ei%WF?EW!-zVq{?pc zLHV>xzn#rj-xZ76R`BWW`N)dK#nF3gEbN*^c=9?n1}xt*x8n@Mdi@U{E`DPuyZv(I z-)VV!%%aml2X<^eZ|8lqOSH4IQ&Cg1^SFF{jN?Zw(C%E&@i!M1I!hWPFqmdvd-8mK zJ!HF~o;q`W+Jv>ywLP<+Ejykgzngz1i$Ub9C+RPbF;uSK{OG&$+1bt=r@m~?n%qC7 zCM<^~qDrS)d-?N8W|otTKWr7uTG(!(|<$1N^-c?ooDx2Sg5heO=@pxNTff3z-dNOYd27wa`$FLuZKeZN6d}`MVM})s>3PSN$1T_U4V@UH@~EZ?`n*n;w|K8t10>!7y#({5^?m2>~x`_z&MW z_e;F_HTRr--wo7GIsLP*wO5-dCmg+Is*cwrto+2^f4FUU$%BoJkKf~AN`euS4!|clke9SG`LiSxf8M~3~<5Q=Km*)iL zSpME<#gnv1N5Eq5e&6=ygzZ7LTGf$fXXi{1OZeBh`Tl144N;2wn&uu!Xs{FZoA9Jj z`qT~0Z1c!zHMu2@=VuzXcZq6$0_{+){FGw%??>{fX}Z7V-(5VtD)X_VX_m-hH9eKL z{PpFVe;j_Zzkl8?p7--}R0~S9=l50h9lUJ*?_s`sn6KKtOWJQf%*y#!XXRY|CE?ey zE8YJ*zv=w%Nj&Ukn0LqGnD37!k^FD{ORwmD1{r$Tt+py_=Jyw2cCx08Dc>z8r_8*_Y6B@3I|SKVT4 zZrk3zA!TCw{BN^OvW1T=n3iaAHrwc(xBl9E{)3H#M8!2j|I#HE{wrpF-ZisEI&f0N>-zYgHkF?YGA<~Dt%*3;$jlD9JNj@t|MAyf zUp1*IiTe~g$3Oh^YP)%=g%M?G);l)iZJ;{Ph?=xsR*^Y8Bi zT{sO|)%N@Se)-i`t2*sFXZuIpJLMf+b7S`2Lz}{9J#9I}z}>!e`SB_78&tnNe*XQP zt?it^17|N@VOaO#L@;B~=P7w#w%eTC@_U{7S>B23j2EXiFodnK{QYUqmI>^qpP&6V z`MU2caV2Gc{%Il~*fvU-fg>r1f`5S&wv!vU#h1x~`u!n!T@HYSR5&!o=s+ZW* z%`Rzfl-;&``G>saUN`kEH%{~0I^C$|?BY3ALC?&rLS+sGd#ek)x2lY8oh~^yd(-oY z`IV2J{h9u!=GLA{V`g?f6-~`f@%WmF>~a+h-LAQ8N}sNmOr7vQ#Os&5^Fr&-@0%Z; zydHOAlkxWIL`Ok|>R9uHw78j$ACqou$rS$h@nguUkj~QA*Dh{MK7Lw2gt18?_1%M& z>bEDDuGY7IcQ!BIxL+sz)jhF2=0Eh#7*~0G*ZJQuE%%o}?kx|YM~`Og`sMIV-YF(0 z&S0W?)IFcGuQ)^A{pMt6X8)NKqW?&%BsJJ?`umyXo;mYA^VzcPJ2_$A_JEbo+27dh zXq;#p6==9n$+qf_^VeBdSAKh>bLT+kU2lmik1LP$z3_K>>Q}SF55C;Z}&artGa)Fe{ZM${(!ZG_ND5-^*YY4)RwJTb>OY&=h$hV7v|jD z1lr5&Gsj}$_4xX^$K|Sh#5F-v@jIuzy}y6{ks~hi>V9Q9IXmmWuKp-%wDQ^-(X~9> zwf50W2YzKvUU6@!VC#oDeIMP!irlA9e`#K5(r;tb!1~_ykT$>Fp;=qrB#6a4e)6*a z`o^4N9$sgp4xPxqyfRk1I&5Y!d!K2)&ck{8yemJfd}nj@cjX`MxTllX8g9vp6+e8d z<=y(4W2?@`A2Y9I+Q0kmnH`@47u~iw^Vr|UPrR7-JkQU@mTS2Of_l&2d+EH^>1O%) zu-tpHP2Dp-9oBmwVH|(sH1qcVf4{V|hn-J7J~vnM@s2&kb?RzrMgCC@5yf*KBlh5Ast@(KF>y{N0#P`4dQvBlG-`fX*-d+iQb3Js|+^lD2 z*Oo7N^nK^fjp^B57CeVe-#Nu>-oD{&gj!$6htKZ}bARhIgxMQjsaScx zeK%?%yp9K+NqwLb*OuiL_u-w6%L){`Q(lyIB#*7pkDU7d6`bJ>?_zwJ@Cxzvre zWNK`lH~;XmC9HpHp4Wzy{EcP!^67KCaYlW)>KZFamnT|_Bx78jOgj0+;i+2eO0zY_ zvQm1B-ga0XVQ@eH*`Vv~m8&Pcb|lXE{%Pv=8N4XQsj`{!>y-yq);WRhEG9_q)ofAMdRo7MZGrDd+Xy~1An&EeJ^cKtIE-@Y>%Ea(39 z)O`KrP6efK(XBeCToW!V+&DQrs_j$I>43OQ*;CrO&Xp<$-Tu{ASe0eIc=GmKqDlXc z_xEzIO8L2&Z+*QXHt44D!5g^=Pgc7<{~arFP3=zfZV86E`!D0yuVY%OAsch_Pw36> znzP)?KJ5*h{p#rF2L0aqm;LW0R80|`U%g}ZwXYV%e9O;fGHiRJc`%pn(-E=bU-ll` z@bdMCN2#CRT$kMWL_=|&a713glRr`4ie(Dio-b$Ge|hEc-%EDyxvw{or45^VgsEY3$RA`h%9Asf;qqH8bd+zpMV!fnTj}R@|Rvr02Z688l+C z_1Kf<^|e11traPp;WPJb;=^+ap{uPb_pW{XF4|}s! z&UCK_0-KNXCg1qf+EbG#lifG#&ZU2>v%Y5eXSN^I|9o%Z{SB%tlj4(ZTv__#Q)u#? zD~&zx%d8I{>s{>LKeOjY4u|eHz))wZRjc(uQfA{DX$M2H`raSC5E&P(1Vw85}Hj7#PY4g*QXW0I; z-NqLF>0^(`t$K?YlTN!|Tv4T(WitD)bAHJ}kB=4a%&%WqmlnG3UhT8V0f8oa_RD^~ zeb4dCPWhi1M!{|UiA#4*-5xjJ;ZvZweg6!v-`8F>iA`L+!PCR}dCbjq^A;BK{3gQD(>9-GVh;IgJ<&0OQ&IM`_OF%uoB?1Mi1lO}Pxt(aOUJEz9-=-H&z zj&c2-rzHEgwYf`7*>Lcf@w}?C)+TG=E;mV;vz3Q_E(r=`TdD5DH+PPo!Kq`rb7g8w zkDd(=KX|3?|NVuR@cTag@nXJ!Nuzcr1(azUQlfw_Z zK303~f@S?iqrC0nw`cz5-BI{^?hcv%cCRY$W?&<2!f4;n{*Hd$xS)w(!Vp^)f z3v-U70{?9B+_^V{x&QqrWfLxQye_s-WtR_M@=}K|G5uQk1=-H*tTAri>ef1hHE_zO zZ`!+H?(+PtkH1Sk>2I0JZCsh1ZBWgZ@qMc~`^bR!T?dWd)RoY%$)FEAhURZ}A5G1KZ=Pf3U8IzU>(4 zJwdbBIC77=$Ca;&TeLEk#;EAbegAOh&bUj(ZNeKFtgls86x6U;8*0QJXcEj)G`!~Y zsbB6_-Mhv;W?E^#B5OgzaX-GW^q>DUA#;0C{9OULoCgZU-Ti^wHgAqExV2XC$?*ko zncBa%#aI_Rbc2@Eotk=>>t1tRn7r9WM$MCUcK1@5*rs+zeEhxe*;z%G)L)TXPafY@ z>-deGC#sAu-9d8l$<_X5-m})+mtyd-i4+LF&^1$X!F6}ZH@a^VLT0}So3FF!`4lzn z6SGPzHeNsf%Va}aga(tv=bJm!@41~ylRrJ_k4)#Lplf-XzDUhvj&gB%k`!RRB_>7f zvUr7t%-lXK~|)?^WxeszpSiJ z+&ur{6E};`hUk~q?L=Ou@!P7U8*ZB|Y;C#sUizOWmU9G;h%qtxO#A=f;p92h%DoZX zXSFOhfQAUK2MP%a?hMF^oYH-a-@uwqC`F~zlF4A=753ltb4p%=s|3fz7kKTFTyDJVWg3eR;-{biEnBS>ejTzSKO|}S6 zlT5f!9=9hVSi#?wKQM&(=H3S~JJ+VAfLq-2rj$W!!G)$*))a z`7>uG|NgsP&%Zt7$xBFoxJRe!mhZHc+`qJ*KDt(RcH@52Y2`uMpAu%f$oxOAYqd_> z_xiigj&nQ1znxen`ljH2Xu+3HpN|x}Og**!u%gm+ky7W=t_hzmUY}W0^6t_7%`eRU z^%Ultr3b1UV%YELb*`ZF?(z92_B zSMOm3frKa1<@#>>1QqR?5w804#V*b6MGq%bo_S)hp(d)~?yIJKRguak!_T@M;&uIJ zqrrAZ#+Ua&*T2UF)|r`kGV3loUvCj#>v=wpk*VST1Fx$4*3;DMRylRfIlbuji@<`X zmbNkHPD(}`%VHA~+_`Nw+>h<({)wf(w!S;_=A+2lumfE4&Rll< zc;;*Qa~9WzyT?tIWaMp2|LMLZ=W=`IWw%KJ;>WLCWiRrZwzlWZAA@A|zNh!RbU|}Q zi}y#K6kl8=7`{+@_8qypIX|qlo3&qldGh+tp6?3rJg-ju{yhCd-tJXCw@PFhm{&jF z^!VJiDTNs@2d#^GxY2Bt+ku6T*{d4a=VIeY10+eEGNY?)L?+a-7`Hc`v?&m)$gad*jN@Ki|x? zn{w`sN4Mm{J$s({ox0ljZ1og|2Tz~#r(gIvF>h8Qc;)CiF?GIQFTZb$Zxy)ndwb*3 zTPv5pSiic&`s$gFYy3Tz-rDG#@aqGE>*@O}ZT%kFGX$4EFaGxYj_$+nGX&=Kx0(NO zdENEV=I^oPTk>b{2VGAqcf3Ev|0rn7_(X5jeRJmaU3IqDV>Nl9v}yR}Kheh{wtvm| z#`^Y(=vl8F6Ys{>1fRZlrS7X-6;E++)p@O7dwv_~JvcHyZ@t3YYnPOh~Yvr~x zwU1)mrpKM=`<1KtI7oa8sD&6~Bwfz-@w%nuo8A+xvFn1)M?Fis`CH$}e2?DJ=EpPk zzhW@IrqxiHqJCd`Yvq;-Ilm=32@}@}&oAHAy=NQW5x4a7{BO5B&$*hJu(_S_0e^+% zr`uk3(ZbH#-Q$*JEv>oocHWUgr;Ic1uk8jcuuTknvR_OtG2jrJbpOm5GaZAN6L=ZQ znuGjqOv{^Bes^*VV(vD*cV>jvDTS{sCl~5mwY<(Llk{!3vc#T4+}gh{S5^il9X@1X zP|c^U8#;5t3QOD5sfD*|w@?1~r)Qce!P?aZDH)$RbOQ7`+D`aC2q0g_&qB{u70iTdYj0e{`*&gFDMn| z8u?tlqP!@4!=9h(76*NwU*Hn+_y6Km#j7W?cS--5l^39Q#Q*xs$S-f-EOJQ`P|~$% zaGq^zvsuFdM@6|sH%vkMKuf>z&A1EaE^oi}@i)&W_d};zXYSc*T3(W^U$@di)4E>S z%Ia7A-nxf}T0sX6Fem(XSJ3pxuju6~Cok{6o}RYpa?-*pEC28RU77WV^Vu7Iq2Nny69c$AH;PTF|8n7bG5?dT zfrgjQzj~}DkaBFfdPU8&cAwo-f4^$`kig77GrFcH>OrDC+toZ?$HQvzt7Tl=<{2J) z)H%1tpxV+?d)mg#=W=ELc1_U~o*uCOVQ+oS%B!VSKCTA@B|XdRwmjRlKm#W1xGngeq;{a2ssdQPMv((&4o z!<~EUt}SmgJ>n#IVCMN_wja%c%zj5qdvaaCYLc!^-}khh#UJK9tuaVsy01F-!Mo>i ziw=H$xT?3~1nVW+?xK@5miL{*LfLh-Zd5FBmQWPl>@(W>;?w>pt(J}x z`&Bt!FDXnQ07|# zD`j&mm*+V#a$9fvVcF)m_}aoNeGQ9L-|o43#j&I5(bR);4=q1jb#YU1`mesK`TWOy z6ITEJUpaeAIs5Gqr&ZE|Ib6?)s$-da`&xFFq zX_J>NR#qxRlZ=$Yx%gUOJy7EOzlOx+M3TgPF>d@Wb}SM z&#si-MInyd+}qzRv2IA$*1vPP`H1KJk4{b&tLTy4+d>|;R2wK{=vuVa2qn|90 z>9A?WGUu99-mf&ZyItkxeesk|*Nsijo61_HCS(R}FO*iKi&aN+HjI~O2TfyZJl z6zxpikX|dM`kv!3>yA@q&u%~5Z=n3tqLlk-y92|Tus!cg3fzj0ZC2gUFQ@w8l=G6q zTuVN??hliB^~1L;VvE?4-1F`nb0od*W-eh{HE|7O>=T76Z!HysGQ&ZWGPF`Q={4FFe>_f^tzOM9j`=cE{atXUVoc&g1 z|4#v1_wz~f#kuZ(RGF(C^`q=g@sZFq9;eUE^;{7*xy4I&#?F@Z;#Ozb&dD4t82>TSY{{DRl;>I@ zudJBOGiwarC_d7%@!NF!$&q)eJg+|5B)fl6Us$BR@AkaE%;}%!l)o_i;M?|PPpg-$ zeM0XUnRTX>pKtey?AXfu_f*S;^cSn|lzSUKk}_YoE%D?# z@sByrd$&)!w9(zainogY{gjV?>fYH-+$Qv8zIC59FZbd3JKD8k-8LM2dmyp&^Go6H zlddM4F4oYXv|?_ z=VuRXH?{mves9OTc*Qk?zW?UCPpaKixx%7($(qrrzhp^AiGYja#AO|`T5+LPj4)Z)9?MLvDUTT zzWj52RZ_fpkL2z36PG>n*!S|@yf)QG>YY*_U%rlsw`^OuZPS+jOKk7%o}TLW{$5Lj z>D9HMX275Oi=5wnnN{1quK3-DpQj?eDyYre^TPk|zT%rZ9$2p`X#1BNmwavE?22Vx z-dWoxP2u!mU$U^Q`<#W}zf))52`EU0xzqg<=+AZ@RX(j%f zUe;Y!nsnmj9M_3YY8qzy%(1JT)h(_+$u#@g^IdOdY}TGxyHR1SR=RTRy;D8+40`3J z-cP=G&)ej*bbVphUA`FypFC|msH+_l<8^b{>Ys&&J*K5}EO@9Rw?qDO^11iLF=-_Q zxo__4B%WBI7190e_`eh0eP?9*x87o(;+MTJW3GSX!@R{|(^uwLh4MC7n^hkQe>i`> z{fnX-2Pe$7;*}2g6L)}n|FO=)W<1MIU0EOd`(Mg~h0oU|9X`gfBx6_i(M9;dD$=edQgJ)&ZM%9>;Le<~- zww)-*ad?xdE~jRE@wmD1$$&YtQa1|~W?1iMDm0Ux_wM7~CN+QNm&V6FI{t6{em>P_ zs-CFCrwQt>y;F=Oetde)>%wI2|0c2gQ1+XR4{U^6c{yzB_3Zx4x)ji|dCMz<&eLJm z7sbslExcW%6S?V8m+ZL<^Z64FAL~qwH!O;_Fu&Hn`1^W&;$Oub3Da zM@L7{VX?c*=O1>N^3Lu_@!RAF^;RGLK6(Fhi-)e*^`HE2wU2!-_$9%!rG7vAeg3x| z-MLpI?rdaw$$j-mVU0v;_m97!e8;X3_3F!V%D1}m%qu-Vq-J&lx%=XCL-=xl_ z@hCh$7kEWS_=tAt^w0myb5Bg#to7LHdV1dfat*bQAE!yCBTE)ZHLK7~}-Cir(WA#EsVL$s}t;&L$ z*RpLgWs^)VG^@E;$II6^O(m0%^?ptFxn&;9S$b(tTT zUdL8g_BMWq{uu5XzUqPN>L!DlKe2zjc09HcIo@YsP%(4Hr9)>fMTR*Q&8ob9-tYa{ zk9Wh)N>v3b&+Tp$eqUrInEU@M^X9^zha@j8^ZgxS_x{St;F^CwpTD`WvH86H|2bm1 zQ3)n?yI)2>N&L&trRcWXFeZCJsyHI!4Rx2{rg< z|A5cecIGC9HrYZ3wp&-soFiiXq|0r&_`7kN>oG>bJ{M7u`K>mUpA7Qv*?=w}y1p*< z=eynSU)vitsF@d^ofj_PN>SDx#5>RBu# zb@op6sf=wl-ztq8E;`-XGVQoc?m?y->th~-U(b!Q{P$yLeZ;+FwG@+Cy7k5L+%`n~ znfG(szN)WU%~ro5 z9^Ub`@J8cl_T&u9e|+j|*tpoMKNK}s`xZ&x-11NFa@o)N)!spW~HC>WPk8vI&0Xj+#XhYW!Jo;8$ZkumMN<`=gHCaF7Hd-ai^ap zY#+*>-}UKVvt#$f>qfgbRWz)b$3I8zcq~=C zeBIN#s@2bmUdn-S(cG?`>A}L+@eXhbbox7d7ocXsqIygYvbY~i+f^JS*?l*Ul z_jEn3E~QWFSEdKfn`gnke@^iwmHF03%`GZb7dKwjp4(C>S@0m~{>C4AvuCnD`mt$F zr`FELyK{>~K0HuWma&jM{Z!j;k#j6h+_Q=^Ofh$Q9{+P)y*z*Cfrm-)A}z5635Wm2 zePdQIJ>tJn_<72Ko{So!nG$i=CkL_KnOXPsd*O%guVxp{-Ddb~jq%akO;KC30zr4L z`Oo(|Jxy0LYKz9--`~~i|NS|ixA0hx$=iGCg`$V_`E!MCTm5x5o89|<|NnV{%5DMc z<7}OroK8&9oV;(k%ai}h=DAj`3d`v~+BR{TukFT`V;f&KK7ZG`V}^wI1Eq7E4^j?U zNh^onme{E`tyr$jes00TzD&#@})@|vpk@mBWwj~|cA7r(t_`u2in!{wJL&(6$bVPyraUo*?Q zbK*#+uqb#FecYO8PUUUi+^ra51IxnV3Lj0s)%*Cj+>S0*=9NNDQA?fFcDzlv%d4KS z2y#>}+qjne)v4|4;tqesh%+ z6dHn-dR^R}AFrgOwB@jR(CVHrqyW<=fB#(Vq=l5F#f+$2vV6&h7;>%Fj>ZJUTu zklkwb_^qECPFPf(?NiQOFf(T6-IEV9A0ONkX>nn0l;qt5cjxM#a=caZE}`UOTxPjU zq^&{X0yk6sW3fdS6gFG`&wO~SQCncj&ED;6{PLK$^B*;MwPtd|9JwuL#OG_>U-^3O z1{U_k(=CJleDj@aW$N6{_w=&AJ?M~d5#6XMda=7Mtc$g_v9T#(P++9Dq>NsJ%cxAEB@A^BP8L#H-U!9YxFD2xCo%bW-r3a}`ev8dpGq1Wm_Q29; z{`W;<`plC5pI))_fAl{4faudlZin_8R~*b=xK5>f_JQTLrRVk+J~<&+_2q@5hX=>r zs;`gMZohZwinjiVna1fauCJE|t(uPCU$-Ui?ys4CwJJ(XQf7xQ6o~};Ug-)#zyGk@c z>lZIA@tmR?J?-l1@YM$AFMfS}{pXv_=T`(R?`cPlA8((m?*Hkic>IH>PnSOGI8nSf@r8?C@HCl}HUC6kehv>e z43c==Yy3t=^vv21vw!^fp|Yy9*g^(0i?6@`&!h(r5c z9$pc**9x=}B6=I>IEOs~uOfY(r1{N1v(oi#=Z-Drk=r-F(N1GC|Cm&u?5|bT=-)80 z@qOfs8`lnfuu3Zuu21y+e=LqC=aFxJ_@wV!BMrCf6<0(tAFKHuyNJ6BbhTF5+gpXN zu4s0NYP&r>H5IfXeBr`{RvG{K^`>`Mets6X$b~a(ecahqp{r-u)$Rfx%W`UR=*pnh zo5sruQl3S-#9GhExh*M>ptAVz!ROpbH&V=vHY^l3R8PM7dA9KXGw(|3?#_L&y6o4v z?uT6`d;c(=EWh5{yU*{$mS11qeUy~1`B1kgE<8U!AGDOTQ&|1Or%#JqI)y;PgfCuX z$nN=np^aC1MZ`v>#qRxShgvxQd^)W!q8k+gUOW3Udu#Ufv`60;TiWb3ytW~w>U$FV z4cU+BE>CnblwQ4vWy|Y-{5!QgQufsm>&=~a7VCfH;yUSl* z+8I14@_ot6ONK>HJifiVd-~_+=b&;Gv|zVvcP#sdc#D!30$*QWU+&z_2deh&?k*2r z>Lm(3&gAL*%v)PDmD_lqeCX%hDP3n6x&7-CN9n!h|LaUkQ)C1lK8W-2TgQB%ELxU=h3MPFSNdUJC+f9$T3hmZU1mn9x>-pN7HG`K0Eb$QRld(M1Cu=QXoW{e*$oR`hc%q8q_cp&pZTzx6At5H+=_U&U zx?3e|_e>7`trh-lk!RS9jfWhU&T-o${^h|_Z!+JIf&!+fo%FPToKHKV;#Vx%)WrgMJhgVhGeBxqX zeEfc@e8BQPb6%mi*;D35UFu}Go>>?B-Med#^6{^)uS=TcNQmpj?D&6UsrPiyB@q7e zY)(GDzT>3J>1nzvBR4PGw*j;!GLC2YWzWOM@;AQknfk|f`QtR7S#yu_w;kPIlUtkr zYQmPok7mYKJ*ur%k2YU>`1FE&ch~ z8I}LuY<{|b1g0Bj@?E~yS(^B%e1ARr^y53r*{5HRD|+mgbLno{^XvQv-)`^CiP`Vv z_>oc8s$_v{w-{HKl2T_+mDuFY88al}*2Z;b_pN!d);@~WbBSK=&pvDSWgD}Pr^P<_ z&~UU{{PNmpb3Qp6(CvI&;0uDzJC&4}h-!tapIXkcGw%O-u1P0iZkkU05UYG+YppTp zDq%;VJNs&9A2{HUc7C33E0<^pck)Y2v!&~Yjim6adwmfv3~p`_F~Vc9v)w4mjFXP3o?2mhMd74zkTYIZB1)+uhW zsuaoT*S@8_UiOu%t3P6M8ZYP|A{7;vqXL4SOG+BOjh?)Je|}nTcBhTW%+uEDD;4xF zoDF_$vHOkjwY71M2b)+!C$NKVjRYN6b(r7&g`S#{Qs<1eg_AcHKR*|^EXUO-HeB0vR<=uZaB^joko)+pT_wnWO`AJ)&X4+J4>QYpi)R8G+C3$+HGW+bh zSm&Eup$1hIlNC42>-sduCFa)gkCwU-7XQ^*j~>6VDV3XBT+e6Y_f9dvz=$a>%Ju*L ztcl+|=~QU^;TP{*f7M(KoszEeYWrfDxnISMm-#Nqy1Gj1gmO|+QpV+FyvfIU6iZ)S z(dy{%P+DFr7TmC4fkIv6z8ROfZwuFzb#r~uN=vT~s(PSaxfkSik?D*IzeZ%^Nj*E93MQHrb>aZr3X;DAH}?e|7fv`|Y8Q za>?iC*@Es;ac<{(`2PL+rj8C3##fO(j;lge9(p4gY+?0TchS0+F&mek6f2!OA@l3Y z&z0(|N7X@>CzZawR`dC+IVg>Bbx(@8x-NEh=i~iPKYTeBaoBIAg1<7$UC!qpuKX(b z@xj)r{Fe`FWz+wE-}k4SpEq~={kmvS{5hHM%ysgeXVZG_?(d)_U%B>NN(*)Tr@C9W zYl7z2itoKyHGi)Lh(6(Ta&iJ)oXXB8qY%8z2NW(si&ifTUU6-0w4kW0@BaVSmS^0K zH~5_v5cp5g{Z4G(*^0?*>shrm{zcA};fvay2Ra!I)YzS*!0Ws`LDss)>$(4}f{HBL zNAK>0IR0Z?ZaeLFo;PAX;u;s$m-_Lz!f97tD z);;><gu`b zR{fnu*VX00D~>w#$lcRAA-p3-&+kmW`@1U>L5U4i_@A18WmV8pFO$qmE&ld@Ls$g` zJr|kq=rZ*D%@bMA_Wb#cH)$4c%|xGY`su!OiMn1nO+R+Qf*;|(uiV&ZG1t%T=M!PD zLxayRy7RomD)Yt<-EVU@PTK#nXW{G{dy}<6 z8$xWWzl8+;JM#Pee*fuuv7ocKCNX~1IVFGO$dM0OU!!7*Q&jW+Uew$tH+@^p>AA}G zo3f9mF)=aiD19vkx(R5${rm?H65bm0&oD~mN>?d1PQfW`Weeshf#q6z`8g%ffXY3>4 zO)T4ezrEdCSR}h#d|UG2U7;e{VQ>87trHKifEt|Ja&H$Dd+(I}YMyszLBYdAI&phu zTw3b=dV!#zr<)0nZoOvkGM}8gTjp($Ro=1d{qmH%*P@?qE6V!E{%2Fo$9Oi^j{UE$ z-@ZLNaIxFc7qv^A+xblL@5MLiv6vm_xACN zGv<*N`DUT3!~WiWZ(aP1=itGEZ*Fbvma!~avg0pksIuhUotdD*;px+*%&wpa3fjcX z&KI!rRrV6yOEQx!LpNnkzbttAtF7?Nua8|`U0eIR|Gs_AkAiD!A`id1x;psEKLJ5O z$Akn0 zZ9ay-oo;W-1>N8E=Eg=V1F+5~=VfdvCP%ic!a+Lr6>;lZ(Q z-@aRn5OwRnytz3U)G_#c-d_E6S=8DvE^)mxI|?8FE6ra6T8!1T#KLTL?;_W3P}fV! zBx6F(?VAh1S2)kKDqUr^3p88Q@kcm%Th7C^+wVOB-7)GlRqN)~{MXmkg3eu(v8$P3 zl6fhkxf?X;al-!V$K&$C($c3}IE5#L?oLWd;^5*+`tabONzRP`=_pXxx%}&0u|k81 znR%vFsaEK!5KptWuP!cTPd`5|_1&GFw@!f4jMAt75gU_OWv$D6%qEAvzq@<-t*zO` zFD@uDF)?jvbae^hxckokiR8-A)nZG%r!VuLZ|AvBx3I8qOWxgC>-YWg0>yZ1;Q2)- zj7uIL<7HxIj@*_bsp>t=XVY5=g9HZa@^>jWH>KWM0kO;E>>SI<4-dC*PCqZ_qZT|V z`oFS!A84Bm=$7AUI+0r(U0s4Cc%8d#lF!aIS1*2chDX{==j7VmhaWB|eSM8b#v%Z0 zVbJ+SCyEPRUQ+d%qH*xq+1V$j>&H)ue7}DGzgeQ%VH?WcM)9yUn=Jtcsf{*hdH?eH zb)W{Zr&0yzzOC@M%GSBo<&%ul{c;@yCyIC-zMypeq`Q1A%Yhw*kA?p3+7+V*I=Axt zJX=sX9%S)Vr_07&-EYnUr&g{vcXv;p9$#m9aozJBg^y?0)mlYw%Q<+~cfTfhEzOgi zpq}FGZMn%mKRxX%`l}bWr{e#gQ-6Mb&Tj7PSW@uHNTBlEuF}`XrfP@JFwG8I6tlnV z?X8O|gVkAG1qB(eY}WYX1hT@a^p${+kkhX(FNM!mfsV2bT*-CI5VP%F1_)t8K{tE;{q zy1qPcv73la#Dqp>_N>Pp9Vcp~(uFD=_w3m-!yu7KH+tKX4-XIj`1NbwhSHxupU=0f z`(v>={rt5yLBWabFPOw9im&jRs&!>WV6&WU)t&mB+uL$wtV&+Y)GPn@=ckLiJ9EM3 zXTDZtZ%#ZpIoWXb*;^V)N}mLHoxArWY|p>1W?(SkTfyA{N{SC3|e|)YxZ^H z$a_b-#ZOMtmHzkd-OE+)5a^kMbX9O$wm_%-A_!dt*wFU<80mf zWKQ1QUB2;V&iDD&MNdu`M?O0>Rr@`^m z>3UDk&9&yYeOF?2b4%uBfzP^_Kbvtcy;p=OHePADUB=zhr;A$_KYQ}vV6#ca1qW&H5jUSZ=HH-G&0?byqbO_iUYE#K8s{am}Mx*C)|($3B4T)upHm%fsc|B^~E)yVFyu1Bw~ zuJ+8D8>=U-?B191=f}qFLRz`(*dbu`0d9z01(>h~?ZRwND;RuRr~+Kq#$Ot+}5+FV8bS;NhFI z1$r?%9Ima2+?w3oVdFk)O7x_yGpx&J#H=i zN=l%i9neAJT-_iNbd)z;Y74vQR%w-3_Hj@v)q;rSyxzR3K| z&HKEUAD_)zqCZh^`rW>RQwlt!dL;@H`u8+zKK4Gc?$@jhuI~T;8cCHY)fQZ1pO$#M z?xl*Lcio{f!&mwBeG3a}|J<+Q_20ib=4Xil-&zm|M#`DZX$;*a4y3+8+G z{(oFLAz>F9pF{5l`~-*#W(n(P1EKep$l zq0NtqYc)HbpZxHlN#|xE|C#^o&1V@N%$GHPpCMyr{op|1PnN#P$2ZQu<9TG0tIL}^ z?SC)6vu@^JcF5K6=kI@ikL`Li{h^ZB@2A2?a%UXfTG?*!pj*}A|NHfy&-ZEOe`^>2 zo-jK`yra8=dH3IqZ|ifSb?rWuo|(mOep>nV1H-MNf)kIp-esPYWNBV={w#a5bb>;i z#I1cF_TTxS>3<;n@ka%*$@Nul+Qo~WiYrUnuj;*dyW?*k^W^P<*Vi`3{L0~Y_AUN# zyumchkQKgU@$d`7O5Zz~6%(4&_f?8k`$*eO=<2NvwMBADnuycaO=lISh%ryFPuI{&hqCzj@WoXV-oB;s054rmdnveCPk&mklmG`En@Lum5CW zTlW8#ts9TZ-)7k;VZ%K!^%KvX9=Di@?mIrcbl^F=UnTkK^p2e(&Eetld9~JZ!H2K? zNd0&?bF(VXN9&HLAExHlqBvfBf9Je)uT9OmGB3f2M{ev~B-CLu?>qbZ2f0@6FJ2hl zu@i2tPOz=XJFi%Czcj|B@b!M(9Y2aW+DsZN($DtA7pT2ypDuJQU&3%1gOBU22^+oS z@_6%3Z=EM!`1Shgh`8SppBI)Mu{@Asb$)7o{iA2s_BJpx$1QnjJ8}AP-=|O2SPhRH zvD@R+R+I7~g#Y?I&X|`sr*_vqbDWkCm-X^&uWmS_^)}ntoOAbx79>~~$mqo$DtvBZ zu9mlFf6ta?x6hxR++1?{x!;#xk}diD4N7l4kGg*SoYg_H}@oCASRO83L-D-UT6=kA>2oZ~ z<{s!|juGEDuj9z#H_KCGFSXouW_Mh-W=F?SQQ`j+w~5{^NIIfDo!#%jyEfM&?;?G! z%u42De53c!_IAO_TJLlIH(MWHeE57#V}+FBw?9vR?AOmzJI1!`%F2zp_c|tjS&}=6 zJDIKiKu3Nr*9@useYa=EckY(|w5a1&t(dv8K-mQ8%hQ-2#962_%oBb6aQbb>D~)$u zWG%n7o2l~HHUG6TOw@~UKd-jMzGp+fl~C0-fthZ8hi-G6vnYt;IP9u(&Gh%d(vA;b z)elY)-QU!|er-pGik(!OSf!5sot_6gx6hpwTrJF$Vt1xd|7bj8&Y#EDi`;mRU0q}P z^0c_py-R)Q|u3ayx7-z)e{fo`~NU6y>*Ps9fLE zK4E8#!A$%8$DST6E-W^0d@W?|#`82T<@1U-#^1`hyZ!~XtG8s!2Yr?HnYHEljw=s~ zFZIfQ{jhTm=i8#Fw+*}n^*On3+v_i{Q&*aFVnuo&OTm$2-)HW8ly}_z!LEj{MLf$6 z>3T%(mS}7E#M1M&?dWxed-EP_INomC%+7Y8Qk!40ZHsc6ll-~=<;-lC9*b2memJnb zuHaAlvy+|D_GN!O$K$Sen{yxBm7m=G@$|^uwhd>G++i&4+{DSA=Vx=d z;cd#FDOMA+za@S+-E{n{ZVO*z|nPh%9YUTD2t?{>lY`xAKH?{&I! zRa$~)=ZP;rt#?Q=eRydztKq?)ANH5jf}@z%wP@$XFqA(QVk)%!v#C1SdIw*7eWA^n zhVoU_8-rOv*M@%!ukhOl|AQ|p)a*EAQOxz}d?4=Lx9 zVkueQxZKLkeJs5ayh>;9R-E2x`~J}G96=q|(-U{OWy)Bq6+VthG};#{wYXjWScur3 z*P%z>JnnSY6Yeu{^$1qwDE@axBKA0^{D0kY8?K4VqGWZJHhIPxvS0kX?@;7M=9Nav z56xJrkk@7~M>Xm}ycKs|-n$vz&yQ~_74G}N)0Y37amlF{-J0%!GS9CUWoMgA3y)X! zotmJtb@jutJkjgN)^^o@Y>lqCTHE@<)45}39ar+tT^43O&(5ukuGsUFmGR*BX@MNK zuk}WR#~+P(EV}3Ow1cL`ooV%-3hx=bC_v5rr zhhOkMi`x3Z>vgaDy^ME9QjaY^puI$EK6mqM$>MoNJMQjka8Hoi)3o|==t9PQFV_{; z9uRmZQ~Gb=wnT5f|1Y=A`5G;ezcs6M|Ni}DIf4_PpLkHTGe)afCH!fZ_~N}69bcNP z{Qp;f)ta9V--*g3*4tKYJ!o%z%p}3S@?Rr6v%yo(?$1vO=Ut1e%&YvIyrY!qxboyS z=|8oFcccBg`6CwpzbSqw`P%=Dxm9mw*A`UYZ2J7D)PN(9NMd|#&bbJCZ;X4Z!qg8uDDJo13+XyK`!^;g4OT;g~@ zO`-E`{*_F##1zeRWbT(adw%tDyXxYi!WndaQHPE6#b}Pd=f$o%x~Oypp9dd!P11?k z9z_d1cdAAjS66=gx9;wqIclMsau?rydE{NeqnGpc@IMbW&r8}Eu_d^>!zP_=`st@Z zk}5m9dU~E*TIzjrSFf~LPQtr8J9}iU%_6s7PTDxZwz}-?6(yzfGpoc@BR_xsyf|Q? z)5%@E&(F<0eEqul&F#T3KjCe}5!u#Tv_S*ZCL~fdiZac9PgMcWs-)!Pn_IKR^`>_# zyY~sm$(?(4b~Ynuy}-`m=V23FT%N2n*b$>Q{glF_Eq}gT_TTgO+wCP@Q@g6$Hylh* zP*Q5D`S}TSWWK1Ui_1K&uR2{Sj;7hyj^vmL3krU`@Hj3muI9r*cC85=9X6&`>47So zUURKXpPrif`|xAG<&(GHuZw0C0xcq*IAvYz?y?`he%XDA0Nq|c!*FqzGPw7k8G9FU zd;S0IN0S!D?k>~n0rw6){`~9&Ex-S}|81Fdue3Sn*b2}|+mH7C|M&ZQ`=ZlRwcTG` zSs5wB{QR73AORUA@I9sbX$t6`@$LEXY3Jtrj9KT?*V(D4rNvcW|37`- zninrJn3$L(EQ_8bFMlT}IPr&=r3XII1PxgWHmbqVe^A zO*1bq3r&=}u%j^f+xz?e3!PduA~&hnR((;>((-Br$D8NAJ{e1=UMbTTx3+2@ZsScp z*u)ANU2Uyo^7r@ORsR0mxw+Omi=J}*{PZ;W(UDGzxqhPVE-IXtEW4Hb3mzZi1x>~t zYUO@;aWQ-Dtu3A(51;*i?BCztFR!hYE`N6?@yUsaA*(_>T?GX_oj@ZK9Xobd?ECdf zn@dEa;bcnE|9^kCWMB7l{1^{97&!adnuXre^|s{PoCJ!^Z*L++-N1d3myvra3|YCw zKv!{q?rTXr+&0mwG)wfEX2z8jg1^7LRo2kxxU|&!^49EdC1qvE3HK9^=&Pu-tO{KX znri=W*8Dzb5!9zACl?>GNln{4b6xE2P1)D=oSdCc&NkP7e7yfUD9$=(oQs?yqN}6R zGtahqQ^m(5(6tXy+w*!aW`rcpo6OA42Ri&rNm*IavPdO#RY+i-FEhsRE-BqHfq~rvNu*y0<84HJ} zr>26ACIiiMzq_;Z$+@}Ft)41p(v0U?m#3YdXM1N?X?M-fPoOhEWcb>?>Bp~MMGp=z-rAD6c)<)85zU|$(Df3l!}aa{d~kksZSCom!OKG= z1SdW}b7Ot{{yME@74y%}&MtOrW;=51m{IMo5-Ib%o|`#!t4{CORL=Rh?qBO(>;8n3 z(Jef2{Qtl0+Ei>9{477=`LVk@p6d9d-9PU0>g|rB&noRdmCjF1Ss%<<&wrRX^SjO6 z?ogFQw|1xO+c`VZc-g1Bk7kFg4rAzFF1`AuVY!a6`TOS|uC0wR+~f04r>>#7`Sfzb z^rvcNu`LUnA`L8+EO!)1757JKOt$~aJU=lt@_(Mp>9Bc!GUvU!_4M@gezV+LA8zOG zzqlYNt=d zERypray3jm#If&B<$vY#ZH9Yu<<)$C^o3lV6<~E)xT)8W_omUYjsLE++S{cY1@C$5 z{>0~Pr~fvllycd`^Dl2KmlER7wQS-wKDagh`MVh#d+z2ebeehQdsyG1r_3`AUW#1J zo_;WA_nAYS!IC%sMmsHf%U>UI#8d5Tze0Y))0tOp9X{NA-tKqL-sgE?vy=ZhmF^6-@o7fe%)_f@9BD} z2b)+ysqO0O@Yf6ipq^UgrpL$oC#(C*U0UXQx{;Y3G{v5mcRv04wwpR7?=Ck7o$LLx z>qW>x?WE#-tnw~XZ@9h z8xQvWyS!b6<(kH{+U;|vtrxFaz-xGfk>B4WCHZ*|>z2HTNzY5oG7=ILPEXTa3|i)t zdwW{k-l~@l5MS>BPXPN!vhheXtY82BbL&C;fI5!C0aXs&;Qa_KK^>^ zij0D0`#G;NQXSv+d;EUJUi0at`j*1Ci(fCeDzff&Ly*LY_~pXw{Q1w6uc!6>6YG8b z%nWVE#eBVIi!M*UYAE*N=#4zzlNl^NH!FXit~&D1W}fUry_j&b54pFVP4W3HzPDA- zedfO999O%RRlmEvuk7tD(20T0?R-#Q{+uj(^R4UKQ-w!v{?)j@LSFB8&9prG)6;Hx zSF8@L)cMAKUVlqr#aAV+5>3ZHk+p73tF|53Sj|5D5ySRVBB!Tw^+>L~yvnri?(ZFb zQbx}zed1%!xYm_g?I``HAiZt=k_*=lOJ3ZklD64;$J8Z02@DS$Z`rsRI6m3=L+SRXwYllVdbg$LlooLUP;AwHnr(%D~ zlPBxdH=6MuVOnx(j^K_CYlg55%xNB?69n%(^7y)BO4{~jMe|ZAvjso)7PA~)FPK++ zw_eKl$lcS^%B0zXQ(s+QS8M;Hb>42T7k|xc4yaG)x36oR7QZcN?n=wmx0nC#=DKmi zDw*-@t*ws2LY*Bd{=Yo+KGk>pcxw~ZF1sOM^U>uDHP`k@i5+xF__BCxg+Us`Bjrec+jQ_MS!;yJqj}p83E8fZ3ebnE)WI0EA z=G8=@>1)dt>E|tR-v0E4?nn6qFOKE!9M;GNv8EgrwVt!kUj9H^ukh)8Nl&&F@Nmk` zH;YzTIB|dcU73*emJFfQ*HjEx6uG1q@fUsq4R zu==z~+3vaOho{`Tq9574E%g0|sLn4poWFm(dLomd#=b;E|LfnDL+k&x7f!Ffu*Rv} zYU}sJpBt}7+O1KtGdy0Q{ypH{JF6-D#_`Gh4~xUfwT~WbJuLZbOK{pLouVtJ53N#t zT{wp${p3C^li7w1zGth0HlOj}ITm&1md%uG?tMwyi_IUWuY0~&>EaqI$>aM1Kd<( z#|1jo>z;|5bL`EzX#~2+Y?7*Xi9tt)4fCr=k;-#DY20=Fp6O}b^3UqOIY#_B^Z$C$ zS+UdhG2g2fUg)^VA))&tb8=UQO14!xXbJ*+KZ^^f$>rkW;xbQZ?q-coAvbRrmTOrb zj-9bFv72FYeOT9YeczXRX8!-U{I1QeM$Y^C3fJ42GbX(jyX$>z@s&ThCg)y%-hM5$ zc7Kg2ko$2(YL_}CK; zXO*h#x#yUYqGEPjd859O(Cm}HzeoR*G`_Uuak~g_MS_Du;;%W?`FnT#i=I_}qs{am zugI#ujAu{V-dOxn!N_LL33st)9zSkNWj;NaE_2P}ka8}6>&3aoVi$Hrdfwd#o^p2i zr~mz3QPbQT8RF}1c>BD&*tW>E`;x&1m4m7F&tL3Z`{n(G$%V>Q!HXiaug&RZ%gOn- zPx$OV>zOB+cN>04(UFs|*ztm0icKu-*TUN$4;Gvbk=koHqqVp#2vkRL-#mk;SuCVB zwbk`DRn(tiWQcjF$dma+?eVj7OVdv#ZIRvIcVf0;&zlP~7O8e!T6kD`ie_|MP`utz zu9%qrvcBx+QXikU%`mE}t_fCp^!`TB+6=4Qc)^JxPS16^Y`E>%zP*V(|0?A8&F{hW zX>ZP*+wk)(=Z!nfp2wnUc0MzXnSSU-;CtIkebElDoA_*=nJ#<1;Od&3>OTK#V#&An zh(+vZ&5lb=Dir)trK|N#PPqPx+nZ(Ij=M+h%9ps=yPZQiaOUJ=jU~S}SiG&Wo!JQ1+O9>%=N1I4_jM0y-Y$D$`npe7NlCM{O3Za$Ylb-6`z?FE z1o>rsjX1Q8nS13_bKQM*Y&}c6CL1dVyK>FE`KGAg^&dyG9DC1&>)VYN)e7?I{_4EM z>n&^_yNx|lxNTwYGn4P_OO*X&59sC>{^@1Nd?8XaVH#_&u(&kugo1O6W}4Wo6uS1@ zXtm$5K%MAUEe5kEYc0LQ$=RzE>tJ=~6U(HlNe7IhiNlh!#1D{B01}bzYx&EsV-K4GbP`=Dsq9}NIB6FAeo#>mQ zb<-rT%eXx0u6z>9e|)pVZrmp|Csk~$HvM4C+_}5~SSa!3+GD7nBg~kxA;y!t`DJfU=tv9~idG5UF{^chR+>FWisdo1F z{KbFn+PZw3>DK?|`j2I=BV^Q@ex#-|_E;|~IJI;3&CmQ3&s+8RIonp~{xv+c=cj7; zao#17)5}dkk$y-bMsK=z>VzdZPftw+okH!}Ev9<0M$-Oo@5%H0pPBh@uL=9GCE*z3 zF?AP{*}U3nI%$s&zuz1o_iIi2Jt4`?yKmZ${wWVT+jW(#u1+lHn#YGPhv%HQ^k`G+ z=^2K}Y~Is!E*?}+`osfDTdu~lHvaf)xO1lNv}*l|Pt|ABbo^`X3*Y(};uTj0N}a zKa-|vbm`ZL>fDxg_eTr|oE(l_kqD|}Na)?7W;5?VqNV8WhJ1e2tk0<)~ z^{8BWb>W#Hcogi&p(m2|2fudSwPrXm$FA2)`=+T&W@(?+kJTzPyBvAz+dJRpSKJzF zthaV_aDbW(hB4)a64@7*_!T<&TN&)XJi#rsh)JOG9S6hm^8Cc}n!?WQb{7Va z_`SK)CsLVgaJ|cH(T@3y8y-zt&M@-~n-8O>Pyc)8~vVVK||A`j*gU|^Na3$;5nRde_!pAWy^%b z#L6B&an+tWM^wuAQ?vTY-x>LAqQ54E-#Rw&-S>nWuM5k&#nSWV&E4@^pz+VuNRw=t z$?djY{nqLqKdqTMWzUmI%xnH`k>`KE>g=ZQc=iAHW~mguJ0!VZzM#q0gyryw=BfKS z_pq)mcJF4IdEVpMh0c;in|9`PSmoW_C3-kuhp6Drpevg-%=g^dnjO3{NL4Fj#epX$ zC+`$Y+fdRnhui#Sw(S}9{_82vF8r3*|B53ri94b4e!`J(=?@PQs}Fg&>DxP=YOBoI z68>;yq5a1!W^wyJH7xEPM|S4de(QN(SN5B6pVZz7?NQfM?iA0E&HgiCTIEdd0$tCU z_IdyRX**e&E=_FK`cwWO_)q!qLZ*3#eJYC-_AiP5|8`&UalWXnSx1j_3ctL)ef>QS zaORvYCMS39$;rt-f4yG+^T*@ZuBw)4sFcD>d4n`D#Sls&_sQr`ES{?T?@sf)jEez%O^akINH;lG@Z`XPfCKMU;b zBreX@J1uRvxIEWu%d69mL%&^G9vXY zp&Pv|V0T&WUO^X^bpnTX3GPaC`KNe#(+rbLp})VsFOS-q1-jGs$?56u+wM$nINp1k zIWX|~{k+@GL6h1)tkC%P>)TyMn>_n-1?mqDzAY5W%YA%sm2ySZ&EFzw4i#5gw|^|# zJ^6r~-;T?B^j<&w{u>lVpdA%!qqnEs*Z>+3b!Ok&aYA(2JlCB&_x+V%1Ko^zdwagR zo?hQ9(`=)X7Xf$jzVmL^UuOJg(E^SKDzEaJl^SMBiWtWUzhim*h4Wg(@(&OHI0vwB z@b~Y^Yku_S=jWACTeXUxpL_cI{r=~FzuylJ5E0xNRUdtIvxfQRW_JEhe?Fi8{P+8P z=!6sJFY5P60*3;hH z+`QUAN$L8GwuMipmA<&3`0MNI<4vsG5+)e}bFIsdrCt8Me0A>YYiqx}zTW@$_xI1& zqVq5AsVo+Cb$Md7Xn#-9=~Hv9&4tx`5}uuz3EE$EYisuAoSR0X&$KMd-bnb{eib=8 z%XD$->1jFl_C!KD7*Bo$?<`8K`}5HqWEtpA+~t0AZ){HQ=PGXfQDb+n^11Az$Bzql z#^l`I)_ZSnb@KM?Z-L;-=XJ#IZ*OnztF`|4@#B?sv9leU*(T1Jv*u8Y+vfE1YIb(> zRK2HdD0vwKGGbHe>1Bt&^DuWLV)Vp8TWLQ(J1b*fH-}49%b@O0Mbp6rU7!DNsQjE3 zwJqmlxBk8pH#R1N_V#K-Zd$^n434$rQ&Y9Y=huFd+?;;?*=ha#PrlvGPd_>|JZcgTtwc1eqJx}ym ze#PE6w|+U_ni`vl6DNZ9Yl8-;_PBukH#>N?S?-N(xzblxhcC~&y9;#3!jdH_qR)iV z&dzdudTMIqz8cHZ)AiH8yttThc2;O>;vtjku(bf-*@&GxE$90E`u_gB{{BBsK|w;I zS|JZE``cgr*)!8D_m<1&XJytAxC;Qv~owBp|`KPbf z*|1#K$xn{Rja#l^*$7Zx;1#X`EXYe840 ziO1JWJlxJ7|FLxE-8{efcE0cK>|Fn2m(S%T-qZDL{(LxmBS&fmc$jeF>o_MTCi$8V zjGUaDT5TO2F=el$1O&m&9}pQBAp)XWK{xV&22)AkP@olJ-Nem2r#I3gOs(`>bsFor9~_?^~tR*>NH@F`UyS&n9mD^sle4$A7rd z$jr{r(bpH&=;HEZ(?x?VCwNz+ot^dM#Kgz^{qAmVm$v7}i+Z?##xU|qV|SGt%=`cN zfx}|A-Vhl`;<{S;_SV!16DAbrr_JsydwVO^$e|08#ga}<)2%)jP!bU#!Eg6t0T*Ou zbkf4;?RlV;qA_~n;1UlqGGbKn?vCVPgAKRerq_#sO%`O}64i29=+ye*_urDYw?bJ3 z1t*^NOFz^dlh)bUxyR^@DpTG6(8&0VX9i!6zu#Ye_1nh6$;qAsmiMg(8xV1C?apRxL zyi>K>qLSx+GY0qSC>3>Iu_q!x_al)Ost0tQKaO_)ir`J^?@b2wbygJ}Eg!$ds>qp`y3_sX$w3u{NM+C3|ExhWz9+-9=WRANX?IWgBONSI{cq09 z)161-_B@ckmj6V*@v%^UcSlD|SKC6@b(cSsvn#4Eq`5leIrO$ z<96eo+NcCsyPlcm`Z5tG4{kS~yf{6U$LYkcCmc@o_FbDRbl4_jKk44#pQ^qp!fem} z`3LHvdCZuW!;>B(YClRCpXg*9&0Ts0vkMVaR{=JoXVLj#X{PhYQ zom=f2_r_LFo|7UJWpUzR`@8i2U+x=x5;^xHOd@!;PUYMk>h`A>J}fp!(h{0hZhU-R z9lJk+lh?7wM&AowOiTx@Mt4aJd=ka==L6&K-QOPnICfa#-0c(p-kfd`>pq;?RDQm( zNBwQI*Dm%qk(-@XFf%Ae9}C!;XnuZg&vEgG(E83j&Qb{v&sz8!?U;2~I4rsQT;tYCu{hIya6XH%p7=Lj-Fpt_wYW+5 zs%9x?3mcwrOsO%`IWSMz?DNc(KY|&nPt4bQ-PD~VbFN2$Gr|1dk3Ai2Pvow(7u@HU zsrcjFG-FQXG-dz3p8D)E1(Q1g%AKIn_{7#nwrN6tEBsl^YZq$ zWD!lrc@y^*{7*|{T4xo@!CxpGd!$t`!)sgL!WmDyQ(`1ON2v4p{hfKh_UK8&8lioQ z)f)vieXZ@(2~*kD{Q2XXxz1HV;)_0ARnM=NXOELz4@zRtg7wo(lT4vt_PqzBUAbd^ zaX#F5<4z~bVeUs7b6Ov~w->gkdU*Rn*dCQ*C*Rgr{AqgrM4#jLH-SIq(&BR6lGEHC zWEi>mI9lv#um8=s;nx$*d;By0{J30Yx~=1cE~qOXKHJ(*`N7Wb4{n#dT+ULS^ZWKu zmhW$W&c7yGkp3q__U__s0)i9wFD({x1q~l3VvZ;ix>NbdvgML7F6$a{)%`cVN|`ph z=>D~NK1z2b*SUfAC5lY^4{Akp>IHQ0H-|5OY^Q2E_43J+PfpJKylRd9T~WvOKG~}m zl$5q}9Nwhkohq=iP*p|c!$EfW2@@tPNb2Y~k-D6Gr-7EfxPU7cklzS-1XVSbkRYf4 zw+(r+Wm6xV2Q|oF8mA?&uAQB=U&<^8RMA~IpafdX{$u|vvs@|uviF{SU9kpl7n+}~ zi*&S4wtsb~<6-m5o1X(sw{(E=nZQ;zAxkHVo1En9r*8IWzYX2y}4HQ9rxdd zAMWnx=s5H!{^QS@JJ&aI3oqAQbm;JBjsiL>=VU;41H0s%-7sUq1c%4V%7a!s-TkC*f4uv_$|NuI z#Ubtb8TVg0+Wjq=!v*nJ_RkN8`(NC6n&?$N%c*ZtZz)TR(>110&F>d-e(f{pQFLwx z-OQ`BeM9uCWp|j__@4dsR|{NsyS(MO#-eNgxgQ!H)%gFR=2&0$;eCJEfBrbUamKQh zhhp3(O`5bphkN_&-t*5v4NiuZwl>h!N=)GPQpB{>yKk%|Ian$-MDst>jq`u`d%1v+ z;Oc4r+yBf<+2;Gw>1LB)&F`<%TiV)IX&54vZ@>Tk`0KAiLPAcjudNML0L9!N^*&i^x39Nfe|-HiXUeM& ztZ5!QqW;uddib!bM%8-~Sf zNl(?feqi!qxufr8yU%V}z4iZmuDga)J`~+u8S}QxEa%38rkG^V0w)`rKm3ZDTsnoc z`oKwjYU%51flI4&m9%aB4!SX4JvBY>-mCrz-}?lw z|Fr1h-s*DD;v*5U|EKm;e$KeCLh|SAy{`AN_*b^BZbCc{;nMg2XLa0oly!6U zk>Xzhv+eVJZ|{~eDm#;rV>Y|1NOb4jyrL&37!RxnTwL+xqB~?co=v=g#FLB1=RLS> z>8)A(|GLHEyx`SwssFxQ&iT7Yc&hi)C#&_^0PP%bYNigF1h&HucFNC ze4ssF;dX1%?!jb<(Wgi+_}Sg6=jTUn&yxl9ygfZV_xyUbI%G}6$7Q$nKY5Z8 zSO2#(?cAJ`3mltY+}x~QwmZ*TRcXTgQ) zt^Ylt_49sZJ-v6+ZahES&VPA*y#2HD^Us4!xU}@NZ|%Ql`uqPJ0^Jbze*gb-D*_j1 zTw2n}S0gxa_7&dm*K-~p>pc>_)-z+~I+0AP7{`xRQ`TP-z7v+uvGtW%%Ew1X&A;wj zwtTs9&JBY{j~;b&b|xNf<1Kl6>+7=p7nb|XG*VGf`Ebcw|KjR!{aL2j$F}9(F8Ki3 z))W`rEvCE3@9(iEnP0emW_=5J9eX@a`+i%}TKk9Rs&5&UzPd8INPhpHPu`#vf4{!I zj@(tEslWHjrJJ{J+|in4o`3Gv)@)F(b8GhXXPxTvF8q0@q+~u*I!&VL(vy>4YWDD} z-s)A4ei$`9b>_yOZ*3wEGctPEzexVU^}qFKr+ul1T)^CQ%1_qk}dr&4;mQusflG`-)H8~*8` z?;oR&c5OUwyyq{kx-XJH%QXAihbzJU6KBmj^?LpOb8olbU*|9M;K#?upyitR`~OaZ zjw!vYaTA<4`vC97%@&WoPPDnVTfQ(hJ!abijtM)?pU=MkyW?bD(3TjEQoUVn%Y2>1 zT{q26`TXm@i3XF-&4=vM8(;J9-)J%C#q$d*6@E13*s?Bs_EgqPUpjqxpo&iX+tlg< z3i&-J-@mri@jU(G@44JMrkg&O>OXH(IlJJotlq8gQXyf^m*Q^8ucmK$?8y^%?QQp& z&}m$EpBvRYVgK^(@^tB+e!lH|vWtqJpSx3h-nQiBrPkEb(=;{asaJn{(<5ctWm){p zp!!?RqeqWs*j8@?53p1`jhN=TUj6rnV>1e;ytnH=aoa2YTj`F;x=k~eW`y0e5ufYi z`zqx;M;YULSH|Os0^->d9-k>wW4DxJsAuR2Xx_8%`8=!IM61K$DJ*C2s{fZblC+4o zE6$`ya^BBhH5b_)irenb*y8Auwxah$?(*wz{fcfFH@#f`c~3<{k?8rQamtre+8E;3 zaWjZbe!l$ksUmp`J)X60TRb`2k6t?4csedQyu|KQ`TWyYTI4g!@BjT#vrqb!@1$wd zj$K&j47##%XYq5;EsrJd?nFL6{jW1JKrykl+u7DJ{(iSmV6Wr*q+95?R}l!-8A(t zF>OoILdE%~8vp#;;(AX)diS+uhBdJQO#xfnG# zUX6lVFYcdm)e)F^)$LOrf3sNxPtLO^X44(BcDc?t-JoL6yx-%v#+wH-Z}I(!YnBPz zkQQil(93U*gvnm>o(-&-J-45yS0+vPwcT=u_NCKjQZix=J0@(OAAj!tQ|~E?cIO)J zzD-YjHC1!7vdJtX{xb2;`_484Ep`G8T3RS6nNN7VDgEP@mzS^PUTFDRT{Gq7 zr?*qNyJpU5RJd&Z^MJ|D)q8k1T->zc;2lxBbEi8cHkQe)%t$|IV$QtZ^Xc8MbzNa) zh4*Jx3#^Bn&sskN8?D2zX7HJeXd^|tSWfDHzrtFG_UGGFsAer?p& zE-BM2gRCnW$NJ^Zw{QxJi2R?Pzqri0-}ajXXuagL)WA(Cp7*L=ua#a`9_iwe_|dB9 z!vjaH@YP2HH=C&5>N}pcpDXxyt3=MN7mv#qB-|8uHLYBr^2+h@PVut6&$>++x#ORe z8oW09`5_^|sGoN?Z&~iSAIv|FI`PZ#bRChB`5tREC-hAAqwmgVS1GzI{~5Xa=;>oA zH%c!xH5~OnSx}PCz~6A%E$+b7yMC`XmGR|#-&C|%-al